Compare commits
37 Commits
marwan/tmp
...
s/eq
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e6f564f7e | ||
|
|
5783adcc6f | ||
|
|
503b6dd8be | ||
|
|
9e9ea6e974 | ||
|
|
459744c9ea | ||
|
|
7675d323fa | ||
|
|
270942094f | ||
|
|
be190e990f | ||
|
|
4d7927047c | ||
|
|
ddb4040aa0 | ||
|
|
c1e6888fc7 | ||
|
|
3ae7140690 | ||
|
|
bcf7b63d7e | ||
|
|
c5bf868940 | ||
|
|
42fd964090 | ||
|
|
979d29b5f5 | ||
|
|
1f4a34588b | ||
|
|
a82f275619 | ||
|
|
b3c3a9f174 | ||
|
|
042f82ea32 | ||
|
|
633d08bd7b | ||
|
|
d35ce1add9 | ||
|
|
c3ab36cb9d | ||
|
|
8032b966a1 | ||
|
|
d78b334964 | ||
|
|
161d1d281a | ||
|
|
1145b9751d | ||
|
|
1e876a3c1d | ||
|
|
a8f10c23b2 | ||
|
|
b2b5379348 | ||
|
|
13de36303d | ||
|
|
095d3edd33 | ||
|
|
43819309e1 | ||
|
|
1b8a0dfe5e | ||
|
|
018a382729 | ||
|
|
2e07245384 | ||
|
|
aa87e999dc |
4
.github/workflows/go-licenses.yml
vendored
4
.github/workflows/go-licenses.yml
vendored
@@ -53,8 +53,8 @@ jobs:
|
||||
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
author: License Updater <noreply@tailscale.com>
|
||||
committer: License Updater <noreply@tailscale.com>
|
||||
author: License Updater <noreply+license-updater@tailscale.com>
|
||||
committer: License Updater <noreply+license-updater@tailscale.com>
|
||||
branch: licenses/cli
|
||||
commit-message: "licenses: update tailscale{,d} licenses"
|
||||
title: "licenses: update tailscale{,d} licenses"
|
||||
|
||||
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
102
.github/workflows/installer.yml
vendored
Normal file
102
.github/workflows/installer.yml
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
name: test installer.sh
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- scripts/installer.sh
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
paths:
|
||||
- scripts/installer.sh
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
# Don't abort the entire matrix if one element fails.
|
||||
fail-fast: false
|
||||
# Don't start all of these at once, which could saturate Github workers.
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
image:
|
||||
# This is a list of Docker images against which we test our installer.
|
||||
# If you find that some of these no longer exist, please feel free
|
||||
# to remove them from the list.
|
||||
# When adding new images, please only use official ones.
|
||||
- "debian:oldstable-slim"
|
||||
- "debian:stable-slim"
|
||||
- "debian:testing-slim"
|
||||
- "debian:sid-slim"
|
||||
- "ubuntu:18.04"
|
||||
- "ubuntu:20.04"
|
||||
- "ubuntu:22.04"
|
||||
- "ubuntu:22.10"
|
||||
- "ubuntu:23.04"
|
||||
- "elementary/docker:stable"
|
||||
- "elementary/docker:unstable"
|
||||
- "parrotsec/core:lts-amd64"
|
||||
- "parrotsec/core:latest"
|
||||
- "kalilinux/kali-rolling"
|
||||
- "kalilinux/kali-dev"
|
||||
- "oraclelinux:9"
|
||||
- "oraclelinux:8"
|
||||
- "fedora:latest"
|
||||
- "rockylinux:8.7"
|
||||
- "rockylinux:9"
|
||||
- "amazonlinux:latest"
|
||||
- "opensuse/leap:latest"
|
||||
- "opensuse/tumbleweed:latest"
|
||||
- "archlinux:latest"
|
||||
- "alpine:3.14"
|
||||
- "alpine:latest"
|
||||
- "alpine:edge"
|
||||
deps:
|
||||
# Run all images installing curl as a dependency.
|
||||
- curl
|
||||
include:
|
||||
# Check a few images with wget rather than curl.
|
||||
- { image: "debian:oldstable-slim", deps: "wget" }
|
||||
- { image: "debian:sid-slim", deps: "wget" }
|
||||
- { image: "ubuntu:23.04", deps: "wget" }
|
||||
# Ubuntu 16.04 also needs apt-transport-https installed.
|
||||
- { image: "ubuntu:16.04", deps: "curl apt-transport-https" }
|
||||
- { image: "ubuntu:16.04", deps: "wget apt-transport-https" }
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
options: --user root
|
||||
steps:
|
||||
- name: install dependencies (yum)
|
||||
# tar and gzip are needed by the actions/checkout below.
|
||||
run: yum install -y --allowerasing tar gzip ${{ matrix.deps }}
|
||||
if: |
|
||||
contains(matrix.image, 'centos')
|
||||
|| contains(matrix.image, 'oraclelinux')
|
||||
|| contains(matrix.image, 'fedora')
|
||||
|| contains(matrix.image, 'amazonlinux')
|
||||
- name: install dependencies (zypper)
|
||||
# tar and gzip are needed by the actions/checkout below.
|
||||
run: zypper --non-interactive install tar gzip
|
||||
if: contains(matrix.image, 'opensuse')
|
||||
- name: install dependencies (apt-get)
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y ${{ matrix.deps }}
|
||||
if: |
|
||||
contains(matrix.image, 'debian')
|
||||
|| contains(matrix.image, 'ubuntu')
|
||||
|| contains(matrix.image, 'elementary')
|
||||
|| contains(matrix.image, 'parrotsec')
|
||||
|| contains(matrix.image, 'kalilinux')
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: run installer
|
||||
run: scripts/installer.sh
|
||||
# Package installation can fail in docker because systemd is not running
|
||||
# as PID 1, so ignore errors at this step. The real check is the
|
||||
# `tailscale --version` command below.
|
||||
continue-on-error: true
|
||||
- name: check tailscale version
|
||||
run: tailscale --version
|
||||
18
.github/workflows/test.yml
vendored
18
.github/workflows/test.yml
vendored
@@ -65,8 +65,9 @@ jobs:
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-
|
||||
- name: build all
|
||||
run: ./tool/go build ${{matrix.buildflags}} ./...
|
||||
@@ -89,7 +90,11 @@ jobs:
|
||||
- name: build test wrapper
|
||||
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
|
||||
- name: test all
|
||||
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper -bench=. -benchtime=1x ./...
|
||||
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper
|
||||
env:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
- name: bench all
|
||||
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper -test.bench=. -test.benchtime=1x -test.run=^$
|
||||
env:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
- name: check that no tracked files changed
|
||||
@@ -131,8 +136,9 @@ jobs:
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
${{ github.job }}-${{ runner.os }}-go-2-
|
||||
- name: test
|
||||
# Don't use -bench=. -benchtime=1x.
|
||||
@@ -206,8 +212,9 @@ jobs:
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-
|
||||
- name: build all
|
||||
run: ./tool/go build ./cmd/...
|
||||
@@ -271,8 +278,9 @@ jobs:
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
${{ github.job }}-${{ runner.os }}-go-2-
|
||||
- name: build tsconnect client
|
||||
run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli
|
||||
|
||||
4
.github/workflows/update-flake.yml
vendored
4
.github/workflows/update-flake.yml
vendored
@@ -38,8 +38,8 @@ jobs:
|
||||
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
author: Flakes Updater <noreply@tailscale.com>
|
||||
committer: Flakes Updater <noreply@tailscale.com>
|
||||
author: Flakes Updater <noreply+flakes-updater@tailscale.com>
|
||||
committer: Flakes Updater <noreply+flakes-updater@tailscale.com>
|
||||
branch: flakes
|
||||
commit-message: "go.mod.sri: update SRI hash for go.mod changes"
|
||||
title: "go.mod.sri: update SRI hash for go.mod changes"
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.39.0
|
||||
1.41.0
|
||||
|
||||
@@ -16,7 +16,7 @@ if [ -n "${TS_USE_TOOLCHAIN:-}" ]; then
|
||||
go="./tool/go"
|
||||
fi
|
||||
|
||||
eval `GOOS=$($go env GOHOSTOS) GOARCH=$($go env GOHOSTARCH) $go run ./cmd/mkversion`
|
||||
eval `CGO_ENABLED=0 GOOS=$($go env GOHOSTOS) GOARCH=$($go env GOHOSTARCH) $go run ./cmd/mkversion`
|
||||
|
||||
if [ "$1" = "shellvars" ]; then
|
||||
cat <<EOF
|
||||
|
||||
@@ -81,7 +81,7 @@ func (m *manualCertManager) TLSConfig() *tls.Config {
|
||||
return &tls.Config{
|
||||
Certificates: nil,
|
||||
NextProtos: []string{
|
||||
"h2", "http/1.1", // enable HTTP/2
|
||||
"http/1.1",
|
||||
},
|
||||
GetCertificate: m.getCertificate,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
|
||||
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
|
||||
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
|
||||
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
|
||||
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
@@ -13,7 +15,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil+
|
||||
github.com/golang/protobuf/ptypes/timestamp from github.com/prometheus/client_model/go
|
||||
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+
|
||||
@@ -64,7 +65,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
google.golang.org/protobuf/runtime/protoiface from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/runtime/protoimpl from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/types/descriptorpb from google.golang.org/protobuf/reflect/protodesc
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/golang/protobuf/ptypes/timestamp+
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
||||
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
|
||||
|
||||
177
cmd/equaler/equaler.go
Normal file
177
cmd/equaler/equaler.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Equaler is a tool to automate the creation of an Equals method.
|
||||
//
|
||||
// This tool assumes that if a type you give it contains another named struct
|
||||
// type, that type will also have an Equal method, and that all fields are
|
||||
// comparable unless explicitly excluded.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/util/codegen"
|
||||
)
|
||||
|
||||
var (
|
||||
flagTypes = flag.String("type", "", "comma-separated list of types; required")
|
||||
flagBuildTags = flag.String("tags", "", "compiler build tags to apply")
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("equaler: ")
|
||||
flag.Parse()
|
||||
if len(*flagTypes) == 0 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
typeNames := strings.Split(*flagTypes, ",")
|
||||
|
||||
pkg, namedTypes, err := codegen.LoadTypes(*flagBuildTags, ".")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
it := codegen.NewImportTracker(pkg.Types)
|
||||
buf := new(bytes.Buffer)
|
||||
for _, typeName := range typeNames {
|
||||
typ, ok := namedTypes[typeName]
|
||||
if !ok {
|
||||
log.Fatalf("could not find type %s", typeName)
|
||||
}
|
||||
gen(buf, it, typ, typeNames)
|
||||
}
|
||||
|
||||
cloneOutput := pkg.Name + "_equal.go"
|
||||
if err := codegen.WritePackageFile("tailscale.com/cmd/equaler", pkg, cloneOutput, it, buf); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, typeNames []string) {
|
||||
t, ok := typ.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
name := typ.Obj().Name()
|
||||
fmt.Fprintf(buf, "// Equal reports whether a and b are equal.\n")
|
||||
fmt.Fprintf(buf, "func (a *%s) Equal(b *%s) bool {\n", name, name)
|
||||
writef := func(format string, args ...any) {
|
||||
fmt.Fprintf(buf, "\t"+format+"\n", args...)
|
||||
}
|
||||
writef("if a == b {")
|
||||
writef("\treturn true")
|
||||
writef("}")
|
||||
|
||||
writef("return a != nil && b != nil &&")
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
fname := t.Field(i).Name()
|
||||
ft := t.Field(i).Type()
|
||||
|
||||
// Fields which are explicitly ignored are skipped.
|
||||
if codegen.HasNoEqual(t.Tag(i)) {
|
||||
writef("\t// Skipping %s because of codegen:noequal", fname)
|
||||
continue
|
||||
}
|
||||
|
||||
// Fields which are named types that have an Equal() method, get that method used
|
||||
if named, _ := ft.(*types.Named); named != nil {
|
||||
if implementsEqual(ft) || slices.Contains(typeNames, named.Obj().Name()) {
|
||||
writef("\ta.%s.Equal(b.%s) &&", fname, fname)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Fields which are just values are directly compared, unless they have an Equal() method.
|
||||
if !codegen.ContainsPointers(ft) {
|
||||
writef("\ta.%s == b.%s &&", fname, fname)
|
||||
continue
|
||||
}
|
||||
|
||||
switch ft := ft.Underlying().(type) {
|
||||
case *types.Pointer:
|
||||
if named, _ := ft.Elem().(*types.Named); named != nil {
|
||||
if slices.Contains(typeNames, named.Obj().Name()) || implementsEqual(ft) {
|
||||
writef("\t((a.%s == nil) == (b.%s == nil)) && (a.%s == nil || a.%s.Equal(b.%s)) &&", fname, fname, fname, fname, fname)
|
||||
continue
|
||||
}
|
||||
if implementsEqual(ft.Elem()) {
|
||||
writef("\t((a.%s == nil) == (b.%s == nil)) && (a.%s == nil || a.%s.Equal(*b.%s)) &&", fname, fname, fname, fname, fname)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !codegen.ContainsPointers(ft.Elem()) {
|
||||
writef("\t((a.%s == nil) == (b.%s == nil)) && (a.%s == nil || *a.%s == *b.%s) &&", fname, fname, fname, fname, fname)
|
||||
continue
|
||||
}
|
||||
log.Fatalf("unimplemented: %s (%T)", fname, ft)
|
||||
case *types.Slice:
|
||||
// Empty slices and nil slices are different.
|
||||
writef("\t((a.%s == nil) == (b.%s == nil)) &&", fname, fname)
|
||||
if named, _ := ft.Elem().(*types.Named); named != nil {
|
||||
if implementsEqual(ft.Elem()) {
|
||||
it.Import("golang.org/x/exp/slices")
|
||||
writef("\tslices.EqualFunc(a.%s, b.%s, func(aa %s, bb %s) bool {return aa.Equal(bb)}) &&", fname, fname, named.Obj().Name(), named.Obj().Name())
|
||||
continue
|
||||
}
|
||||
if slices.Contains(typeNames, named.Obj().Name()) || implementsEqual(types.NewPointer(ft.Elem())) {
|
||||
it.Import("golang.org/x/exp/slices")
|
||||
writef("\tslices.EqualFunc(a.%s, b.%s, func(aa %s, bb %s) bool {return aa.Equal(&bb)}) &&", fname, fname, named.Obj().Name(), named.Obj().Name())
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !codegen.ContainsPointers(ft.Elem()) {
|
||||
it.Import("golang.org/x/exp/slices")
|
||||
writef("\tslices.Equal(a.%s, b.%s) &&", fname, fname)
|
||||
continue
|
||||
}
|
||||
log.Fatalf("unimplemented: %s (%T)", fname, ft)
|
||||
case *types.Map:
|
||||
if !codegen.ContainsPointers(ft.Elem()) {
|
||||
it.Import("golang.org/x/exp/maps")
|
||||
writef("\tmaps.Equal(a.%s, b.%s) &&", fname, fname)
|
||||
continue
|
||||
}
|
||||
log.Fatalf("unimplemented: %s (%T)", fname, ft)
|
||||
default:
|
||||
log.Fatalf("unimplemented: %s (%T)", fname, ft)
|
||||
}
|
||||
}
|
||||
writef("\ttrue")
|
||||
fmt.Fprintf(buf, "}\n\n")
|
||||
|
||||
buf.Write(codegen.AssertStructUnchanged(t, name, "Equal", it))
|
||||
}
|
||||
|
||||
// hasBasicUnderlying reports true when typ.Underlying() is a slice or a map.
|
||||
func hasBasicUnderlying(typ types.Type) bool {
|
||||
switch typ.Underlying().(type) {
|
||||
case *types.Slice, *types.Map:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// implementsEqual reports whether typ has an Equal(typ) bool method.
|
||||
func implementsEqual(typ types.Type) bool {
|
||||
return types.Implements(typ, types.NewInterfaceType(
|
||||
[]*types.Func{types.NewFunc(
|
||||
token.NoPos, nil, "Equal", types.NewSignatureType(
|
||||
types.NewVar(token.NoPos, nil, "a", typ),
|
||||
nil, nil,
|
||||
types.NewTuple(types.NewVar(token.NoPos, nil, "b", typ)),
|
||||
types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.Bool])), false))},
|
||||
[]types.Type{},
|
||||
))
|
||||
}
|
||||
@@ -29,9 +29,9 @@ func main() {
|
||||
tags := flag.String("tags", "", "comma-separated list of tags to apply to the authkey")
|
||||
flag.Parse()
|
||||
|
||||
clientId := os.Getenv("TS_API_CLIENT_ID")
|
||||
clientID := os.Getenv("TS_API_CLIENT_ID")
|
||||
clientSecret := os.Getenv("TS_API_CLIENT_SECRET")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
if clientID == "" || clientSecret == "" {
|
||||
log.Fatal("TS_API_CLIENT_ID and TS_API_CLIENT_SECRET must be set")
|
||||
}
|
||||
|
||||
@@ -39,22 +39,22 @@ func main() {
|
||||
log.Fatal("at least one tag must be specified")
|
||||
}
|
||||
|
||||
baseUrl := os.Getenv("TS_BASE_URL")
|
||||
if baseUrl == "" {
|
||||
baseUrl = "https://api.tailscale.com"
|
||||
baseURL := os.Getenv("TS_BASE_URL")
|
||||
if baseURL == "" {
|
||||
baseURL = "https://api.tailscale.com"
|
||||
}
|
||||
|
||||
credentials := clientcredentials.Config{
|
||||
ClientID: clientId,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
TokenURL: baseUrl + "/api/v2/oauth/token",
|
||||
TokenURL: baseURL + "/api/v2/oauth/token",
|
||||
Scopes: []string{"device"},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
tsClient := tailscale.NewClient("-", nil)
|
||||
tsClient.HTTPClient = credentials.Client(ctx)
|
||||
tsClient.BaseURL = baseUrl
|
||||
tsClient.BaseURL = baseURL
|
||||
|
||||
caps := tailscale.KeyCapabilities{
|
||||
Devices: tailscale.KeyDeviceCapabilities{
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"inet.af/tcpproxy"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tsnet"
|
||||
"tailscale.com/types/nettype"
|
||||
@@ -36,6 +37,8 @@ func main() {
|
||||
log.Fatal("no ports")
|
||||
}
|
||||
|
||||
hostinfo.SetApp("sniproxy")
|
||||
|
||||
var s server
|
||||
defer s.ts.Close()
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -412,6 +413,7 @@ func cleanMountPoint(mount string) (string, error) {
|
||||
if mount == "" {
|
||||
return "", errors.New("mount point cannot be empty")
|
||||
}
|
||||
mount = cleanMinGWPathConversionIfNeeded(mount)
|
||||
if !strings.HasPrefix(mount, "/") {
|
||||
mount = "/" + mount
|
||||
}
|
||||
@@ -422,6 +424,26 @@ func cleanMountPoint(mount string) (string, error) {
|
||||
return "", fmt.Errorf("invalid mount point %q", mount)
|
||||
}
|
||||
|
||||
// cleanMinGWPathConversionIfNeeded strips the EXEPATH prefix from the given
|
||||
// path if the path is a MinGW(ish) (Windows) shell arg.
|
||||
//
|
||||
// MinGW(ish) (Windows) shells perform POSIX-to-Windows path conversion
|
||||
// converting the leading "/" of any shell arg to the EXEPATH, which mangles the
|
||||
// mount point. Strip the EXEPATH prefix if it exists. #7963
|
||||
//
|
||||
// "/C:/Program Files/Git/foo" -> "/foo"
|
||||
func cleanMinGWPathConversionIfNeeded(path string) string {
|
||||
// Only do this on Windows.
|
||||
if runtime.GOOS != "windows" {
|
||||
return path
|
||||
}
|
||||
if _, ok := os.LookupEnv("MSYSTEM"); ok {
|
||||
exepath := filepath.ToSlash(os.Getenv("EXEPATH"))
|
||||
path = strings.TrimPrefix(path, exepath)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func expandProxyTarget(source string) (string, error) {
|
||||
if !strings.Contains(source, "://") {
|
||||
source = "http://" + source
|
||||
|
||||
@@ -13,11 +13,13 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
@@ -26,6 +28,9 @@ import (
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
qrcode "github.com/skip2/go-qrcode"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health/healthmsg"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
@@ -663,6 +668,10 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authKey, err = resolveAuthKey(ctx, authKey, upArgs.advertiseTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := localClient.Start(ctx, ipn.Options{
|
||||
AuthKey: authKey,
|
||||
UpdatePrefs: prefs,
|
||||
@@ -1102,3 +1111,96 @@ func anyPeerAdvertisingRoutes(st *ipnstate.Status) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Required to use our client API. We're fine with the instability since the
|
||||
// client lives in the same repo as this code.
|
||||
tailscale.I_Acknowledge_This_API_Is_Unstable = true
|
||||
}
|
||||
|
||||
// resolveAuthKey either returns v unchanged (in the common case) or, if it
|
||||
// starts with "tskey-client-" (as Tailscale OAuth secrets do) parses it like
|
||||
//
|
||||
// tskey-client-xxxx[?ephemeral=false&bar&preauthorized=BOOL&baseURL=...]
|
||||
//
|
||||
// and does the OAuth2 dance to get and return an authkey. The "ephemeral"
|
||||
// property defaults to true if unspecified. The "preauthorized" defaults to
|
||||
// false. The "baseURL" defaults to https://api.tailscale.com.
|
||||
// The passed in tags are required, and must be non-empty. These will be
|
||||
// set on the authkey generated by the OAuth2 dance.
|
||||
func resolveAuthKey(ctx context.Context, v, tags string) (string, error) {
|
||||
if !strings.HasPrefix(v, "tskey-client-") {
|
||||
return v, nil
|
||||
}
|
||||
if !envknob.Bool("TS_EXPERIMENT_OAUTH_AUTHKEY") {
|
||||
return "", errors.New("oauth authkeys are in experimental status")
|
||||
}
|
||||
if tags == "" {
|
||||
return "", errors.New("oauth authkeys require --advertise-tags")
|
||||
}
|
||||
|
||||
clientSecret, named, _ := strings.Cut(v, "?")
|
||||
attrs, err := url.ParseQuery(named)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for k := range attrs {
|
||||
switch k {
|
||||
case "ephemeral", "preauthorized", "baseURL":
|
||||
default:
|
||||
return "", fmt.Errorf("unknown attribute %q", k)
|
||||
}
|
||||
}
|
||||
getBool := func(name string, def bool) (bool, error) {
|
||||
v := attrs.Get(name)
|
||||
if v == "" {
|
||||
return def, nil
|
||||
}
|
||||
ret, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid attribute boolean attribute %s value %q", name, v)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
ephemeral, err := getBool("ephemeral", true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
preauth, err := getBool("preauthorized", false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseURL := "https://api.tailscale.com"
|
||||
if v := attrs.Get("baseURL"); v != "" {
|
||||
baseURL = v
|
||||
}
|
||||
|
||||
credentials := clientcredentials.Config{
|
||||
ClientID: "some-client-id", // ignored
|
||||
ClientSecret: clientSecret,
|
||||
TokenURL: baseURL + "/api/v2/oauth/token",
|
||||
Scopes: []string{"device"},
|
||||
}
|
||||
|
||||
tsClient := tailscale.NewClient("-", nil)
|
||||
tsClient.HTTPClient = credentials.Client(ctx)
|
||||
tsClient.BaseURL = baseURL
|
||||
|
||||
caps := tailscale.KeyCapabilities{
|
||||
Devices: tailscale.KeyDeviceCapabilities{
|
||||
Create: tailscale.KeyDeviceCreateCapabilities{
|
||||
Reusable: false,
|
||||
Ephemeral: ephemeral,
|
||||
Preauthorized: preauth,
|
||||
Tags: strings.Split(tags, ","),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
authkey, _, err := tsClient.CreateKey(ctx, caps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return authkey, nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
|
||||
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
|
||||
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
|
||||
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
|
||||
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
@@ -152,9 +154,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
golang.org/x/net/icmp from tailscale.com/net/ping
|
||||
golang.org/x/net/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/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/oauth2 from golang.org/x/oauth2/clientcredentials
|
||||
golang.org/x/oauth2/clientcredentials from tailscale.com/cmd/tailscale/cli
|
||||
golang.org/x/oauth2/internal from golang.org/x/oauth2+
|
||||
golang.org/x/sync/errgroup from tailscale.com/derp+
|
||||
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
||||
LD golang.org/x/sys/unix from tailscale.com/net/netns+
|
||||
|
||||
@@ -3,7 +3,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
|
||||
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
|
||||
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
|
||||
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
|
||||
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
@@ -12,7 +14,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2 from github.com/aws/aws-sdk-go-v2/internal/ini
|
||||
L github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/ipn/store/awsstore
|
||||
L github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||
L github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/middleware from github.com/aws/aws-sdk-go-v2/aws/retry+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/query from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/restjson from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
@@ -38,6 +40,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/internal/rand from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/sdk from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/sdkio from github.com/aws/aws-sdk-go-v2/credentials/processcreds
|
||||
L github.com/aws/aws-sdk-go-v2/internal/shareddefaults from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/strings from github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4
|
||||
L github.com/aws/aws-sdk-go-v2/internal/sync/singleflight from github.com/aws/aws-sdk-go-v2/aws
|
||||
L github.com/aws/aws-sdk-go-v2/internal/timeconv from github.com/aws/aws-sdk-go-v2/aws/retry
|
||||
@@ -48,16 +51,19 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/service/sso from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/service/sso/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sso
|
||||
L github.com/aws/aws-sdk-go-v2/service/sso/types from github.com/aws/aws-sdk-go-v2/service/sso
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssooidc from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssooidc/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssooidc
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssooidc/types from github.com/aws/aws-sdk-go-v2/service/ssooidc
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts/types from github.com/aws/aws-sdk-go-v2/credentials/stscreds+
|
||||
L github.com/aws/smithy-go from github.com/aws/aws-sdk-go-v2/aws/protocol/restjson+
|
||||
L github.com/aws/smithy-go/auth/bearer from github.com/aws/aws-sdk-go-v2/aws
|
||||
L github.com/aws/smithy-go/auth/bearer from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/context from github.com/aws/smithy-go/auth/bearer
|
||||
L github.com/aws/smithy-go/document from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/smithy-go/encoding from github.com/aws/smithy-go/encoding/json+
|
||||
L github.com/aws/smithy-go/encoding/httpbinding from github.com/aws/aws-sdk-go-v2/aws/protocol/query+
|
||||
L github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||
L github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/smithy-go/encoding/xml from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/smithy-go/internal/sync/singleflight from github.com/aws/smithy-go/auth/bearer
|
||||
L github.com/aws/smithy-go/io from github.com/aws/aws-sdk-go-v2/feature/ec2/imds+
|
||||
@@ -73,6 +79,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
|
||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com
|
||||
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled
|
||||
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
|
||||
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
||||
@@ -93,7 +100,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
|
||||
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd+
|
||||
github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/zstd from tailscale.com/smallzstd
|
||||
github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd
|
||||
@@ -105,12 +112,17 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
|
||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||
L github.com/pierrec/lz4/v4 from github.com/u-root/uio/uio
|
||||
L github.com/pierrec/lz4/v4/internal/lz4block from github.com/pierrec/lz4/v4+
|
||||
L github.com/pierrec/lz4/v4/internal/lz4errors from github.com/pierrec/lz4/v4+
|
||||
L github.com/pierrec/lz4/v4/internal/lz4stream from github.com/pierrec/lz4/v4
|
||||
L github.com/pierrec/lz4/v4/internal/xxh32 from github.com/pierrec/lz4/v4/internal/lz4stream
|
||||
W github.com/pkg/errors from github.com/tailscale/certstore
|
||||
LD github.com/pkg/sftp from tailscale.com/ssh/tailssh
|
||||
LD github.com/pkg/sftp/internal/encoding/ssh/filexfer from github.com/pkg/sftp
|
||||
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
|
||||
LD github.com/tailscale/golang-x-crypto/chacha20 from github.com/tailscale/golang-x-crypto/ssh
|
||||
LD 💣 github.com/tailscale/golang-x-crypto/internal/subtle from github.com/tailscale/golang-x-crypto/chacha20
|
||||
LD 💣 github.com/tailscale/golang-x-crypto/internal/alias from github.com/tailscale/golang-x-crypto/chacha20
|
||||
LD github.com/tailscale/golang-x-crypto/ssh from tailscale.com/ipn/ipnlocal+
|
||||
LD github.com/tailscale/golang-x-crypto/ssh/internal/bcrypt_pbkdf from github.com/tailscale/golang-x-crypto/ssh
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
@@ -245,7 +257,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnauth+
|
||||
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/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/routetable from tailscale.com/doctor/routetable
|
||||
|
||||
@@ -61,6 +61,7 @@ import (
|
||||
type Direct struct {
|
||||
httpc *http.Client // HTTP client used to talk to tailcontrol
|
||||
dialer *tsdial.Dialer
|
||||
dnsCache *dnscache.Resolver
|
||||
serverURL string // URL of the tailcontrol server
|
||||
timeNow func() time.Time
|
||||
lastPrintMap time.Time
|
||||
@@ -199,6 +200,14 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
opts.Logf = log.Printf
|
||||
}
|
||||
|
||||
dnsCache := &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward, // use default cache's forwarder
|
||||
UseLastGood: true,
|
||||
LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, opts.NetMon),
|
||||
Logf: opts.Logf,
|
||||
NetMon: opts.NetMon,
|
||||
}
|
||||
|
||||
httpc := opts.HTTPTestClient
|
||||
if httpc == nil && runtime.GOOS == "js" {
|
||||
// In js/wasm, net/http.Transport (as of Go 1.18) will
|
||||
@@ -208,13 +217,6 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
httpc = http.DefaultClient
|
||||
}
|
||||
if httpc == nil {
|
||||
dnsCache := &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward, // use default cache's forwarder
|
||||
UseLastGood: true,
|
||||
LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, opts.NetMon),
|
||||
Logf: opts.Logf,
|
||||
NetMon: opts.NetMon,
|
||||
}
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
||||
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
|
||||
@@ -250,6 +252,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
onControlTime: opts.OnControlTime,
|
||||
c2nHandler: opts.C2NHandler,
|
||||
dialer: opts.Dialer,
|
||||
dnsCache: dnsCache,
|
||||
dialPlan: opts.DialPlan,
|
||||
}
|
||||
if opts.Hostinfo == nil {
|
||||
@@ -1509,7 +1512,16 @@ func (c *Direct) getNoiseClient() (*NoiseClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.logf("creating new noise client")
|
||||
nc, err := NewNoiseClient(k, serverNoiseKey, c.serverURL, c.dialer, c.logf, c.netMon, dp)
|
||||
nc, err := NewNoiseClient(NoiseOpts{
|
||||
PrivKey: k,
|
||||
ServerPubKey: serverNoiseKey,
|
||||
ServerURL: c.serverURL,
|
||||
Dialer: c.dialer,
|
||||
DNSCache: c.dnsCache,
|
||||
Logf: c.logf,
|
||||
NetMon: c.netMon,
|
||||
DialPlan: dp,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"golang.org/x/net/http2"
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/control/controlhttp"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -158,6 +159,7 @@ type NoiseClient struct {
|
||||
sfDial singleflight.Group[struct{}, *noiseConn]
|
||||
|
||||
dialer *tsdial.Dialer
|
||||
dnsCache *dnscache.Resolver
|
||||
privKey key.MachinePrivate
|
||||
serverPubKey key.MachinePublic
|
||||
host string // the host part of serverURL
|
||||
@@ -179,13 +181,39 @@ type NoiseClient struct {
|
||||
connPool map[int]*noiseConn // active connections not yet closed; see noiseConn.Close
|
||||
}
|
||||
|
||||
// NoiseOpts contains options for the NewNoiseClient function. All fields are
|
||||
// required unless otherwise specified.
|
||||
type NoiseOpts struct {
|
||||
// PrivKey is this node's private key.
|
||||
PrivKey key.MachinePrivate
|
||||
// ServerPubKey is the public key of the server.
|
||||
ServerPubKey key.MachinePublic
|
||||
// ServerURL is the URL of the server to connect to.
|
||||
ServerURL string
|
||||
// Dialer's SystemDial function is used to connect to the server.
|
||||
Dialer *tsdial.Dialer
|
||||
// DNSCache is the caching Resolver to use to connect to the server.
|
||||
//
|
||||
// This field can be nil.
|
||||
DNSCache *dnscache.Resolver
|
||||
// Logf is the log function to use. This field can be nil.
|
||||
Logf logger.Logf
|
||||
// NetMon is the network monitor that, if set, will be used to get the
|
||||
// network interface state. This field can be nil; if so, the current
|
||||
// state will be looked up dynamically.
|
||||
NetMon *netmon.Monitor
|
||||
// DialPlan, if set, is a function that should return an explicit plan
|
||||
// on how to connect to the server.
|
||||
DialPlan func() *tailcfg.ControlDialPlan
|
||||
}
|
||||
|
||||
// NewNoiseClient returns a new noiseClient for the provided server and machine key.
|
||||
// serverURL is of the form https://<host>:<port> (no trailing slash).
|
||||
//
|
||||
// netMon may be nil, if non-nil it's used to do faster interface lookups.
|
||||
// dialPlan may be nil
|
||||
func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string, dialer *tsdial.Dialer, logf logger.Logf, netMon *netmon.Monitor, dialPlan func() *tailcfg.ControlDialPlan) (*NoiseClient, error) {
|
||||
u, err := url.Parse(serverURL)
|
||||
func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
|
||||
u, err := url.Parse(opts.ServerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -205,16 +233,18 @@ func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic,
|
||||
httpPort = "80"
|
||||
httpsPort = "443"
|
||||
}
|
||||
|
||||
np := &NoiseClient{
|
||||
serverPubKey: serverPubKey,
|
||||
privKey: privKey,
|
||||
serverPubKey: opts.ServerPubKey,
|
||||
privKey: opts.PrivKey,
|
||||
host: u.Hostname(),
|
||||
httpPort: httpPort,
|
||||
httpsPort: httpsPort,
|
||||
dialer: dialer,
|
||||
dialPlan: dialPlan,
|
||||
logf: logf,
|
||||
netMon: netMon,
|
||||
dialer: opts.Dialer,
|
||||
dnsCache: opts.DNSCache,
|
||||
dialPlan: opts.DialPlan,
|
||||
logf: opts.Logf,
|
||||
netMon: opts.NetMon,
|
||||
}
|
||||
|
||||
// Create the HTTP/2 Transport using a net/http.Transport
|
||||
@@ -373,6 +403,7 @@ func (nc *NoiseClient) dial() (*noiseConn, error) {
|
||||
ControlKey: nc.serverPubKey,
|
||||
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
|
||||
Dialer: nc.dialer.SystemDial,
|
||||
DNSCache: nc.dnsCache,
|
||||
DialPlan: dialPlan,
|
||||
Logf: nc.logf,
|
||||
NetMon: nc.netMon,
|
||||
|
||||
@@ -74,7 +74,12 @@ func (tt noiseClientTest) run(t *testing.T) {
|
||||
defer hs.Close()
|
||||
|
||||
dialer := new(tsdial.Dialer)
|
||||
nc, err := NewNoiseClient(clientPrivate, serverPrivate.Public(), hs.URL, dialer, nil, nil, nil)
|
||||
nc, err := NewNoiseClient(NoiseOpts{
|
||||
PrivKey: clientPrivate,
|
||||
ServerPubKey: serverPrivate.Public(),
|
||||
ServerURL: hs.URL,
|
||||
Dialer: dialer,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -374,6 +374,22 @@ func (a *Dialer) dialURL(ctx context.Context, u *url.URL, addr netip.Addr) (*Cli
|
||||
}, nil
|
||||
}
|
||||
|
||||
// resolver returns a.DNSCache if non-nil or a new *dnscache.Resolver
|
||||
// otherwise.
|
||||
func (a *Dialer) resolver() *dnscache.Resolver {
|
||||
if a.DNSCache != nil {
|
||||
return a.DNSCache
|
||||
}
|
||||
|
||||
return &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward,
|
||||
LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon),
|
||||
UseLastGood: true,
|
||||
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
|
||||
NetMon: a.NetMon,
|
||||
}
|
||||
}
|
||||
|
||||
// tryURLUpgrade connects to u, and tries to upgrade it to a net.Conn. If addr
|
||||
// is valid, then no DNS is used and the connection will be made to the
|
||||
// provided address.
|
||||
@@ -392,13 +408,7 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr,
|
||||
NetMon: a.NetMon,
|
||||
}
|
||||
} else {
|
||||
dns = &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward,
|
||||
LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon),
|
||||
UseLastGood: true,
|
||||
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
|
||||
NetMon: a.NetMon,
|
||||
}
|
||||
dns = a.resolver()
|
||||
}
|
||||
|
||||
var dialer dnscache.DialContextFunc
|
||||
|
||||
@@ -67,6 +67,11 @@ type Dialer struct {
|
||||
// If not specified, this defaults to net.Dialer.DialContext.
|
||||
Dialer dnscache.DialContextFunc
|
||||
|
||||
// DNSCache is the caching Resolver used by this Dialer.
|
||||
//
|
||||
// If not specified, a new Resolver is created per attempt.
|
||||
DNSCache *dnscache.Resolver
|
||||
|
||||
// Logf, if set, is a logging function to use; if unset, logs are
|
||||
// dropped.
|
||||
Logf logger.Logf
|
||||
|
||||
61
derp/README.md
Normal file
61
derp/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# DERP
|
||||
|
||||
This directory (and subdirectories) contain the DERP code. The server itself is
|
||||
in `../cmd/derper`.
|
||||
|
||||
DERP is a packet relay system (client and servers) where peers are addressed
|
||||
using WireGuard public keys instead of IP addresses.
|
||||
|
||||
It relays two types of packets:
|
||||
|
||||
* "Disco" discovery messages (see `../disco`) as the a side channel during [NAT
|
||||
traversal](https://tailscale.com/blog/how-nat-traversal-works/).
|
||||
|
||||
* Encrypted WireGuard packets as the fallback of last resort when UDP is blocked
|
||||
or NAT traversal fails.
|
||||
|
||||
## DERP Map
|
||||
|
||||
Each client receives a "[DERP
|
||||
Map](https://pkg.go.dev/tailscale.com/tailcfg#DERPMap)" from the coordination
|
||||
server describing the DERP servers the client should try to use.
|
||||
|
||||
The client picks its home "DERP home" based on latency. This is done to keep
|
||||
costs low by avoid using cloud load balancers (pricey) or anycast, which would
|
||||
necessarily require server-side routing between DERP regions.
|
||||
|
||||
Clients pick their DERP home and report it to the coordination server which
|
||||
shares it to all the peers in the tailnet. When a peer wants to send a packet
|
||||
and it doesn't already have a WireGuard session open, it sends disco messages
|
||||
(some direct, and some over DERP), trying to do the NAT traversal. The client
|
||||
will make connections to multiple DERP regions as needed. Only the DERP home
|
||||
region connection needs to be alive forever.
|
||||
|
||||
## DERP Regions
|
||||
|
||||
Tailscale runs 1 or more DERP nodes (instances of `cmd/derper`) in various
|
||||
geographic regions to make sure users have low latency to their DERP home.
|
||||
|
||||
Regions generally have multiple nodes per region "meshed" (routing to each
|
||||
other) together for redundancy: it allows for cloud failures or upgrades without
|
||||
kicking users out to a higher latency region. Instead, clients will reconnect to
|
||||
the next node in the region. Each node in the region is required to to be meshed
|
||||
with every other node in the region and forward packets to the other nodes in
|
||||
the region. Packets are forwarded only one hop within the region. There is no
|
||||
routing between regions. The assumption is that the mesh TCP connections are
|
||||
over a VPC that's very fast, low latency, and not charged per byte. The
|
||||
coordination server assigns the list of nodes in a region as a function of the
|
||||
tailnet, so all nodes within a tailnet should generally be on the same node and
|
||||
not require forwarding. Only after a failure do clients of a particular tailnet
|
||||
get split between nodes in a region and require inter-node forwarding. But over
|
||||
time it balances back out. There's also an admin-only DERP frame type to force
|
||||
close the TCP connection of a particular client to force them to reconnect to
|
||||
their primary if the operator wants to force things to balance out sooner.
|
||||
(Using the `(*derphttp.Client).ClosePeer` method, as used by Tailscale's
|
||||
internal rarely-used `cmd/derpprune` maintenance tool)
|
||||
|
||||
We generally run a minimum of three nodes in a region not for quorum reasons
|
||||
(there's no voting) but just because two is too uncomfortably few for cascading
|
||||
failure reasons: if you're running two nodes at 51% load (CPU, memory, etc) and
|
||||
then one fails, that makes the second one fail. With three or more nodes, you
|
||||
can run each node a bit hotter.
|
||||
@@ -115,4 +115,4 @@
|
||||
in
|
||||
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
|
||||
}
|
||||
# nix-direnv cache busting line: sha256-lirn07XE3JOS6oiwZBMwxzywkbXHowOJUMWWLrZtccY=
|
||||
# nix-direnv cache busting line: sha256-ZQ6aE+9PfAxfeNQeDzwcOCXpztLORVriHkEw51lbeHM=
|
||||
|
||||
257
go.mod
257
go.mod
@@ -3,93 +3,93 @@ module tailscale.com
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
filippo.io/mkcert v1.4.3
|
||||
github.com/Microsoft/go-winio v0.6.0
|
||||
filippo.io/mkcert v1.4.4
|
||||
github.com/Microsoft/go-winio v0.6.1
|
||||
github.com/akutz/memconn v0.1.0
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
|
||||
github.com/andybalholm/brotli v1.0.3
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3
|
||||
github.com/aws/aws-sdk-go-v2/config v1.11.0
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.4
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.21.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.35.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.22
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.3
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
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/dblohm7/wingoes v0.0.0-20221124203957-6ac47ab19aa5
|
||||
github.com/creack/pty v1.1.18
|
||||
github.com/dave/jennifer v1.6.1
|
||||
github.com/dblohm7/wingoes v0.0.0-20230426155039-111c8c3b57c8
|
||||
github.com/dsnet/try v0.0.3
|
||||
github.com/evanw/esbuild v0.14.53
|
||||
github.com/frankban/quicktest v1.14.3
|
||||
github.com/frankban/quicktest v1.14.5
|
||||
github.com/fxamacker/cbor/v2 v2.4.0
|
||||
github.com/go-json-experiment/json v0.0.0-20221017203807-c5ed296b8c92
|
||||
github.com/go-json-experiment/json v0.0.0-20230321051131-ccbac49a6929
|
||||
github.com/go-logr/zapr v1.2.3
|
||||
github.com/go-ole/go-ole v1.2.6
|
||||
github.com/godbus/dbus/v5 v5.0.6
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||
github.com/golangci/golangci-lint v1.52.2
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/go-containerregistry v0.9.0
|
||||
github.com/google/go-containerregistry v0.14.0
|
||||
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/goreleaser/nfpm v1.10.3
|
||||
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3
|
||||
github.com/hdevalence/ed25519consensus v0.1.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/illarion/gonotify v1.0.1
|
||||
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
||||
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b
|
||||
github.com/jsimonetti/rtnetlink v1.3.2
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.15.4
|
||||
github.com/klauspost/compress v1.16.5
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-isatty v0.0.17
|
||||
github.com/mdlayher/genetlink v1.2.0
|
||||
github.com/mdlayher/netlink v1.7.1
|
||||
github.com/mattn/go-isatty v0.0.18
|
||||
github.com/mdlayher/genetlink v1.3.2
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/mdlayher/sdnotify v1.0.0
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/miekg/dns v1.1.54
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/peterbourgon/ff/v3 v3.1.2
|
||||
github.com/peterbourgon/ff/v3 v3.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.4
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/prometheus/common v0.41.0
|
||||
github.com/pkg/sftp v1.13.5
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/prometheus/common v0.42.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20221115211329-17a3db2c30d2
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
|
||||
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
|
||||
github.com/tailscale/mkctr v0.0.0-20220601142259-c0b937af2e89
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
|
||||
github.com/tailscale/wireguard-go v0.0.0-20230410165232-af172621b4dd
|
||||
github.com/tc-hib/winres v0.1.6
|
||||
github.com/tc-hib/winres v0.2.0
|
||||
github.com/tcnksm/go-httpstat v0.2.0
|
||||
github.com/toqueteos/webbrowser v1.2.0
|
||||
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
|
||||
github.com/u-root/u-root v0.11.0
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
go.uber.org/zap v1.24.0
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94
|
||||
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
|
||||
golang.org/x/crypto v0.6.0
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
|
||||
golang.org/x/mod v0.9.0
|
||||
golang.org/x/net v0.8.0
|
||||
golang.org/x/oauth2 v0.5.0
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13
|
||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
|
||||
golang.org/x/crypto v0.8.0
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
golang.org/x/mod v0.10.0
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.6.0
|
||||
golang.org/x/term v0.6.0
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
|
||||
golang.org/x/tools v0.7.0
|
||||
golang.org/x/sys v0.7.0
|
||||
golang.org/x/term v0.7.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.8.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
gvisor.dev/gvisor v0.0.0-20230328175328-162ed5ef888d
|
||||
honnef.co/go/tools v0.4.3
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
|
||||
inet.af/wf v0.0.0-20220728202103-50d96caab2f6
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884
|
||||
k8s.io/api v0.25.0
|
||||
k8s.io/apimachinery v0.25.0
|
||||
k8s.io/client-go v0.25.0
|
||||
@@ -102,37 +102,38 @@ require (
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/Abirdcfly/dupword v0.0.11 // indirect
|
||||
github.com/Antonboom/errname v0.1.9 // indirect
|
||||
github.com/Antonboom/nilnil v0.1.3 // indirect
|
||||
github.com/Antonboom/nilnil v0.1.4 // indirect
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/Djarvur/go-err113 v0.1.0 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/OpenPeeDeeP/depguard v1.1.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.5.1 // indirect
|
||||
github.com/ashanbrown/makezero v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.6.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.0 // indirect
|
||||
@@ -141,41 +142,40 @@ require (
|
||||
github.com/bombsimon/wsl/v3 v3.4.0 // indirect
|
||||
github.com/breml/bidichk v0.2.4 // indirect
|
||||
github.com/breml/errchkjson v0.3.1 // indirect
|
||||
github.com/butuzov/ireturn v0.1.1 // indirect
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
|
||||
github.com/butuzov/ireturn v0.2.0 // indirect
|
||||
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/curioswitch/go-reassign v0.2.0 // indirect
|
||||
github.com/daixiang0/gci v0.10.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.4.3 // indirect
|
||||
github.com/docker/cli v20.10.16+incompatible // indirect
|
||||
github.com/docker/cli v23.0.5+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.16+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/docker/docker v23.0.5+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/esimonov/ifshort v1.0.4 // indirect
|
||||
github.com/ettle/strcase v0.1.1 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/gliderlabs/ssh v0.3.3 // indirect
|
||||
github.com/go-critic/go-critic v0.7.0 // indirect
|
||||
github.com/go-critic/go-critic v0.8.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.4.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.6.1 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.1.0 // indirect
|
||||
@@ -187,7 +187,7 @@ require (
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
|
||||
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect
|
||||
@@ -197,11 +197,11 @@ require (
|
||||
github.com/golangci/misspell v0.4.0 // indirect
|
||||
github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect
|
||||
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1 // indirect
|
||||
github.com/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
|
||||
github.com/goreleaser/chglog v0.1.2 // indirect
|
||||
github.com/goreleaser/fileglob v0.3.1 // indirect
|
||||
@@ -214,9 +214,9 @@ require (
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jgautheron/goconst v1.5.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
@@ -226,10 +226,11 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/julz/importas v0.1.0 // indirect
|
||||
github.com/junk1tm/musttag v0.5.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/kisielk/errcheck v1.6.3 // indirect
|
||||
github.com/kisielk/gotool v1.0.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
@@ -237,18 +238,18 @@ require (
|
||||
github.com/kunwardeep/paralleltest v1.0.6 // indirect
|
||||
github.com/kyoh86/exportloopref v0.1.11 // indirect
|
||||
github.com/ldez/gomoddirectives v0.2.3 // indirect
|
||||
github.com/ldez/tagliatelle v0.4.0 // indirect
|
||||
github.com/ldez/tagliatelle v0.5.0 // indirect
|
||||
github.com/leonklingele/grouper v1.1.1 // indirect
|
||||
github.com/lufeee/execinquery v1.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/mgechev/revive v1.3.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
@@ -261,75 +262,77 @@ require (
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/nishanths/exhaustive v0.9.5 // indirect
|
||||
github.com/nishanths/exhaustive v0.10.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.9.0 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.11.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.4.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.4.1 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.3.19 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.3.0 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect
|
||||
github.com/sassoftware/go-rpmutils v0.1.0 // indirect
|
||||
github.com/sassoftware/go-rpmutils v0.2.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.15.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sivchari/containedctx v1.0.2 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sivchari/nosnakecase v1.7.0 // indirect
|
||||
github.com/sivchari/tenv v1.7.1 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/sonatard/noctx v0.0.2 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.8.2 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.6.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.12.0 // indirect
|
||||
github.com/spf13/viper v1.15.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.2 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
|
||||
github.com/tdakkota/asciicheck v0.2.0 // indirect
|
||||
github.com/tetafro/godot v1.4.11 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
|
||||
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/ultraware/funlen v0.0.3 // indirect
|
||||
github.com/ultraware/whitespace v0.0.5 // indirect
|
||||
github.com/uudashr/gocognit v1.0.6 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.2.0 // indirect
|
||||
gitlab.com/bosi/decorder v0.2.3 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect
|
||||
golang.org/x/image v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
@@ -341,13 +344,13 @@ require (
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.25.0 // indirect
|
||||
k8s.io/component-base v0.25.0 // indirect
|
||||
k8s.io/klog/v2 v2.70.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
mvdan.cc/gofumpt v0.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
mvdan.cc/gofumpt v0.5.0 // indirect
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
|
||||
mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
sha256-lirn07XE3JOS6oiwZBMwxzywkbXHowOJUMWWLrZtccY=
|
||||
sha256-ZQ6aE+9PfAxfeNQeDzwcOCXpztLORVriHkEw51lbeHM=
|
||||
|
||||
@@ -2479,7 +2479,7 @@ func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret ipn.EngineSt
|
||||
// [GRINDER STATS LINES] - please don't remove (used for log parsing)
|
||||
if peerStats.Len() > 0 {
|
||||
b.keyLogf("[v1] peer keys: %s", strings.TrimSpace(peerKeys.String()))
|
||||
b.statsLogf("[v1] v%v peers: %v", version.Long, strings.TrimSpace(peerStats.String()))
|
||||
b.statsLogf("[v1] v%v peers: %v", version.Long(), strings.TrimSpace(peerStats.String()))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"tailscale.com/util/winutil"
|
||||
)
|
||||
|
||||
var errAlreadyMigrated = errors.New("profile migration already completed")
|
||||
|
||||
// profileManager is a wrapper around a StateStore that manages
|
||||
// multiple profiles and the current profile.
|
||||
type profileManager struct {
|
||||
@@ -66,7 +68,7 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
|
||||
b, err := pm.store.ReadState(ipn.CurrentProfileKey(string(uid)))
|
||||
if err == ipn.ErrStateNotExist || len(b) == 0 {
|
||||
if runtime.GOOS == "windows" {
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil {
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil && !errors.Is(err, errAlreadyMigrated) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -544,7 +546,14 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, goos stri
|
||||
if err := pm.setPrefsLocked(prefs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(knownProfiles) == 0 && goos != "windows" {
|
||||
// Most platform behavior is controlled by the goos parameter, however
|
||||
// some behavior is implied by build tag and fails when run on Windows,
|
||||
// so we explicitly avoid that behavior when running on Windows.
|
||||
// Specifically this reaches down into legacy preference loading that is
|
||||
// specialized by profiles_windows.go and fails in tests on an invalid
|
||||
// uid passed in from the unix tests. The uid's used for Windows tests
|
||||
// and runtime must be valid Windows security identifier structures.
|
||||
} else if len(knownProfiles) == 0 && goos != "windows" && runtime.GOOS != "windows" {
|
||||
// No known profiles, try a migration.
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil {
|
||||
return nil, err
|
||||
@@ -562,7 +571,7 @@ func (pm *profileManager) migrateFromLegacyPrefs() error {
|
||||
sentinel, prefs, err := pm.loadLegacyPrefs()
|
||||
if err != nil {
|
||||
metricMigrationError.Add(1)
|
||||
return err
|
||||
return fmt.Errorf("load legacy prefs: %w", err)
|
||||
}
|
||||
if err := pm.SetPrefs(prefs); err != nil {
|
||||
metricMigrationError.Add(1)
|
||||
|
||||
@@ -5,7 +5,7 @@ package ipnlocal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -18,9 +18,6 @@ import (
|
||||
)
|
||||
|
||||
func TestProfileCurrentUserSwitch(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
}
|
||||
store := new(mem.Store)
|
||||
|
||||
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
|
||||
@@ -77,9 +74,6 @@ func TestProfileCurrentUserSwitch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProfileList(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
}
|
||||
store := new(mem.Store)
|
||||
|
||||
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
|
||||
@@ -158,9 +152,6 @@ func TestProfileList(t *testing.T) {
|
||||
|
||||
// TestProfileManagement tests creating, loading, and switching profiles.
|
||||
func TestProfileManagement(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
}
|
||||
store := new(mem.Store)
|
||||
|
||||
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
|
||||
@@ -312,10 +303,11 @@ func TestProfileManagement(t *testing.T) {
|
||||
// TestProfileManagementWindows tests going into and out of Unattended mode on
|
||||
// Windows.
|
||||
func TestProfileManagementWindows(t *testing.T) {
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
uid := ipn.WindowsUserID(u.Uid)
|
||||
|
||||
store := new(mem.Store)
|
||||
|
||||
@@ -365,8 +357,8 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
|
||||
{
|
||||
t.Logf("Set user1 as logged in user")
|
||||
if err := pm.SetCurrentUserID("user1"); err != nil {
|
||||
t.Fatal(err)
|
||||
if err := pm.SetCurrentUserID(uid); err != nil {
|
||||
t.Fatalf("can't set user id: %s", err)
|
||||
}
|
||||
checkProfiles(t)
|
||||
t.Logf("Save prefs for user1")
|
||||
@@ -401,7 +393,7 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
|
||||
{
|
||||
t.Logf("Set user1 as current user")
|
||||
if err := pm.SetCurrentUserID("user1"); err != nil {
|
||||
if err := pm.SetCurrentUserID(uid); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantCurProfile = "test"
|
||||
@@ -411,8 +403,8 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
t.Logf("set unattended mode")
|
||||
wantProfiles["test"] = setPrefs(t, "test", true)
|
||||
}
|
||||
if pm.CurrentUserID() != "user1" {
|
||||
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), "user1")
|
||||
if pm.CurrentUserID() != uid {
|
||||
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
|
||||
}
|
||||
|
||||
// Recreate the profile manager to ensure that it starts with test profile.
|
||||
@@ -421,7 +413,7 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkProfiles(t)
|
||||
if pm.CurrentUserID() != "user1" {
|
||||
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), "user1")
|
||||
if pm.CurrentUserID() != uid {
|
||||
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package ipnlocal
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
@@ -21,8 +22,6 @@ const (
|
||||
legacyPrefsExt = ".conf"
|
||||
)
|
||||
|
||||
var errAlreadyMigrated = errors.New("profile migration already completed")
|
||||
|
||||
func legacyPrefsDir(uid ipn.WindowsUserID) (string, error) {
|
||||
// TODO(aaron): Ideally we'd have the impersonation token for the pipe's
|
||||
// client and use it to call SHGetKnownFolderPath, thus yielding the correct
|
||||
@@ -56,6 +55,9 @@ func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
|
||||
|
||||
prefsPath := filepath.Join(userLegacyPrefsDir, legacyPrefsFile+legacyPrefsExt)
|
||||
prefs, err := ipn.LoadPrefs(prefsPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return "", ipn.PrefsView{}, errAlreadyMigrated
|
||||
}
|
||||
if err != nil {
|
||||
return "", ipn.PrefsView{}, err
|
||||
}
|
||||
|
||||
@@ -71,11 +71,11 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47842c84: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/+/334a2380:shiny/LICENSE))
|
||||
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.8.0: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/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/a3b23cc7:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/LICENSE))
|
||||
- [inet.af/netaddr](https://pkg.go.dev/inet.af/netaddr) ([BSD-3-Clause](https://github.com/inetaf/netaddr/blob/097006376321/LICENSE))
|
||||
|
||||
@@ -59,11 +59,11 @@ and [iOS][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [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/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/cafedaf6:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0: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/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/LICENSE))
|
||||
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
||||
|
||||
@@ -13,84 +13,87 @@ well as an [option for macOS][].
|
||||
Some packages may only be included on certain architectures or operating systems.
|
||||
|
||||
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.0/LICENSE))
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
|
||||
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.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.17.3/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.27/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.4.21/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.17.3/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.35.0/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.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/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.18.0/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.22/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.13.21/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.13.3/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.33/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.4.27/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.34/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.18.0/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.9.27/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.36.3/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.12.9/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.14.9/service/ssooidc/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.18.10/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.13.5/LICENSE))
|
||||
- [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.13.5/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/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/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/6ac47ab19aa5/LICENSE))
|
||||
- [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.18/LICENSE))
|
||||
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/111c8c3b57c8/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/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.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/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/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
||||
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.3.0/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/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
||||
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/de60144f33f8/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/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/5c7d0dd6ab86/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/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/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/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.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.16.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.16.5/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/kr/fs](https://pkg.go.dev/github.com/kr/fs) ([BSD-3-Clause](https://github.com/kr/fs/blob/v0.1.0/LICENSE))
|
||||
- [github.com/mattn/go-colorable](https://pkg.go.dev/github.com/mattn/go-colorable) ([MIT](https://github.com/mattn/go-colorable/blob/v0.1.13/LICENSE))
|
||||
- [github.com/mattn/go-isatty](https://pkg.go.dev/github.com/mattn/go-isatty) ([MIT](https://github.com/mattn/go-isatty/blob/v0.0.17/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.7.1/LICENSE.md))
|
||||
- [github.com/mattn/go-isatty](https://pkg.go.dev/github.com/mattn/go-isatty) ([MIT](https://github.com/mattn/go-isatty/blob/v0.0.18/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/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.4.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/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/peterbourgon/ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3) ([Apache-2.0](https://github.com/peterbourgon/ff/blob/v3.3.0/LICENSE))
|
||||
- [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.17/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/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) ([BSD-2-Clause](https://github.com/pkg/sftp/blob/v1.13.5/LICENSE))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/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/bc99ab8c2d17/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/17a3db2c30d2/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/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/af172621b4dd/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/948a78c969ad/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/c3537552635f/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/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.11.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/3e8cd9d6bf63/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/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/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47842c84:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.8.0: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/f1b76eb4bb35/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.7.0: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/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
|
||||
- [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/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))
|
||||
- [inet.af/wf](https://pkg.go.dev/inet.af/wf) ([BSD-3-Clause](https://github.com/inetaf/wf/blob/36129f591884/LICENSE))
|
||||
- [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.25.0/LICENSE))
|
||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||
- [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([MIT](https://github.com/kubernetes-sigs/yaml/blob/v1.3.0/LICENSE))
|
||||
|
||||
@@ -32,8 +32,8 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [github.com/nfnt/resize](https://pkg.go.dev/github.com/nfnt/resize) ([ISC](https://github.com/nfnt/resize/blob/83c6a9932646/LICENSE))
|
||||
- [github.com/peterbourgon/diskv](https://pkg.go.dev/github.com/peterbourgon/diskv) ([MIT](https://github.com/peterbourgon/diskv/blob/v2.0.1/LICENSE))
|
||||
- [github.com/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/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/f6f2f17d9da1/LICENSE))
|
||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/ad93eed16885/LICENSE))
|
||||
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/f374e3278cd0/LICENSE))
|
||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/59dfb47dfef1/LICENSE))
|
||||
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.1.6/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))
|
||||
@@ -41,12 +41,12 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/cafedaf6:LICENSE))
|
||||
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.10.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0: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/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
|
||||
- [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))
|
||||
|
||||
@@ -1321,10 +1321,7 @@ func (c *Client) measureAllICMPLatency(ctx context.Context, rs *reportState, nee
|
||||
ctx, done := context.WithTimeout(ctx, icmpProbeTimeout)
|
||||
defer done()
|
||||
|
||||
p, err := ping.New(ctx, c.logf, c.NetMon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := ping.New(ctx, c.logf, netns.Listener(c.logf, c.NetMon))
|
||||
defer p.Close()
|
||||
|
||||
c.logf("UDP is blocked, trying ICMP")
|
||||
|
||||
171
net/ping/ping.go
171
net/ping/ping.go
@@ -11,16 +11,25 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"golang.org/x/net/ipv6"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/multierr"
|
||||
)
|
||||
|
||||
const (
|
||||
v4Type = "ip4:icmp"
|
||||
v6Type = "ip6:icmp"
|
||||
)
|
||||
|
||||
type response struct {
|
||||
@@ -33,12 +42,21 @@ type outstanding struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
// PacketListener defines the interface required to listen to packages
|
||||
// on an address.
|
||||
type ListenPacketer interface {
|
||||
ListenPacket(ctx context.Context, typ string, addr string) (net.PacketConn, error)
|
||||
}
|
||||
|
||||
// Pinger represents a set of ICMP echo requests to be sent at a single time.
|
||||
//
|
||||
// A new instance should be created for each concurrent set of ping requests;
|
||||
// this type should not be reused.
|
||||
type Pinger struct {
|
||||
c net.PacketConn
|
||||
lp ListenPacketer
|
||||
|
||||
// closed guards against send incrementing the waitgroup concurrently with close.
|
||||
closed atomic.Bool
|
||||
Logf logger.Logf
|
||||
Verbose bool
|
||||
timeNow func() time.Time
|
||||
@@ -46,16 +64,37 @@ type Pinger struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Following fields protected by mu
|
||||
mu sync.Mutex
|
||||
mu sync.Mutex
|
||||
// conns is a map of "type" to net.PacketConn, type is either
|
||||
// "ip4:icmp" or "ip6:icmp"
|
||||
conns map[string]net.PacketConn
|
||||
seq uint16 // uint16 per RFC 792
|
||||
pings map[uint16]outstanding
|
||||
}
|
||||
|
||||
// New creates a new Pinger. The Context provided will be used to create
|
||||
// network listeners, and to set an absolute deadline (if any) on the net.Conn
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func New(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) {
|
||||
p, err := newUnstarted(ctx, logf, netMon)
|
||||
func New(ctx context.Context, logf logger.Logf, lp ListenPacketer) *Pinger {
|
||||
var id [2]byte
|
||||
if _, err := io.ReadFull(rand.Reader, id[:]); err != nil {
|
||||
panic("net/ping: New:" + err.Error())
|
||||
}
|
||||
|
||||
return &Pinger{
|
||||
lp: lp,
|
||||
Logf: logf,
|
||||
timeNow: time.Now,
|
||||
id: binary.LittleEndian.Uint16(id[:]),
|
||||
pings: make(map[uint16]outstanding),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pinger) mkconn(ctx context.Context, typ, addr string) (net.PacketConn, error) {
|
||||
if p.closed.Load() {
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
|
||||
c, err := p.lp.ListenPacket(ctx, typ, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -64,35 +103,36 @@ func New(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger
|
||||
// applies to all future I/O, so we only need to do it once.
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok {
|
||||
if err := p.c.SetReadDeadline(deadline); err != nil {
|
||||
if err := c.SetReadDeadline(deadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
p.wg.Add(1)
|
||||
go p.run(ctx)
|
||||
return p, nil
|
||||
go p.run(ctx, c, typ)
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func newUnstarted(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) {
|
||||
var id [2]byte
|
||||
_, err := rand.Read(id[:])
|
||||
// getConn creates or returns a conn matching typ which is ip4:icmp
|
||||
// or ip6:icmp.
|
||||
func (p *Pinger) getConn(ctx context.Context, typ string) (net.PacketConn, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if c, ok := p.conns[typ]; ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var addr = "0.0.0.0"
|
||||
if typ == v6Type {
|
||||
addr = "::"
|
||||
}
|
||||
c, err := p.mkconn(ctx, typ, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := netns.Listener(logf, netMon).ListenPacket(ctx, "ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Pinger{
|
||||
c: conn,
|
||||
Logf: logf,
|
||||
timeNow: time.Now,
|
||||
id: binary.LittleEndian.Uint16(id[:]),
|
||||
pings: make(map[uint16]outstanding),
|
||||
}, nil
|
||||
mak.Set(&p.conns, typ, c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (p *Pinger) logf(format string, a ...any) {
|
||||
@@ -110,13 +150,34 @@ func (p *Pinger) vlogf(format string, a ...any) {
|
||||
}
|
||||
|
||||
func (p *Pinger) Close() error {
|
||||
err := p.c.Close()
|
||||
p.closed.Store(true)
|
||||
|
||||
p.mu.Lock()
|
||||
conns := p.conns
|
||||
p.conns = nil
|
||||
p.mu.Unlock()
|
||||
|
||||
var errors []error
|
||||
for _, c := range conns {
|
||||
if err := c.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
p.wg.Wait()
|
||||
return err
|
||||
p.cleanupOutstanding()
|
||||
|
||||
return multierr.New(errors...)
|
||||
}
|
||||
|
||||
func (p *Pinger) run(ctx context.Context) {
|
||||
func (p *Pinger) run(ctx context.Context, conn net.PacketConn, typ string) {
|
||||
defer p.wg.Done()
|
||||
defer func() {
|
||||
conn.Close()
|
||||
p.mu.Lock()
|
||||
delete(p.conns, typ)
|
||||
p.mu.Unlock()
|
||||
}()
|
||||
buf := make([]byte, 1500)
|
||||
|
||||
loop:
|
||||
@@ -127,7 +188,7 @@ loop:
|
||||
default:
|
||||
}
|
||||
|
||||
n, addr, err := p.c.ReadFrom(buf)
|
||||
n, _, err := conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
// Ignore temporary errors; everything else is fatal
|
||||
if netErr, ok := err.(net.Error); !ok || !netErr.Temporary() {
|
||||
@@ -136,10 +197,8 @@ loop:
|
||||
continue
|
||||
}
|
||||
|
||||
p.handleResponse(buf[:n], addr, p.timeNow())
|
||||
p.handleResponse(buf[:n], p.timeNow(), typ)
|
||||
}
|
||||
|
||||
p.cleanupOutstanding()
|
||||
}
|
||||
|
||||
func (p *Pinger) cleanupOutstanding() {
|
||||
@@ -151,16 +210,28 @@ func (p *Pinger) cleanupOutstanding() {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pinger) handleResponse(buf []byte, addr net.Addr, now time.Time) {
|
||||
const ProtocolICMP = 1
|
||||
m, err := icmp.ParseMessage(ProtocolICMP, buf)
|
||||
func (p *Pinger) handleResponse(buf []byte, now time.Time, typ string) {
|
||||
// We need to handle responding to both IPv4
|
||||
// and IPv6.
|
||||
var icmpType icmp.Type
|
||||
switch typ {
|
||||
case v4Type:
|
||||
icmpType = ipv4.ICMPTypeEchoReply
|
||||
case v6Type:
|
||||
icmpType = ipv6.ICMPTypeEchoReply
|
||||
default:
|
||||
p.vlogf("handleResponse: unknown icmp.Type")
|
||||
return
|
||||
}
|
||||
|
||||
m, err := icmp.ParseMessage(icmpType.Protocol(), buf)
|
||||
if err != nil {
|
||||
p.vlogf("handleResponse: invalid packet: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if m.Type != ipv4.ICMPTypeEchoReply {
|
||||
p.vlogf("handleResponse: wanted m.Type=%d; got %d", ipv4.ICMPTypeEchoReply, m.Type)
|
||||
if m.Type != icmpType {
|
||||
p.vlogf("handleResponse: wanted m.Type=%d; got %d", icmpType, m.Type)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -212,9 +283,27 @@ func (p *Pinger) Send(ctx context.Context, dest net.Addr, data []byte) (time.Dur
|
||||
seq := p.seq
|
||||
p.mu.Unlock()
|
||||
|
||||
// Check whether the address is IPv4 or IPv6 to
|
||||
// determine the icmp.Type and conn to use.
|
||||
var conn net.PacketConn
|
||||
var icmpType icmp.Type = ipv4.ICMPTypeEcho
|
||||
ap, err := netip.ParseAddr(dest.String())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ap.Is6() {
|
||||
icmpType = ipv6.ICMPTypeEchoRequest
|
||||
conn, err = p.getConn(ctx, v6Type)
|
||||
} else {
|
||||
conn, err = p.getConn(ctx, v4Type)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
m := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho,
|
||||
Code: 0,
|
||||
Type: icmpType,
|
||||
Code: icmpType.Protocol(),
|
||||
Body: &icmp.Echo{
|
||||
ID: int(p.id),
|
||||
Seq: int(seq),
|
||||
@@ -234,7 +323,7 @@ func (p *Pinger) Send(ctx context.Context, dest net.Addr, data []byte) (time.Dur
|
||||
p.mu.Unlock()
|
||||
|
||||
start := p.timeNow()
|
||||
n, err := p.c.WriteTo(b, dest)
|
||||
n, err := conn.WriteTo(b, dest)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if n != len(b) {
|
||||
|
||||
@@ -6,18 +6,20 @@ package ping
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
var (
|
||||
localhost = &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}
|
||||
localhostUDP = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 12345}
|
||||
localhost = &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}
|
||||
)
|
||||
|
||||
func TestPinger(t *testing.T) {
|
||||
@@ -35,7 +37,7 @@ func TestPinger(t *testing.T) {
|
||||
// Start a ping in the background
|
||||
r := make(chan time.Duration, 1)
|
||||
go func() {
|
||||
dur, err := p.Send(ctx, localhostUDP, bodyData)
|
||||
dur, err := p.Send(ctx, localhost, bodyData)
|
||||
if err != nil {
|
||||
t.Errorf("p.Send: %v", err)
|
||||
r <- 0
|
||||
@@ -49,7 +51,7 @@ func TestPinger(t *testing.T) {
|
||||
// Fake a response from ourself
|
||||
fakeResponse := mustMarshal(t, &icmp.Message{
|
||||
Type: ipv4.ICMPTypeEchoReply,
|
||||
Code: 0,
|
||||
Code: ipv4.ICMPTypeEchoReply.Protocol(),
|
||||
Body: &icmp.Echo{
|
||||
ID: 1234,
|
||||
Seq: 1,
|
||||
@@ -58,7 +60,65 @@ func TestPinger(t *testing.T) {
|
||||
})
|
||||
|
||||
const fakeDuration = 100 * time.Millisecond
|
||||
p.handleResponse(fakeResponse, localhost, clock.Now().Add(fakeDuration))
|
||||
p.handleResponse(fakeResponse, clock.Now().Add(fakeDuration), v4Type)
|
||||
|
||||
select {
|
||||
case dur := <-r:
|
||||
want := fakeDuration
|
||||
if dur != want {
|
||||
t.Errorf("wanted ping response time = %d; got %d", want, dur)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("did not get response by timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestV6Pinger(t *testing.T) {
|
||||
if c, err := net.ListenPacket("udp6", "::1"); err != nil {
|
||||
// skip test if we can't use IPv6.
|
||||
t.Skipf("IPv6 not supported: %s", err)
|
||||
} else {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
clock := &tstest.Clock{}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
p, closeP := mockPinger(t, clock)
|
||||
defer closeP()
|
||||
|
||||
bodyData := []byte("data goes here")
|
||||
|
||||
// Start a ping in the background
|
||||
r := make(chan time.Duration, 1)
|
||||
go func() {
|
||||
dur, err := p.Send(ctx, &net.IPAddr{IP: net.ParseIP("::")}, bodyData)
|
||||
if err != nil {
|
||||
t.Errorf("p.Send: %v", err)
|
||||
r <- 0
|
||||
} else {
|
||||
r <- dur
|
||||
}
|
||||
}()
|
||||
|
||||
p.waitOutstanding(t, ctx, 1)
|
||||
|
||||
// Fake a response from ourself
|
||||
fakeResponse := mustMarshal(t, &icmp.Message{
|
||||
Type: ipv6.ICMPTypeEchoReply,
|
||||
Code: ipv6.ICMPTypeEchoReply.Protocol(),
|
||||
Body: &icmp.Echo{
|
||||
ID: 1234,
|
||||
Seq: 1,
|
||||
Data: bodyData,
|
||||
},
|
||||
})
|
||||
|
||||
const fakeDuration = 100 * time.Millisecond
|
||||
p.handleResponse(fakeResponse, clock.Now().Add(fakeDuration), v6Type)
|
||||
|
||||
select {
|
||||
case dur := <-r:
|
||||
@@ -83,7 +143,7 @@ func TestPingerTimeout(t *testing.T) {
|
||||
// Send a ping in the background
|
||||
r := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := p.Send(ctx, localhostUDP, []byte("data goes here"))
|
||||
_, err := p.Send(ctx, localhost, []byte("data goes here"))
|
||||
r <- err
|
||||
}()
|
||||
|
||||
@@ -115,7 +175,7 @@ func TestPingerMismatch(t *testing.T) {
|
||||
// Start a ping in the background
|
||||
r := make(chan time.Duration, 1)
|
||||
go func() {
|
||||
dur, err := p.Send(ctx, localhostUDP, bodyData)
|
||||
dur, err := p.Send(ctx, localhost, bodyData)
|
||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Errorf("p.Send: %v", err)
|
||||
r <- 0
|
||||
@@ -185,11 +245,11 @@ func TestPingerMismatch(t *testing.T) {
|
||||
|
||||
for _, tt := range badPackets {
|
||||
fakeResponse := mustMarshal(t, tt.pkt)
|
||||
p.handleResponse(fakeResponse, localhost, tm)
|
||||
p.handleResponse(fakeResponse, tm, v4Type)
|
||||
}
|
||||
|
||||
// Also "receive" a packet that does not unmarshal as an ICMP packet
|
||||
p.handleResponse([]byte("foo"), localhost, tm)
|
||||
p.handleResponse([]byte("foo"), tm, v4Type)
|
||||
|
||||
select {
|
||||
case <-r:
|
||||
@@ -199,23 +259,59 @@ func TestPingerMismatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// udpingPacketConn will convert potentially ICMP destination addrs to UDP
|
||||
// destination addrs in WriteTo so that a test that is intending to send ICMP
|
||||
// traffic will instead send UDP traffic, without the higher level Pinger being
|
||||
// aware of this difference.
|
||||
type udpingPacketConn struct {
|
||||
net.PacketConn
|
||||
// destPort will be configured by the test to be the peer expected to respond to a ping.
|
||||
destPort uint16
|
||||
}
|
||||
|
||||
func (u *udpingPacketConn) WriteTo(body []byte, dest net.Addr) (int, error) {
|
||||
switch d := dest.(type) {
|
||||
case *net.IPAddr:
|
||||
udpAddr := &net.UDPAddr{
|
||||
IP: d.IP,
|
||||
Port: int(u.destPort),
|
||||
Zone: d.Zone,
|
||||
}
|
||||
return u.PacketConn.WriteTo(body, udpAddr)
|
||||
}
|
||||
return 0, fmt.Errorf("unimplemented udpingPacketConn for %T", dest)
|
||||
}
|
||||
|
||||
func mockPinger(t *testing.T, clock *tstest.Clock) (*Pinger, func()) {
|
||||
p := New(context.Background(), t.Logf, nil)
|
||||
p.timeNow = clock.Now
|
||||
p.Verbose = true
|
||||
p.id = 1234
|
||||
|
||||
// In tests, we use UDP so that we can test without being root; this
|
||||
// doesn't matter because we mock out the ICMP reply below to be a real
|
||||
// ICMP echo reply packet.
|
||||
conn, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||
conn4, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.ListenPacket: %v", err)
|
||||
}
|
||||
|
||||
p := &Pinger{
|
||||
c: conn,
|
||||
Logf: t.Logf,
|
||||
Verbose: true,
|
||||
timeNow: clock.Now,
|
||||
id: 1234,
|
||||
pings: make(map[uint16]outstanding),
|
||||
conn6, err := net.ListenPacket("udp6", "[::]:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.ListenPacket: %v", err)
|
||||
}
|
||||
|
||||
conn4 = &udpingPacketConn{
|
||||
destPort: 12345,
|
||||
PacketConn: conn4,
|
||||
}
|
||||
conn6 = &udpingPacketConn{
|
||||
PacketConn: conn6,
|
||||
destPort: 12345,
|
||||
}
|
||||
|
||||
mak.Set(&p.conns, v4Type, conn4)
|
||||
mak.Set(&p.conns, v6Type, conn6)
|
||||
done := func() {
|
||||
if err := p.Close(); err != nil {
|
||||
t.Errorf("error on close: %v", err)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"go4.org/mem"
|
||||
"golang.org/x/exp/slices"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/net/connstats"
|
||||
@@ -590,16 +591,33 @@ func natConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
|
||||
dstMasqAddrs map[key.NodePublic]netip.Addr
|
||||
listenAddrs map[netip.Addr]struct{}
|
||||
)
|
||||
|
||||
// When using an exit node that requires masquerading, we need to
|
||||
// fill out the routing table with all peers not just the ones that
|
||||
// require masquerading.
|
||||
exitNodeRequiresMasq := false // true if using an exit node and it requires masquerading
|
||||
for _, p := range wcfg.Peers {
|
||||
isExitNode := slices.Contains(p.AllowedIPs, tsaddr.AllIPv4()) || slices.Contains(p.AllowedIPs, tsaddr.AllIPv6())
|
||||
if isExitNode && p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
|
||||
exitNodeRequiresMasq = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := range wcfg.Peers {
|
||||
p := &wcfg.Peers[i]
|
||||
if p.V4MasqAddr == nil || !p.V4MasqAddr.IsValid() {
|
||||
var addrToUse netip.Addr
|
||||
if p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
|
||||
addrToUse = *p.V4MasqAddr
|
||||
mak.Set(&listenAddrs, addrToUse, struct{}{})
|
||||
} else if exitNodeRequiresMasq {
|
||||
addrToUse = nativeAddr
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
rt.InsertOrReplace(p.PublicKey, p.AllowedIPs...)
|
||||
mak.Set(&dstMasqAddrs, p.PublicKey, *p.V4MasqAddr)
|
||||
mak.Set(&listenAddrs, *p.V4MasqAddr, struct{}{})
|
||||
mak.Set(&dstMasqAddrs, p.PublicKey, addrToUse)
|
||||
}
|
||||
if len(listenAddrs) == 0 || len(dstMasqAddrs) == 0 {
|
||||
if len(listenAddrs) == 0 && len(dstMasqAddrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &natV4Config{
|
||||
|
||||
@@ -602,13 +602,13 @@ func TestFilterDiscoLoop(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNATCfg(t *testing.T) {
|
||||
node := func(ip, eip netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
|
||||
node := func(ip, masqIP netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
|
||||
p := wgcfg.Peer{
|
||||
PublicKey: key.NewNode().Public(),
|
||||
AllowedIPs: []netip.Prefix{
|
||||
netip.PrefixFrom(ip, ip.BitLen()),
|
||||
},
|
||||
V4MasqAddr: ptr.To(eip),
|
||||
V4MasqAddr: ptr.To(masqIP),
|
||||
}
|
||||
p.AllowedIPs = append(p.AllowedIPs, otherAllowedIPs...)
|
||||
return p
|
||||
@@ -619,13 +619,16 @@ func TestNATCfg(t *testing.T) {
|
||||
selfNativeIP = netip.MustParseAddr("100.64.0.1")
|
||||
selfEIP1 = netip.MustParseAddr("100.64.1.1")
|
||||
selfEIP2 = netip.MustParseAddr("100.64.1.2")
|
||||
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
|
||||
|
||||
peer1IP = netip.MustParseAddr("100.64.0.2")
|
||||
peer2IP = netip.MustParseAddr("100.64.0.3")
|
||||
|
||||
subnet = netip.MustParseAddr("192.168.0.1")
|
||||
subnet = netip.MustParsePrefix("192.168.0.0/24")
|
||||
subnetIP = netip.MustParseAddr("192.168.0.1")
|
||||
|
||||
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
|
||||
exitRoute = netip.MustParsePrefix("0.0.0.0/0")
|
||||
publicIP = netip.MustParseAddr("8.8.8.8")
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
@@ -638,9 +641,9 @@ func TestNATCfg(t *testing.T) {
|
||||
name: "no-cfg",
|
||||
wcfg: nil,
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
subnet: selfNativeIP,
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
subnetIP: selfNativeIP,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
@@ -658,15 +661,15 @@ func TestNATCfg(t *testing.T) {
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfEIP1,
|
||||
subnet: selfNativeIP,
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfEIP1,
|
||||
subnetIP: selfNativeIP,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfNativeIP,
|
||||
selfEIP2: selfEIP2,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -679,15 +682,15 @@ func TestNATCfg(t *testing.T) {
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
subnet: selfNativeIP,
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
subnetIP: selfNativeIP,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfNativeIP,
|
||||
selfEIP2: selfNativeIP,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -696,19 +699,19 @@ func TestNATCfg(t *testing.T) {
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2, netip.MustParsePrefix("192.168.0.0/24")),
|
||||
node(peer2IP, selfEIP2, subnet),
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
subnet: selfEIP2,
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
subnetIP: selfEIP2,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfNativeIP,
|
||||
selfEIP2: selfNativeIP,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -717,19 +720,19 @@ func TestNATCfg(t *testing.T) {
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2, netip.MustParsePrefix("0.0.0.0/0")),
|
||||
node(peer2IP, selfEIP2, exitRoute),
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
netip.MustParseAddr("8.8.8.8"): selfEIP2,
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
publicIP: selfEIP2,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfNativeIP,
|
||||
selfEIP2: selfNativeIP,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -742,15 +745,35 @@ func TestNATCfg(t *testing.T) {
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
subnet: selfNativeIP,
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
subnetIP: selfNativeIP,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfEIP1,
|
||||
selfEIP2: selfEIP2,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exit-node-require-nat-peer-doesnt",
|
||||
wcfg: &wgcfg.Config{
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, noIP),
|
||||
node(peer2IP, selfEIP2, exitRoute),
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfEIP2,
|
||||
publicIP: selfEIP2,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP2: selfNativeIP,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -430,7 +430,9 @@ main() {
|
||||
|
||||
|
||||
# Step 4: run the installation.
|
||||
echo "Installing Tailscale for $OS $VERSION, using method $PACKAGETYPE"
|
||||
OSVERSION="$OS"
|
||||
[ "$VERSION" != "" ] && OSVERSION="$OSVERSION $VERSION"
|
||||
echo "Installing Tailscale for $OSVERSION, using method $PACKAGETYPE"
|
||||
case "$PACKAGETYPE" in
|
||||
apt)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
@@ -469,6 +471,7 @@ main() {
|
||||
;;
|
||||
dnf)
|
||||
set -x
|
||||
$SUDO dnf install -y 'dnf-command(config-manager)'
|
||||
$SUDO dnf config-manager --add-repo "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo"
|
||||
$SUDO dnf install -y tailscale
|
||||
$SUDO systemctl enable --now tailscaled
|
||||
@@ -483,14 +486,15 @@ main() {
|
||||
;;
|
||||
zypper)
|
||||
set -x
|
||||
$SUDO zypper ar -g -r "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo"
|
||||
$SUDO zypper ref
|
||||
$SUDO zypper in tailscale
|
||||
$SUDO zypper --non-interactive ar -g -r "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo"
|
||||
$SUDO zypper --non-interactive --gpg-auto-import-keys refresh
|
||||
$SUDO zypper --non-interactive install tailscale
|
||||
$SUDO systemctl enable --now tailscaled
|
||||
set +x
|
||||
;;
|
||||
pacman)
|
||||
set -x
|
||||
$SUDO pacman -Sy
|
||||
$SUDO pacman -S tailscale --noconfirm
|
||||
$SUDO systemctl enable --now tailscaled
|
||||
set +x
|
||||
@@ -510,7 +514,7 @@ main() {
|
||||
;;
|
||||
xbps)
|
||||
set -x
|
||||
$SUDO xbps-install tailscale -y
|
||||
$SUDO xbps-install tailscale -y
|
||||
set +x
|
||||
;;
|
||||
emerge)
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
) {
|
||||
src = ./.;
|
||||
}).shellNix
|
||||
# nix-direnv cache busting line: sha256-lirn07XE3JOS6oiwZBMwxzywkbXHowOJUMWWLrZtccY=
|
||||
# nix-direnv cache busting line: sha256-ZQ6aE+9PfAxfeNQeDzwcOCXpztLORVriHkEw51lbeHM=
|
||||
|
||||
@@ -67,6 +67,7 @@ type ipnLocalBackend interface {
|
||||
WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool)
|
||||
DoNoiseRequest(req *http.Request) (*http.Response, error)
|
||||
Dialer() *tsdial.Dialer
|
||||
TailscaleVarRoot() string
|
||||
}
|
||||
|
||||
type server struct {
|
||||
@@ -236,6 +237,12 @@ func (c *conn) logf(format string, args ...any) {
|
||||
c.srv.logf(format, args...)
|
||||
}
|
||||
|
||||
func (c *conn) vlogf(format string, args ...any) {
|
||||
if sshVerboseLogging() {
|
||||
c.logf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// isAuthorized walks through the action chain and returns nil if the connection
|
||||
// is authorized. If the connection is not authorized, it returns
|
||||
// gossh.ErrDenied. If the action chain resolution fails, it returns the
|
||||
@@ -841,6 +848,7 @@ func (c *conn) newSSHSession(s ssh.Session) *sshSession {
|
||||
// isStillValid reports whether the conn is still valid.
|
||||
func (c *conn) isStillValid() bool {
|
||||
a, localUser, err := c.evaluatePolicy(c.pubKey)
|
||||
c.vlogf("stillValid: %+v %v %v", a, localUser, err)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -1147,6 +1155,11 @@ func (ss *sshSession) run() {
|
||||
return
|
||||
}
|
||||
|
||||
// recordSSHToLocalDisk is a deprecated dev knob to allow recording SSH sessions
|
||||
// to local storage. It is only used if there is no recording configured by the
|
||||
// coordination server. This will be removed in the future.
|
||||
var recordSSHToLocalDisk = envknob.RegisterBool("TS_DEBUG_LOG_SSH")
|
||||
|
||||
// recorders returns the list of recorders to use for this session.
|
||||
// If the final action has a non-empty list of recorders, that list is
|
||||
// returned. Otherwise, the list of recorders from the initial action
|
||||
@@ -1160,7 +1173,7 @@ func (ss *sshSession) recorders() ([]netip.AddrPort, *tailcfg.SSHRecorderFailure
|
||||
|
||||
func (ss *sshSession) shouldRecord() bool {
|
||||
recs, _ := ss.recorders()
|
||||
return len(recs) > 0
|
||||
return len(recs) > 0 || recordSSHToLocalDisk()
|
||||
}
|
||||
|
||||
type sshConnInfo struct {
|
||||
@@ -1211,6 +1224,10 @@ var (
|
||||
)
|
||||
|
||||
func (c *conn) matchRule(r *tailcfg.SSHRule, pubKey gossh.PublicKey) (a *tailcfg.SSHAction, localUser string, err error) {
|
||||
defer func() {
|
||||
c.vlogf("matchRule(%+v): %v", r, err)
|
||||
}()
|
||||
|
||||
if c == nil {
|
||||
return nil, "", errInvalidConn
|
||||
}
|
||||
@@ -1499,12 +1516,33 @@ func (ss *sshSession) connectToRecorder(ctx context.Context, recs []netip.AddrPo
|
||||
return nil, nil, multierr.New(errs...)
|
||||
}
|
||||
|
||||
func (ss *sshSession) openFileForRecording(now time.Time) (_ io.WriteCloser, err error) {
|
||||
varRoot := ss.conn.srv.lb.TailscaleVarRoot()
|
||||
if varRoot == "" {
|
||||
return nil, errors.New("no var root for recording storage")
|
||||
}
|
||||
dir := filepath.Join(varRoot, "ssh-sessions")
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.CreateTemp(dir, fmt.Sprintf("ssh-session-%v-*.cast", now.UnixNano()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// startNewRecording starts a new SSH session recording.
|
||||
// It may return a nil recording if recording is not available.
|
||||
func (ss *sshSession) startNewRecording() (_ *recording, err error) {
|
||||
recorders, onFailure := ss.recorders()
|
||||
var localRecording bool
|
||||
if len(recorders) == 0 {
|
||||
return nil, errors.New("no recorders configured")
|
||||
if recordSSHToLocalDisk() {
|
||||
localRecording = true
|
||||
} else {
|
||||
return nil, errors.New("no recorders configured")
|
||||
}
|
||||
}
|
||||
|
||||
var w ssh.Window
|
||||
@@ -1519,48 +1557,54 @@ func (ss *sshSession) startNewRecording() (_ *recording, err error) {
|
||||
|
||||
now := time.Now()
|
||||
rec := &recording{
|
||||
ss: ss,
|
||||
start: now,
|
||||
ss: ss,
|
||||
start: now,
|
||||
failOpen: onFailure == nil || onFailure.TerminateSessionWithMessage == "",
|
||||
}
|
||||
|
||||
// We want to use a background context for uploading and not ss.ctx.
|
||||
// ss.ctx is closed when the session closes, but we don't want to break the upload at that time.
|
||||
// Instead we want to wait for the session to close the writer when it finishes.
|
||||
ctx := context.Background()
|
||||
wc, errChan, err := ss.connectToRecorder(ctx, recorders)
|
||||
if err != nil {
|
||||
// TODO(catzkorn): notify control here.
|
||||
if onFailure != nil && onFailure.RejectSessionWithMessage != "" {
|
||||
ss.logf("recording: error starting recording (rejecting session): %v", err)
|
||||
return nil, userVisibleError{
|
||||
error: err,
|
||||
msg: onFailure.RejectSessionWithMessage,
|
||||
if localRecording {
|
||||
rec.out, err = ss.openFileForRecording(now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
var errChan <-chan error
|
||||
rec.out, errChan, err = ss.connectToRecorder(ctx, recorders)
|
||||
if err != nil {
|
||||
// TODO(catzkorn): notify control here.
|
||||
if onFailure != nil && onFailure.RejectSessionWithMessage != "" {
|
||||
ss.logf("recording: error starting recording (rejecting session): %v", err)
|
||||
return nil, userVisibleError{
|
||||
error: err,
|
||||
msg: onFailure.RejectSessionWithMessage,
|
||||
}
|
||||
}
|
||||
ss.logf("recording: error starting recording (failing open): %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
ss.logf("recording: error starting recording (failing open): %v", err)
|
||||
return nil, nil
|
||||
go func() {
|
||||
err := <-errChan
|
||||
if err == nil {
|
||||
// Success.
|
||||
return
|
||||
}
|
||||
// TODO(catzkorn): notify control here.
|
||||
if onFailure != nil && onFailure.TerminateSessionWithMessage != "" {
|
||||
ss.logf("recording: error uploading recording (closing session): %v", err)
|
||||
ss.cancelCtx(userVisibleError{
|
||||
error: err,
|
||||
msg: onFailure.TerminateSessionWithMessage,
|
||||
})
|
||||
return
|
||||
}
|
||||
ss.logf("recording: error uploading recording (failing open): %v", err)
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := <-errChan
|
||||
if err == nil {
|
||||
// Success.
|
||||
return
|
||||
}
|
||||
// TODO(catzkorn): notify control here.
|
||||
if onFailure != nil && onFailure.TerminateSessionWithMessage != "" {
|
||||
ss.logf("recording: error uploading recording (closing session): %v", err)
|
||||
ss.cancelCtx(userVisibleError{
|
||||
error: err,
|
||||
msg: onFailure.TerminateSessionWithMessage,
|
||||
})
|
||||
return
|
||||
}
|
||||
ss.logf("recording: error uploading recording (failing open): %v", err)
|
||||
}()
|
||||
|
||||
rec.out = wc
|
||||
|
||||
ch := CastHeader{
|
||||
Version: 2,
|
||||
Width: w.Width,
|
||||
@@ -1611,6 +1655,10 @@ type recording struct {
|
||||
ss *sshSession
|
||||
start time.Time
|
||||
|
||||
// failOpen specifies whether the session should be allowed to
|
||||
// continue if writing to the recording fails.
|
||||
failOpen bool
|
||||
|
||||
mu sync.Mutex // guards writes to, close of out
|
||||
out io.WriteCloser
|
||||
}
|
||||
@@ -1642,7 +1690,7 @@ func (r *recording) writer(dir string, w io.Writer) io.Writer {
|
||||
// passwords.
|
||||
return w
|
||||
}
|
||||
return &loggingWriter{r, dir, w}
|
||||
return &loggingWriter{r: r, dir: dir, w: w}
|
||||
}
|
||||
|
||||
// loggingWriter is an io.Writer wrapper that writes first an
|
||||
@@ -1651,20 +1699,30 @@ type loggingWriter struct {
|
||||
r *recording
|
||||
dir string // "i" or "o" (input or output)
|
||||
w io.Writer // underlying Writer, after writing to r.out
|
||||
|
||||
// recordingFailedOpen specifies whether we've failed to write to
|
||||
// r.out and should stop trying. It is set to true if we fail to write
|
||||
// to r.out and r.failOpen is set.
|
||||
recordingFailedOpen bool
|
||||
}
|
||||
|
||||
func (w loggingWriter) Write(p []byte) (n int, err error) {
|
||||
j, err := json.Marshal([]any{
|
||||
time.Since(w.r.start).Seconds(),
|
||||
w.dir,
|
||||
string(p),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
j = append(j, '\n')
|
||||
if err := w.writeCastLine(j); err != nil {
|
||||
return 0, err
|
||||
func (w *loggingWriter) Write(p []byte) (n int, err error) {
|
||||
if !w.recordingFailedOpen {
|
||||
j, err := json.Marshal([]any{
|
||||
time.Since(w.r.start).Seconds(),
|
||||
w.dir,
|
||||
string(p),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
j = append(j, '\n')
|
||||
if err := w.writeCastLine(j); err != nil {
|
||||
if !w.r.failOpen {
|
||||
return 0, err
|
||||
}
|
||||
w.recordingFailedOpen = true
|
||||
}
|
||||
}
|
||||
return w.w.Write(p)
|
||||
}
|
||||
|
||||
@@ -208,6 +208,8 @@ func (m *Map[K, V]) Delete(key K) {
|
||||
delete(m.m, key)
|
||||
}
|
||||
|
||||
// Range iterates over the map in undefined order calling f for each entry.
|
||||
// Iteration stops if f returns false. Map changes are blocked during iteration.
|
||||
func (m *Map[K, V]) Range(f func(key K, value V) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
@@ -218,6 +220,13 @@ func (m *Map[K, V]) Range(f func(key K, value V) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of the map.
|
||||
func (m *Map[K, V]) Len() int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return len(m.m)
|
||||
}
|
||||
|
||||
// WaitGroup is identical to [sync.WaitGroup],
|
||||
// but provides a Go method to start a goroutine.
|
||||
type WaitGroup struct{ sync.WaitGroup }
|
||||
|
||||
@@ -5,8 +5,9 @@ package tailcfg
|
||||
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode,SSHRule,SSHAction,SSHPrincipal,ControlDialPlan --clonefunc
|
||||
|
||||
//go:generage go run tailscale.com/cmd/equaler --type Node,Hostinfo,NetInfo,Service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -497,7 +498,7 @@ const (
|
||||
|
||||
// Service represents a service running on a node.
|
||||
type Service struct {
|
||||
_ structs.Incomparable
|
||||
_ structs.Incomparable `codegen:"noequal"`
|
||||
|
||||
// Proto is the type of service. It's usually the constant TCP
|
||||
// or UDP ("tcp" or "udp"), but it can also be one of the
|
||||
@@ -582,9 +583,6 @@ type Hostinfo struct {
|
||||
Cloud string `json:",omitempty"`
|
||||
Userspace opt.Bool `json:",omitempty"` // if the client is running in userspace (netstack) mode
|
||||
UserspaceRouter opt.Bool `json:",omitempty"` // if the client's subnet router is running in userspace (netstack) mode
|
||||
|
||||
// NOTE: any new fields containing pointers in this type
|
||||
// require changes to Hostinfo.Equal.
|
||||
}
|
||||
|
||||
// TailscaleSSHEnabled reports whether or not this node is acting as a
|
||||
@@ -664,9 +662,7 @@ type NetInfo struct {
|
||||
// This should only be updated rarely, or when there's a
|
||||
// material change, as any change here also gets uploaded to
|
||||
// the control plane.
|
||||
DERPLatency map[string]float64 `json:",omitempty"`
|
||||
|
||||
// Update BasicallyEqual when adding fields.
|
||||
DERPLatency map[string]float64 `json:",omitempty" codegen:"noequal"`
|
||||
}
|
||||
|
||||
func (ni *NetInfo) String() string {
|
||||
@@ -704,40 +700,6 @@ func conciseOptBool(b opt.Bool, trueVal string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// BasicallyEqual reports whether ni and ni2 are basically equal, ignoring
|
||||
// changes in DERP ServerLatency & RegionLatency.
|
||||
func (ni *NetInfo) BasicallyEqual(ni2 *NetInfo) bool {
|
||||
if (ni == nil) != (ni2 == nil) {
|
||||
return false
|
||||
}
|
||||
if ni == nil {
|
||||
return true
|
||||
}
|
||||
return ni.MappingVariesByDestIP == ni2.MappingVariesByDestIP &&
|
||||
ni.HairPinning == ni2.HairPinning &&
|
||||
ni.WorkingIPv6 == ni2.WorkingIPv6 &&
|
||||
ni.OSHasIPv6 == ni2.OSHasIPv6 &&
|
||||
ni.WorkingUDP == ni2.WorkingUDP &&
|
||||
ni.WorkingICMPv4 == ni2.WorkingICMPv4 &&
|
||||
ni.HavePortMap == ni2.HavePortMap &&
|
||||
ni.UPnP == ni2.UPnP &&
|
||||
ni.PMP == ni2.PMP &&
|
||||
ni.PCP == ni2.PCP &&
|
||||
ni.PreferredDERP == ni2.PreferredDERP &&
|
||||
ni.LinkType == ni2.LinkType
|
||||
}
|
||||
|
||||
// Equal reports whether h and h2 are equal.
|
||||
func (h *Hostinfo) Equal(h2 *Hostinfo) bool {
|
||||
if h == nil && h2 == nil {
|
||||
return true
|
||||
}
|
||||
if (h == nil) != (h2 == nil) {
|
||||
return false
|
||||
}
|
||||
return reflect.DeepEqual(h, h2)
|
||||
}
|
||||
|
||||
// HowUnequal returns a list of paths through Hostinfo where h and h2 differ.
|
||||
// If they differ in nil-ness, the path is "nil", otherwise the path is like
|
||||
// "ShieldsUp" or "NetInfo.nil" or "NetInfo.PCP".
|
||||
@@ -1689,82 +1651,6 @@ func (id UserID) String() string { return fmt.Sprintf("userid:%x", int64(id)) }
|
||||
func (id LoginID) String() string { return fmt.Sprintf("loginid:%x", int64(id)) }
|
||||
func (id NodeID) String() string { return fmt.Sprintf("nodeid:%x", int64(id)) }
|
||||
|
||||
// Equal reports whether n and n2 are equal.
|
||||
func (n *Node) Equal(n2 *Node) bool {
|
||||
if n == nil && n2 == nil {
|
||||
return true
|
||||
}
|
||||
return n != nil && n2 != nil &&
|
||||
n.ID == n2.ID &&
|
||||
n.StableID == n2.StableID &&
|
||||
n.Name == n2.Name &&
|
||||
n.User == n2.User &&
|
||||
n.Sharer == n2.Sharer &&
|
||||
n.UnsignedPeerAPIOnly == n2.UnsignedPeerAPIOnly &&
|
||||
n.Key == n2.Key &&
|
||||
n.KeyExpiry.Equal(n2.KeyExpiry) &&
|
||||
bytes.Equal(n.KeySignature, n2.KeySignature) &&
|
||||
n.Machine == n2.Machine &&
|
||||
n.DiscoKey == n2.DiscoKey &&
|
||||
eqPtr(n.Online, n2.Online) &&
|
||||
eqCIDRs(n.Addresses, n2.Addresses) &&
|
||||
eqCIDRs(n.AllowedIPs, n2.AllowedIPs) &&
|
||||
eqCIDRs(n.PrimaryRoutes, n2.PrimaryRoutes) &&
|
||||
eqStrings(n.Endpoints, n2.Endpoints) &&
|
||||
n.DERP == n2.DERP &&
|
||||
n.Cap == n2.Cap &&
|
||||
n.Hostinfo.Equal(n2.Hostinfo) &&
|
||||
n.Created.Equal(n2.Created) &&
|
||||
eqTimePtr(n.LastSeen, n2.LastSeen) &&
|
||||
n.MachineAuthorized == n2.MachineAuthorized &&
|
||||
eqStrings(n.Capabilities, n2.Capabilities) &&
|
||||
n.ComputedName == n2.ComputedName &&
|
||||
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
|
||||
n.ComputedNameWithHost == n2.ComputedNameWithHost &&
|
||||
eqStrings(n.Tags, n2.Tags) &&
|
||||
n.Expired == n2.Expired &&
|
||||
eqPtr(n.SelfNodeV4MasqAddrForThisPeer, n2.SelfNodeV4MasqAddrForThisPeer) &&
|
||||
n.IsWireGuardOnly == n2.IsWireGuardOnly
|
||||
}
|
||||
|
||||
func eqPtr[T comparable](a, b *T) bool {
|
||||
if a == b { // covers nil
|
||||
return true
|
||||
}
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
|
||||
func eqStrings(a, b []string) bool {
|
||||
if len(a) != len(b) || ((a == nil) != (b == nil)) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func eqCIDRs(a, b []netip.Prefix) bool {
|
||||
if len(a) != len(b) || ((a == nil) != (b == nil)) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func eqTimePtr(a, b *time.Time) bool {
|
||||
return ((a == nil) == (b == nil)) && (a == nil || a.Equal(*b))
|
||||
}
|
||||
|
||||
// Oauth2Token is a copy of golang.org/x/oauth2.Token, to avoid the
|
||||
// go.mod dependency on App Engine and grpc, which was causing problems.
|
||||
// All we actually needed was this struct on the client side.
|
||||
|
||||
244
tailcfg/tailcfg_equal.go
Normal file
244
tailcfg/tailcfg_equal.go
Normal file
@@ -0,0 +1,244 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by tailscale.com/cmd/equaler; DO NOT EDIT.
|
||||
|
||||
package tailcfg
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/structs"
|
||||
"tailscale.com/types/tkatype"
|
||||
)
|
||||
|
||||
// Equal reports whether a and b are equal.
|
||||
func (a *Node) Equal(b *Node) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
return a != nil && b != nil &&
|
||||
a.ID == b.ID &&
|
||||
a.StableID == b.StableID &&
|
||||
a.Name == b.Name &&
|
||||
a.User == b.User &&
|
||||
a.Sharer == b.Sharer &&
|
||||
a.Key == b.Key &&
|
||||
a.KeyExpiry.Equal(b.KeyExpiry) &&
|
||||
((a.KeySignature == nil) == (b.KeySignature == nil)) &&
|
||||
slices.Equal(a.KeySignature, b.KeySignature) &&
|
||||
a.Machine == b.Machine &&
|
||||
a.DiscoKey == b.DiscoKey &&
|
||||
((a.Addresses == nil) == (b.Addresses == nil)) &&
|
||||
slices.Equal(a.Addresses, b.Addresses) &&
|
||||
((a.AllowedIPs == nil) == (b.AllowedIPs == nil)) &&
|
||||
slices.Equal(a.AllowedIPs, b.AllowedIPs) &&
|
||||
((a.Endpoints == nil) == (b.Endpoints == nil)) &&
|
||||
slices.Equal(a.Endpoints, b.Endpoints) &&
|
||||
a.DERP == b.DERP &&
|
||||
a.Hostinfo.Equal(b.Hostinfo) &&
|
||||
a.Created.Equal(b.Created) &&
|
||||
a.Cap == b.Cap &&
|
||||
((a.Tags == nil) == (b.Tags == nil)) &&
|
||||
slices.Equal(a.Tags, b.Tags) &&
|
||||
((a.PrimaryRoutes == nil) == (b.PrimaryRoutes == nil)) &&
|
||||
slices.Equal(a.PrimaryRoutes, b.PrimaryRoutes) &&
|
||||
((a.LastSeen == nil) == (b.LastSeen == nil)) && (a.LastSeen == nil || a.LastSeen.Equal(*b.LastSeen)) &&
|
||||
((a.Online == nil) == (b.Online == nil)) && (a.Online == nil || *a.Online == *b.Online) &&
|
||||
a.KeepAlive == b.KeepAlive &&
|
||||
a.MachineAuthorized == b.MachineAuthorized &&
|
||||
((a.Capabilities == nil) == (b.Capabilities == nil)) &&
|
||||
slices.Equal(a.Capabilities, b.Capabilities) &&
|
||||
a.UnsignedPeerAPIOnly == b.UnsignedPeerAPIOnly &&
|
||||
a.ComputedName == b.ComputedName &&
|
||||
a.computedHostIfDifferent == b.computedHostIfDifferent &&
|
||||
a.ComputedNameWithHost == b.ComputedNameWithHost &&
|
||||
a.DataPlaneAuditLogID == b.DataPlaneAuditLogID &&
|
||||
a.Expired == b.Expired &&
|
||||
((a.SelfNodeV4MasqAddrForThisPeer == nil) == (b.SelfNodeV4MasqAddrForThisPeer == nil)) && (a.SelfNodeV4MasqAddrForThisPeer == nil || *a.SelfNodeV4MasqAddrForThisPeer == *b.SelfNodeV4MasqAddrForThisPeer) &&
|
||||
a.IsWireGuardOnly == b.IsWireGuardOnly &&
|
||||
true
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _NodeEqualNeedsRegeneration = Node(struct {
|
||||
ID NodeID
|
||||
StableID StableNodeID
|
||||
Name string
|
||||
User UserID
|
||||
Sharer UserID
|
||||
Key key.NodePublic
|
||||
KeyExpiry time.Time
|
||||
KeySignature tkatype.MarshaledSignature
|
||||
Machine key.MachinePublic
|
||||
DiscoKey key.DiscoPublic
|
||||
Addresses []netip.Prefix
|
||||
AllowedIPs []netip.Prefix
|
||||
Endpoints []string
|
||||
DERP string
|
||||
Hostinfo HostinfoView
|
||||
Created time.Time
|
||||
Cap CapabilityVersion
|
||||
Tags []string
|
||||
PrimaryRoutes []netip.Prefix
|
||||
LastSeen *time.Time
|
||||
Online *bool
|
||||
KeepAlive bool
|
||||
MachineAuthorized bool
|
||||
Capabilities []string
|
||||
UnsignedPeerAPIOnly bool
|
||||
ComputedName string
|
||||
computedHostIfDifferent string
|
||||
ComputedNameWithHost string
|
||||
DataPlaneAuditLogID string
|
||||
Expired bool
|
||||
SelfNodeV4MasqAddrForThisPeer *netip.Addr
|
||||
IsWireGuardOnly bool
|
||||
}{})
|
||||
|
||||
// Equal reports whether a and b are equal.
|
||||
func (a *Hostinfo) Equal(b *Hostinfo) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
return a != nil && b != nil &&
|
||||
a.IPNVersion == b.IPNVersion &&
|
||||
a.FrontendLogID == b.FrontendLogID &&
|
||||
a.BackendLogID == b.BackendLogID &&
|
||||
a.OS == b.OS &&
|
||||
a.OSVersion == b.OSVersion &&
|
||||
a.Container == b.Container &&
|
||||
a.Env == b.Env &&
|
||||
a.Distro == b.Distro &&
|
||||
a.DistroVersion == b.DistroVersion &&
|
||||
a.DistroCodeName == b.DistroCodeName &&
|
||||
a.App == b.App &&
|
||||
a.Desktop == b.Desktop &&
|
||||
a.Package == b.Package &&
|
||||
a.DeviceModel == b.DeviceModel &&
|
||||
a.PushDeviceToken == b.PushDeviceToken &&
|
||||
a.Hostname == b.Hostname &&
|
||||
a.ShieldsUp == b.ShieldsUp &&
|
||||
a.ShareeNode == b.ShareeNode &&
|
||||
a.NoLogsNoSupport == b.NoLogsNoSupport &&
|
||||
a.WireIngress == b.WireIngress &&
|
||||
a.AllowsUpdate == b.AllowsUpdate &&
|
||||
a.Machine == b.Machine &&
|
||||
a.GoArch == b.GoArch &&
|
||||
a.GoArchVar == b.GoArchVar &&
|
||||
a.GoVersion == b.GoVersion &&
|
||||
((a.RoutableIPs == nil) == (b.RoutableIPs == nil)) &&
|
||||
slices.Equal(a.RoutableIPs, b.RoutableIPs) &&
|
||||
((a.RequestTags == nil) == (b.RequestTags == nil)) &&
|
||||
slices.Equal(a.RequestTags, b.RequestTags) &&
|
||||
((a.Services == nil) == (b.Services == nil)) &&
|
||||
slices.EqualFunc(a.Services, b.Services, func(aa Service, bb Service) bool { return aa.Equal(&bb) }) &&
|
||||
((a.NetInfo == nil) == (b.NetInfo == nil)) && (a.NetInfo == nil || a.NetInfo.Equal(b.NetInfo)) &&
|
||||
((a.SSH_HostKeys == nil) == (b.SSH_HostKeys == nil)) &&
|
||||
slices.Equal(a.SSH_HostKeys, b.SSH_HostKeys) &&
|
||||
a.Cloud == b.Cloud &&
|
||||
a.Userspace == b.Userspace &&
|
||||
a.UserspaceRouter == b.UserspaceRouter &&
|
||||
true
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _HostinfoEqualNeedsRegeneration = Hostinfo(struct {
|
||||
IPNVersion string
|
||||
FrontendLogID string
|
||||
BackendLogID string
|
||||
OS string
|
||||
OSVersion string
|
||||
Container opt.Bool
|
||||
Env string
|
||||
Distro string
|
||||
DistroVersion string
|
||||
DistroCodeName string
|
||||
App string
|
||||
Desktop opt.Bool
|
||||
Package string
|
||||
DeviceModel string
|
||||
PushDeviceToken string
|
||||
Hostname string
|
||||
ShieldsUp bool
|
||||
ShareeNode bool
|
||||
NoLogsNoSupport bool
|
||||
WireIngress bool
|
||||
AllowsUpdate bool
|
||||
Machine string
|
||||
GoArch string
|
||||
GoArchVar string
|
||||
GoVersion string
|
||||
RoutableIPs []netip.Prefix
|
||||
RequestTags []string
|
||||
Services []Service
|
||||
NetInfo *NetInfo
|
||||
SSH_HostKeys []string
|
||||
Cloud string
|
||||
Userspace opt.Bool
|
||||
UserspaceRouter opt.Bool
|
||||
}{})
|
||||
|
||||
// Equal reports whether a and b are equal.
|
||||
func (a *NetInfo) Equal(b *NetInfo) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
return a != nil && b != nil &&
|
||||
a.MappingVariesByDestIP == b.MappingVariesByDestIP &&
|
||||
a.HairPinning == b.HairPinning &&
|
||||
a.WorkingIPv6 == b.WorkingIPv6 &&
|
||||
a.OSHasIPv6 == b.OSHasIPv6 &&
|
||||
a.WorkingUDP == b.WorkingUDP &&
|
||||
a.WorkingICMPv4 == b.WorkingICMPv4 &&
|
||||
a.HavePortMap == b.HavePortMap &&
|
||||
a.UPnP == b.UPnP &&
|
||||
a.PMP == b.PMP &&
|
||||
a.PCP == b.PCP &&
|
||||
a.PreferredDERP == b.PreferredDERP &&
|
||||
a.LinkType == b.LinkType &&
|
||||
// Skipping DERPLatency because of codegen:noequal
|
||||
true
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _NetInfoEqualNeedsRegeneration = NetInfo(struct {
|
||||
MappingVariesByDestIP opt.Bool
|
||||
HairPinning opt.Bool
|
||||
WorkingIPv6 opt.Bool
|
||||
OSHasIPv6 opt.Bool
|
||||
WorkingUDP opt.Bool
|
||||
WorkingICMPv4 opt.Bool
|
||||
HavePortMap bool
|
||||
UPnP opt.Bool
|
||||
PMP opt.Bool
|
||||
PCP opt.Bool
|
||||
PreferredDERP int
|
||||
LinkType string
|
||||
DERPLatency map[string]float64
|
||||
}{})
|
||||
|
||||
// Equal reports whether a and b are equal.
|
||||
func (a *Service) Equal(b *Service) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
return a != nil && b != nil &&
|
||||
// Skipping _ because of codegen:noequal
|
||||
a.Proto == b.Proto &&
|
||||
a.Port == b.Port &&
|
||||
a.Description == b.Description &&
|
||||
true
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _ServiceEqualNeedsRegeneration = Service(struct {
|
||||
_ structs.Incomparable
|
||||
Proto ServiceProto
|
||||
Port uint16
|
||||
Description string
|
||||
}{})
|
||||
@@ -572,7 +572,7 @@ func TestNetInfoFields(t *testing.T) {
|
||||
"DERPLatency",
|
||||
}
|
||||
if have := fieldsOf(reflect.TypeOf(NetInfo{})); !reflect.DeepEqual(have, handled) {
|
||||
t.Errorf("NetInfo.Clone/BasicallyEqually check might be out of sync\nfields: %q\nhandled: %q\n",
|
||||
t.Errorf("NetInfo.Clone/Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
||||
have, handled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,6 +402,7 @@ func (v NetInfoView) LinkType() string { return v.ж.LinkType }
|
||||
|
||||
func (v NetInfoView) DERPLatency() views.Map[string, float64] { return views.MapOf(v.ж.DERPLatency) }
|
||||
func (v NetInfoView) String() string { return v.ж.String() }
|
||||
func (v NetInfoView) Equal(v2 NetInfoView) bool { return v.ж.Equal(v2.ж) }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _NetInfoViewNeedsRegeneration = NetInfo(struct {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/imports"
|
||||
"tailscale.com/util/mak"
|
||||
@@ -47,12 +48,13 @@ func LoadTypes(buildTags string, pkgName string) (*packages.Package, map[string]
|
||||
// HasNoClone reports whether the provided tag has `codegen:noclone`.
|
||||
func HasNoClone(structTag string) bool {
|
||||
val := reflect.StructTag(structTag).Get("codegen")
|
||||
for _, v := range strings.Split(val, ",") {
|
||||
if v == "noclone" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(strings.Split(val, ","), "noclone")
|
||||
}
|
||||
|
||||
// HasNoEqual reports whether the provided tag has `codegen:noequal`.
|
||||
func HasNoEqual(structTag string) bool {
|
||||
val := reflect.StructTag(structTag).Get("codegen")
|
||||
return slices.Contains(strings.Split(val, ","), "noequal")
|
||||
}
|
||||
|
||||
const copyrightHeader = `// Copyright (c) Tailscale Inc & AUTHORS
|
||||
|
||||
@@ -50,6 +50,7 @@ import (
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/ping"
|
||||
"tailscale.com/net/portmapper"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/net/stun"
|
||||
@@ -59,6 +60,7 @@ import (
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/nettype"
|
||||
@@ -209,11 +211,16 @@ func (m *peerMap) upsertEndpoint(ep *endpoint, oldDiscoKey key.DiscoPublic) {
|
||||
if epDisco == nil || oldDiscoKey != epDisco.key {
|
||||
delete(m.nodesOfDisco[oldDiscoKey], ep.publicKey)
|
||||
}
|
||||
if epDisco == nil {
|
||||
// If the peer does not support Disco, but it does have an endpoint address,
|
||||
// attempt to use that (e.g. WireGuardOnly peers).
|
||||
if ep.bestAddr.AddrPort.IsValid() {
|
||||
m.setNodeKeyForIPPort(ep.bestAddr.AddrPort, ep.publicKey)
|
||||
if ep.isWireguardOnly {
|
||||
// If the peer is a WireGuard only peer, add all of its endpoints.
|
||||
|
||||
// TODO(raggi,catzkorn): this could mean that if a "isWireguardOnly"
|
||||
// peer has, say, 192.168.0.2 and so does a tailscale peer, the
|
||||
// wireguard one will win. That may not be the outcome that we want -
|
||||
// perhaps we should prefer bestAddr.AddrPort if it is set?
|
||||
// see tailscale/tailscale#7994
|
||||
for ipp := range ep.endpointState {
|
||||
m.setNodeKeyForIPPort(ipp, ep.publicKey)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -473,6 +480,9 @@ type Conn struct {
|
||||
// peerLastDerp tracks which DERP node we last used to speak with a
|
||||
// peer. It's only used to quiet logging, so we only log on change.
|
||||
peerLastDerp map[key.NodePublic]int
|
||||
|
||||
// wgPinger is the WireGuard only pinger used for latency measurements.
|
||||
wgPinger lazy.SyncValue[*ping.Pinger]
|
||||
}
|
||||
|
||||
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
|
||||
@@ -941,7 +951,7 @@ func (c *Conn) pickDERPFallback() int {
|
||||
func (c *Conn) callNetInfoCallback(ni *tailcfg.NetInfo) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if ni.BasicallyEqual(c.netInfoLast) {
|
||||
if ni.Equal(c.netInfoLast) {
|
||||
return
|
||||
}
|
||||
c.callNetInfoCallbackLocked(ni)
|
||||
@@ -2766,6 +2776,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
|
||||
sentPing: map[stun.TxID]sentPing{},
|
||||
endpointState: map[netip.AddrPort]*endpointState{},
|
||||
heartbeatDisabled: heartbeatDisabled,
|
||||
isWireguardOnly: n.IsWireGuardOnly,
|
||||
}
|
||||
if len(n.Addresses) > 0 {
|
||||
ep.nodeAddr = n.Addresses[0].Addr()
|
||||
@@ -3143,6 +3154,11 @@ func (c *Conn) Close() error {
|
||||
for c.goroutinesRunningLocked() {
|
||||
c.muCond.Wait()
|
||||
}
|
||||
|
||||
if pinger := c.getPinger(); pinger != nil {
|
||||
pinger.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4084,9 +4100,14 @@ type endpointDisco struct {
|
||||
short string // ShortString of discoKey.
|
||||
}
|
||||
|
||||
// endpoint is a wireguard/conn.Endpoint that picks the best
|
||||
// available path to communicate with a peer, based on network
|
||||
// conditions and what the peer supports.
|
||||
// endpoint is a wireguard/conn.Endpoint. In wireguard-go and kernel WireGuard
|
||||
// there is only one endpoint for a peer, but in Tailscale we distribute a
|
||||
// number of possible endpoints for a peer which would include the all the
|
||||
// likely addresses at which a peer may be reachable. This endpoint type holds
|
||||
// the information required that when WiregGuard-Go wants to send to a
|
||||
// particular peer (essentally represented by this endpoint type), the send
|
||||
// function can use the currnetly best known Tailscale endpoint to send packets
|
||||
// to the peer.
|
||||
type endpoint struct {
|
||||
// atomically accessed; declared first for alignment reasons
|
||||
lastRecv mono.Time
|
||||
@@ -4108,7 +4129,7 @@ type endpoint struct {
|
||||
|
||||
heartBeatTimer *time.Timer // nil when idle
|
||||
lastSend mono.Time // last time there was outgoing packets sent to this peer (from wireguard-go)
|
||||
lastFullPing mono.Time // last time we pinged all endpoints
|
||||
lastFullPing mono.Time // last time we pinged all disco endpoints
|
||||
derpAddr netip.AddrPort // fallback/bootstrap path, if non-zero (non-zero for well-behaved clients)
|
||||
|
||||
bestAddr addrLatency // best non-DERP path; zero if none
|
||||
@@ -4126,7 +4147,8 @@ type endpoint struct {
|
||||
heartbeatDisabled bool
|
||||
pathFinderRunning bool
|
||||
|
||||
expired bool // whether the node has expired
|
||||
expired bool // whether the node has expired
|
||||
isWireguardOnly bool // whether the endpoint is WireGuard only
|
||||
}
|
||||
|
||||
type pendingCLIPing struct {
|
||||
@@ -4238,6 +4260,15 @@ func (st *endpointState) shouldDeleteLocked() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// latencyLocked returns the most recent latency measurement, if any.
|
||||
// endpoint.mu must be held.
|
||||
func (st *endpointState) latencyLocked() (lat time.Duration, ok bool) {
|
||||
if len(st.recentPongs) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return st.recentPongs[st.recentPong].latency, true
|
||||
}
|
||||
|
||||
func (de *endpoint) deleteEndpointLocked(why string, ep netip.AddrPort) {
|
||||
de.debugUpdates.Add(EndpointChange{
|
||||
When: time.Now(),
|
||||
@@ -4321,17 +4352,87 @@ func (de *endpoint) DstToBytes() []byte { return packIPPort(de.fakeWGAddr) }
|
||||
|
||||
// addrForSendLocked returns the address(es) that should be used for
|
||||
// sending the next packet. Zero, one, or both of UDP address and DERP
|
||||
// addr may be non-zero.
|
||||
// addr may be non-zero. If the endpoint is WireGuard only and does not have
|
||||
// latency information, a bool is returned to indiciate that the
|
||||
// WireGuard latency discovery pings should be sent.
|
||||
//
|
||||
// de.mu must be held.
|
||||
func (de *endpoint) addrForSendLocked(now mono.Time) (udpAddr, derpAddr netip.AddrPort) {
|
||||
func (de *endpoint) addrForSendLocked(now mono.Time) (udpAddr, derpAddr netip.AddrPort, sendWGPing bool) {
|
||||
udpAddr = de.bestAddr.AddrPort
|
||||
if !udpAddr.IsValid() || now.After(de.trustBestAddrUntil) {
|
||||
// We had a bestAddr but it expired so send both to it
|
||||
// and DERP.
|
||||
derpAddr = de.derpAddr
|
||||
|
||||
if udpAddr.IsValid() && !now.After(de.trustBestAddrUntil) {
|
||||
return udpAddr, netip.AddrPort{}, false
|
||||
}
|
||||
return
|
||||
|
||||
if de.isWireguardOnly {
|
||||
// If the endpoint is wireguard-only, we don't have a DERP
|
||||
// address to send to, so we have to send to the UDP address.
|
||||
udpAddr, shouldPing := de.addrForWireGuardSendLocked(now)
|
||||
return udpAddr, netip.AddrPort{}, shouldPing
|
||||
}
|
||||
|
||||
// We had a bestAddr but it expired so send both to it
|
||||
// and DERP.
|
||||
return udpAddr, de.derpAddr, false
|
||||
}
|
||||
|
||||
// addrForWireGuardSendLocked returns the address that should be used for
|
||||
// sending the next packet. If a packet has never or not recently been sent to
|
||||
// the endpoint, then a randomly selected address for the endpoint is returned,
|
||||
// as well as a bool indiciating that WireGuard discovery pings should be started.
|
||||
// If the addresses have latency information available, then the address with the
|
||||
// best latency is used.
|
||||
//
|
||||
// de.mu must be held.
|
||||
func (de *endpoint) addrForWireGuardSendLocked(now mono.Time) (udpAddr netip.AddrPort, shouldPing bool) {
|
||||
// lowestLatency is a high duration initially, so we
|
||||
// can be sure we're going to have a duration lower than this
|
||||
// for the first latency retrieved.
|
||||
lowestLatency := time.Hour
|
||||
for ipp, state := range de.endpointState {
|
||||
if latency, ok := state.latencyLocked(); ok {
|
||||
if latency < lowestLatency || latency == lowestLatency && ipp.Addr().Is6() {
|
||||
// If we have the same latency,IPv6 is prioritized.
|
||||
// TODO(catzkorn): Consider a small increase in latency to use
|
||||
// IPv6 in comparison to IPv4, when possible.
|
||||
lowestLatency = latency
|
||||
udpAddr = ipp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if udpAddr.IsValid() {
|
||||
// Set trustBestAddrUntil to an hour, so we will
|
||||
// continue to use this address for a long period of time.
|
||||
de.bestAddr.AddrPort = udpAddr
|
||||
de.trustBestAddrUntil = now.Add(1 * time.Hour)
|
||||
return udpAddr, false
|
||||
}
|
||||
|
||||
candidates := make([]netip.AddrPort, 0, len(de.endpointState))
|
||||
for ipp := range de.endpointState {
|
||||
if ipp.Addr().Is4() && de.c.noV4.Load() {
|
||||
continue
|
||||
}
|
||||
if ipp.Addr().Is6() && de.c.noV6.Load() {
|
||||
continue
|
||||
}
|
||||
candidates = append(candidates, ipp)
|
||||
}
|
||||
// Randomly select an address to use until we retrieve latency information
|
||||
// and give it a short trustBestAddrUntil time so we avoid flapping between
|
||||
// addresses while waiting on latency information to be populated.
|
||||
udpAddr = candidates[rand.Intn(len(candidates))]
|
||||
de.bestAddr.AddrPort = udpAddr
|
||||
if len(candidates) == 1 {
|
||||
// if we only have one address that we can send data too,
|
||||
// we should trust it for a longer period of time.
|
||||
de.trustBestAddrUntil = now.Add(1 * time.Hour)
|
||||
} else {
|
||||
de.trustBestAddrUntil = now.Add(15 * time.Second)
|
||||
}
|
||||
|
||||
return udpAddr, len(candidates) > 1
|
||||
}
|
||||
|
||||
// heartbeat is called every heartbeatInterval to keep the best UDP path alive,
|
||||
@@ -4359,14 +4460,14 @@ func (de *endpoint) heartbeat() {
|
||||
}
|
||||
|
||||
now := mono.Now()
|
||||
udpAddr, _ := de.addrForSendLocked(now)
|
||||
udpAddr, _, _ := de.addrForSendLocked(now)
|
||||
if udpAddr.IsValid() {
|
||||
// We have a preferred path. Ping that every 2 seconds.
|
||||
de.startPingLocked(udpAddr, now, pingHeartbeat)
|
||||
de.startDiscoPingLocked(udpAddr, now, pingHeartbeat)
|
||||
}
|
||||
|
||||
if de.wantFullPingLocked(now) {
|
||||
de.sendPingsLocked(now, true)
|
||||
de.sendDiscoPingsLocked(now, true)
|
||||
}
|
||||
|
||||
de.heartBeatTimer = time.AfterFunc(heartbeatInterval, de.heartbeat)
|
||||
@@ -4417,19 +4518,19 @@ func (de *endpoint) cliPing(res *ipnstate.PingResult, cb func(*ipnstate.PingResu
|
||||
de.pendingCLIPings = append(de.pendingCLIPings, pendingCLIPing{res, cb})
|
||||
|
||||
now := mono.Now()
|
||||
udpAddr, derpAddr := de.addrForSendLocked(now)
|
||||
udpAddr, derpAddr, _ := de.addrForSendLocked(now)
|
||||
if derpAddr.IsValid() {
|
||||
de.startPingLocked(derpAddr, now, pingCLI)
|
||||
de.startDiscoPingLocked(derpAddr, now, pingCLI)
|
||||
}
|
||||
if udpAddr.IsValid() && now.Before(de.trustBestAddrUntil) {
|
||||
// Already have an active session, so just ping the address we're using.
|
||||
// Otherwise "tailscale ping" results to a node on the local network
|
||||
// can look like they're bouncing between, say 10.0.0.0/9 and the peer's
|
||||
// IPv6 address, both 1ms away, and it's random who replies first.
|
||||
de.startPingLocked(udpAddr, now, pingCLI)
|
||||
de.startDiscoPingLocked(udpAddr, now, pingCLI)
|
||||
} else {
|
||||
for ep := range de.endpointState {
|
||||
de.startPingLocked(ep, now, pingCLI)
|
||||
de.startDiscoPingLocked(ep, now, pingCLI)
|
||||
}
|
||||
}
|
||||
de.noteActiveLocked()
|
||||
@@ -4459,9 +4560,14 @@ func (de *endpoint) send(buffs [][]byte) error {
|
||||
}
|
||||
|
||||
now := mono.Now()
|
||||
udpAddr, derpAddr := de.addrForSendLocked(now)
|
||||
if !udpAddr.IsValid() || now.After(de.trustBestAddrUntil) {
|
||||
de.sendPingsLocked(now, true)
|
||||
udpAddr, derpAddr, startWGPing := de.addrForSendLocked(now)
|
||||
|
||||
if de.isWireguardOnly {
|
||||
if startWGPing {
|
||||
de.sendWireGuardOnlyPingsLocked(now)
|
||||
}
|
||||
} else if !udpAddr.IsValid() || now.After(de.trustBestAddrUntil) {
|
||||
de.sendDiscoPingsLocked(now, true)
|
||||
}
|
||||
de.noteActiveLocked()
|
||||
de.mu.Unlock()
|
||||
@@ -4499,7 +4605,7 @@ func (de *endpoint) send(buffs [][]byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (de *endpoint) pingTimeout(txid stun.TxID) {
|
||||
func (de *endpoint) discoPingTimeout(txid stun.TxID) {
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
sp, ok := de.sentPing[txid]
|
||||
@@ -4509,20 +4615,20 @@ func (de *endpoint) pingTimeout(txid stun.TxID) {
|
||||
if debugDisco() || !de.bestAddr.IsValid() || mono.Now().After(de.trustBestAddrUntil) {
|
||||
de.c.dlogf("[v1] magicsock: disco: timeout waiting for pong %x from %v (%v, %v)", txid[:6], sp.to, de.publicKey.ShortString(), de.discoShort())
|
||||
}
|
||||
de.removeSentPingLocked(txid, sp)
|
||||
de.removeSentDiscoPingLocked(txid, sp)
|
||||
}
|
||||
|
||||
// forgetPing is called by a timer when a ping either fails to send or
|
||||
// forgetDiscoPing is called by a timer when a ping either fails to send or
|
||||
// has taken too long to get a pong reply.
|
||||
func (de *endpoint) forgetPing(txid stun.TxID) {
|
||||
func (de *endpoint) forgetDiscoPing(txid stun.TxID) {
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
if sp, ok := de.sentPing[txid]; ok {
|
||||
de.removeSentPingLocked(txid, sp)
|
||||
de.removeSentDiscoPingLocked(txid, sp)
|
||||
}
|
||||
}
|
||||
|
||||
func (de *endpoint) removeSentPingLocked(txid stun.TxID, sp sentPing) {
|
||||
func (de *endpoint) removeSentDiscoPingLocked(txid stun.TxID, sp sentPing) {
|
||||
// Stop the timer for the case where sendPing failed to write to UDP.
|
||||
// In the case of a timer already having fired, this is a no-op:
|
||||
sp.timer.Stop()
|
||||
@@ -4542,7 +4648,7 @@ func (de *endpoint) sendDiscoPing(ep netip.AddrPort, discoKey key.DiscoPublic, t
|
||||
NodeKey: de.c.publicKeyAtomic.Load(),
|
||||
}, logLevel)
|
||||
if !sent {
|
||||
de.forgetPing(txid)
|
||||
de.forgetDiscoPing(txid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4564,7 +4670,7 @@ const (
|
||||
pingCLI
|
||||
)
|
||||
|
||||
func (de *endpoint) startPingLocked(ep netip.AddrPort, now mono.Time, purpose discoPingPurpose) {
|
||||
func (de *endpoint) startDiscoPingLocked(ep netip.AddrPort, now mono.Time, purpose discoPingPurpose) {
|
||||
if runtime.GOOS == "js" {
|
||||
return
|
||||
}
|
||||
@@ -4587,7 +4693,7 @@ func (de *endpoint) startPingLocked(ep netip.AddrPort, now mono.Time, purpose di
|
||||
de.sentPing[txid] = sentPing{
|
||||
to: ep,
|
||||
at: now,
|
||||
timer: time.AfterFunc(pingTimeoutDuration, func() { de.pingTimeout(txid) }),
|
||||
timer: time.AfterFunc(pingTimeoutDuration, func() { de.discoPingTimeout(txid) }),
|
||||
purpose: purpose,
|
||||
}
|
||||
logLevel := discoLog
|
||||
@@ -4597,7 +4703,7 @@ func (de *endpoint) startPingLocked(ep netip.AddrPort, now mono.Time, purpose di
|
||||
go de.sendDiscoPing(ep, epDisco.key, txid, logLevel)
|
||||
}
|
||||
|
||||
func (de *endpoint) sendPingsLocked(now mono.Time, sendCallMeMaybe bool) {
|
||||
func (de *endpoint) sendDiscoPingsLocked(now mono.Time, sendCallMeMaybe bool) {
|
||||
de.lastFullPing = now
|
||||
var sentAny bool
|
||||
for ep, st := range de.endpointState {
|
||||
@@ -4619,7 +4725,7 @@ func (de *endpoint) sendPingsLocked(now mono.Time, sendCallMeMaybe bool) {
|
||||
de.c.dlogf("[v1] magicsock: disco: send, starting discovery for %v (%v)", de.publicKey.ShortString(), de.discoShort())
|
||||
}
|
||||
|
||||
de.startPingLocked(ep, now, pingDiscovery)
|
||||
de.startDiscoPingLocked(ep, now, pingDiscovery)
|
||||
}
|
||||
derpAddr := de.derpAddr
|
||||
if sentAny && sendCallMeMaybe && derpAddr.IsValid() {
|
||||
@@ -4632,9 +4738,99 @@ func (de *endpoint) sendPingsLocked(now mono.Time, sendCallMeMaybe bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// sendWireGuardOnlyPingsLocked evaluates all available addresses for
|
||||
// a WireGuard only endpoint and initates an ICMP ping for useable
|
||||
// addresses.
|
||||
func (de *endpoint) sendWireGuardOnlyPingsLocked(now mono.Time) {
|
||||
if runtime.GOOS == "js" {
|
||||
return
|
||||
}
|
||||
|
||||
// Normally the we only send pings at a low rate as the decision to start
|
||||
// sending a ping sets bestAddrAtUntil with a reasonable time to keep trying
|
||||
// that address, however, if that code changed we may want to be sure that
|
||||
// we don't ever send excessive pings to avoid impact to the client/user.
|
||||
if !now.After(de.lastFullPing.Add(10 * time.Second)) {
|
||||
return
|
||||
}
|
||||
de.lastFullPing = now
|
||||
|
||||
for ipp := range de.endpointState {
|
||||
if ipp.Addr().Is4() && de.c.noV4.Load() {
|
||||
continue
|
||||
}
|
||||
if ipp.Addr().Is6() && de.c.noV6.Load() {
|
||||
continue
|
||||
}
|
||||
|
||||
go de.sendWireGuardOnlyPing(ipp, now)
|
||||
}
|
||||
}
|
||||
|
||||
// getPinger lazily instantiates a pinger and returns it, if it was
|
||||
// already instantiated it returns the existing one.
|
||||
func (c *Conn) getPinger() *ping.Pinger {
|
||||
return c.wgPinger.Get(func() *ping.Pinger {
|
||||
return ping.New(c.connCtx, c.dlogf, netns.Listener(c.logf, c.netMon))
|
||||
})
|
||||
}
|
||||
|
||||
// sendWireGuardOnlyPing sends a ICMP ping to a WireGuard only address to
|
||||
// discover the latency.
|
||||
func (de *endpoint) sendWireGuardOnlyPing(ipp netip.AddrPort, now mono.Time) {
|
||||
ctx, cancel := context.WithTimeout(de.c.connCtx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
de.setLastPing(ipp, now)
|
||||
|
||||
addr := &net.IPAddr{
|
||||
IP: net.IP(ipp.Addr().AsSlice()),
|
||||
Zone: ipp.Addr().Zone(),
|
||||
}
|
||||
|
||||
p := de.c.getPinger()
|
||||
if p == nil {
|
||||
de.c.logf("[v2] magicsock: sendWireGuardOnlyPingLocked: pinger is nil")
|
||||
return
|
||||
}
|
||||
|
||||
latency, err := p.Send(ctx, addr, nil)
|
||||
if err != nil {
|
||||
de.c.logf("[v2] magicsock: sendWireGuardOnlyPingLocked: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
|
||||
state, ok := de.endpointState[ipp]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
state.addPongReplyLocked(pongReply{
|
||||
latency: latency,
|
||||
pongAt: now,
|
||||
from: ipp,
|
||||
pongSrc: netip.AddrPort{}, // We don't know this.
|
||||
})
|
||||
}
|
||||
|
||||
// setLastPing sets lastPing on the endpointState to now.
|
||||
func (de *endpoint) setLastPing(ipp netip.AddrPort, now mono.Time) {
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
state, ok := de.endpointState[ipp]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
state.lastPing = now
|
||||
}
|
||||
|
||||
// updateFromNode updates the endpoint based on a tailcfg.Node from a NetMap
|
||||
// update.
|
||||
func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
|
||||
if n == nil {
|
||||
panic("nil node when updating disco ep")
|
||||
panic("nil node when updating endpoint")
|
||||
}
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
@@ -4642,22 +4838,6 @@ func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
|
||||
de.heartbeatDisabled = heartbeatDisabled
|
||||
de.expired = n.Expired
|
||||
|
||||
// TODO(#7826): add support for more than one endpoint for pure WireGuard
|
||||
// peers, and/or support for probing "bestness" for endpoints.
|
||||
if n.IsWireGuardOnly {
|
||||
for _, ep := range n.Endpoints {
|
||||
ipp, err := netip.ParseAddrPort(ep)
|
||||
if err != nil {
|
||||
de.c.logf("magicsock: invalid endpoint: %s %s", ep, err)
|
||||
continue
|
||||
}
|
||||
de.bestAddr = addrLatency{
|
||||
AddrPort: ipp,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
epDisco := de.disco.Load()
|
||||
var discoKey key.DiscoPublic
|
||||
if epDisco != nil {
|
||||
@@ -4810,7 +4990,7 @@ func (de *endpoint) handlePongConnLocked(m *disco.Pong, di *discoInfo, src netip
|
||||
return false
|
||||
}
|
||||
knownTxID = true // for naked returns below
|
||||
de.removeSentPingLocked(m.TxID, sp)
|
||||
de.removeSentDiscoPingLocked(m.TxID, sp)
|
||||
|
||||
now := mono.Now()
|
||||
latency := now.Sub(sp.at)
|
||||
@@ -4921,6 +5101,28 @@ func betterAddr(a, b addrLatency) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, then both addresses are the same IP type (i.e. both
|
||||
// IPv4 or both IPv6). All decisions below are made solely on latency.
|
||||
//
|
||||
// Determine how much the latencies differ; we ensure the larger
|
||||
// latency is the denominator, so this fraction will always be <= 1.0.
|
||||
var latencyFraction float64
|
||||
if a.latency >= b.latency {
|
||||
latencyFraction = float64(b.latency) / float64(a.latency)
|
||||
} else {
|
||||
latencyFraction = float64(a.latency) / float64(b.latency)
|
||||
}
|
||||
|
||||
// Don't change anything if the latency improvement is less than 1%; we
|
||||
// want a bit of "stickiness" (a.k.a. hysteresis) to avoid flapping if
|
||||
// there's two roughly-equivalent endpoints.
|
||||
if latencyFraction >= 0.99 {
|
||||
return false
|
||||
}
|
||||
|
||||
// The total difference is >1%, so a is better than b if it's
|
||||
// lower-latency.
|
||||
return a.latency < b.latency
|
||||
}
|
||||
|
||||
@@ -5004,7 +5206,7 @@ func (de *endpoint) handleCallMeMaybe(m *disco.CallMeMaybe) {
|
||||
for _, st := range de.endpointState {
|
||||
st.lastPing = 0
|
||||
}
|
||||
de.sendPingsLocked(mono.Now(), false)
|
||||
de.sendDiscoPingsLocked(mono.Now(), false)
|
||||
}
|
||||
|
||||
func (de *endpoint) populatePeerStatus(ps *ipnstate.PeerStatus) {
|
||||
@@ -5021,7 +5223,7 @@ func (de *endpoint) populatePeerStatus(ps *ipnstate.PeerStatus) {
|
||||
ps.LastWrite = de.lastSend.WallTime()
|
||||
ps.Active = now.Sub(de.lastSend) < sessionActiveTimeout
|
||||
|
||||
if udpAddr, derpAddr := de.addrForSendLocked(now); udpAddr.IsValid() && !derpAddr.IsValid() {
|
||||
if udpAddr, derpAddr, _ := de.addrForSendLocked(now); udpAddr.IsValid() && !derpAddr.IsValid() {
|
||||
ps.CurAddr = udpAddr.String()
|
||||
}
|
||||
}
|
||||
@@ -5064,7 +5266,7 @@ func (de *endpoint) resetLocked() {
|
||||
es.lastPing = 0
|
||||
}
|
||||
for txid, sp := range de.sentPing {
|
||||
de.removeSentPingLocked(txid, sp)
|
||||
de.removeSentDiscoPingLocked(txid, sp)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
@@ -33,6 +34,8 @@ import (
|
||||
"go4.org/mem"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"tailscale.com/cmd/testwrapper/flakytest"
|
||||
"tailscale.com/derp"
|
||||
@@ -42,11 +45,13 @@ import (
|
||||
"tailscale.com/net/connstats"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/ping"
|
||||
"tailscale.com/net/stun/stuntest"
|
||||
"tailscale.com/net/tstun"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/tstest/natlab"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/netlogtype"
|
||||
@@ -1633,9 +1638,13 @@ func TestBetterAddr(t *testing.T) {
|
||||
{a: zero, b: zero, want: false},
|
||||
{a: al("10.0.0.2:123", 5*ms), b: zero, want: true},
|
||||
{a: zero, b: al("10.0.0.2:123", 5*ms), want: false},
|
||||
{a: al("10.0.0.2:123", 5*ms), b: al("1.2.3.4:555", 6*ms), want: true},
|
||||
{a: al("10.0.0.2:123", 5*ms), b: al("1.2.3.4:555", 10*ms), want: true},
|
||||
{a: al("10.0.0.2:123", 5*ms), b: al("10.0.0.2:123", 10*ms), want: false}, // same IPPort
|
||||
|
||||
// Don't prefer b to a if it's not substantially better.
|
||||
{a: al("10.0.0.2:123", 100*ms), b: al("1.2.3.4:555", 101*ms), want: false},
|
||||
{a: al("10.0.0.2:123", 100*ms), b: al("1.2.3.4:555", 103*ms), want: true},
|
||||
|
||||
// Prefer IPv6 if roughly equivalent:
|
||||
{
|
||||
a: al("[2001::5]:123", 100*ms),
|
||||
@@ -2113,9 +2122,8 @@ func Test_batchingUDPConn_coalesceMessages(t *testing.T) {
|
||||
}
|
||||
|
||||
// newWireguard starts up a new wireguard-go device attached to a test tun, and
|
||||
// returns the device, tun and netpoint address. To add peers call device.IpcSet
|
||||
// with UAPI instructions.
|
||||
func newWireguard(t *testing.T, uapi string, aips []netip.Prefix) (*device.Device, *tuntest.ChannelTUN, netip.AddrPort) {
|
||||
// returns the device, tun and endpoint port. To add peers call device.IpcSet with UAPI instructions.
|
||||
func newWireguard(t *testing.T, uapi string, aips []netip.Prefix) (*device.Device, *tuntest.ChannelTUN, uint16) {
|
||||
wgtun := tuntest.NewChannelTUN()
|
||||
wglogf := func(f string, args ...any) {
|
||||
t.Logf("wg-go: "+f, args...)
|
||||
@@ -2134,8 +2142,7 @@ func newWireguard(t *testing.T, uapi string, aips []netip.Prefix) (*device.Devic
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var wgEp netip.AddrPort
|
||||
|
||||
var port uint16
|
||||
s, err := wgdev.IpcGet()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2147,17 +2154,16 @@ func newWireguard(t *testing.T, uapi string, aips []netip.Prefix) (*device.Devic
|
||||
}
|
||||
k, v, _ := strings.Cut(line, "=")
|
||||
if k == "listen_port" {
|
||||
wgEp = netip.MustParseAddrPort("127.0.0.1:" + v)
|
||||
p, err := strconv.ParseUint(v, 10, 16)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
port = uint16(p)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !wgEp.IsValid() {
|
||||
t.Fatalf("failed to get endpoint out of wg-go")
|
||||
}
|
||||
t.Logf("wg-go endpoint: %s", wgEp)
|
||||
|
||||
return wgdev, wgtun, wgEp
|
||||
return wgdev, wgtun, port
|
||||
}
|
||||
|
||||
func TestIsWireGuardOnlyPeer(t *testing.T) {
|
||||
@@ -2172,8 +2178,9 @@ func TestIsWireGuardOnlyPeer(t *testing.T) {
|
||||
|
||||
uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n",
|
||||
wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), tsaip.String())
|
||||
wgdev, wgtun, wgEp := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
||||
wgdev, wgtun, port := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
||||
defer wgdev.Close()
|
||||
wgEp := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), port)
|
||||
|
||||
m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey)
|
||||
defer m.Close()
|
||||
@@ -2229,8 +2236,9 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) {
|
||||
|
||||
uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n",
|
||||
wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), masqip.String())
|
||||
wgdev, wgtun, wgEp := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
||||
wgdev, wgtun, port := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
||||
defer wgdev.Close()
|
||||
wgEp := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), port)
|
||||
|
||||
m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey)
|
||||
defer m.Close()
|
||||
@@ -2393,3 +2401,461 @@ func TestEndpointTracker(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applyNetworkMap is a test helper that sets the network map and
|
||||
// configures WG.
|
||||
func applyNetworkMap(t *testing.T, m *magicStack, nm *netmap.NetworkMap) {
|
||||
t.Helper()
|
||||
m.conn.SetNetworkMap(nm)
|
||||
// Make sure we can't use v6 to avoid test failures.
|
||||
m.conn.noV6.Store(true)
|
||||
|
||||
// Turn the network map into a wireguard config (for the tailscale internal wireguard device).
|
||||
cfg, err := nmcfg.WGCfg(nm, t.Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Apply the wireguard config to the tailscale internal wireguard device.
|
||||
if err := m.Reconfig(cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsWireGuardOnlyPickEndpointByPing(t *testing.T) {
|
||||
t.Skip("This test is flaky; see https://github.com/tailscale/tailscale/issues/8037")
|
||||
|
||||
clock := &tstest.Clock{}
|
||||
derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1))
|
||||
defer cleanup()
|
||||
|
||||
// Create a TS client.
|
||||
tskey := key.NewNode()
|
||||
tsaip := netip.MustParsePrefix("100.111.222.111/32")
|
||||
|
||||
// Create a WireGuard only client.
|
||||
wgkey := key.NewNode()
|
||||
wgaip := netip.MustParsePrefix("100.222.111.222/32")
|
||||
|
||||
uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n",
|
||||
wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), tsaip.String())
|
||||
|
||||
wgdev, wgtun, port := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
||||
defer wgdev.Close()
|
||||
wgEp := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), port)
|
||||
wgEp2 := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.2"), port)
|
||||
|
||||
m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey)
|
||||
defer m.Close()
|
||||
|
||||
pr := newPingResponder(t)
|
||||
// Get a destination address which includes a port, so that UDP packets flow
|
||||
// to the correct place, the mockPinger will use this to direct port-less
|
||||
// pings to this place.
|
||||
pingDest := pr.LocalAddr()
|
||||
|
||||
// Create and start the pinger that is used for the
|
||||
// wireguard only endpoint pings
|
||||
p, closeP := mockPinger(t, clock, pingDest)
|
||||
defer closeP()
|
||||
m.conn.wgPinger.Set(p)
|
||||
|
||||
// Create an IPv6 endpoint which should not receive any traffic.
|
||||
v6, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.ParseIP("::"), Port: 0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
badEpRecv := make(chan []byte)
|
||||
go func() {
|
||||
defer v6.Close()
|
||||
for {
|
||||
b := make([]byte, 1500)
|
||||
n, _, err := v6.ReadFrom(b)
|
||||
if err != nil {
|
||||
close(badEpRecv)
|
||||
return
|
||||
}
|
||||
badEpRecv <- b[:n]
|
||||
}
|
||||
}()
|
||||
wgEpV6 := netip.MustParseAddrPort(v6.LocalAddr().String())
|
||||
|
||||
nm := &netmap.NetworkMap{
|
||||
Name: "ts",
|
||||
PrivateKey: m.privateKey,
|
||||
NodeKey: m.privateKey.Public(),
|
||||
Addresses: []netip.Prefix{tsaip},
|
||||
Peers: []*tailcfg.Node{
|
||||
{
|
||||
Key: wgkey.Public(),
|
||||
Endpoints: []string{wgEp.String(), wgEp2.String(), wgEpV6.String()},
|
||||
IsWireGuardOnly: true,
|
||||
Addresses: []netip.Prefix{wgaip},
|
||||
AllowedIPs: []netip.Prefix{wgaip},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
applyNetworkMap(t, m, nm)
|
||||
|
||||
buf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
|
||||
m.tun.Outbound <- buf
|
||||
|
||||
select {
|
||||
case p := <-wgtun.Inbound:
|
||||
if !bytes.Equal(p, buf) {
|
||||
t.Errorf("got unexpected packet: %x", p)
|
||||
}
|
||||
case <-badEpRecv:
|
||||
t.Fatal("got packet on bad endpoint")
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("no packet after 1s")
|
||||
}
|
||||
|
||||
pi, ok := m.conn.peerMap.byNodeKey[wgkey.Public()]
|
||||
if !ok {
|
||||
t.Fatal("wgkey doesn't exist in peer map")
|
||||
}
|
||||
|
||||
// Check that we got a valid address set on the first send - this
|
||||
// will be randomly selected, but because we have noV6 set to true,
|
||||
// it will be the IPv4 address.
|
||||
if !pi.ep.bestAddr.Addr().IsValid() {
|
||||
t.Fatal("bestaddr was nil")
|
||||
}
|
||||
|
||||
if pi.ep.trustBestAddrUntil.Before(mono.Now().Add(14 * time.Second)) {
|
||||
t.Errorf("trustBestAddrUntil time wasn't set to 15 seconds in the future: got %v", pi.ep.trustBestAddrUntil)
|
||||
}
|
||||
|
||||
for ipp, state := range pi.ep.endpointState {
|
||||
if ipp == wgEp {
|
||||
if len(state.recentPongs) != 1 {
|
||||
t.Errorf("IPv4 address did not have a recentPong entry: got %v, want %v", len(state.recentPongs), 1)
|
||||
}
|
||||
// Set the latency extremely low so we choose this endpoint during the next
|
||||
// addrForSendLocked call.
|
||||
state.recentPongs[state.recentPong].latency = time.Nanosecond
|
||||
}
|
||||
|
||||
if ipp == wgEp2 {
|
||||
if len(state.recentPongs) != 1 {
|
||||
t.Errorf("IPv4 address did not have a recentPong entry: got %v, want %v", len(state.recentPongs), 1)
|
||||
}
|
||||
// Set the latency extremely high so we dont choose endpoint during the next
|
||||
// addrForSendLocked call.
|
||||
state.recentPongs[state.recentPong].latency = time.Second
|
||||
}
|
||||
|
||||
if ipp == wgEpV6 && len(state.recentPongs) != 0 {
|
||||
t.Fatal("IPv6 should not have recentPong: IPv6 is not useable")
|
||||
}
|
||||
}
|
||||
|
||||
// Set trustBestAddrUnitl to now, so addrForSendLocked goes through the
|
||||
// latency selection flow.
|
||||
pi.ep.trustBestAddrUntil = mono.Now().Add(-time.Second)
|
||||
|
||||
buf = tuntest.Ping(wgaip.Addr(), tsaip.Addr())
|
||||
m.tun.Outbound <- buf
|
||||
|
||||
select {
|
||||
case p := <-wgtun.Inbound:
|
||||
if !bytes.Equal(p, buf) {
|
||||
t.Errorf("got unexpected packet: %x", p)
|
||||
}
|
||||
case <-badEpRecv:
|
||||
t.Fatal("got packet on bad endpoint")
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("no packet after 1s")
|
||||
}
|
||||
|
||||
// Check that we have responded to a WireGuard only ping twice.
|
||||
if pr.responseCount != 2 {
|
||||
t.Fatal("pingresponder response count was not 2", pr.responseCount)
|
||||
}
|
||||
|
||||
pi, ok = m.conn.peerMap.byNodeKey[wgkey.Public()]
|
||||
if !ok {
|
||||
t.Fatal("wgkey doesn't exist in peer map")
|
||||
}
|
||||
|
||||
if !pi.ep.bestAddr.Addr().IsValid() {
|
||||
t.Error("no bestAddr address was set")
|
||||
}
|
||||
|
||||
if pi.ep.bestAddr.Addr() != wgEp.Addr() {
|
||||
t.Errorf("bestAddr was not set to the expected IPv4 address: got %v, want %v", pi.ep.bestAddr.Addr().String(), wgEp.Addr())
|
||||
}
|
||||
|
||||
if pi.ep.trustBestAddrUntil.IsZero() {
|
||||
t.Fatal("trustBestAddrUntil was not set")
|
||||
}
|
||||
|
||||
if pi.ep.trustBestAddrUntil.Before(mono.Now().Add(55 * time.Minute)) {
|
||||
// Set to 55 minutes incase of sloooow tests.
|
||||
t.Errorf("trustBestAddrUntil time wasn't set to an hour in the future: got %v", pi.ep.trustBestAddrUntil)
|
||||
}
|
||||
}
|
||||
|
||||
// udpingPacketConn will convert potentially ICMP destination addrs to UDP
|
||||
// destination addrs in WriteTo so that a test that is intending to send ICMP
|
||||
// traffic will instead send UDP traffic, without the higher level Pinger being
|
||||
// aware of this difference.
|
||||
type udpingPacketConn struct {
|
||||
net.PacketConn
|
||||
// destPort will be configured by the test to be the peer expected to respond to a ping.
|
||||
destPort uint16
|
||||
}
|
||||
|
||||
func (u *udpingPacketConn) WriteTo(body []byte, dest net.Addr) (int, error) {
|
||||
switch d := dest.(type) {
|
||||
case *net.IPAddr:
|
||||
udpAddr := &net.UDPAddr{
|
||||
IP: d.IP,
|
||||
Port: int(u.destPort),
|
||||
Zone: d.Zone,
|
||||
}
|
||||
return u.PacketConn.WriteTo(body, udpAddr)
|
||||
}
|
||||
return 0, fmt.Errorf("unimplemented udpingPacketConn for %T", dest)
|
||||
}
|
||||
|
||||
type mockListenPacketer struct {
|
||||
conn4 net.PacketConn
|
||||
conn6 net.PacketConn
|
||||
}
|
||||
|
||||
func (mlp *mockListenPacketer) ListenPacket(ctx context.Context, typ string, addr string) (net.PacketConn, error) {
|
||||
switch typ {
|
||||
case "ip4:icmp":
|
||||
return mlp.conn4, nil
|
||||
case "ip6:icmp":
|
||||
return mlp.conn6, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unimplemented ListenPacketForTesting for %s", typ)
|
||||
}
|
||||
|
||||
func mockPinger(t *testing.T, clock *tstest.Clock, dest net.Addr) (*ping.Pinger, func()) {
|
||||
ctx := context.Background()
|
||||
|
||||
dIPP := netip.MustParseAddrPort(dest.String())
|
||||
// In tests, we use UDP so that we can test without being root; this
|
||||
// doesn't matter because we mock out the ICMP reply below to be a real
|
||||
// ICMP echo reply packet.
|
||||
conn4, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.ListenPacket: %v", err)
|
||||
}
|
||||
conn6, err := net.ListenPacket("udp6", "[::]:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.ListenPacket: %v", err)
|
||||
}
|
||||
|
||||
conn4 = &udpingPacketConn{
|
||||
PacketConn: conn4,
|
||||
destPort: dIPP.Port(),
|
||||
}
|
||||
|
||||
conn6 = &udpingPacketConn{
|
||||
PacketConn: conn6,
|
||||
destPort: dIPP.Port(),
|
||||
}
|
||||
|
||||
p := ping.New(ctx, t.Logf, &mockListenPacketer{conn4: conn4, conn6: conn6})
|
||||
|
||||
done := func() {
|
||||
if err := p.Close(); err != nil {
|
||||
t.Errorf("error on close: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return p, done
|
||||
}
|
||||
|
||||
type pingResponder struct {
|
||||
net.PacketConn
|
||||
running atomic.Bool
|
||||
responseCount int
|
||||
}
|
||||
|
||||
func (p *pingResponder) start() {
|
||||
buf := make([]byte, 1500)
|
||||
for p.running.Load() {
|
||||
n, addr, err := p.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m, err := icmp.ParseMessage(1, buf[:n])
|
||||
if err != nil {
|
||||
panic("got a non-ICMP message:" + fmt.Sprintf("%x", m))
|
||||
}
|
||||
|
||||
r := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEchoReply,
|
||||
Code: m.Code,
|
||||
Body: m.Body,
|
||||
}
|
||||
|
||||
b, err := r.Marshal(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := p.PacketConn.WriteTo(b, addr); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.responseCount++
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pingResponder) stop() {
|
||||
p.running.Store(false)
|
||||
p.Close()
|
||||
}
|
||||
|
||||
func newPingResponder(t *testing.T) *pingResponder {
|
||||
t.Helper()
|
||||
// global binds should be both IPv4 and IPv6 (if our test platforms don't,
|
||||
// we might need to bind two sockets instead)
|
||||
conn, err := net.ListenPacket("udp", ":")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pr := &pingResponder{PacketConn: conn}
|
||||
pr.running.Store(true)
|
||||
go pr.start()
|
||||
t.Cleanup(pr.stop)
|
||||
return pr
|
||||
}
|
||||
|
||||
func TestAddrForSendLockedForWireGuardOnly(t *testing.T) {
|
||||
testTime := mono.Now()
|
||||
|
||||
type endpointDetails struct {
|
||||
addrPort netip.AddrPort
|
||||
latency time.Duration
|
||||
}
|
||||
|
||||
wgTests := []struct {
|
||||
name string
|
||||
noV4 bool
|
||||
noV6 bool
|
||||
sendWGPing bool
|
||||
ep []endpointDetails
|
||||
want netip.AddrPort
|
||||
}{
|
||||
{
|
||||
name: "choose lowest latency for useable IPv4 and IPv6",
|
||||
sendWGPing: true,
|
||||
ep: []endpointDetails{
|
||||
{
|
||||
addrPort: netip.MustParseAddrPort("1.1.1.1:111"),
|
||||
latency: 100 * time.Millisecond,
|
||||
},
|
||||
{
|
||||
addrPort: netip.MustParseAddrPort("[2345:0425:2CA1:0000:0000:0567:5673:23b5]:222"),
|
||||
latency: 10 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
want: netip.MustParseAddrPort("[2345:0425:2CA1:0000:0000:0567:5673:23b5]:222"),
|
||||
},
|
||||
{
|
||||
name: "choose IPv4 when IPv6 is not useable",
|
||||
sendWGPing: false,
|
||||
noV6: true,
|
||||
ep: []endpointDetails{
|
||||
{
|
||||
addrPort: netip.MustParseAddrPort("1.1.1.1:111"),
|
||||
latency: 100 * time.Millisecond,
|
||||
},
|
||||
{
|
||||
addrPort: netip.MustParseAddrPort("[1::1]:567"),
|
||||
},
|
||||
},
|
||||
want: netip.MustParseAddrPort("1.1.1.1:111"),
|
||||
},
|
||||
{
|
||||
name: "choose IPv6 when IPv4 is not useable",
|
||||
sendWGPing: false,
|
||||
noV4: true,
|
||||
ep: []endpointDetails{
|
||||
{
|
||||
addrPort: netip.MustParseAddrPort("1.1.1.1:111"),
|
||||
},
|
||||
{
|
||||
addrPort: netip.MustParseAddrPort("[1::1]:567"),
|
||||
latency: 100 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
want: netip.MustParseAddrPort("[1::1]:567"),
|
||||
},
|
||||
{
|
||||
name: "choose IPv6 address when latency is the same for v4 and v6",
|
||||
sendWGPing: true,
|
||||
ep: []endpointDetails{
|
||||
{
|
||||
addrPort: netip.MustParseAddrPort("1.1.1.1:111"),
|
||||
latency: 100 * time.Millisecond,
|
||||
},
|
||||
{
|
||||
addrPort: netip.MustParseAddrPort("[1::1]:567"),
|
||||
latency: 100 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
want: netip.MustParseAddrPort("[1::1]:567"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range wgTests {
|
||||
endpoint := &endpoint{
|
||||
isWireguardOnly: true,
|
||||
endpointState: map[netip.AddrPort]*endpointState{},
|
||||
c: &Conn{
|
||||
noV4: atomic.Bool{},
|
||||
noV6: atomic.Bool{},
|
||||
},
|
||||
}
|
||||
endpoint.c.noV4.Store(test.noV4)
|
||||
endpoint.c.noV6.Store(test.noV6)
|
||||
|
||||
for _, epd := range test.ep {
|
||||
endpoint.endpointState[epd.addrPort] = &endpointState{}
|
||||
}
|
||||
|
||||
udpAddr, _, shouldPing := endpoint.addrForSendLocked(testTime)
|
||||
if !udpAddr.IsValid() {
|
||||
t.Error("udpAddr returned is not valid")
|
||||
}
|
||||
if shouldPing != test.sendWGPing {
|
||||
t.Errorf("addrForSendLocked did not indiciate correct ping state; got %v, want %v", shouldPing, test.sendWGPing)
|
||||
}
|
||||
|
||||
for _, epd := range test.ep {
|
||||
state, ok := endpoint.endpointState[epd.addrPort]
|
||||
if !ok {
|
||||
t.Errorf("addr does not exist in endpoint state map")
|
||||
}
|
||||
|
||||
latency, ok := state.latencyLocked()
|
||||
if ok {
|
||||
t.Errorf("latency was set for %v: %v", epd.addrPort, latency)
|
||||
}
|
||||
state.recentPongs = append(state.recentPongs, pongReply{
|
||||
latency: epd.latency,
|
||||
})
|
||||
state.recentPong = 0
|
||||
}
|
||||
|
||||
udpAddr, _, shouldPing = endpoint.addrForSendLocked(testTime.Add(2 * time.Minute))
|
||||
if udpAddr != test.want {
|
||||
t.Errorf("udpAddr returned is not expected: got %v, want %v", udpAddr, test.want)
|
||||
}
|
||||
if shouldPing {
|
||||
t.Error("addrForSendLocked should not indicate ping is required")
|
||||
}
|
||||
if endpoint.bestAddr.AddrPort != test.want {
|
||||
t.Errorf("bestAddr.AddrPort is not as expected: got %v, want %v", endpoint.bestAddr.AddrPort, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +379,6 @@ cusimanse
|
||||
polecat
|
||||
degu
|
||||
coatimundi
|
||||
stringray
|
||||
diplodocus
|
||||
stegosaurus
|
||||
zuul
|
||||
|
||||
Reference in New Issue
Block a user