Compare commits
2 Commits
ip6tables
...
bradfitz/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
698defd54b | ||
|
|
c378a9900c |
17
.github/licenses.tmpl
vendored
17
.github/licenses.tmpl
vendored
@@ -1,17 +0,0 @@
|
||||
# Tailscale CLI and daemon dependencies
|
||||
|
||||
The following open source dependencies are used to build the [tailscale][] and
|
||||
[tailscaled][] commands. These are primarily used on Linux and BSD variants as
|
||||
well as an [option for macOS][].
|
||||
|
||||
[tailscale]: https://pkg.go.dev/tailscale.com/cmd/tailscale
|
||||
[tailscaled]: https://pkg.go.dev/tailscale.com/cmd/tailscaled
|
||||
[option for macOS]: https://tailscale.com/kb/1065/macos-variants/
|
||||
|
||||
## Go Packages
|
||||
|
||||
Some packages may only be included on certain architectures or operating systems.
|
||||
|
||||
{{ range . }}
|
||||
- [{{.Name}}](https://pkg.go.dev/{{.Name}}) ([{{.LicenseName}}]({{.LicenseURL}}))
|
||||
{{- end }}
|
||||
5
.github/workflows/cifuzz.yml
vendored
5
.github/workflows/cifuzz.yml
vendored
@@ -1,10 +1,5 @@
|
||||
name: CIFuzz
|
||||
on: [pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -20,10 +20,6 @@ on:
|
||||
schedule:
|
||||
- cron: '31 14 * * 5'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
|
||||
4
.github/workflows/cross-darwin.yml
vendored
4
.github/workflows/cross-darwin.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/cross-freebsd.yml
vendored
4
.github/workflows/cross-freebsd.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/cross-openbsd.yml
vendored
4
.github/workflows/cross-openbsd.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
8
.github/workflows/cross-wasm.yml
vendored
8
.github/workflows/cross-wasm.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -38,9 +34,7 @@ jobs:
|
||||
- name: tsconnect static build
|
||||
# Use our custom Go toolchain, we set build tags (to control binary size)
|
||||
# that depend on it.
|
||||
run: |
|
||||
./tool/go run ./cmd/tsconnect --fast-compression build
|
||||
./tool/go run ./cmd/tsconnect build-pkg
|
||||
run: ./tool/go run ./cmd/tsconnect --fast-compression build
|
||||
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
|
||||
4
.github/workflows/cross-windows.yml
vendored
4
.github/workflows/cross-windows.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
14
.github/workflows/depaware.yml
vendored
14
.github/workflows/depaware.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -25,8 +21,8 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: depaware
|
||||
run: go run github.com/tailscale/depaware --check
|
||||
tailscale.com/cmd/tailscaled
|
||||
tailscale.com/cmd/tailscale
|
||||
tailscale.com/cmd/derper
|
||||
- name: depaware tailscaled
|
||||
run: go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
|
||||
|
||||
- name: depaware tailscale
|
||||
run: go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscale
|
||||
|
||||
64
.github/workflows/go-licenses.yml
vendored
64
.github/workflows/go-licenses.yml
vendored
@@ -1,64 +0,0 @@
|
||||
name: go-licenses
|
||||
|
||||
on:
|
||||
# run action when a change lands in the main branch which updates go.mod or
|
||||
# our license template file. Also allow manual triggering.
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- go.mod
|
||||
- .github/licenses.tmpl
|
||||
- .github/workflows/go-licenses.yml
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tailscale:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install go-licenses
|
||||
run: |
|
||||
go install github.com/google/go-licenses@v1.2.2-0.20220825154955-5eedde1c6584
|
||||
|
||||
- name: Run go-licenses
|
||||
env:
|
||||
# include all build tags to include platform-specific dependencies
|
||||
GOFLAGS: "-tags=android,cgo,darwin,freebsd,ios,js,linux,openbsd,wasm,windows"
|
||||
run: |
|
||||
[ -d licenses ] || mkdir licenses
|
||||
go-licenses report tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled > licenses/tailscale.md --template .github/licenses.tmpl
|
||||
|
||||
- name: Get access token
|
||||
uses: tibdex/github-app-token@f717b5ecd4534d3c4df4ce9b5c1c2214f0f7cd06 # v1.6.0
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.LICENSING_APP_ID }}
|
||||
installation_id: ${{ secrets.LICENSING_APP_INSTALLATION_ID }}
|
||||
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Send pull request
|
||||
uses: peter-evans/create-pull-request@18f90432bedd2afd6a825469ffd38aa24712a91d #v4.1.1
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
author: License Updater <noreply@tailscale.com>
|
||||
committer: License Updater <noreply@tailscale.com>
|
||||
branch: licenses/cli
|
||||
commit-message: "licenses: update tailscale{,d} licenses"
|
||||
title: "licenses: update tailscale{,d} licenses"
|
||||
body: Triggered by ${{ github.repository }}@${{ github.sha }}
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
team-reviewers: opensource-license-reviewers
|
||||
4
.github/workflows/go_generate.yml
vendored
4
.github/workflows/go_generate.yml
vendored
@@ -9,10 +9,6 @@ on:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/license.yml
vendored
4
.github/workflows/license.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/linux-race.yml
vendored
4
.github/workflows/linux-race.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/linux32.yml
vendored
4
.github/workflows/linux32.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
112
.github/workflows/static-analysis.yml
vendored
112
.github/workflows/static-analysis.yml
vendored
@@ -1,112 +0,0 @@
|
||||
name: static-analysis
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
gofmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
- name: Run gofmt (goimports)
|
||||
run: go run golang.org/x/tools/cmd/goimports -d --format-only .
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"attachments": [{
|
||||
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
|
||||
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
|
||||
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
|
||||
"color": "danger"
|
||||
}]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: failure() && github.event_name == 'push'
|
||||
|
||||
vet:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
- name: Run go vet
|
||||
run: go vet ./...
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"attachments": [{
|
||||
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
|
||||
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
|
||||
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
|
||||
"color": "danger"
|
||||
}]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: failure() && github.event_name == 'push'
|
||||
|
||||
staticcheck:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: [amd64]
|
||||
include:
|
||||
- goos: windows
|
||||
goarch: 386
|
||||
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install staticcheck
|
||||
run: "GOBIN=~/.local/bin go install honnef.co/go/tools/cmd/staticcheck"
|
||||
|
||||
- name: Print staticcheck version
|
||||
run: "staticcheck -version"
|
||||
|
||||
- name: "Run staticcheck (${{ matrix.goos }}/${{ matrix.goarch }})"
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
|
||||
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"attachments": [{
|
||||
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
|
||||
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
|
||||
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
|
||||
"color": "danger"
|
||||
}]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: failure() && github.event_name == 'push'
|
||||
73
.github/workflows/staticcheck.yml
vendored
Normal file
73
.github/workflows/staticcheck.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: staticcheck
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run gofmt (goimports)
|
||||
run: go run golang.org/x/tools/cmd/goimports -d --format-only .
|
||||
|
||||
- name: Run go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Install staticcheck
|
||||
run: "GOBIN=~/.local/bin go install honnef.co/go/tools/cmd/staticcheck"
|
||||
|
||||
- name: Print staticcheck version
|
||||
run: "staticcheck -version"
|
||||
|
||||
- name: Run staticcheck (linux/amd64)
|
||||
env:
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
|
||||
|
||||
- name: Run staticcheck (darwin/amd64)
|
||||
env:
|
||||
GOOS: darwin
|
||||
GOARCH: amd64
|
||||
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
|
||||
|
||||
- name: Run staticcheck (windows/amd64)
|
||||
env:
|
||||
GOOS: windows
|
||||
GOARCH: amd64
|
||||
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
|
||||
|
||||
- name: Run staticcheck (windows/386)
|
||||
env:
|
||||
GOOS: windows
|
||||
GOARCH: "386"
|
||||
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
|
||||
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"attachments": [{
|
||||
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
|
||||
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
|
||||
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
|
||||
"color": "danger"
|
||||
}]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: failure() && github.event_name == 'push'
|
||||
4
.github/workflows/vm.yml
vendored
4
.github/workflows/vm.yml
vendored
@@ -5,10 +5,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ubuntu2004-LTS-cloud-base:
|
||||
runs-on: [ self-hosted, linux, vm ]
|
||||
|
||||
77
.github/workflows/windows-race.yml
vendored
Normal file
77
.github/workflows/windows-race.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Windows race
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
|
||||
steps:
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Restore Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
# Note: unlike some other setups, this is only grabbing the mod download
|
||||
# cache, rather than the whole mod directory, as the download cache
|
||||
# contains zips that can be unpacked in parallel faster than they can be
|
||||
# fetched and extracted by tar
|
||||
path: |
|
||||
~/go/pkg/mod/cache
|
||||
~\AppData\Local\go-build
|
||||
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
# The -race- here ensures that non-race builds and race builds do not
|
||||
# overwrite each others cache, as while they share some files, they
|
||||
# differ in most by volume (build cache).
|
||||
# TODO(raggi): add a go version here.
|
||||
key: ${{ runner.os }}-go-2-race-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Print toolchain details
|
||||
run: gcc -v
|
||||
|
||||
# There is currently an issue in the race detector in Go on Windows when
|
||||
# used with a newer version of GCC.
|
||||
# See https://github.com/tailscale/tailscale/issues/4926.
|
||||
- name: Downgrade MinGW
|
||||
shell: bash
|
||||
run: |
|
||||
choco install mingw --version 10.2.0 --allow-downgrade
|
||||
|
||||
- name: Test with -race flag
|
||||
# Don't use -bench=. -benchtime=1x.
|
||||
# Somewhere in the layers (powershell?)
|
||||
# the equals signs cause great confusion.
|
||||
run: go test -race -bench . -benchtime 1x ./...
|
||||
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"attachments": [{
|
||||
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
|
||||
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
|
||||
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
|
||||
"color": "danger"
|
||||
}]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: failure() && github.event_name == 'push'
|
||||
|
||||
4
.github/workflows/windows.yml
vendored
4
.github/workflows/windows.yml
vendored
@@ -8,10 +8,6 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
|
||||
17
Makefile
17
Makefile
@@ -12,16 +12,12 @@ tidy:
|
||||
./tool/go mod tidy
|
||||
|
||||
updatedeps:
|
||||
./tool/go run github.com/tailscale/depaware --update \
|
||||
tailscale.com/cmd/tailscaled \
|
||||
tailscale.com/cmd/tailscale \
|
||||
tailscale.com/cmd/derper
|
||||
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscaled
|
||||
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscale
|
||||
|
||||
depaware:
|
||||
./tool/go run github.com/tailscale/depaware --check \
|
||||
tailscale.com/cmd/tailscaled \
|
||||
tailscale.com/cmd/tailscale \
|
||||
tailscale.com/cmd/derper
|
||||
./tool/go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
|
||||
./tool/go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscale
|
||||
|
||||
buildwindows:
|
||||
GOOS=windows GOARCH=amd64 ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
|
||||
@@ -32,13 +28,10 @@ build386:
|
||||
buildlinuxarm:
|
||||
GOOS=linux GOARCH=arm ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
|
||||
|
||||
buildwasm:
|
||||
GOOS=js GOARCH=wasm ./tool/go install ./cmd/tsconnect/wasm ./cmd/tailscale/cli
|
||||
|
||||
buildmultiarchimage:
|
||||
./build_docker.sh
|
||||
|
||||
check: staticcheck vet depaware buildwindows build386 buildlinuxarm buildwasm
|
||||
check: staticcheck vet depaware buildwindows build386 buildlinuxarm
|
||||
|
||||
staticcheck:
|
||||
./tool/go run honnef.co/go/tools/cmd/staticcheck -- $$(./tool/go list ./... | grep -v tempfork)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// WriteFile writes data to filename+some suffix, then renames it
|
||||
// into filename. The perm argument is ignored on Windows.
|
||||
// into filename.
|
||||
func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
|
||||
f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".tmp")
|
||||
if err != nil {
|
||||
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
)
|
||||
|
||||
// defaultLocalClient is the default LocalClient when using the legacy
|
||||
@@ -681,42 +680,6 @@ func (lc *LocalClient) Ping(ctx context.Context, ip netip.Addr, pingtype tailcfg
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// NetworkLockStatus fetches information about the tailnet key authority, if one is configured.
|
||||
func (lc *LocalClient) NetworkLockStatus(ctx context.Context) (*ipnstate.NetworkLockStatus, error) {
|
||||
body, err := lc.send(ctx, "GET", "/localapi/v0/tka/status", 200, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error: %w", err)
|
||||
}
|
||||
pr := new(ipnstate.NetworkLockStatus)
|
||||
if err := json.Unmarshal(body, pr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// NetworkLockInit initializes the tailnet key authority.
|
||||
func (lc *LocalClient) NetworkLockInit(ctx context.Context, keys []tka.Key) (*ipnstate.NetworkLockStatus, error) {
|
||||
var b bytes.Buffer
|
||||
type initRequest struct {
|
||||
Keys []tka.Key
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(&b).Encode(initRequest{Keys: keys}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := lc.send(ctx, "POST", "/localapi/v0/tka/init", 200, &b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error: %w", err)
|
||||
}
|
||||
|
||||
pr := new(ipnstate.NetworkLockStatus)
|
||||
if err := json.Unmarshal(body, pr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// tailscaledConnectHint gives a little thing about why tailscaled (or
|
||||
// platform equivalent) is not answering localapi connections.
|
||||
//
|
||||
|
||||
@@ -12,12 +12,11 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"tailscale.com/syncs"
|
||||
)
|
||||
|
||||
var dnsCache syncs.AtomicValue[[]byte]
|
||||
var dnsCache atomic.Value // of []byte
|
||||
|
||||
var bootstrapDNSRequests = expvar.NewInt("counter_bootstrap_dns_requests")
|
||||
|
||||
@@ -59,7 +58,7 @@ func refreshBootstrapDNS() {
|
||||
func handleBootstrapDNS(w http.ResponseWriter, r *http.Request) {
|
||||
bootstrapDNSRequests.Add(1)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
j := dnsCache.Load()
|
||||
j, _ := dnsCache.Load().([]byte)
|
||||
// Bootstrap DNS requests occur cross-regions,
|
||||
// and are randomized per request,
|
||||
// so keeping a connection open is pointlessly expensive.
|
||||
|
||||
@@ -20,11 +20,6 @@ var unsafeHostnameCharacters = regexp.MustCompile(`[^a-zA-Z0-9-\.]`)
|
||||
|
||||
type certProvider interface {
|
||||
// TLSConfig creates a new TLS config suitable for net/http.Server servers.
|
||||
//
|
||||
// The returned Config must have a GetCertificate function set and that
|
||||
// function must return a unique *tls.Certificate for each call. The
|
||||
// returned *tls.Certificate will be mutated by the caller to append to the
|
||||
// (*tls.Certificate).Certificate field.
|
||||
TLSConfig() *tls.Config
|
||||
// HTTPHandler handle ACME related request, if any.
|
||||
HTTPHandler(fallback http.Handler) http.Handler
|
||||
@@ -92,13 +87,7 @@ func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certif
|
||||
if hi.ServerName != m.hostname {
|
||||
return nil, fmt.Errorf("cert mismatch with hostname: %q", hi.ServerName)
|
||||
}
|
||||
|
||||
// Return a shallow copy of the cert so the caller can append to its
|
||||
// Certificate field.
|
||||
certCopy := new(tls.Certificate)
|
||||
*certCopy = *m.cert
|
||||
certCopy.Certificate = certCopy.Certificate[:len(certCopy.Certificate):len(certCopy.Certificate)]
|
||||
return certCopy, nil
|
||||
return m.cert, nil
|
||||
}
|
||||
|
||||
func (m *manualCertManager) HTTPHandler(fallback http.Handler) http.Handler {
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depaware)
|
||||
|
||||
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||
💣 go4.org/mem from tailscale.com/client/tailscale+
|
||||
go4.org/netipx from tailscale.com/wgengine/filter
|
||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||
nhooyr.io/websocket from tailscale.com/cmd/derper+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/atomicfile from tailscale.com/cmd/derper+
|
||||
tailscale.com/client/tailscale from tailscale.com/derp
|
||||
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale
|
||||
tailscale.com/derp from tailscale.com/cmd/derper+
|
||||
tailscale.com/derp/derphttp from tailscale.com/cmd/derper
|
||||
tailscale.com/disco from tailscale.com/derp
|
||||
tailscale.com/envknob from tailscale.com/derp+
|
||||
tailscale.com/hostinfo from tailscale.com/net/interfaces+
|
||||
tailscale.com/ipn from tailscale.com/client/tailscale
|
||||
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
||||
💣 tailscale.com/metrics from tailscale.com/cmd/derper+
|
||||
tailscale.com/net/dnscache from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/flowtrack from tailscale.com/net/packet+
|
||||
💣 tailscale.com/net/interfaces from tailscale.com/net/netns+
|
||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||
tailscale.com/net/netknob from tailscale.com/net/netns
|
||||
tailscale.com/net/netns from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/netutil from tailscale.com/client/tailscale
|
||||
tailscale.com/net/packet from tailscale.com/wgengine/filter
|
||||
tailscale.com/net/stun from tailscale.com/cmd/derper
|
||||
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/tsaddr from tailscale.com/ipn+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||
tailscale.com/paths from tailscale.com/client/tailscale
|
||||
tailscale.com/safesocket from tailscale.com/client/tailscale
|
||||
tailscale.com/syncs from tailscale.com/cmd/derper+
|
||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||
tailscale.com/tka from tailscale.com/client/tailscale
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
||||
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
||||
tailscale.com/tsweb from tailscale.com/cmd/derper
|
||||
tailscale.com/types/dnstype from tailscale.com/tailcfg
|
||||
tailscale.com/types/empty from tailscale.com/ipn
|
||||
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
|
||||
tailscale.com/types/key from tailscale.com/cmd/derper+
|
||||
tailscale.com/types/logger from tailscale.com/cmd/derper+
|
||||
tailscale.com/types/netmap from tailscale.com/ipn
|
||||
tailscale.com/types/opt from tailscale.com/client/tailscale+
|
||||
tailscale.com/types/pad32 from tailscale.com/derp
|
||||
tailscale.com/types/persist from tailscale.com/ipn
|
||||
tailscale.com/types/preftype from tailscale.com/ipn
|
||||
tailscale.com/types/structs from tailscale.com/ipn+
|
||||
tailscale.com/types/tkatype from tailscale.com/types/key+
|
||||
tailscale.com/types/views from tailscale.com/ipn/ipnstate+
|
||||
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
|
||||
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
|
||||
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
||||
W tailscale.com/util/endian from tailscale.com/net/netns
|
||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||
tailscale.com/util/singleflight from tailscale.com/net/dnscache
|
||||
L tailscale.com/util/strs from tailscale.com/hostinfo
|
||||
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
|
||||
tailscale.com/version from tailscale.com/derp+
|
||||
tailscale.com/version/distro from tailscale.com/hostinfo+
|
||||
tailscale.com/wgengine/filter from tailscale.com/types/netmap
|
||||
golang.org/x/crypto/acme from golang.org/x/crypto/acme/autocert
|
||||
golang.org/x/crypto/acme/autocert from tailscale.com/cmd/derper
|
||||
golang.org/x/crypto/argon2 from tailscale.com/tka
|
||||
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/crypto/blake2s from tailscale.com/tka
|
||||
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
|
||||
golang.org/x/crypto/chacha20poly1305 from crypto/tls
|
||||
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from crypto/tls+
|
||||
golang.org/x/crypto/hkdf from crypto/tls
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||
golang.org/x/net/dns/dnsmessage from net+
|
||||
golang.org/x/net/http/httpguts from net/http
|
||||
golang.org/x/net/http/httpproxy from net/http
|
||||
golang.org/x/net/http2/hpack from net/http
|
||||
golang.org/x/net/idna from golang.org/x/crypto/acme/autocert+
|
||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||
D golang.org/x/net/route from net+
|
||||
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
|
||||
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
||||
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+
|
||||
W golang.org/x/sys/windows from golang.org/x/sys/windows/registry+
|
||||
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
|
||||
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
|
||||
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
|
||||
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
|
||||
golang.org/x/text/unicode/norm from golang.org/x/net/idna
|
||||
golang.org/x/time/rate from tailscale.com/cmd/derper+
|
||||
bufio from compress/flate+
|
||||
bytes from bufio+
|
||||
compress/flate from compress/gzip+
|
||||
compress/gzip from internal/profile+
|
||||
container/list from crypto/tls+
|
||||
context from crypto/tls+
|
||||
crypto from crypto/ecdsa+
|
||||
crypto/aes from crypto/ecdsa+
|
||||
crypto/cipher from crypto/aes+
|
||||
crypto/des from crypto/tls+
|
||||
crypto/dsa from crypto/x509
|
||||
crypto/ecdsa from crypto/tls+
|
||||
crypto/ed25519 from crypto/tls+
|
||||
crypto/elliptic from crypto/ecdsa+
|
||||
crypto/hmac from crypto/tls+
|
||||
crypto/md5 from crypto/tls+
|
||||
crypto/rand from crypto/ed25519+
|
||||
crypto/rc4 from crypto/tls
|
||||
crypto/rsa from crypto/tls+
|
||||
crypto/sha1 from crypto/tls+
|
||||
crypto/sha256 from crypto/tls+
|
||||
crypto/sha512 from crypto/ecdsa+
|
||||
crypto/subtle from crypto/aes+
|
||||
crypto/tls from golang.org/x/crypto/acme+
|
||||
crypto/x509 from crypto/tls+
|
||||
crypto/x509/pkix from crypto/x509+
|
||||
embed from crypto/internal/nistec+
|
||||
encoding from encoding/json+
|
||||
encoding/asn1 from crypto/x509+
|
||||
encoding/base32 from tailscale.com/tka
|
||||
encoding/base64 from encoding/json+
|
||||
encoding/binary from compress/gzip+
|
||||
encoding/hex from crypto/x509+
|
||||
encoding/json from expvar+
|
||||
encoding/pem from crypto/tls+
|
||||
errors from bufio+
|
||||
expvar from tailscale.com/cmd/derper+
|
||||
flag from tailscale.com/cmd/derper
|
||||
fmt from compress/flate+
|
||||
hash from crypto+
|
||||
hash/crc32 from compress/gzip+
|
||||
hash/maphash from go4.org/mem
|
||||
html from net/http/pprof+
|
||||
io from bufio+
|
||||
io/fs from crypto/x509+
|
||||
io/ioutil from github.com/mitchellh/go-ps+
|
||||
log from expvar+
|
||||
math from compress/flate+
|
||||
math/big from crypto/dsa+
|
||||
math/bits from compress/flate+
|
||||
math/rand from github.com/mdlayher/netlink+
|
||||
mime from mime/multipart+
|
||||
mime/multipart from net/http
|
||||
mime/quotedprintable from mime/multipart
|
||||
net from crypto/tls+
|
||||
net/http from expvar+
|
||||
net/http/httptrace from net/http+
|
||||
net/http/internal from net/http
|
||||
net/http/pprof from tailscale.com/tsweb
|
||||
net/netip from go4.org/netipx+
|
||||
net/textproto from golang.org/x/net/http/httpguts+
|
||||
net/url from crypto/x509+
|
||||
os from crypto/rand+
|
||||
os/exec from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
|
||||
path from golang.org/x/crypto/acme/autocert+
|
||||
path/filepath from crypto/x509+
|
||||
reflect from crypto/x509+
|
||||
regexp from internal/profile+
|
||||
regexp/syntax from regexp
|
||||
runtime/debug from golang.org/x/crypto/acme+
|
||||
runtime/pprof from net/http/pprof
|
||||
runtime/trace from net/http/pprof
|
||||
sort from compress/flate+
|
||||
strconv from compress/flate+
|
||||
strings from bufio+
|
||||
sync from compress/flate+
|
||||
sync/atomic from context+
|
||||
syscall from crypto/rand+
|
||||
text/tabwriter from runtime/pprof
|
||||
time from compress/gzip+
|
||||
unicode from bytes+
|
||||
unicode/utf16 from crypto/x509+
|
||||
unicode/utf8 from bufio+
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -357,8 +356,7 @@ func serverSTUNListener(ctx context.Context, pc *net.UDPConn) {
|
||||
} else {
|
||||
stunIPv6.Add(1)
|
||||
}
|
||||
addr, _ := netip.AddrFromSlice(ua.IP)
|
||||
res := stun.Response(txid, netip.AddrPortFrom(addr, uint16(ua.Port)))
|
||||
res := stun.Response(txid, ua.IP, uint16(ua.Port))
|
||||
_, err = pc.WriteTo(res, ua)
|
||||
if err != nil {
|
||||
stunWriteError.Add(1)
|
||||
|
||||
@@ -360,7 +360,7 @@ func probeUDP(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (la
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
txBack, _, err := stun.ParseResponse(buf[:n])
|
||||
txBack, _, _, err := stun.ParseResponse(buf[:n])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parsing STUN response from %v: %v", ip, err)
|
||||
}
|
||||
|
||||
@@ -63,24 +63,17 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
// tailnet of connected node. When accessing shared nodes, this
|
||||
// will be empty because the tailnet of the sharee is not exposed.
|
||||
var tailnet string
|
||||
|
||||
if !info.Node.Hostinfo.ShareeNode() {
|
||||
var ok bool
|
||||
_, tailnet, ok = strings.Cut(info.Node.Name, info.Node.ComputedName+".")
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
|
||||
return
|
||||
}
|
||||
tailnet, _, ok = strings.Cut(tailnet, ".beta.tailscale.net")
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
|
||||
return
|
||||
}
|
||||
_, tailnet, ok := strings.Cut(info.Node.Name, info.Node.ComputedName+".")
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
|
||||
return
|
||||
}
|
||||
tailnet, _, ok = strings.Cut(tailnet, ".beta.tailscale.net")
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if expectedTailnet := r.Header.Get("Expected-Tailnet"); expectedTailnet != "" && expectedTailnet != tailnet {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
|
||||
@@ -169,8 +170,6 @@ change in the future.
|
||||
fileCmd,
|
||||
bugReportCmd,
|
||||
certCmd,
|
||||
netlockCmd,
|
||||
licensesCmd,
|
||||
},
|
||||
FlagSet: rootfs,
|
||||
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
||||
@@ -231,6 +230,8 @@ var rootArgs struct {
|
||||
socket string
|
||||
}
|
||||
|
||||
var gotSignal atomic.Bool
|
||||
|
||||
func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context, context.CancelFunc) {
|
||||
s := safesocket.DefaultConnectionStrategy(rootArgs.socket)
|
||||
c, err := safesocket.Connect(s)
|
||||
@@ -256,6 +257,7 @@ func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context
|
||||
signal.Reset(syscall.SIGINT, syscall.SIGTERM)
|
||||
return
|
||||
}
|
||||
gotSignal.Set(true)
|
||||
c.Close()
|
||||
cancel()
|
||||
}()
|
||||
|
||||
@@ -62,9 +62,6 @@ func runConfigureHost(ctx context.Context, args []string) error {
|
||||
return fmt.Errorf("creating /dev/net/tun: %v, %s", err, out)
|
||||
}
|
||||
}
|
||||
if err := os.Chmod("/dev/net", 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod("/dev/net/tun", 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -489,7 +489,7 @@ func runTS2021(ctx context.Context, args []string) error {
|
||||
return c, err
|
||||
}
|
||||
|
||||
conn, err := controlhttp.Dial(ctx, ts2021Args.host, "80", "443", machinePrivate, keys.PublicKey, uint16(ts2021Args.version), dialFunc)
|
||||
conn, err := controlhttp.Dial(ctx, net.JoinHostPort(ts2021Args.host, "80"), machinePrivate, keys.PublicKey, uint16(ts2021Args.version), dialFunc)
|
||||
log.Printf("controlhttp.Dial = %p, %v", conn, err)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
)
|
||||
|
||||
var licensesCmd = &ffcli.Command{
|
||||
Name: "licenses",
|
||||
ShortUsage: "licenses",
|
||||
ShortHelp: "Get open source license information",
|
||||
LongHelp: "Get open source license information",
|
||||
Exec: runLicenses,
|
||||
}
|
||||
|
||||
func runLicenses(ctx context.Context, args []string) error {
|
||||
var licenseURL string
|
||||
switch runtime.GOOS {
|
||||
case "android":
|
||||
licenseURL = "https://tailscale.com/licenses/android"
|
||||
case "darwin", "ios":
|
||||
licenseURL = "https://tailscale.com/licenses/apple"
|
||||
case "windows":
|
||||
licenseURL = "https://tailscale.com/licenses/windows"
|
||||
default:
|
||||
licenseURL = "https://tailscale.com/licenses/tailscale"
|
||||
}
|
||||
|
||||
outln(`
|
||||
Tailscale wouldn't be possible without the contributions of thousands of open
|
||||
source developers. To see the open source packages included in Tailscale and
|
||||
their respective license information, visit:
|
||||
|
||||
` + licenseURL)
|
||||
return nil
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
var netlockCmd = &ffcli.Command{
|
||||
Name: "lock",
|
||||
ShortUsage: "lock <sub-command> <arguments>",
|
||||
ShortHelp: "Manipulate the tailnet key authority",
|
||||
Subcommands: []*ffcli.Command{nlInitCmd, nlStatusCmd},
|
||||
Exec: runNetworkLockStatus,
|
||||
}
|
||||
|
||||
var nlInitCmd = &ffcli.Command{
|
||||
Name: "init",
|
||||
ShortUsage: "init <public-key>...",
|
||||
ShortHelp: "Initialize the tailnet key authority",
|
||||
Exec: runNetworkLockInit,
|
||||
}
|
||||
|
||||
func runNetworkLockInit(ctx context.Context, args []string) error {
|
||||
st, err := localClient.NetworkLockStatus(ctx)
|
||||
if err != nil {
|
||||
return fixTailscaledConnectError(err)
|
||||
}
|
||||
if st.Enabled {
|
||||
return errors.New("network-lock is already enabled")
|
||||
}
|
||||
|
||||
// Parse the set of initially-trusted keys.
|
||||
// Keys are specified using their key.NLPublic.MarshalText representation,
|
||||
// with an optional '?<votes>' suffix.
|
||||
var keys []tka.Key
|
||||
for i, a := range args {
|
||||
var key key.NLPublic
|
||||
spl := strings.SplitN(a, "?", 2)
|
||||
if err := key.UnmarshalText([]byte(spl[0])); err != nil {
|
||||
return fmt.Errorf("parsing key %d: %v", i+1, err)
|
||||
}
|
||||
|
||||
k := tka.Key{
|
||||
Kind: tka.Key25519,
|
||||
Public: key.Verifier(),
|
||||
Votes: 1,
|
||||
}
|
||||
if len(spl) > 1 {
|
||||
votes, err := strconv.Atoi(spl[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing key %d votes: %v", i+1, err)
|
||||
}
|
||||
k.Votes = uint(votes)
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
status, err := localClient.NetworkLockInit(ctx, keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Status: %+v\n\n", status)
|
||||
return nil
|
||||
}
|
||||
|
||||
var nlStatusCmd = &ffcli.Command{
|
||||
Name: "status",
|
||||
ShortUsage: "status",
|
||||
ShortHelp: "Outputs the state of network lock",
|
||||
Exec: runNetworkLockStatus,
|
||||
}
|
||||
|
||||
func runNetworkLockStatus(ctx context.Context, args []string) error {
|
||||
st, err := localClient.NetworkLockStatus(ctx)
|
||||
if err != nil {
|
||||
return fixTailscaledConnectError(err)
|
||||
}
|
||||
if st.Enabled {
|
||||
fmt.Println("Network-lock is ENABLED.")
|
||||
} else {
|
||||
fmt.Println("Network-lock is NOT enabled.")
|
||||
}
|
||||
p, err := st.PublicKey.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("our public-key: %s\n", p)
|
||||
return nil
|
||||
}
|
||||
@@ -252,7 +252,7 @@ func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
|
||||
if default4 && !default6 {
|
||||
return nil, fmt.Errorf("%s advertised without its IPv6 counterpart, please also advertise %s", ipv4default, ipv6default)
|
||||
} else if default6 && !default4 {
|
||||
return nil, fmt.Errorf("%s advertised without its IPv4 counterpart, please also advertise %s", ipv6default, ipv4default)
|
||||
return nil, fmt.Errorf("%s advertised without its IPv6 counterpart, please also advertise %s", ipv6default, ipv4default)
|
||||
}
|
||||
}
|
||||
if advertiseDefaultRoute {
|
||||
|
||||
@@ -64,7 +64,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
||||
tailscale.com/net/netutil from tailscale.com/client/tailscale+
|
||||
tailscale.com/net/packet from tailscale.com/wgengine/filter
|
||||
tailscale.com/net/ping from tailscale.com/net/netcheck
|
||||
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/stun from tailscale.com/net/netcheck
|
||||
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp+
|
||||
@@ -72,9 +71,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||
tailscale.com/paths from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/syncs from tailscale.com/net/netcheck+
|
||||
tailscale.com/syncs from tailscale.com/net/netcheck
|
||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/tka from tailscale.com/client/tailscale+
|
||||
tailscale.com/tka from tailscale.com/types/key
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
||||
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
||||
@@ -90,7 +89,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/types/persist from tailscale.com/ipn
|
||||
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/types/structs from tailscale.com/ipn+
|
||||
tailscale.com/types/tkatype from tailscale.com/types/key+
|
||||
tailscale.com/types/views from tailscale.com/tailcfg+
|
||||
tailscale.com/util/clientmetric from tailscale.com/net/netcheck+
|
||||
tailscale.com/util/cloudenv from tailscale.com/net/dnscache+
|
||||
@@ -99,9 +97,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
W tailscale.com/util/endian from tailscale.com/net/netns
|
||||
tailscale.com/util/groupmember from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/util/lineread from tailscale.com/net/interfaces+
|
||||
tailscale.com/util/mak from tailscale.com/net/netcheck
|
||||
tailscale.com/util/singleflight from tailscale.com/net/dnscache
|
||||
L tailscale.com/util/strs from tailscale.com/hostinfo
|
||||
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
|
||||
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
|
||||
@@ -118,15 +114,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||
golang.org/x/net/dns/dnsmessage from net+
|
||||
golang.org/x/net/http/httpguts from net/http+
|
||||
golang.org/x/net/http/httpproxy from net/http
|
||||
golang.org/x/net/http2/hpack from net/http
|
||||
golang.org/x/net/icmp from tailscale.com/net/ping
|
||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||
golang.org/x/net/ipv4 from golang.org/x/net/icmp+
|
||||
golang.org/x/net/ipv6 from golang.org/x/net/icmp
|
||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||
D golang.org/x/net/route from net+
|
||||
golang.org/x/sync/errgroup from tailscale.com/derp+
|
||||
|
||||
@@ -227,7 +227,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver
|
||||
tailscale.com/net/netutil from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/packet from tailscale.com/net/tstun+
|
||||
tailscale.com/net/ping from tailscale.com/net/netcheck
|
||||
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
|
||||
@@ -245,7 +244,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/syncs from tailscale.com/net/netcheck+
|
||||
tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+
|
||||
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
|
||||
tailscale.com/tka from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/tka from tailscale.com/types/key+
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+
|
||||
@@ -264,7 +263,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/preftype from tailscale.com/ipn+
|
||||
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/tkatype from tailscale.com/tka+
|
||||
tailscale.com/types/views from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/util/clientmetric from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+
|
||||
@@ -273,7 +271,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
||||
LW tailscale.com/util/endian from tailscale.com/net/dns+
|
||||
tailscale.com/util/groupmember from tailscale.com/ipn/ipnserver
|
||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||
tailscale.com/util/mak from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/multierr from tailscale.com/control/controlclient+
|
||||
@@ -281,7 +278,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
|
||||
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
||||
tailscale.com/util/singleflight from tailscale.com/control/controlclient+
|
||||
L tailscale.com/util/strs from tailscale.com/hostinfo
|
||||
tailscale.com/util/systemd from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/uniq from tailscale.com/wgengine/magicsock
|
||||
💣 tailscale.com/util/winutil from tailscale.com/cmd/tailscaled+
|
||||
@@ -316,7 +312,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+
|
||||
golang.org/x/exp/constraints from golang.org/x/exp/slices
|
||||
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+
|
||||
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal
|
||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||
golang.org/x/net/dns/dnsmessage from net+
|
||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
||||
@@ -324,9 +320,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/net/http2 from golang.org/x/net/http2/h2c+
|
||||
golang.org/x/net/http2/h2c from tailscale.com/ipn/ipnlocal
|
||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
||||
golang.org/x/net/icmp from tailscale.com/net/ping
|
||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||
golang.org/x/net/ipv4 from golang.zx2c4.com/wireguard/device+
|
||||
golang.org/x/net/ipv4 from golang.zx2c4.com/wireguard/device
|
||||
golang.org/x/net/ipv6 from golang.zx2c4.com/wireguard/device+
|
||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||
D golang.org/x/net/route from net+
|
||||
|
||||
@@ -481,7 +481,7 @@ func shouldWrapNetstack() bool {
|
||||
return true
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "windows", "darwin", "freebsd", "openbsd":
|
||||
case "windows", "darwin", "freebsd":
|
||||
// Enable on Windows and tailscaled-on-macOS (this doesn't
|
||||
// affect the GUI clients), and on FreeBSD.
|
||||
return true
|
||||
|
||||
5
cmd/tsconnect/.gitignore
vendored
5
cmd/tsconnect/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
src/wasm_exec.js
|
||||
src/main.wasm
|
||||
node_modules/
|
||||
/dist
|
||||
/pkg
|
||||
dist/
|
||||
|
||||
@@ -28,13 +28,3 @@ To serve them, run:
|
||||
```
|
||||
|
||||
By default the build output is placed in the `dist/` directory and embedded in the binary, but this can be controlled by the `-distdir` flag. The `-addr` flag controls the interface and port that the serve listens on.
|
||||
|
||||
# Library / NPM Package
|
||||
|
||||
The client is also available as an NPM package. To build it, run:
|
||||
|
||||
```
|
||||
./tool/go run ./cmd/tsconnect build-pkg
|
||||
```
|
||||
|
||||
That places the output in the `pkg/` directory, which may then be uploaded to a package registry (or installed from the file path directly).
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
)
|
||||
|
||||
func runBuildPkg() {
|
||||
buildOptions, err := commonSetup(prodMode)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot setup: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Linting...\n")
|
||||
if err := runYarn("lint"); err != nil {
|
||||
log.Fatalf("Linting failed: %v", err)
|
||||
}
|
||||
|
||||
if err := cleanDir(*pkgDir, "package.json"); err != nil {
|
||||
log.Fatalf("Cannot clean %s: %v", *pkgDir, err)
|
||||
}
|
||||
|
||||
buildOptions.EntryPoints = []string{"src/pkg/pkg.ts", "src/pkg/pkg.css"}
|
||||
buildOptions.Outdir = *pkgDir
|
||||
buildOptions.Format = esbuild.FormatESModule
|
||||
buildOptions.AssetNames = "[name]"
|
||||
buildOptions.Write = true
|
||||
buildOptions.MinifyWhitespace = true
|
||||
buildOptions.MinifyIdentifiers = true
|
||||
buildOptions.MinifySyntax = true
|
||||
|
||||
runEsbuild(*buildOptions)
|
||||
|
||||
log.Printf("Generating types...\n")
|
||||
if err := runYarn("pkg-types"); err != nil {
|
||||
log.Fatalf("Type generation failed: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
"tailscale.com/util/precompress"
|
||||
)
|
||||
|
||||
@@ -27,7 +28,7 @@ func runBuild() {
|
||||
log.Fatalf("Linting failed: %v", err)
|
||||
}
|
||||
|
||||
if err := cleanDir(*distDir, "placeholder"); err != nil {
|
||||
if err := cleanDist(); err != nil {
|
||||
log.Fatalf("Cannot clean %s: %v", *distDir, err)
|
||||
}
|
||||
|
||||
@@ -40,7 +41,21 @@ func runBuild() {
|
||||
buildOptions.AssetNames = "[name]-[hash]"
|
||||
buildOptions.Metafile = true
|
||||
|
||||
result := runEsbuild(*buildOptions)
|
||||
log.Printf("Running esbuild...\n")
|
||||
result := esbuild.Build(*buildOptions)
|
||||
if len(result.Errors) > 0 {
|
||||
log.Printf("ESBuild Error:\n")
|
||||
for _, e := range result.Errors {
|
||||
log.Printf("%v", e)
|
||||
}
|
||||
log.Fatal("Build failed")
|
||||
}
|
||||
if len(result.Warnings) > 0 {
|
||||
log.Printf("ESBuild Warnings:\n")
|
||||
for _, w := range result.Warnings {
|
||||
log.Printf("%v", w)
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve build metadata so we can extract hashed file names for serving.
|
||||
metadataBytes, err := fixEsbuildMetadataPaths(result.Metafile)
|
||||
@@ -83,6 +98,8 @@ func fixEsbuildMetadataPaths(metadataStr string) ([]byte, error) {
|
||||
return json.Marshal(metadata)
|
||||
}
|
||||
|
||||
// cleanDist removes files from the dist build directory, except the placeholder
|
||||
// one that we keep to make sure Git still creates the directory.
|
||||
func cleanDist() error {
|
||||
log.Printf("Cleaning %s...\n", *distDir)
|
||||
files, err := os.ReadDir(*distDir)
|
||||
|
||||
@@ -6,7 +6,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -17,7 +16,6 @@ import (
|
||||
"time"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,152 +32,73 @@ func commonSetup(dev bool) (*esbuild.BuildOptions, error) {
|
||||
return nil, fmt.Errorf("Cannot change cwd: %w", err)
|
||||
}
|
||||
}
|
||||
if err := installJSDeps(); err != nil {
|
||||
return nil, fmt.Errorf("Cannot install JS deps: %w", err)
|
||||
if err := buildDeps(dev); err != nil {
|
||||
return nil, fmt.Errorf("Cannot build deps: %w", err)
|
||||
}
|
||||
|
||||
return &esbuild.BuildOptions{
|
||||
EntryPoints: []string{"src/app/index.ts", "src/app/index.css"},
|
||||
EntryPoints: []string{"src/index.ts", "src/index.css"},
|
||||
Loader: map[string]esbuild.Loader{".wasm": esbuild.LoaderFile},
|
||||
Outdir: *distDir,
|
||||
Bundle: true,
|
||||
Sourcemap: esbuild.SourceMapLinked,
|
||||
LogLevel: esbuild.LogLevelInfo,
|
||||
Define: map[string]string{"DEBUG": strconv.FormatBool(dev)},
|
||||
Target: esbuild.ES2017,
|
||||
Plugins: []esbuild.Plugin{
|
||||
{
|
||||
Name: "tailscale-tailwind",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
setupEsbuildTailwind(build, dev)
|
||||
},
|
||||
Plugins: []esbuild.Plugin{{
|
||||
Name: "tailscale-tailwind",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
setupEsbuildTailwind(build, dev)
|
||||
},
|
||||
{
|
||||
Name: "tailscale-go-wasm-exec-js",
|
||||
Setup: setupEsbuildWasmExecJS,
|
||||
},
|
||||
{
|
||||
Name: "tailscale-wasm",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
setupEsbuildWasm(build, dev)
|
||||
},
|
||||
},
|
||||
},
|
||||
JSXMode: esbuild.JSXModeAutomatic,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// cleanDir removes files from dirPath, except the ones specified by
|
||||
// preserveFiles.
|
||||
func cleanDir(dirPath string, preserveFiles ...string) error {
|
||||
log.Printf("Cleaning %s...\n", dirPath)
|
||||
files, err := os.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return os.MkdirAll(dirPath, 0755)
|
||||
}
|
||||
return err
|
||||
// buildDeps builds the static assets that are needed for the server (except for
|
||||
// JS/CSS bundling, which is handled by esbuild).
|
||||
func buildDeps(dev bool) error {
|
||||
if err := copyWasmExec(); err != nil {
|
||||
return fmt.Errorf("Cannot copy wasm_exec.js: %w", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !slices.Contains(preserveFiles, file.Name()) {
|
||||
if err := os.Remove(filepath.Join(dirPath, file.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := buildWasm(dev); err != nil {
|
||||
return fmt.Errorf("Cannot build main.wasm: %w", err)
|
||||
}
|
||||
if err := installJSDeps(); err != nil {
|
||||
return fmt.Errorf("Cannot install JS deps: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runEsbuild(buildOptions esbuild.BuildOptions) esbuild.BuildResult {
|
||||
log.Printf("Running esbuild...\n")
|
||||
result := esbuild.Build(buildOptions)
|
||||
if len(result.Errors) > 0 {
|
||||
log.Printf("ESBuild Error:\n")
|
||||
for _, e := range result.Errors {
|
||||
log.Printf("%v", e)
|
||||
}
|
||||
log.Fatal("Build failed")
|
||||
}
|
||||
if len(result.Warnings) > 0 {
|
||||
log.Printf("ESBuild Warnings:\n")
|
||||
for _, w := range result.Warnings {
|
||||
log.Printf("%v", w)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// setupEsbuildWasmExecJS generates an esbuild plugin that serves the current
|
||||
// wasm_exec.js runtime helper library from the Go toolchain.
|
||||
func setupEsbuildWasmExecJS(build esbuild.PluginBuild) {
|
||||
// copyWasmExec grabs the current wasm_exec.js runtime helper library from the
|
||||
// Go toolchain.
|
||||
func copyWasmExec() error {
|
||||
log.Printf("Copying wasm_exec.js...\n")
|
||||
wasmExecSrcPath := filepath.Join(runtime.GOROOT(), "misc", "wasm", "wasm_exec.js")
|
||||
build.OnResolve(esbuild.OnResolveOptions{
|
||||
Filter: "./wasm_exec$",
|
||||
}, func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
|
||||
return esbuild.OnResolveResult{Path: wasmExecSrcPath}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// setupEsbuildWasm generates an esbuild plugin that builds the Tailscale wasm
|
||||
// binary and serves it as a file that the JS can load.
|
||||
func setupEsbuildWasm(build esbuild.PluginBuild, dev bool) {
|
||||
// Add a resolve hook to convince esbuild that the path exists.
|
||||
build.OnResolve(esbuild.OnResolveOptions{
|
||||
Filter: "./main.wasm$",
|
||||
}, func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
|
||||
return esbuild.OnResolveResult{
|
||||
Path: "./src/main.wasm",
|
||||
Namespace: "generated",
|
||||
}, nil
|
||||
})
|
||||
build.OnLoad(esbuild.OnLoadOptions{
|
||||
Filter: "./src/main.wasm$",
|
||||
}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
||||
contents, err := buildWasm(dev)
|
||||
if err != nil {
|
||||
return esbuild.OnLoadResult{}, fmt.Errorf("Cannot build main.wasm: %w", err)
|
||||
}
|
||||
contentsStr := string(contents)
|
||||
return esbuild.OnLoadResult{
|
||||
Contents: &contentsStr,
|
||||
Loader: esbuild.LoaderFile,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func buildWasm(dev bool) ([]byte, error) {
|
||||
start := time.Now()
|
||||
outputFile, err := ioutil.TempFile("", "main.*.wasm")
|
||||
wasmExecDstPath := filepath.Join("src", "wasm_exec.js")
|
||||
contents, err := os.ReadFile(wasmExecSrcPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot create main.wasm output file: %w", err)
|
||||
return err
|
||||
}
|
||||
outputPath := outputFile.Name()
|
||||
defer os.Remove(outputPath)
|
||||
return os.WriteFile(wasmExecDstPath, contents, 0600)
|
||||
}
|
||||
|
||||
// buildWasm builds the Tailscale wasm binary and places it where the JS can
|
||||
// load it.
|
||||
func buildWasm(dev bool) error {
|
||||
log.Printf("Building wasm...\n")
|
||||
args := []string{"build", "-tags", "tailscale_go,osusergo,netgo,nethttpomithttp2,omitidna,omitpemdecrypt"}
|
||||
if !dev {
|
||||
if *devControl != "" {
|
||||
return nil, fmt.Errorf("Development control URL can only be used in dev mode.")
|
||||
}
|
||||
// Omit long paths and debug symbols in release builds, to reduce the
|
||||
// generated WASM binary size.
|
||||
args = append(args, "-trimpath", "-ldflags", "-s -w")
|
||||
} else if *devControl != "" {
|
||||
args = append(args, "-ldflags", fmt.Sprintf("-X 'main.ControlURL=%v'", *devControl))
|
||||
}
|
||||
|
||||
args = append(args, "-o", outputPath, "./wasm")
|
||||
args = append(args, "-o", "src/main.wasm", "./wasm")
|
||||
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
|
||||
cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot build main.wasm: %w", err)
|
||||
}
|
||||
log.Printf("Built wasm in %v\n", time.Since(start))
|
||||
return os.ReadFile(outputPath)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// installJSDeps installs the JavaScript dependencies specified by package.json
|
||||
@@ -209,7 +128,7 @@ type EsbuildMetadata struct {
|
||||
|
||||
func setupEsbuildTailwind(build esbuild.PluginBuild, dev bool) {
|
||||
build.OnLoad(esbuild.OnLoadOptions{
|
||||
Filter: "./src/.*\\.css$",
|
||||
Filter: "./src/index.css$",
|
||||
}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
||||
start := time.Now()
|
||||
yarnArgs := []string{"--silent", "tailwind", "-i", args.Path}
|
||||
|
||||
@@ -8,13 +8,37 @@
|
||||
<script src="dist/index.js" defer></script>
|
||||
</head>
|
||||
<body class="flex flex-col h-screen overflow-hidden">
|
||||
<!-- Placeholder so that we don't have an empty page while the JS loads.
|
||||
It should match the markup generated by Header component. -->
|
||||
<div class="bg-gray-100 border-b border-gray-200 pt-4 pb-2">
|
||||
<header class="container mx-auto px-4 flex flex-row items-center">
|
||||
<h1 class="text-3xl font-bold grow">Tailscale Connect</h1>
|
||||
<div class="text-gray-600">Loading…</div>
|
||||
<div class="text-gray-600" id="state">Loading…</div>
|
||||
</header>
|
||||
</div>
|
||||
<div
|
||||
id="content"
|
||||
class="flex-grow flex flex-col justify-center overflow-hidden"
|
||||
>
|
||||
<form
|
||||
id="ssh-form"
|
||||
class="container mx-auto px-4 hidden flex justify-center"
|
||||
>
|
||||
<input type="text" class="input username" placeholder="Username" />
|
||||
<div class="select-with-arrow mx-2">
|
||||
<select class="select"></select>
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button bg-green-500 border-green-500 text-white hover:bg-green-600 hover:border-green-600"
|
||||
value="SSH"
|
||||
/>
|
||||
</form>
|
||||
<div id="no-ssh" class="container mx-auto px-4 hidden text-center">
|
||||
None of your machines have
|
||||
<a href="https://tailscale.com/kb/1193/tailscale-ssh/" class="link"
|
||||
>Tailscale SSH</a
|
||||
>
|
||||
enabled. Give it a try!
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
"devDependencies": {
|
||||
"@types/golang-wasm-exec": "^1.15.0",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"dts-bundle-generator": "^6.12.0",
|
||||
"preact": "^10.10.0",
|
||||
"qrcode": "^1.5.0",
|
||||
"tailwindcss": "^3.1.6",
|
||||
"typescript": "^4.7.4",
|
||||
@@ -14,8 +12,7 @@
|
||||
"xterm-addon-fit": "^0.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tsc --noEmit",
|
||||
"pkg-types": "dts-bundle-generator --inline-declare-global=true --no-banner -o pkg/pkg.d.ts src/pkg/pkg.ts"
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"author": "Tailscale Inc.",
|
||||
"description": "Tailscale Connect SDK",
|
||||
"license": "BSD-3-Clause",
|
||||
"name": "@tailscale/connect",
|
||||
"type": "module",
|
||||
"main": "./pkg.js",
|
||||
"types": "./pkg.d.ts",
|
||||
"version": "0.0.5"
|
||||
}
|
||||
@@ -115,8 +115,8 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
|
||||
}
|
||||
|
||||
var entryPointsToDefaultDistPaths = map[string]string{
|
||||
"src/app/index.css": "dist/index.css",
|
||||
"src/app/index.ts": "dist/index.js",
|
||||
"src/index.css": "dist/index.css",
|
||||
"src/index.ts": "dist/index.js",
|
||||
}
|
||||
|
||||
func handleServeDist(w http.ResponseWriter, r *http.Request, distFS fs.FS) {
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import { render, Component } from "preact"
|
||||
import { URLDisplay } from "./url-display"
|
||||
import { Header } from "./header"
|
||||
import { GoPanicDisplay } from "./go-panic-display"
|
||||
import { SSH } from "./ssh"
|
||||
|
||||
type AppState = {
|
||||
ipn?: IPN
|
||||
ipnState: IPNState
|
||||
netMap?: IPNNetMap
|
||||
browseToURL?: string
|
||||
goPanicError?: string
|
||||
}
|
||||
|
||||
class App extends Component<{}, AppState> {
|
||||
state: AppState = { ipnState: "NoState" }
|
||||
#goPanicTimeout?: number
|
||||
|
||||
render() {
|
||||
const { ipn, ipnState, goPanicError, netMap, browseToURL } = this.state
|
||||
|
||||
let goPanicDisplay
|
||||
if (goPanicError) {
|
||||
goPanicDisplay = (
|
||||
<GoPanicDisplay error={goPanicError} dismiss={this.clearGoPanic} />
|
||||
)
|
||||
}
|
||||
|
||||
let urlDisplay
|
||||
if (browseToURL) {
|
||||
urlDisplay = <URLDisplay url={browseToURL} />
|
||||
}
|
||||
|
||||
let machineAuthInstructions
|
||||
if (ipnState === "NeedsMachineAuth") {
|
||||
machineAuthInstructions = (
|
||||
<div class="container mx-auto px-4 text-center">
|
||||
An administrator needs to authorize this device.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
let ssh
|
||||
if (ipn && ipnState === "Running" && netMap) {
|
||||
ssh = <SSH netMap={netMap} ipn={ipn} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header state={ipnState} ipn={ipn} />
|
||||
{goPanicDisplay}
|
||||
<div class="flex-grow flex flex-col justify-center overflow-hidden">
|
||||
{urlDisplay}
|
||||
{machineAuthInstructions}
|
||||
{ssh}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
runWithIPN(ipn: IPN) {
|
||||
this.setState({ ipn }, () => {
|
||||
ipn.run({
|
||||
notifyState: this.handleIPNState,
|
||||
notifyNetMap: this.handleNetMap,
|
||||
notifyBrowseToURL: this.handleBrowseToURL,
|
||||
notifyPanicRecover: this.handleGoPanic,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handleIPNState = (state: IPNState) => {
|
||||
const { ipn } = this.state
|
||||
this.setState({ ipnState: state })
|
||||
if (state === "NeedsLogin") {
|
||||
ipn?.login()
|
||||
} else if (["Running", "NeedsMachineAuth"].includes(state)) {
|
||||
this.setState({ browseToURL: undefined })
|
||||
}
|
||||
}
|
||||
|
||||
handleNetMap = (netMapStr: string) => {
|
||||
const netMap = JSON.parse(netMapStr) as IPNNetMap
|
||||
if (DEBUG) {
|
||||
console.log("Received net map: " + JSON.stringify(netMap, null, 2))
|
||||
}
|
||||
this.setState({ netMap })
|
||||
}
|
||||
|
||||
handleBrowseToURL = (url: string) => {
|
||||
this.setState({ browseToURL: url })
|
||||
}
|
||||
|
||||
handleGoPanic = (error: string) => {
|
||||
if (DEBUG) {
|
||||
console.error("Go panic", error)
|
||||
}
|
||||
this.setState({ goPanicError: error })
|
||||
if (this.#goPanicTimeout) {
|
||||
window.clearTimeout(this.#goPanicTimeout)
|
||||
}
|
||||
this.#goPanicTimeout = window.setTimeout(this.clearGoPanic, 10000)
|
||||
}
|
||||
|
||||
clearGoPanic = () => {
|
||||
window.clearTimeout(this.#goPanicTimeout)
|
||||
this.#goPanicTimeout = undefined
|
||||
this.setState({ goPanicError: undefined })
|
||||
}
|
||||
}
|
||||
|
||||
export function renderApp(): Promise<App> {
|
||||
return new Promise((resolve) => {
|
||||
render(
|
||||
<App ref={(app) => (app ? resolve(app) : undefined)} />,
|
||||
document.body
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
export function GoPanicDisplay({
|
||||
error,
|
||||
dismiss,
|
||||
}: {
|
||||
error: string
|
||||
dismiss: () => void
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
class="rounded bg-red-500 p-2 absolute top-2 right-2 text-white font-bold text-right cursor-pointer"
|
||||
onClick={dismiss}
|
||||
>
|
||||
Tailscale has encountered an error.
|
||||
<div class="text-sm font-normal">Click to reload</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
export function Header({ state, ipn }: { state: IPNState; ipn?: IPN }) {
|
||||
const stateText = STATE_LABELS[state]
|
||||
|
||||
let logoutButton
|
||||
if (state === "Running") {
|
||||
logoutButton = (
|
||||
<button
|
||||
class="button bg-gray-500 border-gray-500 text-white hover:bg-gray-600 hover:border-gray-600 ml-2 font-bold"
|
||||
onClick={() => ipn?.logout()}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div class="bg-gray-100 border-b border-gray-200 pt-4 pb-2">
|
||||
<header class="container mx-auto px-4 flex flex-row items-center">
|
||||
<h1 class="text-3xl font-bold grow">Tailscale Connect</h1>
|
||||
<div class="text-gray-600">{stateText}</div>
|
||||
{logoutButton}
|
||||
</header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const STATE_LABELS = {
|
||||
NoState: "Initializing…",
|
||||
InUseOtherUser: "In-use by another user",
|
||||
NeedsLogin: "Needs login",
|
||||
NeedsMachineAuth: "Needs authorization",
|
||||
Stopped: "Stopped",
|
||||
Starting: "Starting…",
|
||||
Running: "Running",
|
||||
} as const
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import "../wasm_exec"
|
||||
import wasmUrl from "./main.wasm"
|
||||
import { sessionStateStorage } from "../lib/js-state-store"
|
||||
import { renderApp } from "./app"
|
||||
|
||||
async function main() {
|
||||
const app = await renderApp()
|
||||
const go = new Go()
|
||||
const wasmInstance = await WebAssembly.instantiateStreaming(
|
||||
fetch(`./dist/${wasmUrl}`),
|
||||
go.importObject
|
||||
)
|
||||
// The Go process should never exit, if it does then it's an unhandled panic.
|
||||
go.run(wasmInstance.instance).then(() =>
|
||||
app.handleGoPanic("Unexpected shutdown")
|
||||
)
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const authKey = params.get("authkey") ?? undefined
|
||||
|
||||
const ipn = newIPN({
|
||||
// Persist IPN state in sessionStorage in development, so that we don't need
|
||||
// to re-authorize every time we reload the page.
|
||||
stateStorage: DEBUG ? sessionStateStorage : undefined,
|
||||
// authKey allows for an auth key to be
|
||||
// specified as a url param which automatically
|
||||
// authorizes the client for use.
|
||||
authKey: DEBUG ? authKey : undefined,
|
||||
})
|
||||
app.runWithIPN(ipn)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import { useState, useCallback } from "preact/hooks"
|
||||
import { runSSHSession, SSHSessionDef } from "../lib/ssh"
|
||||
|
||||
export function SSH({ netMap, ipn }: { netMap: IPNNetMap; ipn: IPN }) {
|
||||
const [sshSessionDef, setSSHSessionDef] = useState<SSHSessionDef | null>(null)
|
||||
const clearSSHSessionDef = useCallback(() => setSSHSessionDef(null), [])
|
||||
if (sshSessionDef) {
|
||||
return (
|
||||
<SSHSession def={sshSessionDef} ipn={ipn} onDone={clearSSHSessionDef} />
|
||||
)
|
||||
}
|
||||
const sshPeers = netMap.peers.filter(
|
||||
(p) => p.tailscaleSSHEnabled && p.online !== false
|
||||
)
|
||||
|
||||
if (sshPeers.length == 0) {
|
||||
return <NoSSHPeers />
|
||||
}
|
||||
|
||||
return <SSHForm sshPeers={sshPeers} onSubmit={setSSHSessionDef} />
|
||||
}
|
||||
|
||||
function SSHSession({
|
||||
def,
|
||||
ipn,
|
||||
onDone,
|
||||
}: {
|
||||
def: SSHSessionDef
|
||||
ipn: IPN
|
||||
onDone: () => void
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
class="flex-grow bg-black p-2 overflow-hidden"
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
// Run the SSH session aysnchronously, so that the React render
|
||||
// loop is complete (otherwise the SSH form may still be visible,
|
||||
// which affects the size of the terminal, leading to a spurious
|
||||
// initial resize).
|
||||
setTimeout(() => runSSHSession(node, def, ipn, onDone), 0)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function NoSSHPeers() {
|
||||
return (
|
||||
<div class="container mx-auto px-4 text-center">
|
||||
None of your machines have
|
||||
<a href="https://tailscale.com/kb/1193/tailscale-ssh/" class="link">
|
||||
Tailscale SSH
|
||||
</a>
|
||||
enabled. Give it a try!
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SSHForm({
|
||||
sshPeers,
|
||||
onSubmit,
|
||||
}: {
|
||||
sshPeers: IPNNetMapPeerNode[]
|
||||
onSubmit: (def: SSHSessionDef) => void
|
||||
}) {
|
||||
sshPeers = sshPeers.slice().sort((a, b) => a.name.localeCompare(b.name))
|
||||
const [username, setUsername] = useState("")
|
||||
const [hostname, setHostname] = useState(sshPeers[0].name)
|
||||
return (
|
||||
<form
|
||||
class="container mx-auto px-4 flex justify-center"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
onSubmit({ username, hostname })
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="input username"
|
||||
placeholder="Username"
|
||||
onChange={(e) => setUsername(e.currentTarget.value)}
|
||||
/>
|
||||
<div class="select-with-arrow mx-2">
|
||||
<select
|
||||
class="select"
|
||||
onChange={(e) => setHostname(e.currentTarget.value)}
|
||||
>
|
||||
{sshPeers.map((p) => (
|
||||
<option key={p.nodeKey}>{p.name.split(".")[0]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button bg-green-500 border-green-500 text-white hover:bg-green-600 hover:border-green-600"
|
||||
value="SSH"
|
||||
/>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import { useState } from "preact/hooks"
|
||||
import * as qrcode from "qrcode"
|
||||
|
||||
export function URLDisplay({ url }: { url: string }) {
|
||||
const [dataURL, setDataURL] = useState("")
|
||||
qrcode.toDataURL(url, { width: 512 }, (err, dataURL) => {
|
||||
if (err) {
|
||||
console.error("Error generating QR code", err)
|
||||
} else {
|
||||
setDataURL(dataURL)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="flex flex-col items-center justify-items-center">
|
||||
<a href={url} class="link" target="_blank">
|
||||
<img
|
||||
src={dataURL}
|
||||
class="mx-auto"
|
||||
width="256"
|
||||
height="256"
|
||||
alt="QR Code of URL"
|
||||
/>
|
||||
{url}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -73,3 +73,7 @@
|
||||
background-color: currentColor;
|
||||
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
|
||||
}
|
||||
|
||||
body.ssh-active #ssh-form {
|
||||
@apply hidden;
|
||||
}
|
||||
58
cmd/tsconnect/src/index.ts
Normal file
58
cmd/tsconnect/src/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import "./wasm_exec"
|
||||
import wasmUrl from "./main.wasm"
|
||||
import { notifyState, notifyNetMap, notifyBrowseToURL } from "./notifier"
|
||||
import { sessionStateStorage } from "./js-state-store"
|
||||
|
||||
const go = new Go()
|
||||
WebAssembly.instantiateStreaming(
|
||||
fetch(`./dist/${wasmUrl}`),
|
||||
go.importObject
|
||||
).then((result) => {
|
||||
// The Go process should never exit, if it does then it's an unhandled panic.
|
||||
go.run(result.instance).then(() => handleGoPanic())
|
||||
const ipn = newIPN({
|
||||
// Persist IPN state in sessionStorage in development, so that we don't need
|
||||
// to re-authorize every time we reload the page.
|
||||
stateStorage: DEBUG ? sessionStateStorage : undefined,
|
||||
})
|
||||
ipn.run({
|
||||
notifyState: notifyState.bind(null, ipn),
|
||||
notifyNetMap: notifyNetMap.bind(null, ipn),
|
||||
notifyBrowseToURL: notifyBrowseToURL.bind(null, ipn),
|
||||
notifyPanicRecover: handleGoPanic,
|
||||
})
|
||||
})
|
||||
|
||||
function handleGoPanic(err?: string) {
|
||||
if (DEBUG && err) {
|
||||
console.error("Go panic", err)
|
||||
}
|
||||
if (panicNode) {
|
||||
panicNode.remove()
|
||||
}
|
||||
panicNode = document.createElement("div")
|
||||
panicNode.className =
|
||||
"rounded bg-red-500 p-2 absolute top-2 right-2 text-white font-bold text-right cursor-pointer"
|
||||
panicNode.textContent = "Tailscale has encountered an error."
|
||||
const panicDetailNode = document.createElement("div")
|
||||
panicDetailNode.className = "text-sm font-normal"
|
||||
panicDetailNode.textContent = "Click to reload"
|
||||
panicNode.appendChild(panicDetailNode)
|
||||
panicNode.addEventListener("click", () => location.reload(), {
|
||||
once: true,
|
||||
})
|
||||
document.body.appendChild(panicNode)
|
||||
setTimeout(() => {
|
||||
panicNode!.remove()
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
let panicNode: HTMLDivElement | undefined
|
||||
|
||||
export function getContentNode(): HTMLDivElement {
|
||||
return document.querySelector("#content") as HTMLDivElement
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Terminal } from "xterm"
|
||||
import { FitAddon } from "xterm-addon-fit"
|
||||
|
||||
export type SSHSessionDef = {
|
||||
username: string
|
||||
hostname: string
|
||||
}
|
||||
|
||||
export function runSSHSession(
|
||||
termContainerNode: HTMLDivElement,
|
||||
def: SSHSessionDef,
|
||||
ipn: IPN,
|
||||
onDone: () => void
|
||||
) {
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
})
|
||||
const fitAddon = new FitAddon()
|
||||
term.loadAddon(fitAddon)
|
||||
term.open(termContainerNode)
|
||||
fitAddon.fit()
|
||||
|
||||
let onDataHook: ((data: string) => void) | undefined
|
||||
term.onData((e) => {
|
||||
onDataHook?.(e)
|
||||
})
|
||||
|
||||
term.focus()
|
||||
|
||||
const sshSession = ipn.ssh(def.hostname, def.username, {
|
||||
writeFn: (input) => term.write(input),
|
||||
setReadFn: (hook) => (onDataHook = hook),
|
||||
rows: term.rows,
|
||||
cols: term.cols,
|
||||
onDone: () => {
|
||||
resizeObserver.disconnect()
|
||||
term.dispose()
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload)
|
||||
onDone()
|
||||
},
|
||||
})
|
||||
|
||||
// Make terminal and SSH session track the size of the containing DOM node.
|
||||
const resizeObserver = new ResizeObserver(() => fitAddon.fit())
|
||||
resizeObserver.observe(termContainerNode)
|
||||
term.onResize(({ rows, cols }) => sshSession.resize(rows, cols))
|
||||
|
||||
// Close the session if the user closes the window without an explicit
|
||||
// exit.
|
||||
const handleBeforeUnload = () => sshSession.close()
|
||||
window.addEventListener("beforeunload", handleBeforeUnload)
|
||||
}
|
||||
74
cmd/tsconnect/src/login.ts
Normal file
74
cmd/tsconnect/src/login.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import * as qrcode from "qrcode"
|
||||
import { getContentNode } from "./index"
|
||||
|
||||
export async function showLoginURL(url: string) {
|
||||
if (loginNode) {
|
||||
loginNode.remove()
|
||||
}
|
||||
loginNode = document.createElement("div")
|
||||
loginNode.className = "flex flex-col items-center justify-items-center"
|
||||
const linkNode = document.createElement("a")
|
||||
linkNode.className = "link"
|
||||
linkNode.href = url
|
||||
linkNode.target = "_blank"
|
||||
loginNode.appendChild(linkNode)
|
||||
|
||||
try {
|
||||
const dataURL = await qrcode.toDataURL(url, { width: 512 })
|
||||
const imageNode = document.createElement("img")
|
||||
imageNode.className = "mx-auto"
|
||||
imageNode.src = dataURL
|
||||
imageNode.width = 256
|
||||
imageNode.height = 256
|
||||
linkNode.appendChild(imageNode)
|
||||
} catch (err) {
|
||||
console.error("Could not generate QR code:", err)
|
||||
}
|
||||
|
||||
linkNode.appendChild(document.createTextNode(url))
|
||||
|
||||
getContentNode().appendChild(loginNode)
|
||||
}
|
||||
|
||||
export function hideLoginURL() {
|
||||
if (!loginNode) {
|
||||
return
|
||||
}
|
||||
loginNode.remove()
|
||||
loginNode = undefined
|
||||
}
|
||||
|
||||
let loginNode: HTMLDivElement | undefined
|
||||
|
||||
export function showLogoutButton(ipn: IPN) {
|
||||
if (logoutButtonNode) {
|
||||
logoutButtonNode.remove()
|
||||
}
|
||||
logoutButtonNode = document.createElement("button")
|
||||
logoutButtonNode.className =
|
||||
"button bg-gray-500 border-gray-500 text-white hover:bg-gray-600 hover:border-gray-600 ml-2 font-bold"
|
||||
logoutButtonNode.textContent = "Logout"
|
||||
logoutButtonNode.addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
ipn.logout()
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
const headerNode = document.getElementsByTagName("header")[0]!
|
||||
headerNode.appendChild(logoutButtonNode)
|
||||
}
|
||||
|
||||
export function hideLogoutButton() {
|
||||
if (!logoutButtonNode) {
|
||||
return
|
||||
}
|
||||
logoutButtonNode.remove()
|
||||
logoutButtonNode = undefined
|
||||
}
|
||||
|
||||
let logoutButtonNode: HTMLButtonElement | undefined
|
||||
65
cmd/tsconnect/src/notifier.ts
Normal file
65
cmd/tsconnect/src/notifier.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import {
|
||||
showLoginURL,
|
||||
hideLoginURL,
|
||||
showLogoutButton,
|
||||
hideLogoutButton,
|
||||
} from "./login"
|
||||
import { showSSHForm, hideSSHForm } from "./ssh"
|
||||
import { IPNState } from "./wasm_js"
|
||||
|
||||
/**
|
||||
* @fileoverview Notification callback functions (bridged from ipn.Notify)
|
||||
*/
|
||||
|
||||
export function notifyState(ipn: IPN, state: IPNState) {
|
||||
let stateLabel
|
||||
switch (state) {
|
||||
case IPNState.NoState:
|
||||
stateLabel = "Initializing…"
|
||||
break
|
||||
case IPNState.InUseOtherUser:
|
||||
stateLabel = "In-use by another user"
|
||||
break
|
||||
case IPNState.NeedsLogin:
|
||||
stateLabel = "Needs Login"
|
||||
hideLogoutButton()
|
||||
hideSSHForm()
|
||||
ipn.login()
|
||||
break
|
||||
case IPNState.NeedsMachineAuth:
|
||||
stateLabel = "Needs authorization"
|
||||
break
|
||||
case IPNState.Stopped:
|
||||
stateLabel = "Stopped"
|
||||
hideLogoutButton()
|
||||
hideSSHForm()
|
||||
break
|
||||
case IPNState.Starting:
|
||||
stateLabel = "Starting…"
|
||||
break
|
||||
case IPNState.Running:
|
||||
stateLabel = "Running"
|
||||
hideLoginURL()
|
||||
showLogoutButton(ipn)
|
||||
break
|
||||
}
|
||||
const stateNode = document.querySelector("#state") as HTMLDivElement
|
||||
stateNode.textContent = stateLabel ?? ""
|
||||
}
|
||||
|
||||
export function notifyNetMap(ipn: IPN, netMapStr: string) {
|
||||
const netMap = JSON.parse(netMapStr) as IPNNetMap
|
||||
if (DEBUG) {
|
||||
console.log("Received net map: " + JSON.stringify(netMap, null, 2))
|
||||
}
|
||||
|
||||
showSSHForm(netMap.peers, ipn)
|
||||
}
|
||||
|
||||
export function notifyBrowseToURL(ipn: IPN, url: string) {
|
||||
showLoginURL(url)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/* Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. */
|
||||
/* Use of this source code is governed by a BSD-style */
|
||||
/* license that can be found in the LICENSE file. */
|
||||
|
||||
@import "xterm/css/xterm.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Type definitions need to be manually imported for dts-bundle-generator to
|
||||
// discover them.
|
||||
/// <reference path="../types/esbuild.d.ts" />
|
||||
/// <reference path="../types/wasm_js.d.ts" />
|
||||
|
||||
import "../wasm_exec"
|
||||
import wasmURL from "./main.wasm"
|
||||
|
||||
/**
|
||||
* Superset of the IPNConfig type, with additional configuration that is
|
||||
* needed for the package to function.
|
||||
*/
|
||||
type IPNPackageConfig = IPNConfig & {
|
||||
// Auth key used to intitialize the Tailscale client (required)
|
||||
authKey: string
|
||||
// URL of the main.wasm file that is included in the page, if it is not
|
||||
// accessible via a relative URL.
|
||||
wasmURL?: string
|
||||
// Funtion invoked if the Go process panics or unexpectedly exits.
|
||||
panicHandler: (err: string) => void
|
||||
}
|
||||
|
||||
export async function createIPN(config: IPNPackageConfig): Promise<IPN> {
|
||||
const go = new Go()
|
||||
const wasmInstance = await WebAssembly.instantiateStreaming(
|
||||
fetch(config.wasmURL ?? wasmURL),
|
||||
go.importObject
|
||||
)
|
||||
// The Go process should never exit, if it does then it's an unhandled panic.
|
||||
go.run(wasmInstance.instance).then(() =>
|
||||
config.panicHandler("Unexpected shutdown")
|
||||
)
|
||||
|
||||
return newIPN(config)
|
||||
}
|
||||
|
||||
export { runSSHSession } from "../lib/ssh"
|
||||
98
cmd/tsconnect/src/ssh.ts
Normal file
98
cmd/tsconnect/src/ssh.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import { Terminal } from "xterm"
|
||||
import { FitAddon } from "xterm-addon-fit"
|
||||
import { getContentNode } from "./index"
|
||||
|
||||
export function showSSHForm(peers: IPNNetMapPeerNode[], ipn: IPN) {
|
||||
const formNode = document.querySelector("#ssh-form") as HTMLDivElement
|
||||
const noSSHNode = document.querySelector("#no-ssh") as HTMLDivElement
|
||||
|
||||
const sshPeers = peers.filter(
|
||||
(p) => p.tailscaleSSHEnabled && p.online !== false
|
||||
)
|
||||
if (sshPeers.length == 0) {
|
||||
formNode.classList.add("hidden")
|
||||
noSSHNode.classList.remove("hidden")
|
||||
return
|
||||
}
|
||||
sshPeers.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
const selectNode = formNode.querySelector("select")!
|
||||
selectNode.innerHTML = ""
|
||||
for (const p of sshPeers) {
|
||||
const option = document.createElement("option")
|
||||
option.textContent = p.name.split(".")[0]
|
||||
option.value = p.name
|
||||
selectNode.appendChild(option)
|
||||
}
|
||||
|
||||
const usernameNode = formNode.querySelector(".username") as HTMLInputElement
|
||||
formNode.onsubmit = (e) => {
|
||||
e.preventDefault()
|
||||
const hostname = selectNode.value
|
||||
ssh(hostname, usernameNode.value, ipn)
|
||||
}
|
||||
|
||||
noSSHNode.classList.add("hidden")
|
||||
formNode.classList.remove("hidden")
|
||||
}
|
||||
|
||||
export function hideSSHForm() {
|
||||
const formNode = document.querySelector("#ssh-form") as HTMLDivElement
|
||||
formNode.classList.add("hidden")
|
||||
}
|
||||
|
||||
function ssh(hostname: string, username: string, ipn: IPN) {
|
||||
document.body.classList.add("ssh-active")
|
||||
const termContainerNode = document.createElement("div")
|
||||
termContainerNode.className = "flex-grow bg-black p-2 overflow-hidden"
|
||||
getContentNode().appendChild(termContainerNode)
|
||||
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
})
|
||||
const fitAddon = new FitAddon()
|
||||
term.loadAddon(fitAddon)
|
||||
term.open(termContainerNode)
|
||||
fitAddon.fit()
|
||||
|
||||
let onDataHook: ((data: string) => void) | undefined
|
||||
term.onData((e) => {
|
||||
onDataHook?.(e)
|
||||
})
|
||||
|
||||
term.focus()
|
||||
|
||||
const sshSession = ipn.ssh(hostname, username, {
|
||||
writeFn: (input) => term.write(input),
|
||||
setReadFn: (hook) => (onDataHook = hook),
|
||||
rows: term.rows,
|
||||
cols: term.cols,
|
||||
onDone: () => {
|
||||
resizeObserver.disconnect()
|
||||
term.dispose()
|
||||
termContainerNode.remove()
|
||||
document.body.classList.remove("ssh-active")
|
||||
window.removeEventListener("beforeunload", beforeUnloadListener)
|
||||
},
|
||||
})
|
||||
|
||||
// Make terminal and SSH session track the size of the containing DOM node.
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
fitAddon.fit()
|
||||
})
|
||||
resizeObserver.observe(termContainerNode)
|
||||
term.onResize(({ rows, cols }) => {
|
||||
sshSession.resize(rows, cols)
|
||||
})
|
||||
|
||||
// Close the session if the user closes the window without an explicit
|
||||
// exit.
|
||||
const beforeUnloadListener = () => {
|
||||
sshSession.close()
|
||||
}
|
||||
window.addEventListener("beforeunload", beforeUnloadListener)
|
||||
}
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
/**
|
||||
* @fileoverview Type definitions for types exported by the wasm_js.go Go
|
||||
* module.
|
||||
* module. Not actually a .d.ts file so that we can use enums from it in
|
||||
* esbuild's simplified TypeScript compiler (see https://github.com/evanw/esbuild/issues/2298#issuecomment-1146378367)
|
||||
*/
|
||||
|
||||
declare global {
|
||||
@@ -25,7 +26,9 @@ declare global {
|
||||
onDone: () => void
|
||||
}
|
||||
): IPNSSHSession
|
||||
fetch(url: string): Promise<{
|
||||
fetch(
|
||||
url: string
|
||||
): Promise<{
|
||||
status: number
|
||||
statusText: string
|
||||
text: () => Promise<string>
|
||||
@@ -44,8 +47,6 @@ declare global {
|
||||
|
||||
type IPNConfig = {
|
||||
stateStorage?: IPNStateStorage
|
||||
authKey?: string
|
||||
controlURL?: string
|
||||
}
|
||||
|
||||
type IPNCallbacks = {
|
||||
@@ -75,23 +76,23 @@ declare global {
|
||||
online?: boolean
|
||||
tailscaleSSHEnabled: boolean
|
||||
}
|
||||
|
||||
/** Mirrors values from ipn/backend.go */
|
||||
type IPNState =
|
||||
| "NoState"
|
||||
| "InUseOtherUser"
|
||||
| "NeedsLogin"
|
||||
| "NeedsMachineAuth"
|
||||
| "Stopped"
|
||||
| "Starting"
|
||||
| "Running"
|
||||
|
||||
/** Mirrors values from MachineStatus in tailcfg.go */
|
||||
type IPNMachineStatus =
|
||||
| "MachineUnknown"
|
||||
| "MachineUnauthorized"
|
||||
| "MachineAuthorized"
|
||||
| "MachineInvalid"
|
||||
}
|
||||
|
||||
export {}
|
||||
/** Mirrors values from ipn/backend.go */
|
||||
export const enum IPNState {
|
||||
NoState = 0,
|
||||
InUseOtherUser = 1,
|
||||
NeedsLogin = 2,
|
||||
NeedsMachineAuth = 3,
|
||||
Stopped = 4,
|
||||
Starting = 5,
|
||||
Running = 6,
|
||||
}
|
||||
|
||||
/** Mirrors values from MachineStatus in tailcfg.go */
|
||||
export const enum IPNMachineStatus {
|
||||
MachineUnknown = 0,
|
||||
MachineUnauthorized = 1,
|
||||
MachineAuthorized = 2,
|
||||
MachineInvalid = 3,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./index.html", "./src/**/*.ts", "./src/**/*.tsx"],
|
||||
content: ["./index.html", "./src/**/*.ts"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"sourceMap": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact"
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
@@ -20,10 +20,8 @@ import (
|
||||
var (
|
||||
addr = flag.String("addr", ":9090", "address to listen on")
|
||||
distDir = flag.String("distdir", "./dist", "path of directory to place build output in")
|
||||
pkgDir = flag.String("pkgdir", "./pkg", "path of directory to place NPM package build output in")
|
||||
yarnPath = flag.String("yarnpath", "../../tool/yarn", "path yarn executable used to install JavaScript dependencies")
|
||||
fastCompression = flag.Bool("fast-compression", false, "Use faster compression when building, to speed up build time. Meant to iterative/debugging use only.")
|
||||
devControl = flag.String("dev-control", "", "URL of a development control server to be used with dev. If provided without specifying dev, an error will be returned.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -38,8 +36,6 @@ func main() {
|
||||
runDev()
|
||||
case "build":
|
||||
runBuild()
|
||||
case "build-pkg":
|
||||
runBuildPkg()
|
||||
case "serve":
|
||||
runServe()
|
||||
default:
|
||||
|
||||
@@ -31,20 +31,16 @@ import (
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/ipn/ipnserver"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
"tailscale.com/logpolicy"
|
||||
"tailscale.com/logtail"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/wgengine/netstack"
|
||||
"tailscale.com/words"
|
||||
)
|
||||
|
||||
// ControlURL defines the URL to be used for connection to Control.
|
||||
var ControlURL = ipn.DefaultControlURL
|
||||
|
||||
func main() {
|
||||
js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
if len(args) != 1 {
|
||||
@@ -60,37 +56,7 @@ func main() {
|
||||
|
||||
func newIPN(jsConfig js.Value) map[string]any {
|
||||
netns.SetEnabled(false)
|
||||
|
||||
jsStateStorage := jsConfig.Get("stateStorage")
|
||||
var store ipn.StateStore
|
||||
if jsStateStorage.IsUndefined() {
|
||||
store = new(mem.Store)
|
||||
} else {
|
||||
store = &jsStateStore{jsStateStorage}
|
||||
}
|
||||
|
||||
jsControlURL := jsConfig.Get("controlURL")
|
||||
controlURL := ControlURL
|
||||
if jsControlURL.Type() == js.TypeString {
|
||||
controlURL = jsControlURL.String()
|
||||
}
|
||||
|
||||
jsAuthKey := jsConfig.Get("authKey")
|
||||
var authKey string
|
||||
if jsAuthKey.Type() == js.TypeString {
|
||||
authKey = jsAuthKey.String()
|
||||
}
|
||||
|
||||
lpc := getOrCreateLogPolicyConfig(store)
|
||||
c := logtail.Config{
|
||||
Collection: lpc.Collection,
|
||||
PrivateID: lpc.PrivateID,
|
||||
// NewZstdEncoder is intentionally not passed in, compressed requests
|
||||
// set HTTP headers that are not supported by the no-cors fetching mode.
|
||||
HTTPC: &http.Client{Transport: &noCORSTransport{http.DefaultTransport}},
|
||||
}
|
||||
logtail := logtail.NewLogger(c, log.Printf)
|
||||
logf := logtail.Logf
|
||||
var logf logger.Logf = log.Printf
|
||||
|
||||
dialer := new(tsdial.Dialer)
|
||||
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
|
||||
@@ -120,7 +86,14 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
return ns.DialContextTCP(ctx, dst)
|
||||
}
|
||||
|
||||
srv, err := ipnserver.New(logf, lpc.PublicID.String(), store, eng, dialer, nil, ipnserver.Options{
|
||||
jsStateStorage := jsConfig.Get("stateStorage")
|
||||
var store ipn.StateStore
|
||||
if jsStateStorage.IsUndefined() {
|
||||
store = new(mem.Store)
|
||||
} else {
|
||||
store = &jsStateStore{jsStateStorage}
|
||||
}
|
||||
srv, err := ipnserver.New(log.Printf, "some-logid", store, eng, dialer, nil, ipnserver.Options{
|
||||
SurviveDisconnects: true,
|
||||
LoginFlags: controlclient.LoginEphemeral,
|
||||
})
|
||||
@@ -131,11 +104,9 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
ns.SetLocalBackend(lb)
|
||||
|
||||
jsIPN := &jsIPN{
|
||||
dialer: dialer,
|
||||
srv: srv,
|
||||
lb: lb,
|
||||
controlURL: controlURL,
|
||||
authKey: authKey,
|
||||
dialer: dialer,
|
||||
srv: srv,
|
||||
lb: lb,
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
@@ -191,33 +162,14 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
}
|
||||
|
||||
type jsIPN struct {
|
||||
dialer *tsdial.Dialer
|
||||
srv *ipnserver.Server
|
||||
lb *ipnlocal.LocalBackend
|
||||
controlURL string
|
||||
authKey string
|
||||
}
|
||||
|
||||
var jsIPNState = map[ipn.State]string{
|
||||
ipn.NoState: "NoState",
|
||||
ipn.InUseOtherUser: "InUseOtherUser",
|
||||
ipn.NeedsLogin: "NeedsLogin",
|
||||
ipn.NeedsMachineAuth: "NeedsMachineAuth",
|
||||
ipn.Stopped: "Stopped",
|
||||
ipn.Starting: "Starting",
|
||||
ipn.Running: "Running",
|
||||
}
|
||||
|
||||
var jsMachineStatus = map[tailcfg.MachineStatus]string{
|
||||
tailcfg.MachineUnknown: "MachineUnknown",
|
||||
tailcfg.MachineUnauthorized: "MachineUnauthorized",
|
||||
tailcfg.MachineAuthorized: "MachineAuthorized",
|
||||
tailcfg.MachineInvalid: "MachineInvalid",
|
||||
dialer *tsdial.Dialer
|
||||
srv *ipnserver.Server
|
||||
lb *ipnlocal.LocalBackend
|
||||
}
|
||||
|
||||
func (i *jsIPN) run(jsCallbacks js.Value) {
|
||||
notifyState := func(state ipn.State) {
|
||||
jsCallbacks.Call("notifyState", jsIPNState[state])
|
||||
jsCallbacks.Call("notifyState", int(state))
|
||||
}
|
||||
notifyState(ipn.NoState)
|
||||
|
||||
@@ -236,7 +188,7 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
|
||||
if n.State != nil {
|
||||
notifyState(*n.State)
|
||||
}
|
||||
if nm := n.NetMap; nm != nil {
|
||||
if nm := n.NetMap; nm != nil && i.lb.State() == ipn.Running {
|
||||
jsNetMap := jsNetMap{
|
||||
Self: jsNetMapSelfNode{
|
||||
jsNetMapNode: jsNetMapNode{
|
||||
@@ -245,7 +197,7 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
|
||||
NodeKey: nm.NodeKey.String(),
|
||||
MachineKey: nm.MachineKey.String(),
|
||||
},
|
||||
MachineStatus: jsMachineStatus[nm.MachineStatus],
|
||||
MachineStatus: int(nm.MachineStatus),
|
||||
},
|
||||
Peers: mapSlice(nm.Peers, func(p *tailcfg.Node) jsNetMapPeerNode {
|
||||
name := p.Name
|
||||
@@ -280,13 +232,12 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
|
||||
err := i.lb.Start(ipn.Options{
|
||||
StateKey: "wasm",
|
||||
UpdatePrefs: &ipn.Prefs{
|
||||
ControlURL: i.controlURL,
|
||||
ControlURL: ipn.DefaultControlURL,
|
||||
RouteAll: false,
|
||||
AllowSingleHosts: true,
|
||||
WantRunning: true,
|
||||
Hostname: generateHostname(),
|
||||
},
|
||||
AuthKey: i.authKey,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Start error: %v", err)
|
||||
@@ -494,7 +445,7 @@ type jsNetMapNode struct {
|
||||
|
||||
type jsNetMapSelfNode struct {
|
||||
jsNetMapNode
|
||||
MachineStatus string `json:"machineStatus"`
|
||||
MachineStatus int `json:"machineStatus"`
|
||||
}
|
||||
|
||||
type jsNetMapPeerNode struct {
|
||||
@@ -576,40 +527,3 @@ func makePromise(f func() (any, error)) js.Value {
|
||||
promiseConstructor := js.Global().Get("Promise")
|
||||
return promiseConstructor.New(handler)
|
||||
}
|
||||
|
||||
const logPolicyStateKey = "log-policy"
|
||||
|
||||
func getOrCreateLogPolicyConfig(state ipn.StateStore) *logpolicy.Config {
|
||||
if configBytes, err := state.ReadState(logPolicyStateKey); err == nil {
|
||||
if config, err := logpolicy.ConfigFromBytes(configBytes); err == nil {
|
||||
return config
|
||||
} else {
|
||||
log.Printf("Could not parse log policy config: %v", err)
|
||||
}
|
||||
} else if err != ipn.ErrStateNotExist {
|
||||
log.Printf("Could not get log policy config from state store: %v", err)
|
||||
}
|
||||
config := logpolicy.NewConfig(logtail.CollectionNode)
|
||||
if err := state.WriteState(logPolicyStateKey, config.ToBytes()); err != nil {
|
||||
log.Printf("Could not save log policy config to state store: %v", err)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// noCORSTransport wraps a RoundTripper and forces the no-cors mode on requests,
|
||||
// so that we can use it with non-CORS-aware servers.
|
||||
type noCORSTransport struct {
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *noCORSTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("js.fetch:mode", "no-cors")
|
||||
resp, err := t.RoundTripper.RoundTrip(req)
|
||||
if err == nil {
|
||||
// In no-cors mode no response properties are returned. Populate just
|
||||
// the status so that callers do not think this was an error.
|
||||
resp.StatusCode = http.StatusOK
|
||||
resp.Status = http.StatusText(http.StatusOK)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -130,15 +130,6 @@ cliui@^6.0.0:
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
cliui@^7.0.2:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
|
||||
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
color-convert@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||
@@ -190,14 +181,6 @@ dlv@^1.1.3:
|
||||
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
|
||||
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
|
||||
|
||||
dts-bundle-generator@^6.12.0:
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/dts-bundle-generator/-/dts-bundle-generator-6.12.0.tgz#0a221bdce5fdd309a56c8556e645f16ed87ab07d"
|
||||
integrity sha512-k/QAvuVaLIdyWRUHduDrWBe4j8PcE6TDt06+f32KHbW7/SmUPbX1O23fFtQgKwUyTBkbIjJFOFtNrF97tJcKug==
|
||||
dependencies:
|
||||
typescript ">=3.0.1"
|
||||
yargs "^17.2.1"
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
@@ -208,11 +191,6 @@ encode-utf8@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
|
||||
integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||
|
||||
fast-glob@^3.2.11:
|
||||
version "3.2.11"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
|
||||
@@ -256,7 +234,7 @@ function-bind@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
||||
get-caller-file@^2.0.1:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
@@ -465,11 +443,6 @@ postcss@^8.4.14:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
preact@^10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.10.0.tgz#7434750a24b59dae1957d95dc0aa47a4a8e9a180"
|
||||
integrity sha512-fszkg1iJJjq68I4lI8ZsmBiaoQiQHbxf1lNq+72EmC/mZOsFF5zn3k1yv9QGoFgIXzgsdSKtYymLJsrJPoamjQ==
|
||||
|
||||
qrcode@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b"
|
||||
@@ -545,7 +518,7 @@ source-map-js@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
string-width@^4.1.0, string-width@^4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -601,7 +574,7 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
typescript@>=3.0.1, typescript@^4.7.4:
|
||||
typescript@^4.7.4:
|
||||
version "4.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||
@@ -625,15 +598,6 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
xtend@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
@@ -654,11 +618,6 @@ y18n@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||
|
||||
yaml@^1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||
@@ -672,11 +631,6 @@ yargs-parser@^18.1.2:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^21.0.0:
|
||||
version "21.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
||||
yargs@^15.3.1:
|
||||
version "15.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||
@@ -693,16 +647,3 @@ yargs@^15.3.1:
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.2"
|
||||
|
||||
yargs@^17.2.1:
|
||||
version "17.5.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
|
||||
integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
|
||||
dependencies:
|
||||
cliui "^7.0.2"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
require-directory "^2.1.1"
|
||||
string-width "^4.2.3"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.0.0"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone --clone-only-type=OnlyGetClone
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices
|
||||
|
||||
type StructWithoutPtrs struct {
|
||||
Int int
|
||||
@@ -58,7 +58,3 @@ type StructWithSlices struct {
|
||||
Prefixes []netip.Prefix
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type OnlyGetClone struct {
|
||||
SinViewerPorFavor bool
|
||||
}
|
||||
|
||||
@@ -196,19 +196,3 @@ var _StructWithSlicesCloneNeedsRegeneration = StructWithSlices(struct {
|
||||
Prefixes []netip.Prefix
|
||||
Data []byte
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of OnlyGetClone.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *OnlyGetClone) Clone() *OnlyGetClone {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(OnlyGetClone)
|
||||
*dst = *src
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _OnlyGetCloneCloneNeedsRegeneration = OnlyGetClone(struct {
|
||||
SinViewerPorFavor bool
|
||||
}{})
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone
|
||||
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices
|
||||
|
||||
// View returns a readonly view of StructWithPtrs.
|
||||
func (p *StructWithPtrs) View() StructWithPtrsView {
|
||||
|
||||
@@ -327,8 +327,6 @@ var (
|
||||
flagTypes = flag.String("type", "", "comma-separated list of types; required")
|
||||
flagBuildTags = flag.String("tags", "", "compiler build tags to apply")
|
||||
flagCloneFunc = flag.Bool("clonefunc", false, "add a top-level Clone func")
|
||||
|
||||
flagCloneOnlyTypes = flag.String("clone-only-type", "", "comma-separated list of types (a subset of --type) that should only generate a go:generate clone line and not actual views")
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -355,18 +353,10 @@ func main() {
|
||||
}
|
||||
it := codegen.NewImportTracker(pkg.Types)
|
||||
|
||||
cloneOnlyType := map[string]bool{}
|
||||
for _, t := range strings.Split(*flagCloneOnlyTypes, ",") {
|
||||
cloneOnlyType[t] = true
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "//go:generate go run tailscale.com/cmd/cloner %s\n\n", strings.Join(flagArgs, " "))
|
||||
runCloner := false
|
||||
for _, typeName := range typeNames {
|
||||
if cloneOnlyType[typeName] {
|
||||
continue
|
||||
}
|
||||
typ, ok := namedTypes[typeName]
|
||||
if !ok {
|
||||
log.Fatalf("could not find type %s", typeName)
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
@@ -40,7 +41,6 @@ import (
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -710,7 +710,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
c.logf("[v1] PollNetMap: stream=%v ep=%v", allowStream, epStrs)
|
||||
|
||||
vlogf := logger.Discard
|
||||
if DevKnob.DumpNetMaps {
|
||||
if Debug.NetMap {
|
||||
// TODO(bradfitz): update this to use "[v2]" prefix perhaps? but we don't
|
||||
// want to upload it always.
|
||||
vlogf = c.logf
|
||||
@@ -885,7 +885,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
|
||||
if pr := resp.PingRequest; pr != nil && c.isUniquePingRequest(pr) {
|
||||
metricMapResponsePings.Add(1)
|
||||
go c.answerPing(pr)
|
||||
go answerPing(c.logf, c.httpc, pr, c.pinger)
|
||||
}
|
||||
if u := resp.PopBrowserURL; u != "" && u != sess.lastPopBrowserURL {
|
||||
sess.lastPopBrowserURL = u
|
||||
@@ -939,6 +939,8 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
if resp.Debug.GoroutineDumpURL != "" {
|
||||
go dumpGoroutinesToURL(c.httpc, resp.Debug.GoroutineDumpURL)
|
||||
}
|
||||
setControlAtomic(&controlUseDERPRoute, resp.Debug.DERPRoute)
|
||||
setControlAtomic(&controlTrimWGConfig, resp.Debug.TrimWGConfig)
|
||||
if sleep := time.Duration(resp.Debug.SleepSeconds * float64(time.Second)); sleep > 0 {
|
||||
if err := sleepAsRequested(ctx, c.logf, timeoutReset, sleep); err != nil {
|
||||
return err
|
||||
@@ -952,17 +954,12 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
return errors.New("MapResponse lacked node")
|
||||
}
|
||||
|
||||
if d := nm.Debug; d != nil {
|
||||
controlUseDERPRoute.Store(d.DERPRoute)
|
||||
controlTrimWGConfig.Store(d.TrimWGConfig)
|
||||
}
|
||||
|
||||
if DevKnob.StripEndpoints {
|
||||
if Debug.StripEndpoints {
|
||||
for _, p := range resp.Peers {
|
||||
p.Endpoints = nil
|
||||
}
|
||||
}
|
||||
if DevKnob.StripCaps {
|
||||
if Debug.StripCaps {
|
||||
nm.SelfNode.Capabilities = nil
|
||||
}
|
||||
|
||||
@@ -1128,23 +1125,25 @@ func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DevKnob contains temporary internal-only debug knobs.
|
||||
// Debug contains temporary internal-only debug knobs.
|
||||
// They're unexported to not draw attention to them.
|
||||
var DevKnob = initDevKnob()
|
||||
var Debug = initDebug()
|
||||
|
||||
type devKnobs struct {
|
||||
DumpNetMaps bool
|
||||
ForceProxyDNS bool
|
||||
type debug struct {
|
||||
NetMap bool
|
||||
ProxyDNS bool
|
||||
Disco bool
|
||||
StripEndpoints bool // strip endpoints from control (only use disco messages)
|
||||
StripCaps bool // strip all local node's control-provided capabilities
|
||||
}
|
||||
|
||||
func initDevKnob() devKnobs {
|
||||
return devKnobs{
|
||||
DumpNetMaps: envknob.Bool("TS_DEBUG_NETMAP"),
|
||||
ForceProxyDNS: envknob.Bool("TS_DEBUG_PROXY_DNS"),
|
||||
func initDebug() debug {
|
||||
return debug{
|
||||
NetMap: envknob.Bool("TS_DEBUG_NETMAP"),
|
||||
ProxyDNS: envknob.Bool("TS_DEBUG_PROXY_DNS"),
|
||||
StripEndpoints: envknob.Bool("TS_DEBUG_STRIP_ENDPOINTS"),
|
||||
StripCaps: envknob.Bool("TS_DEBUG_STRIP_CAPS"),
|
||||
Disco: envknob.BoolDefaultTrue("TS_DEBUG_USE_DISCO"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1152,20 +1151,29 @@ var clockNow = time.Now
|
||||
|
||||
// opt.Bool configs from control.
|
||||
var (
|
||||
controlUseDERPRoute syncs.AtomicValue[opt.Bool]
|
||||
controlTrimWGConfig syncs.AtomicValue[opt.Bool]
|
||||
controlUseDERPRoute atomic.Value
|
||||
controlTrimWGConfig atomic.Value
|
||||
)
|
||||
|
||||
func setControlAtomic(dst *atomic.Value, v opt.Bool) {
|
||||
old, ok := dst.Load().(opt.Bool)
|
||||
if !ok || old != v {
|
||||
dst.Store(v)
|
||||
}
|
||||
}
|
||||
|
||||
// DERPRouteFlag reports the last reported value from control for whether
|
||||
// DERP route optimization (Issue 150) should be enabled.
|
||||
func DERPRouteFlag() opt.Bool {
|
||||
return controlUseDERPRoute.Load()
|
||||
v, _ := controlUseDERPRoute.Load().(opt.Bool)
|
||||
return v
|
||||
}
|
||||
|
||||
// TrimWGConfig reports the last reported value from control for whether
|
||||
// we should do lazy wireguard configuration.
|
||||
func TrimWGConfig() opt.Bool {
|
||||
return controlTrimWGConfig.Load()
|
||||
v, _ := controlTrimWGConfig.Load().(opt.Bool)
|
||||
return v
|
||||
}
|
||||
|
||||
// ipForwardingBroken reports whether the system's IP forwarding is disabled
|
||||
@@ -1203,30 +1211,21 @@ func (c *Direct) isUniquePingRequest(pr *tailcfg.PingRequest) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Direct) answerPing(pr *tailcfg.PingRequest) {
|
||||
httpc := c.httpc
|
||||
if pr.URLIsNoise {
|
||||
nc, err := c.getNoiseClient()
|
||||
if err != nil {
|
||||
c.logf("failed to get noise client for ping request: %v", err)
|
||||
return
|
||||
}
|
||||
httpc = nc.Client
|
||||
}
|
||||
func answerPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger) {
|
||||
if pr.URL == "" {
|
||||
c.logf("invalid PingRequest with no URL")
|
||||
logf("invalid PingRequest with no URL")
|
||||
return
|
||||
}
|
||||
if pr.Types == "" {
|
||||
answerHeadPing(c.logf, httpc, pr)
|
||||
answerHeadPing(logf, c, pr)
|
||||
return
|
||||
}
|
||||
for _, t := range strings.Split(pr.Types, ",") {
|
||||
switch pt := tailcfg.PingType(t); pt {
|
||||
case tailcfg.PingTSMP, tailcfg.PingDisco, tailcfg.PingICMP, tailcfg.PingPeerAPI:
|
||||
go doPingerPing(c.logf, httpc, pr, c.pinger, pt)
|
||||
go doPingerPing(logf, c, pr, pinger, pt)
|
||||
default:
|
||||
c.logf("unsupported ping request type: %q", t)
|
||||
logf("unsupported ping request type: %q", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1329,7 +1328,7 @@ func (c *Direct) setDNSNoise(ctx context.Context, req *tailcfg.SetDNSRequest) er
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := np.Post(fmt.Sprintf("https://%v/%v", np.host, "machine/set-dns"), "application/json", bytes.NewReader(bodyData))
|
||||
res, err := np.Post(fmt.Sprintf("https://%v/%v", np.serverHost, "machine/set-dns"), "application/json", bytes.NewReader(bodyData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
@@ -47,7 +46,6 @@ type mapSession struct {
|
||||
lastDomain string
|
||||
lastHealth []string
|
||||
lastPopBrowserURL string
|
||||
stickyDebug tailcfg.Debug // accumulated opt.Bool values
|
||||
|
||||
// netMapBuilding is non-nil during a netmapForResponse call,
|
||||
// containing the value to be returned, once fully populated.
|
||||
@@ -116,28 +114,6 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
|
||||
ms.lastHealth = resp.Health
|
||||
}
|
||||
|
||||
debug := resp.Debug
|
||||
if debug != nil {
|
||||
if debug.RandomizeClientPort {
|
||||
debug.SetRandomizeClientPort.Set(true)
|
||||
}
|
||||
if debug.ForceBackgroundSTUN {
|
||||
debug.SetForceBackgroundSTUN.Set(true)
|
||||
}
|
||||
copyDebugOptBools(&ms.stickyDebug, debug)
|
||||
} else if ms.stickyDebug != (tailcfg.Debug{}) {
|
||||
debug = new(tailcfg.Debug)
|
||||
}
|
||||
if debug != nil {
|
||||
copyDebugOptBools(debug, &ms.stickyDebug)
|
||||
if !debug.ForceBackgroundSTUN {
|
||||
debug.ForceBackgroundSTUN, _ = ms.stickyDebug.SetForceBackgroundSTUN.Get()
|
||||
}
|
||||
if !debug.RandomizeClientPort {
|
||||
debug.RandomizeClientPort, _ = ms.stickyDebug.SetRandomizeClientPort.Get()
|
||||
}
|
||||
}
|
||||
|
||||
nm := &netmap.NetworkMap{
|
||||
NodeKey: ms.privateNodeKey.Public(),
|
||||
PrivateKey: ms.privateNodeKey,
|
||||
@@ -150,7 +126,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
|
||||
SSHPolicy: ms.lastSSHPolicy,
|
||||
CollectServices: ms.collectServices,
|
||||
DERPMap: ms.lastDERPMap,
|
||||
Debug: debug,
|
||||
Debug: resp.Debug,
|
||||
ControlHealth: ms.lastHealth,
|
||||
}
|
||||
ms.netMapBuilding = nm
|
||||
@@ -190,7 +166,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
|
||||
}
|
||||
ms.addUserProfile(peer.User)
|
||||
}
|
||||
if DevKnob.ForceProxyDNS {
|
||||
if Debug.ProxyDNS {
|
||||
nm.DNS.Proxied = true
|
||||
}
|
||||
ms.netMapBuilding = nil
|
||||
@@ -310,9 +286,6 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
|
||||
if v := ec.Capabilities; v != nil {
|
||||
n.Capabilities = *v
|
||||
}
|
||||
if v := ec.KeySignature; v != nil {
|
||||
n.KeySignature = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,18 +344,3 @@ func filterSelfAddresses(in []netip.Prefix) (ret []netip.Prefix) {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
func copyDebugOptBools(dst, src *tailcfg.Debug) {
|
||||
copy := func(v *opt.Bool, s opt.Bool) {
|
||||
if s != "" {
|
||||
*v = s
|
||||
}
|
||||
}
|
||||
copy(&dst.DERPRoute, src.DERPRoute)
|
||||
copy(&dst.DisableSubnetsIfPAC, src.DisableSubnetsIfPAC)
|
||||
copy(&dst.DisableUPnP, src.DisableUPnP)
|
||||
copy(&dst.OneCGNATRoute, src.OneCGNATRoute)
|
||||
copy(&dst.SetForceBackgroundSTUN, src.SetForceBackgroundSTUN)
|
||||
copy(&dst.SetRandomizeClientPort, src.SetRandomizeClientPort)
|
||||
copy(&dst.TrimWGConfig, src.TrimWGConfig)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
func TestUndeltaPeers(t *testing.T) {
|
||||
@@ -209,20 +207,6 @@ func TestUndeltaPeers(t *testing.T) {
|
||||
Key: key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change_key_signature",
|
||||
prev: peers(n(1, "foo")),
|
||||
mapRes: &tailcfg.MapResponse{
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{{
|
||||
NodeID: 1,
|
||||
KeySignature: []byte{3, 4},
|
||||
}},
|
||||
}, want: peers(&tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
KeySignature: []byte{3, 4},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change_disco_key",
|
||||
prev: peers(n(1, "foo")),
|
||||
@@ -465,152 +449,3 @@ func TestNetmapForResponse(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestDeltaDebug tests that tailcfg.Debug values can be omitted in MapResposnes
|
||||
// entirely or have their opt.Bool values unspecified between MapResponses in a
|
||||
// session and that should mean no change. (as of capver 37). But two Debug
|
||||
// fields existed prior to capver 37 that weren't opt.Bool; we test that we both
|
||||
// still accept the non-opt.Bool form from control for RandomizeClientPort and
|
||||
// ForceBackgroundSTUN and also accept the new form, keeping the old form in
|
||||
// sync.
|
||||
func TestDeltaDebug(t *testing.T) {
|
||||
type step struct {
|
||||
got *tailcfg.Debug
|
||||
want *tailcfg.Debug
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
steps []step
|
||||
}{
|
||||
{
|
||||
name: "nothing-to-nothing",
|
||||
steps: []step{
|
||||
{nil, nil},
|
||||
{nil, nil},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sticky-with-old-style-randomize-client-port",
|
||||
steps: []step{
|
||||
{
|
||||
&tailcfg.Debug{RandomizeClientPort: true},
|
||||
&tailcfg.Debug{
|
||||
RandomizeClientPort: true,
|
||||
SetRandomizeClientPort: "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
nil, // not sent by server
|
||||
&tailcfg.Debug{
|
||||
RandomizeClientPort: true,
|
||||
SetRandomizeClientPort: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sticky-with-new-style-randomize-client-port",
|
||||
steps: []step{
|
||||
{
|
||||
&tailcfg.Debug{SetRandomizeClientPort: "true"},
|
||||
&tailcfg.Debug{
|
||||
RandomizeClientPort: true,
|
||||
SetRandomizeClientPort: "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
nil, // not sent by server
|
||||
&tailcfg.Debug{
|
||||
RandomizeClientPort: true,
|
||||
SetRandomizeClientPort: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "opt-bool-sticky-changing-over-time",
|
||||
steps: []step{
|
||||
{nil, nil},
|
||||
{nil, nil},
|
||||
{
|
||||
&tailcfg.Debug{OneCGNATRoute: "true"},
|
||||
&tailcfg.Debug{OneCGNATRoute: "true"},
|
||||
},
|
||||
{
|
||||
nil,
|
||||
&tailcfg.Debug{OneCGNATRoute: "true"},
|
||||
},
|
||||
{
|
||||
&tailcfg.Debug{OneCGNATRoute: "false"},
|
||||
&tailcfg.Debug{OneCGNATRoute: "false"},
|
||||
},
|
||||
{
|
||||
nil,
|
||||
&tailcfg.Debug{OneCGNATRoute: "false"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "legacy-ForceBackgroundSTUN",
|
||||
steps: []step{
|
||||
{
|
||||
&tailcfg.Debug{ForceBackgroundSTUN: true},
|
||||
&tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "opt-bool-SetForceBackgroundSTUN",
|
||||
steps: []step{
|
||||
{
|
||||
&tailcfg.Debug{SetForceBackgroundSTUN: "true"},
|
||||
&tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "server-reset-to-default",
|
||||
steps: []step{
|
||||
{
|
||||
&tailcfg.Debug{SetForceBackgroundSTUN: "true"},
|
||||
&tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
|
||||
},
|
||||
{
|
||||
&tailcfg.Debug{SetForceBackgroundSTUN: "unset"},
|
||||
&tailcfg.Debug{ForceBackgroundSTUN: false, SetForceBackgroundSTUN: "unset"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ms := newTestMapSession(t)
|
||||
for stepi, s := range tt.steps {
|
||||
nm := ms.netmapForResponse(&tailcfg.MapResponse{Debug: s.got})
|
||||
if !reflect.DeepEqual(nm.Debug, s.want) {
|
||||
t.Errorf("unexpected result at step index %v; got: %s", stepi, must.Get(json.Marshal(nm.Debug)))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that copyDebugOptBools doesn't missing any opt.Bools.
|
||||
func TestCopyDebugOptBools(t *testing.T) {
|
||||
rt := reflect.TypeOf(tailcfg.Debug{})
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
sf := rt.Field(i)
|
||||
if sf.Type != reflect.TypeOf(opt.Bool("")) {
|
||||
continue
|
||||
}
|
||||
var src, dst tailcfg.Debug
|
||||
reflect.ValueOf(&src).Elem().Field(i).Set(reflect.ValueOf(opt.Bool("true")))
|
||||
if src == (tailcfg.Debug{}) {
|
||||
t.Fatalf("failed to set field %v", sf.Name)
|
||||
}
|
||||
copyDebugOptBools(&dst, &src)
|
||||
if src != dst {
|
||||
t.Fatalf("copyDebugOptBools didn't copy field %v", sf.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package controlclient
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -49,9 +50,7 @@ type noiseClient struct {
|
||||
dialer *tsdial.Dialer
|
||||
privKey key.MachinePrivate
|
||||
serverPubKey key.MachinePublic
|
||||
host string // the host part of serverURL
|
||||
httpPort string // the default port to call
|
||||
httpsPort string // the fallback Noise-over-https port
|
||||
serverHost string // the host:port part of serverURL
|
||||
|
||||
// mu only protects the following variables.
|
||||
mu sync.Mutex
|
||||
@@ -66,28 +65,18 @@ func newNoiseClient(priKey key.MachinePrivate, serverPubKey key.MachinePublic, s
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var httpPort string
|
||||
var httpsPort string
|
||||
var host string
|
||||
if u.Port() != "" {
|
||||
// If there is an explicit port specified, trust the scheme and hope for the best
|
||||
if u.Scheme == "http" {
|
||||
httpPort = u.Port()
|
||||
httpsPort = "443"
|
||||
} else {
|
||||
httpPort = "80"
|
||||
httpsPort = u.Port()
|
||||
}
|
||||
// If there is an explicit port specified use it.
|
||||
host = u.Host
|
||||
} else {
|
||||
// Otherwise, use the standard ports
|
||||
httpPort = "80"
|
||||
httpsPort = "443"
|
||||
// Otherwise, controlhttp.Dial expects an http endpoint.
|
||||
host = fmt.Sprintf("%v:80", u.Hostname())
|
||||
}
|
||||
np := &noiseClient{
|
||||
serverPubKey: serverPubKey,
|
||||
privKey: priKey,
|
||||
host: u.Hostname(),
|
||||
httpPort: httpPort,
|
||||
httpsPort: httpsPort,
|
||||
serverHost: host,
|
||||
dialer: dialer,
|
||||
}
|
||||
|
||||
@@ -165,7 +154,7 @@ func (nc *noiseClient) dial(_, _ string, _ *tls.Config) (net.Conn, error) {
|
||||
// thousand version numbers before getting to this point.
|
||||
panic("capability version is too high to fit in the wire protocol")
|
||||
}
|
||||
conn, err := controlhttp.Dial(ctx, nc.host, nc.httpPort, nc.httpsPort, nc.privKey, nc.serverPubKey, uint16(tailcfg.CurrentCapabilityVersion), nc.dialer.SystemDial)
|
||||
conn, err := controlhttp.Dial(ctx, nc.serverHost, nc.privKey, nc.serverPubKey, uint16(tailcfg.CurrentCapabilityVersion), nc.dialer.SystemDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -43,20 +43,24 @@ import (
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
// Dial connects to the HTTP server at host:httpPort, requests to switch to the
|
||||
// Dial connects to the HTTP server at addr, requests to switch to the
|
||||
// Tailscale control protocol, and returns an established control
|
||||
// protocol connection.
|
||||
//
|
||||
// If Dial fails to connect using addr, it also tries to tunnel over
|
||||
// TLS to host:httpsPort as a compatibility fallback.
|
||||
// TLS to <addr's host>:443 as a compatibility fallback.
|
||||
//
|
||||
// The provided ctx is only used for the initial connection, until
|
||||
// Dial returns. It does not affect the connection once established.
|
||||
func Dial(ctx context.Context, host string, httpPort string, httpsPort string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16, dialer dnscache.DialContextFunc) (*controlbase.Conn, error) {
|
||||
func Dial(ctx context.Context, addr string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16, dialer dnscache.DialContextFunc) (*controlbase.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a := &dialParams{
|
||||
host: host,
|
||||
httpPort: httpPort,
|
||||
httpsPort: httpsPort,
|
||||
httpPort: port,
|
||||
httpsPort: "443",
|
||||
machineKey: machineKey,
|
||||
controlKey: controlKey,
|
||||
version: protocolVersion,
|
||||
|
||||
@@ -18,20 +18,25 @@ import (
|
||||
|
||||
// Variant of Dial that tunnels the request over WebSockets, since we cannot do
|
||||
// bi-directional communication over an HTTP connection when in JS.
|
||||
func Dial(ctx context.Context, host string, httpPort string, httpsPort string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16, dialer dnscache.DialContextFunc) (*controlbase.Conn, error) {
|
||||
func Dial(ctx context.Context, addr string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16, dialer dnscache.DialContextFunc) (*controlbase.Conn, error) {
|
||||
init, cont, err := controlbase.ClientDeferred(machineKey, controlKey, protocolVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wsScheme := "wss"
|
||||
wsHost := host
|
||||
if host == "localhost" {
|
||||
wsScheme = "ws"
|
||||
host = net.JoinHostPort(host, httpPort)
|
||||
wsHost = addr
|
||||
}
|
||||
wsURL := &url.URL{
|
||||
Scheme: wsScheme,
|
||||
Host: host,
|
||||
Host: wsHost,
|
||||
Path: serverUpgradePath,
|
||||
// Can't set HTTP headers on the websocket request, so we have to to send
|
||||
// the handshake via an HTTP header.
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
"io"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
"golang.org/x/time/rate"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
@@ -39,8 +39,8 @@ type Client struct {
|
||||
rate *rate.Limiter // if non-nil, rate limiter to use
|
||||
|
||||
// Owned by Recv:
|
||||
peeked int // bytes to discard on next Recv
|
||||
readErr syncs.AtomicValue[error] // sticky (set by Recv)
|
||||
peeked int // bytes to discard on next Recv
|
||||
readErr atomic.Value // of error; sticky (set by Recv)
|
||||
}
|
||||
|
||||
// ClientOpt is an option passed to NewClient.
|
||||
@@ -445,7 +445,7 @@ func (c *Client) Recv() (m ReceivedMessage, err error) {
|
||||
}
|
||||
|
||||
func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err error) {
|
||||
readErr := c.readErr.Load()
|
||||
readErr, _ := c.readErr.Load().(error)
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
}
|
||||
|
||||
@@ -958,10 +958,10 @@ func TestServerDupClients(t *testing.T) {
|
||||
t.Error("wrong single client")
|
||||
return
|
||||
}
|
||||
if want.isDup.Load() {
|
||||
if want.isDup.Get() {
|
||||
t.Errorf("unexpected isDup on singleClient")
|
||||
}
|
||||
if want.isDisabled.Load() {
|
||||
if want.isDisabled.Get() {
|
||||
t.Errorf("unexpected isDisabled on singleClient")
|
||||
}
|
||||
case nil:
|
||||
@@ -1004,13 +1004,13 @@ func TestServerDupClients(t *testing.T) {
|
||||
}
|
||||
checkDup := func(t *testing.T, c *sclient, want bool) {
|
||||
t.Helper()
|
||||
if got := c.isDup.Load(); got != want {
|
||||
if got := c.isDup.Get(); got != want {
|
||||
t.Errorf("client %q isDup = %v; want %v", clientName[c], got, want)
|
||||
}
|
||||
}
|
||||
checkDisabled := func(t *testing.T, c *sclient, want bool) {
|
||||
t.Helper()
|
||||
if got := c.isDisabled.Load(); got != want {
|
||||
if got := c.isDisabled.Get(); got != want {
|
||||
t.Errorf("client %q isDisabled = %v; want %v", clientName[c], got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
@@ -36,7 +37,6 @@ import (
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -69,7 +69,7 @@ type Client struct {
|
||||
// by SetAddressFamilySelector. It's an atomic because it needs
|
||||
// to be accessed by multiple racing routines started while
|
||||
// Client.conn holds mu.
|
||||
addrFamSelAtomic syncs.AtomicValue[AddressFamilySelector]
|
||||
addrFamSelAtomic atomic.Value // of AddressFamilySelector
|
||||
|
||||
mu sync.Mutex
|
||||
preferred bool
|
||||
|
||||
8
go.mod
8
go.mod
@@ -17,7 +17,7 @@ require (
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/creack/pty v1.1.17
|
||||
github.com/dave/jennifer v1.4.1
|
||||
github.com/evanw/esbuild v0.14.53
|
||||
github.com/evanw/esbuild v0.14.39
|
||||
github.com/frankban/quicktest v1.14.0
|
||||
github.com/fxamacker/cbor/v2 v2.4.0
|
||||
github.com/go-ole/go-ole v1.2.6
|
||||
@@ -51,7 +51,7 @@ require (
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
|
||||
github.com/tcnksm/go-httpstat v0.2.0
|
||||
github.com/toqueteos/webbrowser v1.2.0
|
||||
github.com/u-root/u-root v0.9.0
|
||||
github.com/u-root/u-root v0.8.0
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94
|
||||
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
|
||||
golang.org/x/tools v0.1.11
|
||||
@@ -254,7 +254,7 @@ require (
|
||||
github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.4.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/ultraware/funlen v0.0.3 // indirect
|
||||
github.com/ultraware/whitespace v0.0.4 // indirect
|
||||
|
||||
17
go.sum
17
go.sum
@@ -278,8 +278,8 @@ github.com/esimonov/ifshort v1.0.3 h1:JD6x035opqGec5fZ0TLjXeROD2p5H7oLGn8MKfy9HT
|
||||
github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE=
|
||||
github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw=
|
||||
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
|
||||
github.com/evanw/esbuild v0.14.53 h1:9uU73SZUmP1jRQhaC6hPm9aoqFGYlPwfk7OrhG6AhpQ=
|
||||
github.com/evanw/esbuild v0.14.53/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
|
||||
github.com/evanw/esbuild v0.14.39 h1:1TMZtCXOY4ctAbGY4QT9sjT203I/cQ16vXt2F9rLT58=
|
||||
github.com/evanw/esbuild v0.14.39/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
@@ -1127,11 +1127,11 @@ github.com/tommy-muehle/go-mnd/v2 v2.4.0 h1:1t0f8Uiaq+fqKteUR4N9Umr6E99R+lDnLnq7
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/u-root/u-root v0.9.0 h1:1dpUzrE0FyKrNEjxpKFOkyveuV1f3T0Ko5CQg4gTkCg=
|
||||
github.com/u-root/u-root v0.9.0/go.mod h1:ewc9w6JF1ayZCVC9Y5wsrUiCBw3nMmPC3QItvrEwmew=
|
||||
github.com/u-root/u-root v0.8.0 h1:jqP7uPC2+0eRszYTrmdZ6UDyO1Dbuy0rpMo+BnPZ9cY=
|
||||
github.com/u-root/u-root v0.8.0/go.mod h1:But1FHzS4Ua4ywx6kZOaRzZTucUKIDKOPOLEKOckQ68=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/rzUomQY1q6cM3ncA0wP8GU4=
|
||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
@@ -1484,6 +1484,7 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1494,8 +1495,8 @@ golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
|
||||
@@ -38,7 +38,6 @@ func New() *tailcfg.Hostinfo {
|
||||
Desktop: desktop(),
|
||||
Package: packageTypeCached(),
|
||||
GoArch: runtime.GOARCH,
|
||||
GoVersion: runtime.Version(),
|
||||
DeviceModel: deviceModel(),
|
||||
Cloud: string(cloudenv.Get()),
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/strs"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
@@ -51,7 +50,7 @@ func linuxDeviceModel() string {
|
||||
|
||||
func getQnapQtsVersion(versionInfo string) string {
|
||||
for _, field := range strings.Fields(versionInfo) {
|
||||
if suffix, ok := strs.CutPrefix(field, "QTSFW_"); ok {
|
||||
if suffix := strings.TrimPrefix(field, "QTSFW_"); suffix != field {
|
||||
return "QTS " + suffix
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/util/winutil"
|
||||
)
|
||||
|
||||
@@ -20,10 +20,10 @@ func init() {
|
||||
packageType = packageTypeWindows
|
||||
}
|
||||
|
||||
var winVerCache syncs.AtomicValue[string]
|
||||
var winVerCache atomic.Value // of string
|
||||
|
||||
func osVersionWindows() string {
|
||||
if s, ok := winVerCache.LoadOk(); ok {
|
||||
if s, ok := winVerCache.Load().(string); ok {
|
||||
return s
|
||||
}
|
||||
major, minor, build := windows.RtlGetNtVersionNumbers()
|
||||
|
||||
@@ -40,7 +40,6 @@ import (
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/portlist"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/types/dnstype"
|
||||
@@ -130,8 +129,8 @@ type LocalBackend struct {
|
||||
sshAtomicBool atomic.Bool
|
||||
shutdownCalled bool // if Shutdown has been called
|
||||
|
||||
filterAtomic atomic.Pointer[filter.Filter]
|
||||
containsViaIPFuncAtomic syncs.AtomicValue[func(netip.Addr) bool]
|
||||
filterAtomic atomic.Value // of *filter.Filter
|
||||
containsViaIPFuncAtomic atomic.Value // of func(netip.Addr) bool
|
||||
|
||||
// The mutex protects the following elements.
|
||||
mu sync.Mutex
|
||||
@@ -148,7 +147,7 @@ type LocalBackend struct {
|
||||
inServerMode bool
|
||||
machinePrivKey key.MachinePrivate
|
||||
nlPrivKey key.NLPrivate
|
||||
tka *tkaState
|
||||
tka *tka.Authority
|
||||
state ipn.State
|
||||
capFileSharing bool // whether netMap contains the file sharing capability
|
||||
// hostinfo is mutated in-place while mu is held.
|
||||
@@ -577,8 +576,8 @@ func (b *LocalBackend) PeerCaps(src netip.Addr) []string {
|
||||
if b.netMap == nil {
|
||||
return nil
|
||||
}
|
||||
filt := b.filterAtomic.Load()
|
||||
if filt == nil {
|
||||
filt, ok := b.filterAtomic.Load().(*filter.Filter)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
for _, a := range b.netMap.Addresses {
|
||||
@@ -1039,7 +1038,10 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
})
|
||||
}
|
||||
|
||||
discoPublic := b.e.DiscoPublicKey()
|
||||
var discoPublic key.DiscoPublic
|
||||
if controlclient.Debug.Disco {
|
||||
discoPublic = b.e.DiscoPublicKey()
|
||||
}
|
||||
|
||||
var err error
|
||||
if persistv == nil {
|
||||
@@ -1180,15 +1182,7 @@ func (b *LocalBackend) updateFilterLocked(netMap *netmap.NetworkMap, prefs *ipn.
|
||||
sshPol = *netMap.SSHPolicy
|
||||
}
|
||||
|
||||
changed := deephash.Update(&b.filterHash, &struct {
|
||||
HaveNetmap bool
|
||||
Addrs []netip.Prefix
|
||||
FilterMatch []filter.Match
|
||||
LocalNets []netipx.IPRange
|
||||
LogNets []netipx.IPRange
|
||||
ShieldsUp bool
|
||||
SSHPolicy tailcfg.SSHPolicy
|
||||
}{haveNetmap, addrs, packetFilter, localNets.Ranges(), logNets.Ranges(), shieldsUp, sshPol})
|
||||
changed := deephash.Update(&b.filterHash, haveNetmap, addrs, packetFilter, localNets.Ranges(), logNets.Ranges(), shieldsUp, sshPol)
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
@@ -1506,17 +1500,17 @@ func (b *LocalBackend) tellClientToBrowseToURL(url string) {
|
||||
var panicOnMachineKeyGeneration = envknob.Bool("TS_DEBUG_PANIC_MACHINE_KEY")
|
||||
|
||||
func (b *LocalBackend) createGetMachinePrivateKeyFunc() func() (key.MachinePrivate, error) {
|
||||
var cache syncs.AtomicValue[key.MachinePrivate]
|
||||
var cache atomic.Value
|
||||
return func() (key.MachinePrivate, error) {
|
||||
if panicOnMachineKeyGeneration {
|
||||
panic("machine key generated")
|
||||
}
|
||||
if v, ok := cache.LoadOk(); ok {
|
||||
if v, ok := cache.Load().(key.MachinePrivate); ok {
|
||||
return v, nil
|
||||
}
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if v, ok := cache.LoadOk(); ok {
|
||||
if v, ok := cache.Load().(key.MachinePrivate); ok {
|
||||
return v, nil
|
||||
}
|
||||
if err := b.initMachineKeyLocked(); err != nil {
|
||||
@@ -1528,11 +1522,11 @@ func (b *LocalBackend) createGetMachinePrivateKeyFunc() func() (key.MachinePriva
|
||||
}
|
||||
|
||||
func (b *LocalBackend) createGetNLPublicKeyFunc() func() (key.NLPublic, error) {
|
||||
var cache syncs.AtomicValue[key.NLPublic]
|
||||
var cache atomic.Value
|
||||
return func() (key.NLPublic, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if v, ok := cache.LoadOk(); ok {
|
||||
if v, ok := cache.Load().(key.NLPublic); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
@@ -2507,11 +2501,8 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
|
||||
// used for locked tailnets.
|
||||
//
|
||||
// It should only be called before the LocalBackend is used.
|
||||
func (b *LocalBackend) SetTailnetKeyAuthority(a *tka.Authority, storage *tka.FS) {
|
||||
b.tka = &tkaState{
|
||||
authority: a,
|
||||
storage: storage,
|
||||
}
|
||||
func (b *LocalBackend) SetTailnetKeyAuthority(a *tka.Authority) {
|
||||
b.tka = a
|
||||
}
|
||||
|
||||
// SetVarRoot sets the root directory of Tailscale's writable
|
||||
@@ -2533,7 +2524,8 @@ func (b *LocalBackend) TailscaleVarRoot() string {
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "ios", "android", "darwin":
|
||||
return paths.AppSharedDir.Load()
|
||||
dir, _ := paths.AppSharedDir.Load().(string)
|
||||
return dir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -2800,12 +2792,14 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs, oneCGNA
|
||||
rs.LocalRoutes = internalIPs // unconditionally allow access to guest VM networks
|
||||
if prefs.ExitNodeAllowLANAccess {
|
||||
rs.LocalRoutes = append(rs.LocalRoutes, externalIPs...)
|
||||
if len(externalIPs) != 0 {
|
||||
b.logf("allowing exit node access to internal IPs: %v", internalIPs)
|
||||
}
|
||||
} else {
|
||||
// Explicitly add routes to the local network so that we do not
|
||||
// leak any traffic.
|
||||
rs.Routes = append(rs.Routes, externalIPs...)
|
||||
}
|
||||
b.logf("allowing exit node access to local IPs: %v", rs.LocalRoutes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3064,7 +3058,7 @@ func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && ca
|
||||
// Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to
|
||||
// by Tailscale.
|
||||
func (b *LocalBackend) ShouldHandleViaIP(ip netip.Addr) bool {
|
||||
if f, ok := b.containsViaIPFuncAtomic.LoadOk(); ok {
|
||||
if f, ok := b.containsViaIPFuncAtomic.Load().(func(netip.Addr) bool); ok {
|
||||
return f(ip)
|
||||
}
|
||||
return false
|
||||
@@ -3290,7 +3284,7 @@ func (b *LocalBackend) FileTargets() ([]*apitype.FileTarget, error) {
|
||||
return nil, errors.New("file sharing not enabled by Tailscale admin")
|
||||
}
|
||||
for _, p := range nm.Peers {
|
||||
if p.User != nm.User && !slices.Contains(p.Capabilities, tailcfg.CapabilityFileSharingTarget) {
|
||||
if p.User != nm.User || !slices.Contains(p.Capabilities, tailcfg.CapabilityFileSharingTarget) {
|
||||
continue
|
||||
}
|
||||
peerAPI := peerAPIBase(b.netMap, p)
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/tkatype"
|
||||
)
|
||||
|
||||
var networkLockAvailable = envknob.Bool("TS_EXPERIMENTAL_NETWORK_LOCK")
|
||||
|
||||
type tkaState struct {
|
||||
authority *tka.Authority
|
||||
storage *tka.FS
|
||||
}
|
||||
|
||||
// CanSupportNetworkLock returns true if tailscaled is able to operate
|
||||
// a local tailnet key authority (and hence enforce network lock).
|
||||
func (b *LocalBackend) CanSupportNetworkLock() bool {
|
||||
if b.tka != nil {
|
||||
// The TKA is being used, so yeah its supported.
|
||||
return true
|
||||
}
|
||||
|
||||
if b.TailscaleVarRoot() != "" {
|
||||
// Theres a var root (aka --statedir), so if network lock gets
|
||||
// initialized we have somewhere to store our AUMs. Thats all
|
||||
// we need.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NetworkLockStatus returns a structure describing the state of the
|
||||
// tailnet key authority, if any.
|
||||
func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
|
||||
if b.tka == nil {
|
||||
return &ipnstate.NetworkLockStatus{
|
||||
Enabled: false,
|
||||
PublicKey: b.nlPrivKey.Public(),
|
||||
}
|
||||
}
|
||||
|
||||
var head [32]byte
|
||||
h := b.tka.authority.Head()
|
||||
copy(head[:], h[:])
|
||||
|
||||
return &ipnstate.NetworkLockStatus{
|
||||
Enabled: true,
|
||||
Head: &head,
|
||||
PublicKey: b.nlPrivKey.Public(),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkLockInit enables network-lock for the tailnet, with the tailnets'
|
||||
// key authority initialized to trust the provided keys.
|
||||
//
|
||||
// Initialization involves two RPCs with control, termed 'begin' and 'finish'.
|
||||
// The Begin RPC transmits the genesis Authority Update Message, which
|
||||
// encodes the initial state of the authority, and the list of all nodes
|
||||
// needing signatures is returned as a response.
|
||||
// The Finish RPC submits signatures for all these nodes, at which point
|
||||
// Control has everything it needs to atomically enable network lock.
|
||||
func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
|
||||
if b.tka != nil {
|
||||
return errors.New("network-lock is already initialized")
|
||||
}
|
||||
if !networkLockAvailable {
|
||||
return errors.New("this is an experimental feature in your version of tailscale - Please upgrade to the latest to use this.")
|
||||
}
|
||||
if !b.CanSupportNetworkLock() {
|
||||
return errors.New("network-lock is not supported in this configuration. Did you supply a --statedir?")
|
||||
}
|
||||
nm := b.NetMap()
|
||||
if nm == nil {
|
||||
return errors.New("no netmap: are you logged into tailscale?")
|
||||
}
|
||||
|
||||
// Generates a genesis AUM representing trust in the provided keys.
|
||||
// We use an in-memory tailchonk because we don't want to commit to
|
||||
// the filesystem until we've finished the initialization sequence,
|
||||
// just in case something goes wrong.
|
||||
_, genesisAUM, err := tka.Create(&tka.Mem{}, tka.State{
|
||||
Keys: keys,
|
||||
// TODO(tom): Actually plumb a real disablement value.
|
||||
DisablementSecrets: [][]byte{bytes.Repeat([]byte{1}, 32)},
|
||||
}, b.nlPrivKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tka.Create: %v", err)
|
||||
}
|
||||
|
||||
b.logf("Generated genesis AUM to initialize network lock, trusting the following keys:")
|
||||
for i, k := range genesisAUM.State.Keys {
|
||||
b.logf(" - key[%d] = nlpub:%x with %d votes", i, k.Public, k.Votes)
|
||||
}
|
||||
|
||||
// Phase 1/2 of initialization: Transmit the genesis AUM to Control.
|
||||
initResp, err := b.tkaInitBegin(nm, genesisAUM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tka init-begin RPC: %w", err)
|
||||
}
|
||||
|
||||
// Our genesis AUM was accepted but before Control turns on enforcement of
|
||||
// node-key signatures, we need to sign keys for all the existing nodes.
|
||||
// If we don't get these signatures ahead of time, everyone will loose
|
||||
// connectivity because control won't have any signatures to send which
|
||||
// satisfy network-lock checks.
|
||||
sigs := make(map[tailcfg.NodeID]tkatype.MarshaledSignature, len(initResp.NeedSignatures))
|
||||
for _, nodeInfo := range initResp.NeedSignatures {
|
||||
nks, err := signNodeKey(nodeInfo, b.nlPrivKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating signature: %v", err)
|
||||
}
|
||||
|
||||
sigs[nodeInfo.NodeID] = nks.Serialize()
|
||||
}
|
||||
|
||||
// Finalize enablement by transmitting signature for all nodes to Control.
|
||||
_, err = b.tkaInitFinish(nm, sigs)
|
||||
return err
|
||||
}
|
||||
|
||||
func signNodeKey(nodeInfo tailcfg.TKASignInfo, signer key.NLPrivate) (*tka.NodeKeySignature, error) {
|
||||
p, err := nodeInfo.NodePublic.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig := tka.NodeKeySignature{
|
||||
SigKind: tka.SigDirect,
|
||||
KeyID: signer.KeyID(),
|
||||
Pubkey: p,
|
||||
RotationPubkey: nodeInfo.RotationPubkey,
|
||||
}
|
||||
sig.Signature, err = signer.SignNKS(sig.SigHash())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("signature failed: %w", err)
|
||||
}
|
||||
return &sig, nil
|
||||
}
|
||||
|
||||
func (b *LocalBackend) tkaInitBegin(nm *netmap.NetworkMap, aum tka.AUM) (*tailcfg.TKAInitBeginResponse, error) {
|
||||
var req bytes.Buffer
|
||||
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitBeginRequest{
|
||||
NodeID: nm.SelfNode.ID,
|
||||
GenesisAUM: aum.Serialize(),
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("encoding request: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
bo := backoff.NewBackoff("tka-init-begin", b.logf, 5*time.Second)
|
||||
for {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, fmt.Errorf("ctx: %w", err)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://unused/machine/tka/init/begin", &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("req: %w", err)
|
||||
}
|
||||
res, err := b.DoNoiseRequest(req)
|
||||
if err != nil {
|
||||
bo.BackOff(ctx, err)
|
||||
continue
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return nil, fmt.Errorf("request returned (%d): %s", res.StatusCode, string(body))
|
||||
}
|
||||
a := new(tailcfg.TKAInitBeginResponse)
|
||||
err = json.NewDecoder(res.Body).Decode(a)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding JSON: %w", err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *LocalBackend) tkaInitFinish(nm *netmap.NetworkMap, nks map[tailcfg.NodeID]tkatype.MarshaledSignature) (*tailcfg.TKAInitFinishResponse, error) {
|
||||
var req bytes.Buffer
|
||||
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitFinishRequest{
|
||||
NodeID: nm.SelfNode.ID,
|
||||
Signatures: nks,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("encoding request: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
bo := backoff.NewBackoff("tka-init-finish", b.logf, 5*time.Second)
|
||||
for {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, fmt.Errorf("ctx: %w", err)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://unused/machine/tka/init/finish", &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("req: %w", err)
|
||||
}
|
||||
res, err := b.DoNoiseRequest(req)
|
||||
if err != nil {
|
||||
bo.BackOff(ctx, err)
|
||||
continue
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return nil, fmt.Errorf("request returned (%d): %s", res.StatusCode, string(body))
|
||||
}
|
||||
a := new(tailcfg.TKAInitFinishResponse)
|
||||
err = json.NewDecoder(res.Body).Decode(a)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding JSON: %w", err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
@@ -973,8 +973,8 @@ func (h *peerAPIHandler) replyToDNSQueries() bool {
|
||||
// ourselves. As a proxy for autogroup:internet access, we see
|
||||
// if we would've accepted a packet to 0.0.0.0:53. We treat
|
||||
// the IP 0.0.0.0 as being "the internet".
|
||||
f := b.filterAtomic.Load()
|
||||
if f == nil {
|
||||
f, ok := b.filterAtomic.Load().(*filter.Filter)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// Note: we check TCP here because the Filter type already had
|
||||
|
||||
@@ -504,7 +504,7 @@ func TestDeletedMarkers(t *testing.T) {
|
||||
|
||||
nothingWaiting := func() {
|
||||
t.Helper()
|
||||
ps.knownEmpty.Store(false)
|
||||
ps.knownEmpty.Set(false)
|
||||
if ps.hasFilesWaiting() {
|
||||
t.Fatal("unexpected files waiting")
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func newMockControl(tb testing.TB) *mockControl {
|
||||
}
|
||||
|
||||
func (cc *mockControl) logf(format string, args ...any) {
|
||||
if cc.preventLog.Load() || cc.logfActual == nil {
|
||||
if cc.preventLog.Get() || cc.logfActual == nil {
|
||||
return
|
||||
}
|
||||
cc.logfActual(format, args...)
|
||||
@@ -292,7 +292,7 @@ func TestStateMachine(t *testing.T) {
|
||||
|
||||
cc := newMockControl(t)
|
||||
cc.statusFunc = b.setClientStatus
|
||||
t.Cleanup(func() { cc.preventLog.Store(true) }) // hacky way to pacify issue 3020
|
||||
t.Cleanup(func() { cc.preventLog.Set(true) }) // hacky way to pacify issue 3020
|
||||
|
||||
b.SetControlClientGetterForTesting(func(opts controlclient.Options) (controlclient.Client, error) {
|
||||
cc.mu.Lock()
|
||||
@@ -311,7 +311,7 @@ func TestStateMachine(t *testing.T) {
|
||||
notifies.expect(0)
|
||||
|
||||
b.SetNotifyCallback(func(n ipn.Notify) {
|
||||
if cc.preventLog.Load() {
|
||||
if cc.preventLog.Get() {
|
||||
return
|
||||
}
|
||||
if n.State != nil ||
|
||||
@@ -928,18 +928,18 @@ func (s *testStateStorage) ReadState(id ipn.StateKey) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (s *testStateStorage) WriteState(id ipn.StateKey, bs []byte) error {
|
||||
s.written.Store(true)
|
||||
s.written.Set(true)
|
||||
return s.mem.WriteState(id, bs)
|
||||
}
|
||||
|
||||
// awaitWrite clears the "I've seen writes" bit, in prep for a future
|
||||
// call to sawWrite to see if a write arrived.
|
||||
func (s *testStateStorage) awaitWrite() { s.written.Store(false) }
|
||||
func (s *testStateStorage) awaitWrite() { s.written.Set(false) }
|
||||
|
||||
// sawWrite reports whether there's been a WriteState call since the most
|
||||
// recent awaitWrite call.
|
||||
func (s *testStateStorage) sawWrite() bool {
|
||||
v := s.written.Load()
|
||||
v := s.written.Get()
|
||||
s.awaitWrite()
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -775,15 +775,15 @@ func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engi
|
||||
chonkDir := filepath.Join(root, "chonk")
|
||||
if _, err := os.Stat(chonkDir); err == nil {
|
||||
// The directory exists, which means network-lock has been initialized.
|
||||
storage, err := tka.ChonkDir(chonkDir)
|
||||
chonk, err := tka.ChonkDir(chonkDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening tailchonk: %v", err)
|
||||
}
|
||||
authority, err := tka.Open(storage)
|
||||
authority, err := tka.Open(chonk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing tka: %v", err)
|
||||
}
|
||||
b.SetTailnetKeyAuthority(authority, storage)
|
||||
b.SetTailnetKeyAuthority(authority)
|
||||
logf("tka initialized at head %x", authority.Head())
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -67,21 +67,6 @@ type Status struct {
|
||||
User map[tailcfg.UserID]tailcfg.UserProfile
|
||||
}
|
||||
|
||||
// NetworkLockStatus represents whether network-lock is enabled,
|
||||
// along with details about the locally-known state of the tailnet
|
||||
// key authority.
|
||||
type NetworkLockStatus struct {
|
||||
// Enabled is true if network lock is enabled.
|
||||
Enabled bool
|
||||
|
||||
// Head describes the AUM hash of the leaf AUM. Head is nil
|
||||
// if network lock is not enabled.
|
||||
Head *[32]byte
|
||||
|
||||
// PublicKey describes the nodes' network-lock public key.
|
||||
PublicKey key.NLPublic
|
||||
}
|
||||
|
||||
// TailnetStatus is information about a Tailscale network ("tailnet").
|
||||
type TailnetStatus struct {
|
||||
// Name is the name of the network that's currently in use.
|
||||
|
||||
@@ -31,7 +31,6 @@ import (
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/version"
|
||||
@@ -151,10 +150,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveIDToken(w, r)
|
||||
case "/localapi/v0/upload-client-metrics":
|
||||
h.serveUploadClientMetrics(w, r)
|
||||
case "/localapi/v0/tka/status":
|
||||
h.serveTkaStatus(w, r)
|
||||
case "/localapi/v0/tka/init":
|
||||
h.serveTkaInit(w, r)
|
||||
case "/":
|
||||
io.WriteString(w, "tailscaled\n")
|
||||
default:
|
||||
@@ -796,58 +791,6 @@ func (h *Handler) serveUploadClientMetrics(w http.ResponseWriter, r *http.Reques
|
||||
json.NewEncoder(w).Encode(struct{}{})
|
||||
}
|
||||
|
||||
func (h *Handler) serveTkaStatus(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "lock status access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "use Get", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
j, err := json.MarshalIndent(h.b.NetworkLockStatus(), "", "\t")
|
||||
if err != nil {
|
||||
http.Error(w, "JSON encoding error", 500)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(j)
|
||||
}
|
||||
|
||||
func (h *Handler) serveTkaInit(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "lock init access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "use POST", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
type initRequest struct {
|
||||
Keys []tka.Key
|
||||
}
|
||||
var req initRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid JSON body", 400)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.b.NetworkLockInit(req.Keys); err != nil {
|
||||
http.Error(w, "initialization failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
j, err := json.MarshalIndent(h.b.NetworkLockStatus(), "", "\t")
|
||||
if err != nil {
|
||||
http.Error(w, "JSON encoding error", 500)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(j)
|
||||
}
|
||||
|
||||
func defBool(a string, def bool) bool {
|
||||
if a == "" {
|
||||
return def
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# Tailscale for Android dependencies
|
||||
|
||||
The following open source dependencies are used to build the [Tailscale Android
|
||||
Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||
|
||||
[Tailscale Android Client]: https://github.com/tailscale/tailscale-android
|
||||
|
||||
## Go Packages
|
||||
|
||||
|
||||
- [eliasnaur.com/font/roboto](https://pkg.go.dev/eliasnaur.com/font/roboto) ([BSD-3-Clause](https://git.sr.ht/~eliasnaur/font/tree/832bb8fc08c3/LICENSE))
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [gioui.org](https://pkg.go.dev/gioui.org) ([MIT](https://git.sr.ht/~eliasnaur/gio/tree/a1b5ff059c07/LICENSE))
|
||||
- [gioui.org/cpu](https://pkg.go.dev/gioui.org/cpu) ([MIT](https://git.sr.ht/~eliasnaur/gio-cpu/tree/8d6a761490d2/LICENSE))
|
||||
- [gioui.org/shader](https://pkg.go.dev/gioui.org/shader) ([MIT](https://git.sr.ht/~eliasnaur/gio-shader/tree/v1.0.6/LICENSE))
|
||||
- [github.com/benoitkugler/textlayout](https://pkg.go.dev/github.com/benoitkugler/textlayout) ([MIT](https://github.com/benoitkugler/textlayout/blob/v0.0.10/LICENSE))
|
||||
- [github.com/benoitkugler/textlayout/fonts](https://pkg.go.dev/github.com/benoitkugler/textlayout/fonts) ([MIT](https://github.com/benoitkugler/textlayout/blob/v0.0.10/fonts/LICENSE))
|
||||
- [github.com/benoitkugler/textlayout/graphite](https://pkg.go.dev/github.com/benoitkugler/textlayout/graphite) ([MIT](https://github.com/benoitkugler/textlayout/blob/v0.0.10/graphite/LICENSE))
|
||||
- [github.com/benoitkugler/textlayout/harfbuzz](https://pkg.go.dev/github.com/benoitkugler/textlayout/harfbuzz) ([MIT](https://github.com/benoitkugler/textlayout/blob/v0.0.10/harfbuzz/LICENSE))
|
||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/gioui/uax](https://pkg.go.dev/github.com/gioui/uax) ([BSD-3-Clause](https://github.com/gioui/uax/blob/e3d987515a12/LICENSE))
|
||||
- [github.com/go-text/typesetting](https://pkg.go.dev/github.com/go-text/typesetting) ([BSD-3-Clause](https://github.com/go-text/typesetting/blob/58fe93c84506/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.0.1/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/7d93572ebe8e/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/v1.0.0/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.4/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.4/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.4/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.6.0/LICENSE.md))
|
||||
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.2.3/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/0b941c09a5e1/LICENSE))
|
||||
- [github.com/tailscale/goupnp](https://pkg.go.dev/github.com/tailscale/goupnp) ([BSD-2-Clause](https://github.com/tailscale/goupnp/blob/c64d0f06ea05/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tailscale/tailscale-android](https://pkg.go.dev/github.com/tailscale/tailscale-android) ([BSD-3-Clause](https://github.com/tailscale/tailscale-android/blob/HEAD/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/dac05f7d2cb4/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/intern](https://pkg.go.dev/go4.org/intern) ([BSD-3-Clause](https://github.com/go4org/intern/blob/ae77deb06f29/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/927187094b94/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [go4.org/unsafe/assume-no-moving-gc](https://pkg.go.dev/go4.org/unsafe/assume-no-moving-gc) ([BSD-3-Clause](https://github.com/go4org/unsafe-assume-no-moving-gc/blob/928513b29760/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/eb4f295c:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/a9213eeb:LICENSE))
|
||||
- [golang.org/x/exp/shiny](https://pkg.go.dev/golang.org/x/exp/shiny) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/807a2327:shiny/LICENSE))
|
||||
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/a66eb644:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/c690dde0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/0de741cf:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/c0bba94a:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/03fcf44c:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.3.7:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/f0f3c7e8:LICENSE))
|
||||
- [golang.zx2c4.com/wireguard](https://pkg.go.dev/golang.zx2c4.com/wireguard) ([MIT](https://git.zx2c4.com/wireguard-go/tree/LICENSE?id=c31a7b1ab478))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/850e42eb4444/LICENSE))
|
||||
- [inet.af/netaddr](https://pkg.go.dev/inet.af/netaddr) ([BSD-3-Clause](https://github.com/inetaf/netaddr/blob/097006376321/LICENSE))
|
||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE))
|
||||
@@ -1,56 +0,0 @@
|
||||
# Tailscale for macOS/iOS dependencies
|
||||
|
||||
The following open source dependencies are used to build Tailscale on [macOS][]
|
||||
and [iOS][]. See also the dependencies in the [Tailscale CLI][].
|
||||
|
||||
[macOS]: https://tailscale.com/kb/1016/install-mac/
|
||||
[iOS]: https://tailscale.com/kb/1020/install-ios/
|
||||
[Tailscale CLI]: ./tailscale.md
|
||||
|
||||
## Go Packages
|
||||
|
||||
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.0.1/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/7d93572ebe8e/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/v1.0.0/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/klauspost/compress/flate](https://pkg.go.dev/github.com/klauspost/compress/flate) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.5/LICENSE))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.6.0/LICENSE.md))
|
||||
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.2.3/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/0b941c09a5e1/LICENSE))
|
||||
- [github.com/tailscale/goupnp](https://pkg.go.dev/github.com/tailscale/goupnp) ([BSD-2-Clause](https://github.com/tailscale/goupnp/blob/c64d0f06ea05/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/dac05f7d2cb4/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/6f7dac96:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/a9213eeb:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/c690dde0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/0de741cf:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/c0bba94a:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.3.7:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/f0f3c7e8:LICENSE))
|
||||
- [golang.zx2c4.com/wireguard](https://pkg.go.dev/golang.zx2c4.com/wireguard) ([MIT](https://git.zx2c4.com/wireguard-go/tree/LICENSE?id=c31a7b1ab478))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/850e42eb4444/LICENSE))
|
||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE))
|
||||
|
||||
## Additional Dependencies
|
||||
|
||||
- [Inter Typeface](https://rsms.me/inter/) ([OFL-1.1](https://github.com/rsms/inter/blob/v3.19/LICENSE.txt))
|
||||
- [Sparkle](https://sparkle-project.org/) ([MIT](https://github.com/sparkle-project/Sparkle/blob/2.x/LICENSE))
|
||||
- [wireguard-apple](https://git.zx2c4.com/wireguard-apple) ([MIT](https://git.zx2c4.com/wireguard-apple/tree/COPYING))
|
||||
@@ -1,88 +0,0 @@
|
||||
# Tailscale CLI and daemon dependencies
|
||||
|
||||
The following open source dependencies are used to build the [tailscale][] and
|
||||
[tailscaled][] commands. These are primarily used on Linux and BSD variants as
|
||||
well as an [option for macOS][].
|
||||
|
||||
[tailscale]: https://pkg.go.dev/tailscale.com/cmd/tailscale
|
||||
[tailscaled]: https://pkg.go.dev/tailscale.com/cmd/tailscaled
|
||||
[option for macOS]: https://tailscale.com/kb/1065/macos-variants/
|
||||
|
||||
## Go Packages
|
||||
|
||||
Some packages may only be included on certain architectures or operating systems.
|
||||
|
||||
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [github.com/akutz/memconn](https://pkg.go.dev/github.com/akutz/memconn) ([Apache-2.0](https://github.com/akutz/memconn/blob/v0.1.0/LICENSE))
|
||||
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
|
||||
- [github.com/anmitsu/go-shlex](https://pkg.go.dev/github.com/anmitsu/go-shlex) ([MIT](https://github.com/anmitsu/go-shlex/blob/38f4b401e2be/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.11.2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.11.0/config/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.6.4/credentials/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.8.2/feature/ec2/imds/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.2/internal/configsources/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.0.2/internal/endpoints/v2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.2/internal/ini/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.11.2/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.5.2/service/internal/presigned-url/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.17.1/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.6.2/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.11.1/service/sts/LICENSE.txt))
|
||||
- [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.9.0/LICENSE))
|
||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
|
||||
- [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.17/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/go-ole/go-ole](https://pkg.go.dev/github.com/go-ole/go-ole) ([MIT](https://github.com/go-ole/go-ole/blob/v1.2.6/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.0.1/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/7d93572ebe8e/LICENSE))
|
||||
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/v1.0.0/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/kballard/go-shellquote](https://pkg.go.dev/github.com/kballard/go-shellquote) ([MIT](https://github.com/kballard/go-shellquote/blob/95032a82bc51/LICENSE))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.4/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.4/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.4/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/kr/fs](https://pkg.go.dev/github.com/kr/fs) ([BSD-3-Clause](https://github.com/kr/fs/blob/v0.1.0/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.6.0/LICENSE.md))
|
||||
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.2.3/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/peterbourgon/ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3) ([Apache-2.0](https://github.com/peterbourgon/ff/blob/v3.1.2/LICENSE))
|
||||
- [github.com/pkg/errors](https://pkg.go.dev/github.com/pkg/errors) ([BSD-2-Clause](https://github.com/pkg/errors/blob/v0.9.1/LICENSE))
|
||||
- [github.com/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) ([BSD-2-Clause](https://github.com/pkg/sftp/blob/v1.13.4/LICENSE))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/certstore](https://pkg.go.dev/github.com/tailscale/certstore) ([MIT](https://github.com/tailscale/certstore/blob/78d6e1c49d8d/LICENSE.md))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/0b941c09a5e1/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/toqueteos/webbrowser](https://pkg.go.dev/github.com/toqueteos/webbrowser) ([MIT](https://github.com/toqueteos/webbrowser/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/v0.9.0/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/dac05f7d2cb4/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/927187094b94/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/eb4f295c:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/a9213eeb:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/c690dde0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/0de741cf:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/c0bba94a:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/03fcf44c:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.3.7:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/f0f3c7e8:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=415007cec224))
|
||||
- [golang.zx2c4.com/wireguard](https://pkg.go.dev/golang.zx2c4.com/wireguard) ([MIT](https://git.zx2c4.com/wireguard-go/tree/LICENSE?id=c31a7b1ab478))
|
||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.4.10))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/850e42eb4444/LICENSE))
|
||||
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
||||
- [inet.af/wf](https://pkg.go.dev/inet.af/wf) ([BSD-3-Clause](https://github.com/inetaf/wf/blob/50d96caab2f6/LICENSE))
|
||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE))
|
||||
- [tailscale.com/tempfork/gliderlabs/ssh](https://pkg.go.dev/tailscale.com/tempfork/gliderlabs/ssh) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/tempfork/gliderlabs/ssh/LICENSE))
|
||||
@@ -1,47 +0,0 @@
|
||||
# Tailscale for Windows dependencies
|
||||
|
||||
The following open source dependencies are used to build the [Tailscale client
|
||||
for windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
|
||||
[Tailscale client for windows]: https://tailscale.com/kb/1022/install-windows/
|
||||
[Tailscale CLI]: ./tailscale.md
|
||||
|
||||
## Go Packages
|
||||
|
||||
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
|
||||
- [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE))
|
||||
- [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/v1.0.0/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.5/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.5/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.5/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/lxn/walk](https://pkg.go.dev/github.com/lxn/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/ed127cfb919a/LICENSE))
|
||||
- [github.com/lxn/win](https://pkg.go.dev/github.com/lxn/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/c3f813abca9f/LICENSE))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.6.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.2.3/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/6f7dac96:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/c690dde0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/0de741cf:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/c0bba94a:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/03fcf44c:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=415007cec224))
|
||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.4.10))
|
||||
- [gopkg.in/Knetic/govaluate.v3](https://pkg.go.dev/gopkg.in/Knetic/govaluate.v3) ([MIT](https://github.com/Knetic/govaluate/blob/v3.0.0/LICENSE))
|
||||
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/f81723ceac3f/LICENSE))
|
||||
|
||||
## Additional Dependencies
|
||||
|
||||
- [Nullsoft Scriptable Install System](https://nsis.sourceforge.io/) ([zlib/libpng](https://nsis.sourceforge.io/License))
|
||||
- [Wintun](https://www.wintun.net/) ([Prebuilt Binaries License](https://git.zx2c4.com/wintun/tree/prebuilt-binaries-license.txt))
|
||||
- [wireguard-windows](https://git.zx2c4.com/wireguard-windows/) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING))
|
||||
@@ -1,47 +0,0 @@
|
||||
# Tailscale for Windows dependencies
|
||||
|
||||
The following open source dependencies are used to build [Tailscale on
|
||||
Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
|
||||
[Tailscale on Windows]: https://tailscale.com/kb/1022/install-windows/
|
||||
[Tailscale CLI]: ./tailscale.md
|
||||
|
||||
## Go Packages
|
||||
|
||||
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
|
||||
- [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE))
|
||||
- [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/v1.0.0/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.5/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.5/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.5/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/lxn/walk](https://pkg.go.dev/github.com/lxn/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/ed127cfb919a/LICENSE))
|
||||
- [github.com/lxn/win](https://pkg.go.dev/github.com/lxn/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/c3f813abca9f/LICENSE))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.6.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.2.3/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/6f7dac96:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/c690dde0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/0de741cf:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/c0bba94a:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/03fcf44c:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=415007cec224))
|
||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.4.10))
|
||||
- [gopkg.in/Knetic/govaluate.v3](https://pkg.go.dev/gopkg.in/Knetic/govaluate.v3) ([MIT](https://github.com/Knetic/govaluate/blob/v3.0.0/LICENSE))
|
||||
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE))
|
||||
|
||||
## Additional Dependencies
|
||||
|
||||
- [Nullsoft Scriptable Install System](https://nsis.sourceforge.io/) ([zlib/libpng](https://nsis.sourceforge.io/License))
|
||||
- [Wintun](https://www.wintun.net/) ([Prebuilt Binaries License](https://git.zx2c4.com/wintun/tree/prebuilt-binaries-license.txt))
|
||||
- [wireguard-windows](https://git.zx2c4.com/wireguard-windows/) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user