Compare commits
29 Commits
mihaip/js-
...
bradfitz/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
698defd54b | ||
|
|
c378a9900c | ||
|
|
4aa88bc2c0 | ||
|
|
dfcef3382e | ||
|
|
9e5954c598 | ||
|
|
8cfd775885 | ||
|
|
c13fab2a67 | ||
|
|
4001d0bf25 | ||
|
|
8d45d7e312 | ||
|
|
be5eadbecc | ||
|
|
95d43c54bf | ||
|
|
26f103473c | ||
|
|
adc5ffea99 | ||
|
|
5f6abcfa6f | ||
|
|
7c7e23d87a | ||
|
|
52d769d35c | ||
|
|
f04bc31820 | ||
|
|
9a2171e4ea | ||
|
|
8725b14056 | ||
|
|
eb32847d85 | ||
|
|
9dfcdbf478 | ||
|
|
e846481731 | ||
|
|
02a765743e | ||
|
|
e1309e1323 | ||
|
|
fb82299f5a | ||
|
|
116f55ff66 | ||
|
|
a029989aff | ||
|
|
57275a4912 | ||
|
|
a794963e2f |
2
.github/workflows/cross-darwin.yml
vendored
2
.github/workflows/cross-darwin.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
2
.github/workflows/cross-freebsd.yml
vendored
2
.github/workflows/cross-freebsd.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
2
.github/workflows/cross-openbsd.yml
vendored
2
.github/workflows/cross-openbsd.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
6
.github/workflows/cross-wasm.yml
vendored
6
.github/workflows/cross-wasm.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@@ -29,12 +29,12 @@ jobs:
|
||||
env:
|
||||
GOOS: js
|
||||
GOARCH: wasm
|
||||
run: go build ./cmd/tsconnect/wasm
|
||||
run: go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli
|
||||
|
||||
- name: tsconnect static build
|
||||
# Use our custom Go toolchain, we set build tags (to control binary size)
|
||||
# that depend on it.
|
||||
run: ./tool/go run ./cmd/tsconnect build
|
||||
run: ./tool/go run ./cmd/tsconnect --fast-compression build
|
||||
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/cross-windows.yml
vendored
2
.github/workflows/cross-windows.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
2
.github/workflows/depaware.yml
vendored
2
.github/workflows/depaware.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
2
.github/workflows/go_generate.yml
vendored
2
.github/workflows/go_generate.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
2
.github/workflows/license.yml
vendored
2
.github/workflows/license.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
2
.github/workflows/linux-race.yml
vendored
2
.github/workflows/linux-race.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
2
.github/workflows/linux32.yml
vendored
2
.github/workflows/linux32.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
2
.github/workflows/staticcheck.yml
vendored
2
.github/workflows/staticcheck.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
2
.github/workflows/vm.yml
vendored
2
.github/workflows/vm.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
2
.github/workflows/windows-race.yml
vendored
2
.github/workflows/windows-race.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
2
Makefile
2
Makefile
@@ -9,7 +9,7 @@ vet:
|
||||
./tool/go vet ./...
|
||||
|
||||
tidy:
|
||||
./tool/go mod tidy -compat=1.17
|
||||
./tool/go mod tidy
|
||||
|
||||
updatedeps:
|
||||
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscaled
|
||||
|
||||
@@ -43,10 +43,7 @@ If your distro has conventions that preclude the use of
|
||||
`build_dist.sh`, please do the equivalent of what it does in your
|
||||
distro's way, so that bug reports contain useful version information.
|
||||
|
||||
We only guarantee to support the latest Go release and any Go beta or
|
||||
release candidate builds (currently Go 1.18) in module mode. It might
|
||||
work in earlier Go versions or in GOPATH mode, but we're making no
|
||||
effort to keep those working.
|
||||
We require the latest Go release, currently Go 1.19.
|
||||
|
||||
## Bugs
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package tailscale
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package tailscale
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package tailscale
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package tailscale
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
//go:build !go1.19
|
||||
// +build !go1.19
|
||||
|
||||
package tailscale
|
||||
|
||||
func init() {
|
||||
you_need_Go_1_18_to_compile_Tailscale()
|
||||
you_need_Go_1_19_to_compile_Tailscale()
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package tailscale
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package tailscale
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
// Package tailscale contains Go clients for the Tailscale Local API and
|
||||
// Tailscale control plane API.
|
||||
|
||||
@@ -153,18 +153,25 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
|
||||
}
|
||||
writef("}")
|
||||
case *types.Map:
|
||||
elem := ft.Elem()
|
||||
writef("if dst.%s != nil {", fname)
|
||||
writef("\tdst.%s = map[%s]%s{}", fname, it.QualifiedName(ft.Key()), it.QualifiedName(ft.Elem()))
|
||||
if sliceType, isSlice := ft.Elem().(*types.Slice); isSlice {
|
||||
writef("\tdst.%s = map[%s]%s{}", fname, it.QualifiedName(ft.Key()), it.QualifiedName(elem))
|
||||
if sliceType, isSlice := elem.(*types.Slice); isSlice {
|
||||
n := it.QualifiedName(sliceType.Elem())
|
||||
writef("\tfor k := range src.%s {", fname)
|
||||
// use zero-length slice instead of nil to ensure
|
||||
// the key is always copied.
|
||||
writef("\t\tdst.%s[k] = append([]%s{}, src.%s[k]...)", fname, n, fname)
|
||||
writef("\t}")
|
||||
} else if codegen.ContainsPointers(ft.Elem()) {
|
||||
} else if codegen.ContainsPointers(elem) {
|
||||
writef("\tfor k, v := range src.%s {", fname)
|
||||
writef("\t\tdst.%s[k] = v.Clone()", fname)
|
||||
switch elem.(type) {
|
||||
case *types.Pointer:
|
||||
writef("\t\tdst.%s[k] = v.Clone()", fname)
|
||||
default:
|
||||
writef("\t\tv2 := v.Clone()")
|
||||
writef("\t\tdst.%s[k] = *v2", fname)
|
||||
}
|
||||
writef("\t}")
|
||||
} else {
|
||||
writef("\tfor k, v := range src.%s {", fname)
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
//
|
||||
// Use this Grafana configuration to enable the auth proxy:
|
||||
//
|
||||
// [auth.proxy]
|
||||
// enabled = true
|
||||
// header_name = X-WEBAUTH-USER
|
||||
// header_property = username
|
||||
// auto_sign_up = true
|
||||
// whitelist = 127.0.0.1
|
||||
// headers = Name:X-WEBAUTH-NAME
|
||||
// enable_login_token = true
|
||||
// [auth.proxy]
|
||||
// enabled = true
|
||||
// header_name = X-WEBAUTH-USER
|
||||
// header_property = username
|
||||
// auto_sign_up = true
|
||||
// whitelist = 127.0.0.1
|
||||
// headers = Name:X-WEBAUTH-NAME
|
||||
// enable_login_token = true
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@@ -7,7 +7,6 @@ package cli
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
)
|
||||
@@ -32,6 +31,6 @@ func runBugReport(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(logMarker)
|
||||
outln(logMarker)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ var certCmd = &ffcli.Command{
|
||||
ShortHelp: "get TLS certs",
|
||||
ShortUsage: "cert [flags] <domain>",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("cert", flag.ExitOnError)
|
||||
fs := newFlagSet("cert")
|
||||
fs.StringVar(&certArgs.certFile, "cert-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.crt if --cert-file and --key-file are both unset")
|
||||
fs.StringVar(&certArgs.keyFile, "key-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.key if --cert-file and --key-file are both unset")
|
||||
fs.BoolVar(&certArgs.serve, "serve-demo", false, "if true, serve on port :443 using the cert as a demo, instead of writing out the files to disk")
|
||||
@@ -79,7 +79,7 @@ func runCert(ctx context.Context, args []string) error {
|
||||
domain := args[0]
|
||||
|
||||
printf := func(format string, a ...any) {
|
||||
fmt.Printf(format, a...)
|
||||
printf(format, a...)
|
||||
}
|
||||
if certArgs.certFile == "-" || certArgs.keyFile == "-" {
|
||||
printf = log.Printf
|
||||
@@ -138,7 +138,7 @@ func runCert(ctx context.Context, args []string) error {
|
||||
|
||||
func writeIfChanged(filename string, contents []byte, mode os.FileMode) (changed bool, err error) {
|
||||
if filename == "-" {
|
||||
os.Stdout.Write(contents)
|
||||
Stdout.Write(contents)
|
||||
return false, nil
|
||||
}
|
||||
if old, err := os.ReadFile(filename); err == nil && bytes.Equal(contents, old) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
|
||||
@@ -29,10 +30,25 @@ import (
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
var Stderr io.Writer = os.Stderr
|
||||
var Stdout io.Writer = os.Stdout
|
||||
|
||||
func printf(format string, a ...any) {
|
||||
fmt.Fprintf(Stdout, format, a...)
|
||||
}
|
||||
|
||||
// outln is like fmt.Println in the common case, except when Stdout is
|
||||
// changed (as in js/wasm).
|
||||
//
|
||||
// It's not named println because that looks like the Go built-in
|
||||
// which goes to stderr and formats slightly differently.
|
||||
func outln(a ...any) {
|
||||
fmt.Fprintln(Stdout, a...)
|
||||
}
|
||||
|
||||
// ActLikeCLI reports whether a GUI application should act like the
|
||||
// CLI based on os.Args, GOOS, the context the process is running in
|
||||
// (pty, parent PID), etc.
|
||||
@@ -79,6 +95,16 @@ func ActLikeCLI() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func newFlagSet(name string) *flag.FlagSet {
|
||||
onError := flag.ExitOnError
|
||||
if runtime.GOOS == "js" {
|
||||
onError = flag.ContinueOnError
|
||||
}
|
||||
fs := flag.NewFlagSet(name, onError)
|
||||
fs.SetOutput(Stderr)
|
||||
return fs
|
||||
}
|
||||
|
||||
// CleanUpArgs rewrites command line arguments for simplicity and backwards compatibility.
|
||||
// In particular, it rewrites --authkey to --auth-key.
|
||||
func CleanUpArgs(args []string) []string {
|
||||
@@ -112,11 +138,11 @@ func Run(args []string) (err error) {
|
||||
var warnOnce sync.Once
|
||||
tailscale.SetVersionMismatchHandler(func(clientVer, serverVer string) {
|
||||
warnOnce.Do(func() {
|
||||
fmt.Fprintf(os.Stderr, "Warning: client version %q != tailscaled server version %q\n", clientVer, serverVer)
|
||||
fmt.Fprintf(Stderr, "Warning: client version %q != tailscaled server version %q\n", clientVer, serverVer)
|
||||
})
|
||||
})
|
||||
|
||||
rootfs := flag.NewFlagSet("tailscale", flag.ExitOnError)
|
||||
rootfs := newFlagSet("tailscale")
|
||||
rootfs.StringVar(&rootArgs.socket, "socket", paths.DefaultTailscaledSocket(), "path to tailscaled's unix socket")
|
||||
|
||||
rootCmd := &ffcli.Command{
|
||||
@@ -204,7 +230,7 @@ var rootArgs struct {
|
||||
socket string
|
||||
}
|
||||
|
||||
var gotSignal syncs.AtomicBool
|
||||
var gotSignal atomic.Bool
|
||||
|
||||
func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context, context.CancelFunc) {
|
||||
s := safesocket.DefaultConnectionStrategy(rootArgs.socket)
|
||||
|
||||
@@ -31,7 +31,7 @@ permission to use it.
|
||||
See: https://tailscale.com/kb/1152/synology-outbound/
|
||||
`),
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("configure-host", flag.ExitOnError)
|
||||
fs := newFlagSet("configure-host")
|
||||
return fs
|
||||
})(),
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func runConfigureHost(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
if isDSM6 {
|
||||
fmt.Printf("/dev/net/tun exists and has permissions 0666. Skipping setcap on DSM6.\n")
|
||||
printf("/dev/net/tun exists and has permissions 0666. Skipping setcap on DSM6.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -80,6 +80,6 @@ func runConfigureHost(ctx context.Context, args []string) error {
|
||||
if out, err := exec.Command("/bin/setcap", "cap_net_admin,cap_net_raw+eip", daemonBin).CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("setcap: %v, %s", err, out)
|
||||
}
|
||||
fmt.Printf("Done. To restart Tailscale to use the new permissions, run:\n\n sudo synosystemctl restart pkgctl-Tailscale.service\n\n")
|
||||
printf("Done. To restart Tailscale to use the new permissions, run:\n\n sudo synosystemctl restart pkgctl-Tailscale.service\n\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ var debugCmd = &ffcli.Command{
|
||||
Exec: runDebug,
|
||||
LongHelp: `"tailscale debug" contains misc debug facilities; it is not a stable interface.`,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("debug", flag.ExitOnError)
|
||||
fs := newFlagSet("debug")
|
||||
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
|
||||
fs.StringVar(&debugArgs.cpuFile, "cpu-profile", "", "if non-empty, grab a CPU profile for --profile-sec seconds and write it to this file; - for stdout")
|
||||
fs.StringVar(&debugArgs.memFile, "mem-profile", "", "if non-empty, grab a memory profile and write it to this file; - for stdout")
|
||||
@@ -63,7 +63,7 @@ var debugCmd = &ffcli.Command{
|
||||
Exec: runDaemonMetrics,
|
||||
ShortHelp: "print tailscaled's metrics",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("metrics", flag.ExitOnError)
|
||||
fs := newFlagSet("metrics")
|
||||
fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values")
|
||||
return fs
|
||||
})(),
|
||||
@@ -103,7 +103,7 @@ var debugCmd = &ffcli.Command{
|
||||
Exec: runPrefs,
|
||||
ShortHelp: "print prefs",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("prefs", flag.ExitOnError)
|
||||
fs := newFlagSet("prefs")
|
||||
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
|
||||
return fs
|
||||
})(),
|
||||
@@ -113,7 +113,7 @@ var debugCmd = &ffcli.Command{
|
||||
Exec: runWatchIPN,
|
||||
ShortHelp: "subscribe to IPN message bus",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("watch-ipn", flag.ExitOnError)
|
||||
fs := newFlagSet("watch-ipn")
|
||||
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
|
||||
return fs
|
||||
})(),
|
||||
@@ -128,7 +128,7 @@ var debugCmd = &ffcli.Command{
|
||||
Exec: runTS2021,
|
||||
ShortHelp: "debug ts2021 protocol connectivity",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("ts2021", flag.ExitOnError)
|
||||
fs := newFlagSet("ts2021")
|
||||
fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane")
|
||||
fs.IntVar(&ts2021Args.version, "version", int(tailcfg.CurrentCapabilityVersion), "protocol version")
|
||||
return fs
|
||||
@@ -146,7 +146,7 @@ var debugArgs struct {
|
||||
|
||||
func writeProfile(dst string, v []byte) error {
|
||||
if dst == "-" {
|
||||
_, err := os.Stdout.Write(v)
|
||||
_, err := Stdout.Write(v)
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(dst, v, 0600)
|
||||
@@ -198,7 +198,7 @@ func runDebug(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
fatalf("%v\n", err)
|
||||
}
|
||||
e := json.NewEncoder(os.Stdout)
|
||||
e := json.NewEncoder(Stdout)
|
||||
e.SetIndent("", "\t")
|
||||
e.Encode(wfs)
|
||||
return nil
|
||||
@@ -212,7 +212,7 @@ func runDebug(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
log.Printf("Size: %v\n", size)
|
||||
io.Copy(os.Stdout, rc)
|
||||
io.Copy(Stdout, rc)
|
||||
return nil
|
||||
}
|
||||
if usedFlag {
|
||||
@@ -226,14 +226,14 @@ func runDebug(ctx context.Context, args []string) error {
|
||||
func runLocalCreds(ctx context.Context, args []string) error {
|
||||
port, token, err := safesocket.LocalTCPPortAndToken()
|
||||
if err == nil {
|
||||
fmt.Printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
|
||||
printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
|
||||
return nil
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
fmt.Printf("curl http://localhost:%v/localapi/v0/status\n", safesocket.WindowsLocalPort)
|
||||
printf("curl http://localhost:%v/localapi/v0/status\n", safesocket.WindowsLocalPort)
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
|
||||
printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -247,10 +247,10 @@ func runPrefs(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
if prefsArgs.pretty {
|
||||
fmt.Println(prefs.Pretty())
|
||||
outln(prefs.Pretty())
|
||||
} else {
|
||||
j, _ := json.MarshalIndent(prefs, "", "\t")
|
||||
fmt.Println(string(j))
|
||||
outln(string(j))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -268,7 +268,7 @@ func runWatchIPN(ctx context.Context, args []string) error {
|
||||
n.NetMap = nil
|
||||
}
|
||||
j, _ := json.MarshalIndent(n, "", "\t")
|
||||
fmt.Printf("%s\n", j)
|
||||
printf("%s\n", j)
|
||||
})
|
||||
bc.RequestEngineStatus()
|
||||
pump(ctx, bc, c)
|
||||
@@ -282,7 +282,7 @@ func runDERPMap(ctx context.Context, args []string) error {
|
||||
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
|
||||
)
|
||||
}
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc := json.NewEncoder(Stdout)
|
||||
enc.SetIndent("", "\t")
|
||||
enc.Encode(dm)
|
||||
return nil
|
||||
@@ -299,7 +299,7 @@ func localAPIAction(action string) func(context.Context, []string) error {
|
||||
|
||||
func runEnv(ctx context.Context, args []string) error {
|
||||
for _, e := range os.Environ() {
|
||||
fmt.Println(e)
|
||||
outln(e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -308,18 +308,18 @@ func runStat(ctx context.Context, args []string) error {
|
||||
for _, a := range args {
|
||||
fi, err := os.Lstat(a)
|
||||
if err != nil {
|
||||
fmt.Printf("%s: %v\n", a, err)
|
||||
printf("%s: %v\n", a, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%s: %v, %v\n", a, fi.Mode(), fi.Size())
|
||||
printf("%s: %v, %v\n", a, fi.Mode(), fi.Size())
|
||||
if fi.IsDir() {
|
||||
ents, _ := os.ReadDir(a)
|
||||
for i, ent := range ents {
|
||||
if i == 25 {
|
||||
fmt.Printf(" ...\n")
|
||||
printf(" ...\n")
|
||||
break
|
||||
}
|
||||
fmt.Printf(" - %s\n", ent.Name())
|
||||
printf(" - %s\n", ent.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,7 +338,7 @@ func runDaemonGoroutines(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.Write(goroutines)
|
||||
Stdout.Write(goroutines)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ func runDaemonMetrics(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
if !metricsArgs.watch {
|
||||
os.Stdout.Write(out)
|
||||
Stdout.Write(out)
|
||||
return nil
|
||||
}
|
||||
bs := bufio.NewScanner(bytes.NewReader(out))
|
||||
@@ -391,9 +391,9 @@ func runDaemonMetrics(ctx context.Context, args []string) error {
|
||||
if len(changes) > 0 {
|
||||
format := fmt.Sprintf("%%-%ds %%+5d => %%v\n", maxNameLen)
|
||||
for _, c := range changes {
|
||||
fmt.Fprintf(os.Stdout, format, c.name, c.to-c.from, c.to)
|
||||
fmt.Fprintf(Stdout, format, c.name, c.to-c.from, c.to)
|
||||
}
|
||||
io.WriteString(os.Stdout, "\n")
|
||||
io.WriteString(Stdout, "\n")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
@@ -420,7 +420,7 @@ func runVia(ctx context.Context, args []string) error {
|
||||
v4 := tsaddr.UnmapVia(ipp.Addr())
|
||||
a := ipp.Addr().As16()
|
||||
siteID := binary.BigEndian.Uint32(a[8:12])
|
||||
fmt.Printf("site %v (0x%x), %v\n", siteID, siteID, netip.PrefixFrom(v4, ipp.Bits()-96))
|
||||
printf("site %v (0x%x), %v\n", siteID, siteID, netip.PrefixFrom(v4, ipp.Bits()-96))
|
||||
case 2:
|
||||
siteID, err := strconv.ParseUint(args[0], 0, 32)
|
||||
if err != nil {
|
||||
@@ -437,7 +437,7 @@ func runVia(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(via)
|
||||
outln(via)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/ipn"
|
||||
@@ -24,7 +23,7 @@ var downCmd = &ffcli.Command{
|
||||
}
|
||||
|
||||
func newDownFlagSet() *flag.FlagSet {
|
||||
downf := flag.NewFlagSet("down", flag.ExitOnError)
|
||||
downf := newFlagSet("down")
|
||||
registerAcceptRiskFlag(downf)
|
||||
return downf
|
||||
}
|
||||
@@ -45,7 +44,7 @@ func runDown(ctx context.Context, args []string) error {
|
||||
return fmt.Errorf("error fetching current status: %w", err)
|
||||
}
|
||||
if st.BackendState == "Stopped" {
|
||||
fmt.Fprintf(os.Stderr, "Tailscale was already stopped.\n")
|
||||
fmt.Fprintf(Stderr, "Tailscale was already stopped.\n")
|
||||
return nil
|
||||
}
|
||||
_, err = localClient.EditPrefs(ctx, &ipn.MaskedPrefs{
|
||||
|
||||
@@ -54,7 +54,7 @@ var fileCpCmd = &ffcli.Command{
|
||||
ShortHelp: "Copy file(s) to a host",
|
||||
Exec: runCp,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("cp", flag.ExitOnError)
|
||||
fs := newFlagSet("cp")
|
||||
fs.StringVar(&cpArgs.name, "name", "", "alternate filename to use, especially useful when <file> is \"-\" (stdin)")
|
||||
fs.BoolVar(&cpArgs.verbose, "verbose", false, "verbose output")
|
||||
fs.BoolVar(&cpArgs.targets, "targets", false, "list possible file cp targets")
|
||||
@@ -100,7 +100,7 @@ func runCp(ctx context.Context, args []string) error {
|
||||
return fmt.Errorf("can't send to %s: %v", target, err)
|
||||
}
|
||||
if isOffline {
|
||||
fmt.Fprintf(os.Stderr, "# warning: %s is offline\n", target)
|
||||
fmt.Fprintf(Stderr, "# warning: %s is offline\n", target)
|
||||
}
|
||||
|
||||
if len(files) > 1 {
|
||||
@@ -281,7 +281,7 @@ func runCpTargets(ctx context.Context, args []string) error {
|
||||
if detail != "" {
|
||||
detail = "\t" + detail
|
||||
}
|
||||
fmt.Printf("%s\t%s%s\n", n.Addresses[0].Addr(), n.ComputedName, detail)
|
||||
printf("%s\t%s%s\n", n.Addresses[0].Addr(), n.ComputedName, detail)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -315,7 +315,7 @@ var fileGetCmd = &ffcli.Command{
|
||||
ShortHelp: "Move files out of the Tailscale file inbox",
|
||||
Exec: runFileGet,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("get", flag.ExitOnError)
|
||||
fs := newFlagSet("get")
|
||||
fs.BoolVar(&getArgs.wait, "wait", false, "wait for a file to arrive if inbox is empty")
|
||||
fs.BoolVar(&getArgs.loop, "loop", false, "run get in a loop, receiving files as they come in")
|
||||
fs.BoolVar(&getArgs.verbose, "verbose", false, "verbose output")
|
||||
@@ -415,7 +415,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
|
||||
break
|
||||
}
|
||||
if getArgs.verbose {
|
||||
fmt.Printf("waiting for file...")
|
||||
printf("waiting for file...")
|
||||
}
|
||||
if err := waitForFile(ctx); err != nil {
|
||||
errs = append(errs, err)
|
||||
@@ -436,7 +436,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
|
||||
continue
|
||||
}
|
||||
if getArgs.verbose {
|
||||
fmt.Printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
|
||||
printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
|
||||
}
|
||||
if err = localClient.DeleteWaitingFile(ctx, wf.Name); err != nil {
|
||||
errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err))
|
||||
@@ -448,7 +448,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
|
||||
// persistently stuck files are basically an error
|
||||
errs = append(errs, fmt.Errorf("moved %d/%d files", deleted, len(wfs)))
|
||||
} else if getArgs.verbose {
|
||||
fmt.Printf("moved %d/%d files\n", deleted, len(wfs))
|
||||
printf("moved %d/%d files\n", deleted, len(wfs))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
@@ -471,7 +471,7 @@ func runFileGet(ctx context.Context, args []string) error {
|
||||
for {
|
||||
errs := runFileGetOneBatch(ctx, dir)
|
||||
for _, err := range errs {
|
||||
fmt.Println(err)
|
||||
outln(err)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
// It's possible whatever caused the error(s) (e.g. conflicting target file,
|
||||
@@ -493,7 +493,7 @@ func runFileGet(ctx context.Context, args []string) error {
|
||||
return nil
|
||||
}
|
||||
for _, err := range errs[:len(errs)-1] {
|
||||
fmt.Println(err)
|
||||
outln(err)
|
||||
}
|
||||
return errs[len(errs)-1]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package cli
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
)
|
||||
@@ -29,6 +28,6 @@ func runIDToken(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(tr.IDToken)
|
||||
outln(tr.IDToken)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ var ipCmd = &ffcli.Command{
|
||||
LongHelp: "Show Tailscale IP addresses for peer. Peer defaults to the current machine.",
|
||||
Exec: runIP,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("ip", flag.ExitOnError)
|
||||
fs := newFlagSet("ip")
|
||||
fs.BoolVar(&ipArgs.want1, "1", false, "only print one IP address")
|
||||
fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address")
|
||||
fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address")
|
||||
@@ -85,7 +85,7 @@ func runIP(ctx context.Context, args []string) error {
|
||||
for _, ip := range ips {
|
||||
if ip.Is4() && v4 || ip.Is6() && v6 {
|
||||
match = true
|
||||
fmt.Println(ip)
|
||||
outln(ip)
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
|
||||
@@ -29,7 +29,7 @@ func runNC(ctx context.Context, args []string) error {
|
||||
}
|
||||
description, ok := isRunningOrStarting(st)
|
||||
if !ok {
|
||||
fmt.Printf("%s\n", description)
|
||||
printf("%s\n", description)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -33,7 +32,7 @@ var netcheckCmd = &ffcli.Command{
|
||||
ShortHelp: "Print an analysis of local network conditions",
|
||||
Exec: runNetcheck,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("netcheck", flag.ExitOnError)
|
||||
fs := newFlagSet("netcheck")
|
||||
fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`)
|
||||
fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency")
|
||||
fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs")
|
||||
@@ -60,7 +59,7 @@ func runNetcheck(ctx context.Context, args []string) error {
|
||||
}
|
||||
|
||||
if strings.HasPrefix(netcheckArgs.format, "json") {
|
||||
fmt.Fprintln(os.Stderr, "# Warning: this JSON format is not yet considered a stable interface")
|
||||
fmt.Fprintln(Stderr, "# Warning: this JSON format is not yet considered a stable interface")
|
||||
}
|
||||
|
||||
dm, err := localClient.CurrentDERPMap(ctx)
|
||||
@@ -112,38 +111,38 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
|
||||
}
|
||||
if j != nil {
|
||||
j = append(j, '\n')
|
||||
os.Stdout.Write(j)
|
||||
Stdout.Write(j)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("\nReport:\n")
|
||||
fmt.Printf("\t* UDP: %v\n", report.UDP)
|
||||
printf("\nReport:\n")
|
||||
printf("\t* UDP: %v\n", report.UDP)
|
||||
if report.GlobalV4 != "" {
|
||||
fmt.Printf("\t* IPv4: yes, %v\n", report.GlobalV4)
|
||||
printf("\t* IPv4: yes, %v\n", report.GlobalV4)
|
||||
} else {
|
||||
fmt.Printf("\t* IPv4: (no addr found)\n")
|
||||
printf("\t* IPv4: (no addr found)\n")
|
||||
}
|
||||
if report.GlobalV6 != "" {
|
||||
fmt.Printf("\t* IPv6: yes, %v\n", report.GlobalV6)
|
||||
printf("\t* IPv6: yes, %v\n", report.GlobalV6)
|
||||
} else if report.IPv6 {
|
||||
fmt.Printf("\t* IPv6: (no addr found)\n")
|
||||
printf("\t* IPv6: (no addr found)\n")
|
||||
} else if report.OSHasIPv6 {
|
||||
fmt.Printf("\t* IPv6: no, but OS has support\n")
|
||||
printf("\t* IPv6: no, but OS has support\n")
|
||||
} else {
|
||||
fmt.Printf("\t* IPv6: no, unavailable in OS\n")
|
||||
printf("\t* IPv6: no, unavailable in OS\n")
|
||||
}
|
||||
fmt.Printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
|
||||
fmt.Printf("\t* HairPinning: %v\n", report.HairPinning)
|
||||
fmt.Printf("\t* PortMapping: %v\n", portMapping(report))
|
||||
printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
|
||||
printf("\t* HairPinning: %v\n", report.HairPinning)
|
||||
printf("\t* PortMapping: %v\n", portMapping(report))
|
||||
|
||||
// When DERP latency checking failed,
|
||||
// magicsock will try to pick the DERP server that
|
||||
// most of your other nodes are also using
|
||||
if len(report.RegionLatency) == 0 {
|
||||
fmt.Printf("\t* Nearest DERP: unknown (no response to latency probes)\n")
|
||||
printf("\t* Nearest DERP: unknown (no response to latency probes)\n")
|
||||
} else {
|
||||
fmt.Printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName)
|
||||
fmt.Printf("\t* DERP latency:\n")
|
||||
printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName)
|
||||
printf("\t* DERP latency:\n")
|
||||
var rids []int
|
||||
for rid := range dm.Regions {
|
||||
rids = append(rids, rid)
|
||||
@@ -170,7 +169,7 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
|
||||
if netcheckArgs.verbose {
|
||||
derpNum = fmt.Sprintf("derp%d, ", rid)
|
||||
}
|
||||
fmt.Printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName)
|
||||
printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -46,7 +46,7 @@ relay node.
|
||||
`),
|
||||
Exec: runPing,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("ping", flag.ExitOnError)
|
||||
fs := newFlagSet("ping")
|
||||
fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output")
|
||||
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established")
|
||||
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through WireGuard, but not either host OS stack)")
|
||||
@@ -88,7 +88,7 @@ func runPing(ctx context.Context, args []string) error {
|
||||
}
|
||||
description, ok := isRunningOrStarting(st)
|
||||
if !ok {
|
||||
fmt.Printf("%s\n", description)
|
||||
printf("%s\n", description)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func runPing(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
if self {
|
||||
fmt.Printf("%v is local Tailscale IP\n", ip)
|
||||
printf("%v is local Tailscale IP\n", ip)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func runPing(ctx context.Context, args []string) error {
|
||||
cancel()
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
fmt.Printf("ping %q timed out\n", ip)
|
||||
printf("ping %q timed out\n", ip)
|
||||
if n == pingArgs.num {
|
||||
if !anyPong {
|
||||
return errors.New("no reply")
|
||||
@@ -133,7 +133,7 @@ func runPing(ctx context.Context, args []string) error {
|
||||
}
|
||||
if pr.Err != "" {
|
||||
if pr.IsLocalIP {
|
||||
fmt.Println(pr.Err)
|
||||
outln(pr.Err)
|
||||
return nil
|
||||
}
|
||||
return errors.New(pr.Err)
|
||||
@@ -149,7 +149,7 @@ func runPing(ctx context.Context, args []string) error {
|
||||
via = string(pingType())
|
||||
}
|
||||
if pingArgs.peerAPI {
|
||||
fmt.Printf("hit peerapi of %s (%s) at %s in %s\n", pr.NodeIP, pr.NodeName, pr.PeerAPIURL, latency)
|
||||
printf("hit peerapi of %s (%s) at %s in %s\n", pr.NodeIP, pr.NodeName, pr.PeerAPIURL, latency)
|
||||
return nil
|
||||
}
|
||||
anyPong = true
|
||||
@@ -157,7 +157,7 @@ func runPing(ctx context.Context, args []string) error {
|
||||
if pr.PeerAPIPort != 0 {
|
||||
extra = fmt.Sprintf(", %d", pr.PeerAPIPort)
|
||||
}
|
||||
fmt.Printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
|
||||
printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
|
||||
if pingArgs.tsmp || pingArgs.icmp {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ func presentRiskToUser(riskType, riskMessage string) error {
|
||||
if riskAccepted(riskType) {
|
||||
return nil
|
||||
}
|
||||
fmt.Println(riskMessage)
|
||||
fmt.Printf("To skip this warning, use --accept-risk=%s\n", riskType)
|
||||
outln(riskMessage)
|
||||
printf("To skip this warning, use --accept-risk=%s\n", riskType)
|
||||
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, syscall.SIGINT)
|
||||
@@ -64,15 +64,15 @@ func presentRiskToUser(riskType, riskMessage string) error {
|
||||
for left := riskAbortTimeSeconds; left > 0; left-- {
|
||||
msg := fmt.Sprintf("\rContinuing in %d seconds...", left)
|
||||
msgLen = len(msg)
|
||||
fmt.Print(msg)
|
||||
printf(msg)
|
||||
select {
|
||||
case <-interrupt:
|
||||
fmt.Printf("\r%s\r", strings.Repeat(" ", msgLen+1))
|
||||
printf("\r%s\r", strings.Repeat("x", msgLen+1))
|
||||
return errAborted
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
}
|
||||
}
|
||||
fmt.Printf("\r%s\r", strings.Repeat(" ", msgLen))
|
||||
printf("\r%s\r", strings.Repeat(" ", msgLen))
|
||||
return errAborted
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
//go:build !js && !windows
|
||||
// +build !js,!windows
|
||||
|
||||
package cli
|
||||
|
||||
|
||||
17
cmd/tailscale/cli/ssh_exec_js.go
Normal file
17
cmd/tailscale/cli/ssh_exec_js.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func findSSH() (string, error) {
|
||||
return "", errors.New("Not implemented")
|
||||
}
|
||||
|
||||
func execSSH(ssh string, argv []string) error {
|
||||
return errors.New("Not implemented")
|
||||
}
|
||||
@@ -46,7 +46,7 @@ https://github.com/tailscale/tailscale/blob/main/ipn/ipnstate/ipnstate.go
|
||||
`),
|
||||
Exec: runStatus,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("status", flag.ExitOnError)
|
||||
fs := newFlagSet("status")
|
||||
fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
|
||||
fs.BoolVar(&statusArgs.web, "web", false, "run webserver with HTML showing status")
|
||||
fs.BoolVar(&statusArgs.active, "active", false, "filter output to only peers with active sessions (not applicable to web mode)")
|
||||
@@ -92,7 +92,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s", j)
|
||||
printf("%s", j)
|
||||
return nil
|
||||
}
|
||||
if statusArgs.web {
|
||||
@@ -101,7 +101,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
statusURL := interfaces.HTTPOfListener(ln)
|
||||
fmt.Printf("Serving Tailscale status at %v ...\n", statusURL)
|
||||
printf("Serving Tailscale status at %v ...\n", statusURL)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
ln.Close()
|
||||
@@ -131,16 +131,16 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
// print health check information prior to checking LocalBackend state as
|
||||
// it may provide an explanation to the user if we choose to exit early
|
||||
if len(st.Health) > 0 {
|
||||
fmt.Printf("# Health check:\n")
|
||||
printf("# Health check:\n")
|
||||
for _, m := range st.Health {
|
||||
fmt.Printf("# - %s\n", m)
|
||||
printf("# - %s\n", m)
|
||||
}
|
||||
fmt.Println()
|
||||
outln()
|
||||
}
|
||||
|
||||
description, ok := isRunningOrStarting(st)
|
||||
if !ok {
|
||||
fmt.Println(description)
|
||||
outln(description)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
printPS(ps)
|
||||
}
|
||||
}
|
||||
os.Stdout.Write(buf.Bytes())
|
||||
Stdout.Write(buf.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ var upFlagSet = newUpFlagSet(effectiveGOOS(), &upArgs)
|
||||
func inTest() bool { return flag.Lookup("test.v") != nil }
|
||||
|
||||
func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
|
||||
upf := flag.NewFlagSet("up", flag.ExitOnError)
|
||||
upf := newFlagSet("up")
|
||||
|
||||
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
|
||||
upf.BoolVar(&upArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
|
||||
@@ -178,15 +178,16 @@ var upArgs upArgsT
|
||||
// JSON block will be output. The AuthURL and QR fields will not be present, the
|
||||
// BackendState and Error fields will give the result of the authentication.
|
||||
// Ex:
|
||||
// {
|
||||
// "AuthURL": "https://login.tailscale.com/a/0123456789abcdef",
|
||||
// "QR": "data:image/png;base64,0123...cdef"
|
||||
// "BackendState": "NeedsLogin"
|
||||
// }
|
||||
// {
|
||||
// "BackendState": "Running"
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "AuthURL": "https://login.tailscale.com/a/0123456789abcdef",
|
||||
// "QR": "data:image/png;base64,0123...cdef"
|
||||
// "BackendState": "NeedsLogin"
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "BackendState": "Running"
|
||||
// }
|
||||
type upOutputJSON struct {
|
||||
AuthURL string `json:",omitempty"` // Authentication URL of the form https://login.tailscale.com/a/0123456789
|
||||
QR string `json:",omitempty"` // a DataURL (base64) PNG of a QR code AuthURL
|
||||
@@ -195,7 +196,7 @@ type upOutputJSON struct {
|
||||
}
|
||||
|
||||
func warnf(format string, args ...any) {
|
||||
fmt.Printf("Warning: "+format+"\n", args...)
|
||||
printf("Warning: "+format+"\n", args...)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -538,7 +539,7 @@ func runUp(ctx context.Context, args []string) (retErr error) {
|
||||
if env.upArgs.json {
|
||||
printUpDoneJSON(ipn.NeedsMachineAuth, "")
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
|
||||
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
|
||||
}
|
||||
case ipn.Running:
|
||||
// Done full authentication process
|
||||
@@ -546,7 +547,7 @@ func runUp(ctx context.Context, args []string) (retErr error) {
|
||||
printUpDoneJSON(ipn.Running, "")
|
||||
} else if printed {
|
||||
// Only need to print an update if we printed the "please click" message earlier.
|
||||
fmt.Fprintf(os.Stderr, "Success.\n")
|
||||
fmt.Fprintf(Stderr, "Success.\n")
|
||||
}
|
||||
select {
|
||||
case running <- true:
|
||||
@@ -570,18 +571,18 @@ func runUp(ctx context.Context, args []string) (retErr error) {
|
||||
|
||||
data, err := json.MarshalIndent(js, "", "\t")
|
||||
if err != nil {
|
||||
log.Printf("upOutputJSON marshalling error: %v", err)
|
||||
printf("upOutputJSON marshalling error: %v", err)
|
||||
} else {
|
||||
fmt.Println(string(data))
|
||||
outln(string(data))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
|
||||
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
|
||||
if upArgs.qr {
|
||||
q, err := qrcode.New(*url, qrcode.Medium)
|
||||
if err != nil {
|
||||
log.Printf("QR code error: %v", err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", q.ToString(false))
|
||||
fmt.Fprintf(Stderr, "%s\n", q.ToString(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -695,12 +696,12 @@ func checkSSHUpWarnings(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
if len(st.Health) == 1 && strings.Contains(st.Health[0], "SSH") {
|
||||
fmt.Printf("%s\n", st.Health[0])
|
||||
printf("%s\n", st.Health[0])
|
||||
return
|
||||
}
|
||||
fmt.Printf("# Health check:\n")
|
||||
printf("# Health check:\n")
|
||||
for _, m := range st.Health {
|
||||
fmt.Printf(" - %s\n", m)
|
||||
printf(" - %s\n", m)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,7 +711,7 @@ func printUpDoneJSON(state ipn.State, errorString string) {
|
||||
if err != nil {
|
||||
log.Printf("printUpDoneJSON marshalling error: %v", err)
|
||||
} else {
|
||||
fmt.Println(string(data))
|
||||
outln(string(data))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ var versionCmd = &ffcli.Command{
|
||||
ShortUsage: "version [flags]",
|
||||
ShortHelp: "Print Tailscale version",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("version", flag.ExitOnError)
|
||||
fs := newFlagSet("version")
|
||||
fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version")
|
||||
return fs
|
||||
})(),
|
||||
@@ -34,16 +34,16 @@ func runVersion(ctx context.Context, args []string) error {
|
||||
return fmt.Errorf("too many non-flag arguments: %q", args)
|
||||
}
|
||||
if !versionArgs.daemon {
|
||||
fmt.Println(version.String())
|
||||
outln(version.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Client: %s\n", version.String())
|
||||
printf("Client: %s\n", version.String())
|
||||
|
||||
st, err := localClient.StatusWithoutPeers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Daemon: %s\n", st.Version)
|
||||
printf("Daemon: %s\n", st.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ Tailscale, as opposed to a CLI or a native app.
|
||||
`),
|
||||
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
webf := flag.NewFlagSet("web", flag.ExitOnError)
|
||||
webf := newFlagSet("web")
|
||||
webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic")
|
||||
webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script")
|
||||
return webf
|
||||
|
||||
@@ -57,7 +57,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/net/dnsfallback from tailscale.com/control/controlhttp
|
||||
tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+
|
||||
💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/net/netaddr from tailscale.com/disco+
|
||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/netknob from tailscale.com/net/netns
|
||||
@@ -71,7 +71,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||
tailscale.com/paths from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/syncs from tailscale.com/net/interfaces+
|
||||
tailscale.com/syncs from tailscale.com/net/netcheck
|
||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/tka from tailscale.com/types/key
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/dnsfallback from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/flowtrack from tailscale.com/net/packet+
|
||||
💣 tailscale.com/net/interfaces from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/netaddr from tailscale.com/disco+
|
||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
|
||||
tailscale.com/net/netknob from tailscale.com/net/netns+
|
||||
@@ -241,10 +241,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/safesocket from tailscale.com/client/tailscale+
|
||||
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
|
||||
LD 💣 tailscale.com/ssh/tailssh from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/syncs from tailscale.com/control/controlknobs+
|
||||
tailscale.com/syncs from tailscale.com/net/netcheck+
|
||||
tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+
|
||||
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
|
||||
tailscale.com/tka from tailscale.com/types/key
|
||||
tailscale.com/tka from tailscale.com/types/key+
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+
|
||||
@@ -311,6 +311,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/crypto/poly1305 from golang.zx2c4.com/wireguard/device+
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+
|
||||
golang.org/x/exp/constraints from golang.org/x/exp/slices
|
||||
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal
|
||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||
golang.org/x/net/dns/dnsmessage from net+
|
||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
// HTTP proxy code
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
//go:build !go1.19
|
||||
// +build !go1.19
|
||||
|
||||
package main
|
||||
|
||||
func init() {
|
||||
you_need_Go_1_18_to_compile_Tailscale()
|
||||
you_need_Go_1_19_to_compile_Tailscale()
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
// The tailscaled program is the Tailscale client daemon. It's configured
|
||||
// and controlled via the tailscale CLI program.
|
||||
@@ -274,6 +274,13 @@ func ipnServerOpts() (o ipnserver.Options) {
|
||||
}
|
||||
|
||||
switch goos {
|
||||
case "js":
|
||||
// The js/wasm client has no state storage so for now
|
||||
// treat all interactive logins as ephemeral.
|
||||
// TODO(bradfitz): if we start using browser LocalStorage
|
||||
// or something, then rethink this.
|
||||
o.LoginFlags = controlclient.LoginEphemeral
|
||||
fallthrough
|
||||
default:
|
||||
o.SurviveDisconnects = true
|
||||
o.AutostartStateKey = ipn.GlobalDaemonStateKey
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18 && (linux || darwin || freebsd || openbsd)
|
||||
// +build go1.18
|
||||
//go:build go1.19 && (linux || darwin || freebsd || openbsd)
|
||||
// +build go1.19
|
||||
// +build linux darwin freebsd openbsd
|
||||
|
||||
package main
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows && go1.18
|
||||
// +build !windows,go1.18
|
||||
//go:build !windows && go1.19
|
||||
// +build !windows,go1.19
|
||||
|
||||
package main // import "tailscale.com/cmd/tailscaled"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package main // import "tailscale.com/cmd/tailscaled"
|
||||
|
||||
|
||||
30
cmd/tsconnect/README.md
Normal file
30
cmd/tsconnect/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# tsconnect
|
||||
|
||||
The tsconnect command builds and serves the static site that is generated for
|
||||
the Tailscale Connect JS/WASM client.
|
||||
|
||||
## Development
|
||||
|
||||
To start the development server:
|
||||
|
||||
```
|
||||
./tool/go run ./cmd/tsconnect dev
|
||||
```
|
||||
|
||||
The site is served at http://localhost:9090/. JavaScript and CSS changes can be picked up with a browser reload. Go changes (including to the `wasm` package) require the server to be stopped and restarted. In development mode the state the Tailscale client is stored in `sessionStorage` and will thus survive page reloads (but not the tab being closed).
|
||||
|
||||
## Deployment
|
||||
|
||||
To build the static assets necessary for serving, run:
|
||||
|
||||
```
|
||||
./tool/go run ./cmd/tsconnect build
|
||||
```
|
||||
|
||||
To serve them, run:
|
||||
|
||||
```
|
||||
./tool/go run ./cmd/tsconnect serve
|
||||
```
|
||||
|
||||
By default the build output is placed in the `dist/` directory and embedded in the binary, but this can be controlled by the `-distdir` flag. The `-addr` flag controls the interface and port that the serve listens on.
|
||||
@@ -5,21 +5,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"tailscale.com/util/precompress"
|
||||
)
|
||||
|
||||
func runBuild() {
|
||||
@@ -71,7 +66,7 @@ func runBuild() {
|
||||
log.Fatalf("Cannot write metadata: %v", err)
|
||||
}
|
||||
|
||||
if er := precompressDist(); err != nil {
|
||||
if er := precompressDist(*fastCompression); err != nil {
|
||||
log.Fatalf("Cannot precompress resources: %v", er)
|
||||
}
|
||||
}
|
||||
@@ -125,71 +120,12 @@ func cleanDist() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func precompressDist() error {
|
||||
func precompressDist(fastCompression bool) error {
|
||||
log.Printf("Pre-compressing files in %s/...\n", *distDir)
|
||||
var eg errgroup.Group
|
||||
err := fs.WalkDir(os.DirFS(*distDir), ".", func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if !compressibleExtensions[filepath.Ext(p)] {
|
||||
return nil
|
||||
}
|
||||
p = path.Join(*distDir, p)
|
||||
log.Printf("Pre-compressing %v\n", p)
|
||||
|
||||
eg.Go(func() error {
|
||||
return precompress(p)
|
||||
})
|
||||
return nil
|
||||
return precompress.PrecompressDir(*distDir, precompress.Options{
|
||||
FastCompression: fastCompression,
|
||||
ProgressFn: func(path string) {
|
||||
log.Printf("Pre-compressing %v\n", path)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
var compressibleExtensions = map[string]bool{
|
||||
".js": true,
|
||||
".css": true,
|
||||
".wasm": true,
|
||||
}
|
||||
|
||||
func precompress(path string) error {
|
||||
contents, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = writeCompressed(contents, func(w io.Writer) (io.WriteCloser, error) {
|
||||
return gzip.NewWriterLevel(w, gzip.BestCompression)
|
||||
}, path+".gz", fi.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeCompressed(contents, func(w io.Writer) (io.WriteCloser, error) {
|
||||
return brotli.NewWriterLevel(w, brotli.BestCompression), nil
|
||||
}, path+".br", fi.Mode())
|
||||
}
|
||||
|
||||
func writeCompressed(contents []byte, compressedWriterCreator func(io.Writer) (io.WriteCloser, error), outputPath string, outputMode fs.FileMode) error {
|
||||
var buf bytes.Buffer
|
||||
compressedWriter, err := compressedWriterCreator(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := compressedWriter.Write(contents); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := compressedWriter.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(outputPath, buf.Bytes(), outputMode)
|
||||
}
|
||||
|
||||
@@ -119,6 +119,9 @@ func runYarn(args ...string) error {
|
||||
// from entry points to hashed file names.
|
||||
type EsbuildMetadata struct {
|
||||
Outputs map[string]struct {
|
||||
Inputs map[string]struct {
|
||||
BytesInOutput int64 `json:"bytesInOutput"`
|
||||
} `json:"inputs,omitempty"`
|
||||
EntryPoint string `json:"entryPoint,omitempty"`
|
||||
} `json:"outputs,omitempty"`
|
||||
}
|
||||
|
||||
@@ -3,36 +3,42 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tailscale Connect</title>
|
||||
<link rel="stylesheet" type="text/css" href="dist/index.css" />
|
||||
<script src="dist/index.js" defer></script>
|
||||
</head>
|
||||
<body class="flex flex-col min-h-screen">
|
||||
<div class="bg-gray-100 border-b border-gray-200 pt-4 pb-2 mb-6">
|
||||
<body class="flex flex-col h-screen overflow-hidden">
|
||||
<div class="bg-gray-100 border-b border-gray-200 pt-4 pb-2">
|
||||
<header class="container mx-auto px-4 flex flex-row items-center">
|
||||
<h1 class="text-3xl font-bold grow">Tailscale Connect</h1>
|
||||
<div class="text-gray-600" id="state">Loading…</div>
|
||||
</header>
|
||||
</div>
|
||||
<form
|
||||
id="ssh-form"
|
||||
class="container mx-auto px-4 hidden flex justify-center"
|
||||
<div
|
||||
id="content"
|
||||
class="flex-grow flex flex-col justify-center overflow-hidden"
|
||||
>
|
||||
<input type="text" class="input username" placeholder="Username" />
|
||||
<div class="select-with-arrow mx-2">
|
||||
<select class="select"></select>
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button bg-green-500 border-green-500 text-white hover:bg-green-600 hover:border-green-600"
|
||||
value="SSH"
|
||||
/>
|
||||
</form>
|
||||
<div id="no-ssh" class="container mx-auto px-4 hidden text-center">
|
||||
None of your machines have
|
||||
<a href="https://tailscale.com/kb/1193/tailscale-ssh/" class="link"
|
||||
>Tailscale SSH</a
|
||||
<form
|
||||
id="ssh-form"
|
||||
class="container mx-auto px-4 hidden flex justify-center"
|
||||
>
|
||||
enabled. Give it a try!
|
||||
<input type="text" class="input username" placeholder="Username" />
|
||||
<div class="select-with-arrow mx-2">
|
||||
<select class="select"></select>
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button bg-green-500 border-green-500 text-white hover:bg-green-600 hover:border-green-600"
|
||||
value="SSH"
|
||||
/>
|
||||
</form>
|
||||
<div id="no-ssh" class="container mx-auto px-4 hidden text-center">
|
||||
None of your machines have
|
||||
<a href="https://tailscale.com/kb/1193/tailscale-ssh/" class="link"
|
||||
>Tailscale SSH</a
|
||||
>
|
||||
enabled. Give it a try!
|
||||
</div>
|
||||
</div>
|
||||
<script src="dist/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"qrcode": "^1.5.0",
|
||||
"tailwindcss": "^3.1.6",
|
||||
"typescript": "^4.7.4",
|
||||
"xterm": "^4.18.0"
|
||||
"xterm": "^4.18.0",
|
||||
"xterm-addon-fit": "^0.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tsc --noEmit"
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/tsweb"
|
||||
"tailscale.com/util/precompress"
|
||||
)
|
||||
|
||||
//go:embed index.html
|
||||
@@ -83,10 +84,19 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
|
||||
return nil, fmt.Errorf("Could not parse esbuild-metadata.json: %w", err)
|
||||
}
|
||||
entryPointsToHashedDistPaths := make(map[string]string)
|
||||
mainWasmPath := ""
|
||||
for outputPath, output := range esbuildMetadata.Outputs {
|
||||
if output.EntryPoint != "" {
|
||||
entryPointsToHashedDistPaths[output.EntryPoint] = path.Join("dist", outputPath)
|
||||
}
|
||||
if path.Ext(outputPath) == ".wasm" {
|
||||
for input := range output.Inputs {
|
||||
if input == "src/main.wasm" {
|
||||
mainWasmPath = path.Join("dist", outputPath)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indexBytes := rawIndexBytes
|
||||
@@ -96,6 +106,10 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
|
||||
indexBytes = bytes.ReplaceAll(indexBytes, []byte(defaultDistPath), []byte(hashedDistPath))
|
||||
}
|
||||
}
|
||||
if mainWasmPath != "" {
|
||||
mainWasmPrefetch := fmt.Sprintf("</title>\n<link rel='preload' as='fetch' crossorigin='anonymous' href='%s'>", mainWasmPath)
|
||||
indexBytes = bytes.ReplaceAll(indexBytes, []byte("</title>"), []byte(mainWasmPrefetch))
|
||||
}
|
||||
|
||||
return indexBytes, nil
|
||||
}
|
||||
@@ -107,28 +121,10 @@ var entryPointsToDefaultDistPaths = map[string]string{
|
||||
|
||||
func handleServeDist(w http.ResponseWriter, r *http.Request, distFS fs.FS) {
|
||||
path := r.URL.Path
|
||||
var f fs.File
|
||||
// Prefer pre-compressed versions generated during the build step.
|
||||
if tsweb.AcceptsEncoding(r, "br") {
|
||||
if brotliFile, err := distFS.Open(path + ".br"); err == nil {
|
||||
f = brotliFile
|
||||
w.Header().Set("Content-Encoding", "br")
|
||||
}
|
||||
}
|
||||
if f == nil && tsweb.AcceptsEncoding(r, "gzip") {
|
||||
if gzipFile, err := distFS.Open(path + ".gz"); err == nil {
|
||||
f = gzipFile
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
}
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
if rawFile, err := distFS.Open(path); err == nil {
|
||||
f = rawFile
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
f, err := precompress.OpenPrecompressedFile(w, r, path, distFS)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
|
||||
@@ -73,3 +73,7 @@
|
||||
background-color: currentColor;
|
||||
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
|
||||
}
|
||||
|
||||
body.ssh-active #ssh-form {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
@@ -52,3 +52,7 @@ function handleGoPanic(err?: string) {
|
||||
}
|
||||
|
||||
let panicNode: HTMLDivElement | undefined
|
||||
|
||||
export function getContentNode(): HTMLDivElement {
|
||||
return document.querySelector("#content") as HTMLDivElement
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import * as qrcode from "qrcode"
|
||||
import { getContentNode } from "./index"
|
||||
|
||||
export async function showLoginURL(url: string) {
|
||||
if (loginNode) {
|
||||
@@ -30,7 +31,7 @@ export async function showLoginURL(url: string) {
|
||||
|
||||
linkNode.appendChild(document.createTextNode(url))
|
||||
|
||||
document.body.appendChild(loginNode)
|
||||
getContentNode().appendChild(loginNode)
|
||||
}
|
||||
|
||||
export function hideLoginURL() {
|
||||
|
||||
@@ -47,7 +47,7 @@ export function notifyState(ipn: IPN, state: IPNState) {
|
||||
showLogoutButton(ipn)
|
||||
break
|
||||
}
|
||||
const stateNode = document.getElementById("state") as HTMLDivElement
|
||||
const stateNode = document.querySelector("#state") as HTMLDivElement
|
||||
stateNode.textContent = stateLabel ?? ""
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import { Terminal } from "xterm"
|
||||
import { FitAddon } from "xterm-addon-fit"
|
||||
import { getContentNode } from "./index"
|
||||
|
||||
export function showSSHForm(peers: IPNNetMapPeerNode[], ipn: IPN) {
|
||||
const formNode = document.getElementById("ssh-form") as HTMLDivElement
|
||||
const noSSHNode = document.getElementById("no-ssh") as HTMLDivElement
|
||||
const formNode = document.querySelector("#ssh-form") as HTMLDivElement
|
||||
const noSSHNode = document.querySelector("#no-ssh") as HTMLDivElement
|
||||
|
||||
const sshPeers = peers.filter(
|
||||
(p) => p.tailscaleSSHEnabled && p.online !== false
|
||||
@@ -39,26 +41,23 @@ export function showSSHForm(peers: IPNNetMapPeerNode[], ipn: IPN) {
|
||||
}
|
||||
|
||||
export function hideSSHForm() {
|
||||
const formNode = document.getElementById("ssh-form") as HTMLDivElement
|
||||
const formNode = document.querySelector("#ssh-form") as HTMLDivElement
|
||||
formNode.classList.add("hidden")
|
||||
}
|
||||
|
||||
function ssh(hostname: string, username: string, ipn: IPN) {
|
||||
document.body.classList.add("ssh-active")
|
||||
const termContainerNode = document.createElement("div")
|
||||
termContainerNode.className = "p-3"
|
||||
document.body.appendChild(termContainerNode)
|
||||
termContainerNode.className = "flex-grow bg-black p-2 overflow-hidden"
|
||||
getContentNode().appendChild(termContainerNode)
|
||||
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
})
|
||||
const fitAddon = new FitAddon()
|
||||
term.loadAddon(fitAddon)
|
||||
term.open(termContainerNode)
|
||||
|
||||
// Cancel wheel events from scrolling the page if the terminal has scrollback
|
||||
termContainerNode.addEventListener("wheel", (e) => {
|
||||
if (term.buffer.active.baseY > 0) {
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
fitAddon.fit()
|
||||
|
||||
let onDataHook: ((data: string) => void) | undefined
|
||||
term.onData((e) => {
|
||||
@@ -67,14 +66,33 @@ function ssh(hostname: string, username: string, ipn: IPN) {
|
||||
|
||||
term.focus()
|
||||
|
||||
ipn.ssh(hostname, username, {
|
||||
const sshSession = ipn.ssh(hostname, username, {
|
||||
writeFn: (input) => term.write(input),
|
||||
setReadFn: (hook) => (onDataHook = hook),
|
||||
rows: term.rows,
|
||||
cols: term.cols,
|
||||
onDone: () => {
|
||||
resizeObserver.disconnect()
|
||||
term.dispose()
|
||||
termContainerNode.remove()
|
||||
document.body.classList.remove("ssh-active")
|
||||
window.removeEventListener("beforeunload", beforeUnloadListener)
|
||||
},
|
||||
})
|
||||
|
||||
// Make terminal and SSH session track the size of the containing DOM node.
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
fitAddon.fit()
|
||||
})
|
||||
resizeObserver.observe(termContainerNode)
|
||||
term.onResize(({ rows, cols }) => {
|
||||
sshSession.resize(rows, cols)
|
||||
})
|
||||
|
||||
// Close the session if the user closes the window without an explicit
|
||||
// exit.
|
||||
const beforeUnloadListener = () => {
|
||||
sshSession.close()
|
||||
}
|
||||
window.addEventListener("beforeunload", beforeUnloadListener)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,19 @@ declare global {
|
||||
cols: number
|
||||
onDone: () => void
|
||||
}
|
||||
): void
|
||||
): IPNSSHSession
|
||||
fetch(
|
||||
url: string
|
||||
): Promise<{
|
||||
status: number
|
||||
statusText: string
|
||||
text: () => Promise<string>
|
||||
}>
|
||||
}
|
||||
|
||||
interface IPNSSHSession {
|
||||
resize(rows: number, cols: number): boolean
|
||||
close(): boolean
|
||||
}
|
||||
|
||||
interface IPNStateStorage {
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
// The tsconnect command builds and serves the static site that is generated for
|
||||
// the Tailscale Connect JS/WASM client. Can be run in 3 modes:
|
||||
// - dev: builds the site and serves it. JS and CSS changes can be picked up
|
||||
// with a reload.
|
||||
// - build: builds the site and writes it to dist/
|
||||
// - serve: serves the site from dist/ (embedded in the binary)
|
||||
// - dev: builds the site and serves it. JS and CSS changes can be picked up
|
||||
// with a reload.
|
||||
// - build: builds the site and writes it to dist/
|
||||
// - serve: serves the site from dist/ (embedded in the binary)
|
||||
package main // import "tailscale.com/cmd/tsconnect"
|
||||
|
||||
import (
|
||||
@@ -18,9 +18,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
addr = flag.String("addr", ":9090", "address to listen on")
|
||||
distDir = flag.String("distdir", "./dist", "path of directory to place build output in")
|
||||
yarnPath = flag.String("yarnpath", "../../tool/yarn", "path yarn executable used to install JavaScript dependencies")
|
||||
addr = flag.String("addr", ":9090", "address to listen on")
|
||||
distDir = flag.String("distdir", "./dist", "path of directory to place build output in")
|
||||
yarnPath = flag.String("yarnpath", "../../tool/yarn", "path yarn executable used to install JavaScript dependencies")
|
||||
fastCompression = flag.Bool("fast-compression", false, "Use faster compression when building, to speed up build time. Meant to iterative/debugging use only.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
@@ -100,6 +101,7 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
log.Fatalf("ipnserver.New: %v", err)
|
||||
}
|
||||
lb := srv.LocalBackend()
|
||||
ns.SetLocalBackend(lb)
|
||||
|
||||
jsIPN := &jsIPN{
|
||||
dialer: dialer,
|
||||
@@ -142,11 +144,19 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
log.Printf("Usage: ssh(hostname, userName, termConfig)")
|
||||
return nil
|
||||
}
|
||||
go jsIPN.ssh(
|
||||
return jsIPN.ssh(
|
||||
args[0].String(),
|
||||
args[1].String(),
|
||||
args[2])
|
||||
return nil
|
||||
}),
|
||||
"fetch": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
if len(args) != 1 {
|
||||
log.Printf("Usage: fetch(url)")
|
||||
return nil
|
||||
}
|
||||
|
||||
url := args[0].String()
|
||||
return jsIPN.fetch(url)
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -256,13 +266,42 @@ func (i *jsIPN) logout() {
|
||||
go i.lb.Logout()
|
||||
}
|
||||
|
||||
func (i *jsIPN) ssh(host, username string, termConfig js.Value) {
|
||||
writeFn := termConfig.Get("writeFn")
|
||||
setReadFn := termConfig.Get("setReadFn")
|
||||
rows := termConfig.Get("rows").Int()
|
||||
cols := termConfig.Get("cols").Int()
|
||||
onDone := termConfig.Get("onDone")
|
||||
func (i *jsIPN) ssh(host, username string, termConfig js.Value) map[string]any {
|
||||
jsSSHSession := &jsSSHSession{
|
||||
jsIPN: i,
|
||||
host: host,
|
||||
username: username,
|
||||
termConfig: termConfig,
|
||||
}
|
||||
|
||||
go jsSSHSession.Run()
|
||||
|
||||
return map[string]any{
|
||||
"close": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return jsSSHSession.Close() != nil
|
||||
}),
|
||||
"resize": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
rows := args[0].Int()
|
||||
cols := args[1].Int()
|
||||
return jsSSHSession.Resize(rows, cols) != nil
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
type jsSSHSession struct {
|
||||
jsIPN *jsIPN
|
||||
host string
|
||||
username string
|
||||
termConfig js.Value
|
||||
session *ssh.Session
|
||||
}
|
||||
|
||||
func (s *jsSSHSession) Run() {
|
||||
writeFn := s.termConfig.Get("writeFn")
|
||||
setReadFn := s.termConfig.Get("setReadFn")
|
||||
rows := s.termConfig.Get("rows").Int()
|
||||
cols := s.termConfig.Get("cols").Int()
|
||||
onDone := s.termConfig.Get("onDone")
|
||||
defer onDone.Invoke()
|
||||
|
||||
write := func(s string) {
|
||||
@@ -274,7 +313,7 @@ func (i *jsIPN) ssh(host, username string, termConfig js.Value) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
c, err := i.dialer.UserDial(ctx, "tcp", net.JoinHostPort(host, "22"))
|
||||
c, err := s.jsIPN.dialer.UserDial(ctx, "tcp", net.JoinHostPort(s.host, "22"))
|
||||
if err != nil {
|
||||
writeError("Dial", err)
|
||||
return
|
||||
@@ -283,10 +322,10 @@ func (i *jsIPN) ssh(host, username string, termConfig js.Value) {
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
User: username,
|
||||
User: s.username,
|
||||
}
|
||||
|
||||
sshConn, _, _, err := ssh.NewClientConn(c, host, config)
|
||||
sshConn, _, _, err := ssh.NewClientConn(c, s.host, config)
|
||||
if err != nil {
|
||||
writeError("SSH Connection", err)
|
||||
return
|
||||
@@ -302,6 +341,7 @@ func (i *jsIPN) ssh(host, username string, termConfig js.Value) {
|
||||
writeError("SSH Session", err)
|
||||
return
|
||||
}
|
||||
s.session = session
|
||||
write("Session Established\r\n")
|
||||
defer session.Close()
|
||||
|
||||
@@ -338,11 +378,49 @@ func (i *jsIPN) ssh(host, username string, termConfig js.Value) {
|
||||
|
||||
err = session.Wait()
|
||||
if err != nil {
|
||||
writeError("Exit", err)
|
||||
writeError("Wait", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *jsSSHSession) Close() error {
|
||||
return s.session.Close()
|
||||
}
|
||||
|
||||
func (s *jsSSHSession) Resize(rows, cols int) error {
|
||||
return s.session.WindowChange(rows, cols)
|
||||
}
|
||||
|
||||
func (i *jsIPN) fetch(url string) js.Value {
|
||||
return makePromise(func() (any, error) {
|
||||
c := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: i.dialer.UserDial,
|
||||
},
|
||||
}
|
||||
res, err := c.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"status": res.StatusCode,
|
||||
"statusText": res.Status,
|
||||
"text": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return makePromise(func() (any, error) {
|
||||
defer res.Body.Close()
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := buf.ReadFrom(res.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.String(), nil
|
||||
})
|
||||
}),
|
||||
// TODO: populate a more complete JS Response object
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type termWriter struct {
|
||||
f js.Value
|
||||
}
|
||||
@@ -428,3 +506,24 @@ func generateHostname() string {
|
||||
scale := scales[rand.Intn(len(scales))]
|
||||
return fmt.Sprintf("%s-%s", tail, scale)
|
||||
}
|
||||
|
||||
// makePromise handles the boilerplate of wrapping goroutines with JS promises.
|
||||
// f is run on a goroutine and its return value is used to resolve the promise
|
||||
// (or reject it if an error is returned).
|
||||
func makePromise(f func() (any, error)) js.Value {
|
||||
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
resolve := args[0]
|
||||
reject := args[1]
|
||||
go func() {
|
||||
if res, err := f(); err == nil {
|
||||
resolve.Invoke(res)
|
||||
} else {
|
||||
reject.Invoke(err.Error())
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
|
||||
promiseConstructor := js.Global().Get("Promise")
|
||||
return promiseConstructor.New(handler)
|
||||
}
|
||||
|
||||
@@ -603,6 +603,11 @@ xtend@^4.0.2:
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
xterm-addon-fit@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596"
|
||||
integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ==
|
||||
|
||||
xterm@^4.18.0:
|
||||
version "4.18.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1"
|
||||
|
||||
@@ -20,16 +20,18 @@ type StructWithoutPtrs struct {
|
||||
type Map struct {
|
||||
Int map[string]int
|
||||
SliceInt map[string][]int
|
||||
StructWithPtr map[string]*StructWithPtrs
|
||||
StructWithoutPtr map[string]*StructWithoutPtrs
|
||||
StructPtrWithPtr map[string]*StructWithPtrs
|
||||
StructPtrWithoutPtr map[string]*StructWithoutPtrs
|
||||
StructWithoutPtr map[string]StructWithoutPtrs
|
||||
SlicesWithPtrs map[string][]*StructWithPtrs
|
||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||
StructWithoutPtrKey map[StructWithoutPtrs]int `json:"-"`
|
||||
|
||||
// Unsupported.
|
||||
// Unsupported views.
|
||||
SliceIntPtr map[string][]*int
|
||||
PointerKey map[*string]int `json:"-"`
|
||||
StructWithPtrKey map[StructWithPtrs]int `json:"-"`
|
||||
StructWithPtr map[string]StructWithPtrs
|
||||
}
|
||||
|
||||
type StructWithPtrs struct {
|
||||
|
||||
@@ -73,16 +73,22 @@ func (src *Map) Clone() *Map {
|
||||
dst.SliceInt[k] = append([]int{}, src.SliceInt[k]...)
|
||||
}
|
||||
}
|
||||
if dst.StructWithPtr != nil {
|
||||
dst.StructWithPtr = map[string]*StructWithPtrs{}
|
||||
for k, v := range src.StructWithPtr {
|
||||
dst.StructWithPtr[k] = v.Clone()
|
||||
if dst.StructPtrWithPtr != nil {
|
||||
dst.StructPtrWithPtr = map[string]*StructWithPtrs{}
|
||||
for k, v := range src.StructPtrWithPtr {
|
||||
dst.StructPtrWithPtr[k] = v.Clone()
|
||||
}
|
||||
}
|
||||
if dst.StructPtrWithoutPtr != nil {
|
||||
dst.StructPtrWithoutPtr = map[string]*StructWithoutPtrs{}
|
||||
for k, v := range src.StructPtrWithoutPtr {
|
||||
dst.StructPtrWithoutPtr[k] = v.Clone()
|
||||
}
|
||||
}
|
||||
if dst.StructWithoutPtr != nil {
|
||||
dst.StructWithoutPtr = map[string]*StructWithoutPtrs{}
|
||||
dst.StructWithoutPtr = map[string]StructWithoutPtrs{}
|
||||
for k, v := range src.StructWithoutPtr {
|
||||
dst.StructWithoutPtr[k] = v.Clone()
|
||||
dst.StructWithoutPtr[k] = v
|
||||
}
|
||||
}
|
||||
if dst.SlicesWithPtrs != nil {
|
||||
@@ -121,6 +127,13 @@ func (src *Map) Clone() *Map {
|
||||
dst.StructWithPtrKey[k] = v
|
||||
}
|
||||
}
|
||||
if dst.StructWithPtr != nil {
|
||||
dst.StructWithPtr = map[string]StructWithPtrs{}
|
||||
for k, v := range src.StructWithPtr {
|
||||
v2 := v.Clone()
|
||||
dst.StructWithPtr[k] = *v2
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
@@ -128,14 +141,16 @@ func (src *Map) Clone() *Map {
|
||||
var _MapCloneNeedsRegeneration = Map(struct {
|
||||
Int map[string]int
|
||||
SliceInt map[string][]int
|
||||
StructWithPtr map[string]*StructWithPtrs
|
||||
StructWithoutPtr map[string]*StructWithoutPtrs
|
||||
StructPtrWithPtr map[string]*StructWithPtrs
|
||||
StructPtrWithoutPtr map[string]*StructWithoutPtrs
|
||||
StructWithoutPtr map[string]StructWithoutPtrs
|
||||
SlicesWithPtrs map[string][]*StructWithPtrs
|
||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||
StructWithoutPtrKey map[StructWithoutPtrs]int
|
||||
SliceIntPtr map[string][]*int
|
||||
PointerKey map[*string]int
|
||||
StructWithPtrKey map[StructWithPtrs]int
|
||||
StructWithPtr map[string]StructWithPtrs
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of StructWithSlices.
|
||||
|
||||
@@ -196,18 +196,22 @@ func (v MapView) SliceInt() views.MapFn[string, []int, views.Slice[int]] {
|
||||
})
|
||||
}
|
||||
|
||||
func (v MapView) StructWithPtr() views.MapFn[string, *StructWithPtrs, StructWithPtrsView] {
|
||||
return views.MapFnOf(v.ж.StructWithPtr, func(t *StructWithPtrs) StructWithPtrsView {
|
||||
func (v MapView) StructPtrWithPtr() views.MapFn[string, *StructWithPtrs, StructWithPtrsView] {
|
||||
return views.MapFnOf(v.ж.StructPtrWithPtr, func(t *StructWithPtrs) StructWithPtrsView {
|
||||
return t.View()
|
||||
})
|
||||
}
|
||||
|
||||
func (v MapView) StructWithoutPtr() views.MapFn[string, *StructWithoutPtrs, StructWithoutPtrsView] {
|
||||
return views.MapFnOf(v.ж.StructWithoutPtr, func(t *StructWithoutPtrs) StructWithoutPtrsView {
|
||||
func (v MapView) StructPtrWithoutPtr() views.MapFn[string, *StructWithoutPtrs, StructWithoutPtrsView] {
|
||||
return views.MapFnOf(v.ж.StructPtrWithoutPtr, func(t *StructWithoutPtrs) StructWithoutPtrsView {
|
||||
return t.View()
|
||||
})
|
||||
}
|
||||
|
||||
func (v MapView) StructWithoutPtr() views.Map[string, StructWithoutPtrs] {
|
||||
return views.MapOf(v.ж.StructWithoutPtr)
|
||||
}
|
||||
|
||||
func (v MapView) SlicesWithPtrs() views.MapFn[string, []*StructWithPtrs, views.SliceView[*StructWithPtrs, StructWithPtrsView]] {
|
||||
return views.MapFnOf(v.ж.SlicesWithPtrs, func(t []*StructWithPtrs) views.SliceView[*StructWithPtrs, StructWithPtrsView] {
|
||||
return views.SliceOfViews[*StructWithPtrs, StructWithPtrsView](t)
|
||||
@@ -227,18 +231,26 @@ func (v MapView) SliceIntPtr() map[string][]*int { panic("unsupported"
|
||||
func (v MapView) PointerKey() map[*string]int { panic("unsupported") }
|
||||
func (v MapView) StructWithPtrKey() map[StructWithPtrs]int { panic("unsupported") }
|
||||
|
||||
func (v MapView) StructWithPtr() views.MapFn[string, StructWithPtrs, StructWithPtrsView] {
|
||||
return views.MapFnOf(v.ж.StructWithPtr, func(t StructWithPtrs) StructWithPtrsView {
|
||||
return t.View()
|
||||
})
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _MapViewNeedsRegeneration = Map(struct {
|
||||
Int map[string]int
|
||||
SliceInt map[string][]int
|
||||
StructWithPtr map[string]*StructWithPtrs
|
||||
StructWithoutPtr map[string]*StructWithoutPtrs
|
||||
StructPtrWithPtr map[string]*StructWithPtrs
|
||||
StructPtrWithoutPtr map[string]*StructWithoutPtrs
|
||||
StructWithoutPtr map[string]StructWithoutPtrs
|
||||
SlicesWithPtrs map[string][]*StructWithPtrs
|
||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||
StructWithoutPtrKey map[StructWithoutPtrs]int
|
||||
SliceIntPtr map[string][]*int
|
||||
PointerKey map[*string]int
|
||||
StructWithPtrKey map[StructWithPtrs]int
|
||||
StructWithPtr map[string]StructWithPtrs
|
||||
}{})
|
||||
|
||||
// View returns a readonly view of StructWithSlices.
|
||||
|
||||
@@ -224,6 +224,18 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
|
||||
mElem := m.Elem()
|
||||
var template string
|
||||
switch u := mElem.(type) {
|
||||
case *types.Struct, *types.Named:
|
||||
strucT := u
|
||||
args.FieldType = it.QualifiedName(fieldType)
|
||||
if codegen.ContainsPointers(strucT) {
|
||||
args.MapFn = "t.View()"
|
||||
template = "mapFnField"
|
||||
args.MapValueType = it.QualifiedName(mElem)
|
||||
args.MapValueView = args.MapValueType + "View"
|
||||
} else {
|
||||
template = "mapField"
|
||||
args.MapValueType = it.QualifiedName(mElem)
|
||||
}
|
||||
case *types.Basic:
|
||||
template = "mapField"
|
||||
args.MapValueType = it.QualifiedName(mElem)
|
||||
@@ -342,8 +354,7 @@ func main() {
|
||||
it := codegen.NewImportTracker(pkg.Types)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, `//go:generate go run tailscale.com/cmd/cloner %s`, strings.Join(flagArgs, " "))
|
||||
fmt.Fprintln(buf)
|
||||
fmt.Fprintf(buf, "//go:generate go run tailscale.com/cmd/cloner %s\n\n", strings.Join(flagArgs, " "))
|
||||
runCloner := false
|
||||
for _, typeName := range typeNames {
|
||||
typ, ok := namedTypes[typeName]
|
||||
|
||||
@@ -67,6 +67,7 @@ type Direct struct {
|
||||
linkMon *monitor.Mon // or nil
|
||||
discoPubKey key.DiscoPublic
|
||||
getMachinePrivKey func() (key.MachinePrivate, error)
|
||||
getNLPublicKey func() (key.NLPublic, error) // or nil
|
||||
debugFlags []string
|
||||
keepSharerAndUserSplit bool
|
||||
skipIPForwardingCheck bool
|
||||
@@ -108,6 +109,10 @@ type Options struct {
|
||||
PopBrowserURL func(url string) // optional func to open browser
|
||||
Dialer *tsdial.Dialer // non-nil
|
||||
|
||||
// GetNLPublicKey specifies an optional function to use
|
||||
// Network Lock. If nil, it's not used.
|
||||
GetNLPublicKey func() (key.NLPublic, error)
|
||||
|
||||
// Status is called when there's a change in status.
|
||||
Status func(Status)
|
||||
|
||||
@@ -190,6 +195,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
c := &Direct{
|
||||
httpc: httpc,
|
||||
getMachinePrivKey: opts.GetMachinePrivateKey,
|
||||
getNLPublicKey: opts.GetNLPublicKey,
|
||||
serverURL: opts.ServerURL,
|
||||
timeNow: opts.TimeNow,
|
||||
logf: opts.Logf,
|
||||
@@ -424,6 +430,14 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
||||
oldNodeKey = persist.OldPrivateNodeKey.Public()
|
||||
}
|
||||
|
||||
var nlPub key.NLPublic
|
||||
if c.getNLPublicKey != nil {
|
||||
nlPub, err = c.getNLPublicKey()
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("get nl key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tryingNewKey.IsZero() {
|
||||
if opt.Logout {
|
||||
return false, "", errors.New("no nodekey to log out")
|
||||
@@ -439,6 +453,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
||||
Version: 1,
|
||||
OldNodeKey: oldNodeKey,
|
||||
NodeKey: tryingNewKey.Public(),
|
||||
NLKey: nlPub,
|
||||
Hostinfo: hi,
|
||||
Followup: opt.URL,
|
||||
Timestamp: &now,
|
||||
|
||||
@@ -268,6 +268,24 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
|
||||
if ec.Endpoints != nil {
|
||||
n.Endpoints = ec.Endpoints
|
||||
}
|
||||
if ec.Key != nil {
|
||||
n.Key = *ec.Key
|
||||
}
|
||||
if ec.DiscoKey != nil {
|
||||
n.DiscoKey = *ec.DiscoKey
|
||||
}
|
||||
if v := ec.Online; v != nil {
|
||||
n.Online = ptrCopy(v)
|
||||
}
|
||||
if v := ec.LastSeen; v != nil {
|
||||
n.LastSeen = ptrCopy(v)
|
||||
}
|
||||
if v := ec.KeyExpiry; v != nil {
|
||||
n.KeyExpiry = *v
|
||||
}
|
||||
if v := ec.Capabilities; v != nil {
|
||||
n.Capabilities = *v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,6 +295,16 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
|
||||
mapRes.PeersRemoved = nil
|
||||
}
|
||||
|
||||
// ptrCopy returns a pointer to a newly allocated shallow copy of *v.
|
||||
func ptrCopy[T any](v *T) *T {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
ret := new(T)
|
||||
*ret = *v
|
||||
return ret
|
||||
}
|
||||
|
||||
func nodesSorted(v []*tailcfg.Node) bool {
|
||||
for i, n := range v {
|
||||
if i > 0 && n.ID <= v[i-1].ID {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
@@ -192,7 +193,90 @@ func TestUndeltaPeers(t *testing.T) {
|
||||
},
|
||||
want: peers(n(1, "foo", withDERP("127.3.3.40:2"), withEP("1.2.3.4:56"))),
|
||||
},
|
||||
}
|
||||
{
|
||||
name: "change_key",
|
||||
prev: peers(n(1, "foo")),
|
||||
mapRes: &tailcfg.MapResponse{
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{{
|
||||
NodeID: 1,
|
||||
Key: ptrTo(key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
|
||||
}},
|
||||
}, want: peers(&tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
Key: key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change_disco_key",
|
||||
prev: peers(n(1, "foo")),
|
||||
mapRes: &tailcfg.MapResponse{
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{{
|
||||
NodeID: 1,
|
||||
DiscoKey: ptrTo(key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
|
||||
}},
|
||||
}, want: peers(&tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
DiscoKey: key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change_online",
|
||||
prev: peers(n(1, "foo")),
|
||||
mapRes: &tailcfg.MapResponse{
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{{
|
||||
NodeID: 1,
|
||||
Online: ptrTo(true),
|
||||
}},
|
||||
}, want: peers(&tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
Online: ptrTo(true),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change_last_seen",
|
||||
prev: peers(n(1, "foo")),
|
||||
mapRes: &tailcfg.MapResponse{
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{{
|
||||
NodeID: 1,
|
||||
LastSeen: ptrTo(time.Unix(123, 0).UTC()),
|
||||
}},
|
||||
}, want: peers(&tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
LastSeen: ptrTo(time.Unix(123, 0).UTC()),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change_key_expiry",
|
||||
prev: peers(n(1, "foo")),
|
||||
mapRes: &tailcfg.MapResponse{
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{{
|
||||
NodeID: 1,
|
||||
KeyExpiry: ptrTo(time.Unix(123, 0).UTC()),
|
||||
}},
|
||||
}, want: peers(&tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
KeyExpiry: time.Unix(123, 0).UTC(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change_capabilities",
|
||||
prev: peers(n(1, "foo")),
|
||||
mapRes: &tailcfg.MapResponse{
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{{
|
||||
NodeID: 1,
|
||||
Capabilities: ptrTo([]string{"foo"}),
|
||||
}},
|
||||
}, want: peers(&tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
Capabilities: []string{"foo"},
|
||||
}),
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -207,6 +291,10 @@ func TestUndeltaPeers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func ptrTo[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
func formatNodes(nodes []*tailcfg.Node) string {
|
||||
var sb strings.Builder
|
||||
for i, n := range nodes {
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
package controlknobs
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/syncs"
|
||||
)
|
||||
|
||||
// disableUPnP indicates whether to attempt UPnP mapping.
|
||||
var disableUPnP syncs.AtomicBool
|
||||
var disableUPnP atomic.Bool
|
||||
|
||||
func init() {
|
||||
SetDisableUPnP(envknob.Bool("TS_DISABLE_UPNP"))
|
||||
@@ -21,11 +22,11 @@ func init() {
|
||||
// DisableUPnP reports the last reported value from control
|
||||
// whether UPnP portmapping should be disabled.
|
||||
func DisableUPnP() bool {
|
||||
return disableUPnP.Get()
|
||||
return disableUPnP.Load()
|
||||
}
|
||||
|
||||
// SetDisableUPnP sets whether control says that UPnP should be
|
||||
// disabled.
|
||||
func SetDisableUPnP(v bool) {
|
||||
disableUPnP.Set(v)
|
||||
disableUPnP.Store(v)
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ const (
|
||||
)
|
||||
|
||||
// ProtocolVersion is bumped whenever there's a wire-incompatible change.
|
||||
// * version 1 (zero on wire): consistent box headers, in use by employee dev nodes a bit
|
||||
// * version 2: received packets have src addrs in frameRecvPacket at beginning
|
||||
// - version 1 (zero on wire): consistent box headers, in use by employee dev nodes a bit
|
||||
// - version 2: received packets have src addrs in frameRecvPacket at beginning
|
||||
const ProtocolVersion = 2
|
||||
|
||||
// frameType is the one byte frame type at the beginning of the frame
|
||||
|
||||
@@ -41,7 +41,6 @@ import (
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/metrics"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/pad32"
|
||||
@@ -232,7 +231,7 @@ type dupClientSet struct {
|
||||
}
|
||||
|
||||
func (s *dupClientSet) ActiveClient() *sclient {
|
||||
if s.last != nil && !s.last.isDisabled.Get() {
|
||||
if s.last != nil && !s.last.isDisabled.Load() {
|
||||
return s.last
|
||||
}
|
||||
return nil
|
||||
@@ -499,8 +498,8 @@ func (s *Server) registerClient(c *sclient) {
|
||||
s.dupClientConns.Add(2) // both old and new count
|
||||
s.dupClientConnTotal.Add(1)
|
||||
old := set.ActiveClient()
|
||||
old.isDup.Set(true)
|
||||
c.isDup.Set(true)
|
||||
old.isDup.Store(true)
|
||||
c.isDup.Store(true)
|
||||
s.clients[c.key] = &dupClientSet{
|
||||
last: c,
|
||||
set: map[*sclient]bool{
|
||||
@@ -512,7 +511,7 @@ func (s *Server) registerClient(c *sclient) {
|
||||
case *dupClientSet:
|
||||
s.dupClientConns.Add(1) // the gauge
|
||||
s.dupClientConnTotal.Add(1) // the counter
|
||||
c.isDup.Set(true)
|
||||
c.isDup.Store(true)
|
||||
set.set[c] = true
|
||||
set.last = c
|
||||
set.sendHistory = append(set.sendHistory, c)
|
||||
@@ -571,8 +570,8 @@ func (s *Server) unregisterClient(c *sclient) {
|
||||
if remain == nil {
|
||||
panic("unexpected nil remain from single element dup set")
|
||||
}
|
||||
remain.isDisabled.Set(false)
|
||||
remain.isDup.Set(false)
|
||||
remain.isDisabled.Store(false)
|
||||
remain.isDup.Store(false)
|
||||
s.clients[c.key] = singleClient{remain}
|
||||
}
|
||||
}
|
||||
@@ -1073,11 +1072,11 @@ func (s *Server) sendServerKey(lw *lazyBufioWriter) error {
|
||||
}
|
||||
|
||||
func (s *Server) noteClientActivity(c *sclient) {
|
||||
if !c.isDup.Get() {
|
||||
if !c.isDup.Load() {
|
||||
// Fast path for clients that aren't in a dup set.
|
||||
return
|
||||
}
|
||||
if c.isDisabled.Get() {
|
||||
if c.isDisabled.Load() {
|
||||
// If they're already disabled, no point checking more.
|
||||
return
|
||||
}
|
||||
@@ -1112,7 +1111,7 @@ func (s *Server) noteClientActivity(c *sclient) {
|
||||
for _, prior := range ds.sendHistory {
|
||||
if prior == c {
|
||||
ds.ForeachClient(func(c *sclient) {
|
||||
c.isDisabled.Set(true)
|
||||
c.isDisabled.Store(true)
|
||||
})
|
||||
break
|
||||
}
|
||||
@@ -1253,8 +1252,8 @@ type sclient struct {
|
||||
peerGone chan key.NodePublic // write request that a previous sender has disconnected (not used by mesh peers)
|
||||
meshUpdate chan struct{} // write request to write peerStateChange
|
||||
canMesh bool // clientInfo had correct mesh token for inter-region routing
|
||||
isDup syncs.AtomicBool // whether more than 1 sclient for key is connected
|
||||
isDisabled syncs.AtomicBool // whether sends to this peer are disabled due to active/active dups
|
||||
isDup atomic.Bool // whether more than 1 sclient for key is connected
|
||||
isDisabled atomic.Bool // whether sends to this peer are disabled due to active/active dups
|
||||
|
||||
// replaceLimiter controls how quickly two connections with
|
||||
// the same client key can kick each other off the server by
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
// A discovery message is:
|
||||
//
|
||||
// Header:
|
||||
// magic [6]byte // “TS💬” (0x54 53 f0 9f 92 ac)
|
||||
// senderDiscoPub [32]byte // nacl public key
|
||||
// nonce [24]byte
|
||||
//
|
||||
// magic [6]byte // “TS💬” (0x54 53 f0 9f 92 ac)
|
||||
// senderDiscoPub [32]byte // nacl public key
|
||||
// nonce [24]byte
|
||||
//
|
||||
// The recipient then decrypts the bytes following (the nacl secretbox)
|
||||
// and then the inner payload structure is:
|
||||
//
|
||||
// messageType byte (the MessageType constants below)
|
||||
// messageVersion byte (0 for now; but always ignore bytes at the end)
|
||||
// message-paylod [...]byte
|
||||
// messageType byte (the MessageType constants below)
|
||||
// messageVersion byte (0 for now; but always ignore bytes at the end)
|
||||
// message-paylod [...]byte
|
||||
package disco
|
||||
|
||||
import (
|
||||
@@ -27,7 +28,6 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"go4.org/mem"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
@@ -199,7 +199,7 @@ func parseCallMeMaybe(ver uint8, p []byte) (m *CallMeMaybe, err error) {
|
||||
var a [16]byte
|
||||
copy(a[:], p)
|
||||
m.MyNumber = append(m.MyNumber, netip.AddrPortFrom(
|
||||
netaddr.IPFrom16(a),
|
||||
netip.AddrFrom16(a).Unmap(),
|
||||
binary.BigEndian.Uint16(p[16:18])))
|
||||
p = p[epLength:]
|
||||
}
|
||||
@@ -234,10 +234,10 @@ func parsePong(ver uint8, p []byte) (m *Pong, err error) {
|
||||
copy(m.TxID[:], p)
|
||||
p = p[12:]
|
||||
|
||||
srcIP, _ := netaddr.FromStdIP(net.IP(p[:16]))
|
||||
srcIP, _ := netip.AddrFromSlice(net.IP(p[:16]))
|
||||
p = p[16:]
|
||||
port := binary.BigEndian.Uint16(p)
|
||||
m.Src = netip.AddrPortFrom(srcIP, port)
|
||||
m.Src = netip.AddrPortFrom(srcIP.Unmap(), port)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
|
||||
3
go.mod
3
go.mod
@@ -1,6 +1,6 @@
|
||||
module tailscale.com
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
filippo.io/mkcert v1.4.3
|
||||
@@ -56,6 +56,7 @@ require (
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94
|
||||
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d
|
||||
|
||||
34
go.sum
34
go.sum
@@ -3,7 +3,6 @@
|
||||
4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo=
|
||||
bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
@@ -207,7 +206,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao=
|
||||
github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -305,8 +303,6 @@ github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASx
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
@@ -345,12 +341,8 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
@@ -384,12 +376,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -404,7 +392,6 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -413,7 +400,6 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
@@ -424,10 +410,8 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
@@ -845,10 +829,6 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -1154,8 +1134,6 @@ github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/
|
||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
@@ -1275,7 +1253,6 @@ golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
@@ -1286,17 +1263,16 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220328175248-053ad81199eb h1:fP6C8Xutcp5AlakmT/SkQot0pMicROAsEX7OfNPuG10=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220328175248-053ad81199eb/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -1321,8 +1297,6 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1386,7 +1360,6 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1403,8 +1376,6 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1420,7 +1391,6 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDE
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@@ -1 +1 @@
|
||||
effe2d16a9ed6c4fd97361e8090bb22acc221075
|
||||
6dca83b256c7decd3dd6706ee47e04f21a0b935c
|
||||
|
||||
@@ -162,12 +162,12 @@ type PartialFile struct {
|
||||
//
|
||||
// Various platforms currently set StateKey in different ways:
|
||||
//
|
||||
// * the macOS/iOS GUI apps set it to "ipn-go-bridge"
|
||||
// * the Android app sets it to "ipn-android"
|
||||
// * on Windows, it's the empty string (in client mode) or, via
|
||||
// LocalBackend.userID, a string like "user-$USER_ID" (used in
|
||||
// server mode).
|
||||
// * on Linux/etc, it's always "_daemon" (ipn.GlobalDaemonStateKey)
|
||||
// - the macOS/iOS GUI apps set it to "ipn-go-bridge"
|
||||
// - the Android app sets it to "ipn-android"
|
||||
// - on Windows, it's the empty string (in client mode) or, via
|
||||
// LocalBackend.userID, a string like "user-$USER_ID" (used in
|
||||
// server mode).
|
||||
// - on Linux/etc, it's always "_daemon" (ipn.GlobalDaemonStateKey)
|
||||
type StateKey string
|
||||
|
||||
type Options struct {
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/control/controlclient"
|
||||
"tailscale.com/envknob"
|
||||
@@ -39,8 +40,8 @@ import (
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/portlist"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/empty"
|
||||
"tailscale.com/types/key"
|
||||
@@ -125,7 +126,7 @@ type LocalBackend struct {
|
||||
serverURL string // tailcontrol URL
|
||||
newDecompressor func() (controlclient.Decompressor, error)
|
||||
varRoot string // or empty if SetVarRoot never called
|
||||
sshAtomicBool syncs.AtomicBool
|
||||
sshAtomicBool atomic.Bool
|
||||
shutdownCalled bool // if Shutdown has been called
|
||||
|
||||
filterAtomic atomic.Value // of *filter.Filter
|
||||
@@ -145,6 +146,8 @@ type LocalBackend struct {
|
||||
prefs *ipn.Prefs
|
||||
inServerMode bool
|
||||
machinePrivKey key.MachinePrivate
|
||||
nlPrivKey key.NLPrivate
|
||||
tka *tka.Authority
|
||||
state ipn.State
|
||||
capFileSharing bool // whether netMap contains the file sharing capability
|
||||
// hostinfo is mutated in-place while mu is held.
|
||||
@@ -883,11 +886,11 @@ func (b *LocalBackend) getNewControlClientFunc() clientGen {
|
||||
// startIsNoopLocked reports whether a Start call on this LocalBackend
|
||||
// with the provided Start Options would be a useless no-op.
|
||||
//
|
||||
// TODO(apenwarr): we shouldn't need this.
|
||||
// The state machine is now nearly clean enough where it can accept a new
|
||||
// connection while in any state, not just Running, and on any platform.
|
||||
// We'd want to add a few more tests to state_test.go to ensure this continues
|
||||
// to work as expected.
|
||||
// TODO(apenwarr): we shouldn't need this. The state machine is now
|
||||
// nearly clean enough where it can accept a new connection while in
|
||||
// any state, not just Running, and on any platform. We'd want to add
|
||||
// a few more tests to state_test.go to ensure this continues to work
|
||||
// as expected.
|
||||
//
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) startIsNoopLocked(opts ipn.Options) bool {
|
||||
@@ -996,6 +999,9 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
return fmt.Errorf("initMachineKeyLocked: %w", err)
|
||||
}
|
||||
}
|
||||
if err := b.initNLKeyLocked(); err != nil {
|
||||
return fmt.Errorf("initNLKeyLocked: %w", err)
|
||||
}
|
||||
|
||||
loggedOut := b.prefs.LoggedOut
|
||||
|
||||
@@ -1055,6 +1061,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
// but it won't take effect until the next Start().
|
||||
cc, err := b.getNewControlClientFunc()(controlclient.Options{
|
||||
GetMachinePrivateKey: b.createGetMachinePrivateKeyFunc(),
|
||||
GetNLPublicKey: b.createGetNLPublicKeyFunc(),
|
||||
Logf: logger.WithPrefix(b.logf, "control: "),
|
||||
Persist: *persistv,
|
||||
ServerURL: b.serverURL,
|
||||
@@ -1514,6 +1521,21 @@ func (b *LocalBackend) createGetMachinePrivateKeyFunc() func() (key.MachinePriva
|
||||
}
|
||||
}
|
||||
|
||||
func (b *LocalBackend) createGetNLPublicKeyFunc() func() (key.NLPublic, error) {
|
||||
var cache atomic.Value
|
||||
return func() (key.NLPublic, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if v, ok := cache.Load().(key.NLPublic); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
pub := b.nlPrivKey.Public()
|
||||
cache.Store(pub)
|
||||
return pub, nil
|
||||
}
|
||||
}
|
||||
|
||||
// initMachineKeyLocked is called to initialize b.machinePrivKey.
|
||||
//
|
||||
// b.prefs must already be initialized.
|
||||
@@ -1572,6 +1594,45 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// initNLKeyLocked is called to initialize b.nlPrivKey.
|
||||
//
|
||||
// b.prefs must already be initialized.
|
||||
// b.stateKey should be set too, but just for nicer log messages.
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) initNLKeyLocked() (err error) {
|
||||
if !b.nlPrivKey.IsZero() {
|
||||
// Already set.
|
||||
return nil
|
||||
}
|
||||
|
||||
keyText, err := b.store.ReadState(ipn.NLKeyStateKey)
|
||||
if err == nil {
|
||||
if err := b.nlPrivKey.UnmarshalText(keyText); err != nil {
|
||||
return fmt.Errorf("invalid key in %s key of %v: %w", ipn.NLKeyStateKey, b.store, err)
|
||||
}
|
||||
if b.nlPrivKey.IsZero() {
|
||||
return fmt.Errorf("invalid zero key stored in %v key of %v", ipn.NLKeyStateKey, b.store)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err != ipn.ErrStateNotExist {
|
||||
return fmt.Errorf("error reading %v key of %v: %w", ipn.NLKeyStateKey, b.store, err)
|
||||
}
|
||||
|
||||
// If we didn't find one already on disk, generate a new one.
|
||||
b.logf("generating new network-lock key")
|
||||
b.nlPrivKey = key.NewNLPrivate()
|
||||
|
||||
keyText, _ = b.nlPrivKey.MarshalText()
|
||||
if err := b.store.WriteState(ipn.NLKeyStateKey, keyText); err != nil {
|
||||
b.logf("error writing network-lock key to store: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.logf("network-lock key written to store")
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeServerModeStartState stores the ServerModeStartKey value based on the current
|
||||
// user and prefs. If userID is blank or prefs is blank, no work is done.
|
||||
//
|
||||
@@ -1678,7 +1739,7 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
|
||||
// setAtomicValuesFromPrefs populates sshAtomicBool and containsViaIPFuncAtomic
|
||||
// from the prefs p, which may be nil.
|
||||
func (b *LocalBackend) setAtomicValuesFromPrefs(p *ipn.Prefs) {
|
||||
b.sshAtomicBool.Set(p != nil && p.RunSSH && canSSH)
|
||||
b.sshAtomicBool.Store(p != nil && p.RunSSH && canSSH)
|
||||
|
||||
if p == nil {
|
||||
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(nil))
|
||||
@@ -2436,6 +2497,14 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
|
||||
return dcfg
|
||||
}
|
||||
|
||||
// SetTailnetKeyAuthority sets the key authority which should be
|
||||
// used for locked tailnets.
|
||||
//
|
||||
// It should only be called before the LocalBackend is used.
|
||||
func (b *LocalBackend) SetTailnetKeyAuthority(a *tka.Authority) {
|
||||
b.tka = a
|
||||
}
|
||||
|
||||
// SetVarRoot sets the root directory of Tailscale's writable
|
||||
// storage area . (e.g. "/var/lib/tailscale")
|
||||
//
|
||||
@@ -2926,7 +2995,7 @@ func (b *LocalBackend) RequestEngineStatus() {
|
||||
// feed events into LocalBackend.
|
||||
//
|
||||
// TODO(apenwarr): use a channel or something to prevent re-entrancy?
|
||||
// Or maybe just call the state machine from fewer places.
|
||||
// Or maybe just call the state machine from fewer places.
|
||||
func (b *LocalBackend) stateMachine() {
|
||||
b.enterState(b.nextState())
|
||||
}
|
||||
@@ -2983,7 +3052,7 @@ func (b *LocalBackend) ResetForClientDisconnect() {
|
||||
b.setAtomicValuesFromPrefs(nil)
|
||||
}
|
||||
|
||||
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Get() && canSSH }
|
||||
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && canSSH }
|
||||
|
||||
// ShouldHandleViaIP reports whether whether ip is an IPv6 address in the
|
||||
// Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to
|
||||
@@ -3215,7 +3284,7 @@ func (b *LocalBackend) FileTargets() ([]*apitype.FileTarget, error) {
|
||||
return nil, errors.New("file sharing not enabled by Tailscale admin")
|
||||
}
|
||||
for _, p := range nm.Peers {
|
||||
if p.User != nm.User {
|
||||
if p.User != nm.User || !slices.Contains(p.Capabilities, tailcfg.CapabilityFileSharingTarget) {
|
||||
continue
|
||||
}
|
||||
peerAPI := peerAPIBase(b.netMap, p)
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
@@ -41,7 +42,6 @@ import (
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/wgengine"
|
||||
@@ -58,7 +58,7 @@ type peerAPIServer struct {
|
||||
b *LocalBackend
|
||||
rootDir string // empty means file receiving unavailable
|
||||
selfNode *tailcfg.Node
|
||||
knownEmpty syncs.AtomicBool
|
||||
knownEmpty atomic.Bool
|
||||
resolver *resolver.Resolver
|
||||
|
||||
// directFileMode is whether we're writing files directly to a
|
||||
@@ -144,7 +144,7 @@ func (s *peerAPIServer) hasFilesWaiting() bool {
|
||||
if s == nil || s.rootDir == "" || s.directFileMode {
|
||||
return false
|
||||
}
|
||||
if s.knownEmpty.Get() {
|
||||
if s.knownEmpty.Load() {
|
||||
// Optimization: this is usually empty, so avoid opening
|
||||
// the directory and checking. We can't cache the actual
|
||||
// has-files-or-not values as the macOS/iOS client might
|
||||
@@ -185,7 +185,7 @@ func (s *peerAPIServer) hasFilesWaiting() bool {
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
s.knownEmpty.Set(true)
|
||||
s.knownEmpty.Store(true)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
@@ -493,8 +493,8 @@ func (pln *peerAPIListener) serve() {
|
||||
logf("peerapi: unexpected RemoteAddr %#v", c.RemoteAddr())
|
||||
continue
|
||||
}
|
||||
ipp, ok := netaddr.FromStdAddr(ta.IP, ta.Port, "")
|
||||
if !ok {
|
||||
ipp := netaddr.Unmap(ta.AddrPort())
|
||||
if !ipp.IsValid() {
|
||||
logf("peerapi: bogus TCPAddr %#v", ta)
|
||||
c.Close()
|
||||
continue
|
||||
@@ -808,7 +808,7 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO: some real response
|
||||
success = true
|
||||
io.WriteString(w, "{}\n")
|
||||
h.ps.knownEmpty.Set(false)
|
||||
h.ps.knownEmpty.Store(false)
|
||||
h.ps.b.sendFileNotify()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package ipnlocal
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"tailscale.com/control/controlclient"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/empty"
|
||||
"tailscale.com/types/key"
|
||||
@@ -91,7 +91,7 @@ type mockControl struct {
|
||||
opts controlclient.Options
|
||||
logfActual logger.Logf
|
||||
statusFunc func(controlclient.Status)
|
||||
preventLog syncs.AtomicBool
|
||||
preventLog atomic.Bool
|
||||
|
||||
mu sync.Mutex
|
||||
calls []string
|
||||
@@ -920,7 +920,7 @@ func TestStateMachine(t *testing.T) {
|
||||
|
||||
type testStateStorage struct {
|
||||
mem mem.Store
|
||||
written syncs.AtomicBool
|
||||
written atomic.Bool
|
||||
}
|
||||
|
||||
func (s *testStateStorage) ReadState(id ipn.StateKey) ([]byte, error) {
|
||||
|
||||
@@ -42,6 +42,7 @@ import (
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/smallzstd"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/groupmember"
|
||||
"tailscale.com/util/pidowner"
|
||||
@@ -770,6 +771,25 @@ func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engi
|
||||
return smallzstd.NewDecoder(nil)
|
||||
})
|
||||
|
||||
if root := b.TailscaleVarRoot(); root != "" {
|
||||
chonkDir := filepath.Join(root, "chonk")
|
||||
if _, err := os.Stat(chonkDir); err == nil {
|
||||
// The directory exists, which means network-lock has been initialized.
|
||||
chonk, err := tka.ChonkDir(chonkDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening tailchonk: %v", err)
|
||||
}
|
||||
authority, err := tka.Open(chonk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing tka: %v", err)
|
||||
}
|
||||
b.SetTailnetKeyAuthority(authority)
|
||||
logf("tka initialized at head %x", authority.Head())
|
||||
}
|
||||
} else {
|
||||
logf("network-lock unavailable; no state directory")
|
||||
}
|
||||
|
||||
dg := distro.Get()
|
||||
switch dg {
|
||||
case distro.Synology, distro.TrueNAS, distro.QNAP:
|
||||
|
||||
@@ -545,7 +545,7 @@ func (h *Handler) serveFileTargets(w http.ResponseWriter, r *http.Request) {
|
||||
//
|
||||
// URL format:
|
||||
//
|
||||
// * PUT /localapi/v0/file-put/:stableID/:escaped-filename
|
||||
// - PUT /localapi/v0/file-put/:stableID/:escaped-filename
|
||||
func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "file access denied", http.StatusForbidden)
|
||||
|
||||
@@ -305,8 +305,8 @@ func (bc *BackendClient) RequestStatus() {
|
||||
// MaxMessageSize is the maximum message size, in bytes.
|
||||
const MaxMessageSize = 10 << 20
|
||||
|
||||
// TODO(apenwarr): incremental json decode?
|
||||
// That would let us avoid storing the whole byte array uselessly in RAM.
|
||||
// TODO(apenwarr): incremental json decode? That would let us avoid
|
||||
// storing the whole byte array uselessly in RAM.
|
||||
func ReadMsg(r io.Reader) ([]byte, error) {
|
||||
cb := make([]byte, 4)
|
||||
_, err := io.ReadFull(r, cb)
|
||||
@@ -328,10 +328,11 @@ func ReadMsg(r io.Reader) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// TODO(apenwarr): incremental json encode?
|
||||
// That would save RAM, at the expense of having to encode once so that
|
||||
// we can produce the initial byte count.
|
||||
func WriteMsg(w io.Writer, b []byte) error {
|
||||
// TODO(apenwarr): incremental json encode? That would save RAM, at the
|
||||
// expense of having to encode once so that we can produce the initial byte
|
||||
// count.
|
||||
|
||||
// TODO(bradfitz): this does two writes to w, which likely
|
||||
// does two writes on the wire, two frame generations, etc. We
|
||||
// should take a concrete buffered type, or use a sync.Pool to
|
||||
|
||||
@@ -34,6 +34,10 @@ const (
|
||||
// the server should start with the Prefs JSON loaded from
|
||||
// StateKey "user-1234".
|
||||
ServerModeStartKey = StateKey("server-mode-start-key")
|
||||
|
||||
// NLKeyStateKey is the key under which we store the nodes'
|
||||
// network-lock node key, in its key.NLPrivate.MarshalText representation.
|
||||
NLKeyStateKey = StateKey("_nl-node-key")
|
||||
)
|
||||
|
||||
// StateStore persists state, and produces it back on request.
|
||||
|
||||
@@ -49,13 +49,13 @@ var knownStores map[string]Provider
|
||||
//
|
||||
// By default the following stores are registered:
|
||||
//
|
||||
// * if the string begins with "mem:", the suffix
|
||||
// - if the string begins with "mem:", the suffix
|
||||
// is ignored and an in-memory store is used.
|
||||
// * (Linux-only) if the string begins with "arn:",
|
||||
// - (Linux-only) if the string begins with "arn:",
|
||||
// the suffix an AWS ARN for an SSM.
|
||||
// * (Linux-only) if the string begins with "kube:",
|
||||
// - (Linux-only) if the string begins with "kube:",
|
||||
// the suffix is a Kubernetes secret name
|
||||
// * In all other cases, the path is treated as a filepath.
|
||||
// - In all other cases, the path is treated as a filepath.
|
||||
func New(logf logger.Logf, path string) (ipn.StateStore, error) {
|
||||
regOnce.Do(registerDefaultStores)
|
||||
for prefix, sf := range knownStores {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package logtail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
@@ -51,9 +50,7 @@ func (m *memBuffer) TryReadLine() ([]byte, error) {
|
||||
case ent := <-m.pending:
|
||||
if ent.dropCount > 0 {
|
||||
m.next = ent.msg
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "----------- %d logs dropped ----------", ent.dropCount)
|
||||
return buf.Bytes(), nil
|
||||
return fmt.Appendf(nil, "----------- %d logs dropped ----------", ent.dropCount), nil
|
||||
}
|
||||
return ent.msg, nil
|
||||
default:
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/syncs"
|
||||
tslogger "tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
@@ -270,7 +269,7 @@ func (l *Logger) drainPending(scratch []byte) (res []byte) {
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
b = []byte(fmt.Sprintf("reading ringbuffer: %v", err))
|
||||
b = fmt.Appendf(nil, "reading ringbuffer: %v", err)
|
||||
batchDone = true
|
||||
} else if b == nil {
|
||||
if entries > 0 {
|
||||
@@ -448,15 +447,15 @@ func (l *Logger) Flush() error {
|
||||
}
|
||||
|
||||
// logtailDisabled is whether logtail uploads to logcatcher are disabled.
|
||||
var logtailDisabled syncs.AtomicBool
|
||||
var logtailDisabled atomic.Bool
|
||||
|
||||
// Disable disables logtail uploads for the lifetime of the process.
|
||||
func Disable() {
|
||||
logtailDisabled.Set(true)
|
||||
logtailDisabled.Store(true)
|
||||
}
|
||||
|
||||
func (l *Logger) sendLocked(jsonBlob []byte) (int, error) {
|
||||
if logtailDisabled.Get() {
|
||||
if logtailDisabled.Load() {
|
||||
return len(jsonBlob), nil
|
||||
}
|
||||
n, err := l.buffer.Write(jsonBlob)
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
@@ -394,10 +393,11 @@ func (m windowsManager) getBasePrimaryResolver() (resolvers []netip.Addr, err er
|
||||
|
||||
ipLoop:
|
||||
for _, stdip := range ips {
|
||||
ip, ok := netaddr.FromStdIP(stdip)
|
||||
ip, ok := netip.AddrFromSlice(stdip)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ip = ip.Unmap()
|
||||
// Skip IPv6 site-local resolvers. These are an ancient
|
||||
// and obsolete IPv6 RFC, which Windows still faithfully
|
||||
// implements. The net result is that some low-metric
|
||||
|
||||
@@ -31,7 +31,9 @@ import (
|
||||
// In other cases, resolved may be managing the system DNS configuration directly.
|
||||
// Then the nameserver list will be a concatenation of those for all
|
||||
// the interfaces that register their interest in being a default resolver with
|
||||
// SetLinkDomains([]{{"~.", true}, ...})
|
||||
//
|
||||
// SetLinkDomains([]{{"~.", true}, ...})
|
||||
//
|
||||
// which includes at least the interface with the default route, i.e. not us.
|
||||
// This does not work for us: there is a possibility of getting NXDOMAIN
|
||||
// from the other nameservers before we are asked or get a chance to respond.
|
||||
|
||||
@@ -428,8 +428,8 @@ func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, resolver *net.Resolve
|
||||
return handleError(err)
|
||||
}
|
||||
for _, stdIP := range ips {
|
||||
if ip, ok := netaddr.FromStdIP(stdIP); ok {
|
||||
resp.IPs = append(resp.IPs, ip)
|
||||
if ip, ok := netip.AddrFromSlice(stdIP); ok {
|
||||
resp.IPs = append(resp.IPs, ip.Unmap())
|
||||
}
|
||||
}
|
||||
case dns.TypeTXT:
|
||||
@@ -1025,11 +1025,11 @@ const (
|
||||
// https://tools.ietf.org/html/rfc6763 lists
|
||||
// "five special RR names" for Bonjour service discovery:
|
||||
//
|
||||
// b._dns-sd._udp.<domain>.
|
||||
// db._dns-sd._udp.<domain>.
|
||||
// r._dns-sd._udp.<domain>.
|
||||
// dr._dns-sd._udp.<domain>.
|
||||
// lb._dns-sd._udp.<domain>.
|
||||
// b._dns-sd._udp.<domain>.
|
||||
// db._dns-sd._udp.<domain>.
|
||||
// r._dns-sd._udp.<domain>.
|
||||
// dr._dns-sd._udp.<domain>.
|
||||
// lb._dns-sd._udp.<domain>.
|
||||
func hasRDNSBonjourPrefix(name dnsname.FQDN) bool {
|
||||
s := name.WithTrailingDot()
|
||||
base, rest, ok := strings.Cut(s, ".")
|
||||
@@ -1063,9 +1063,12 @@ func rawNameToLower(name []byte) string {
|
||||
// ptrNameToIPv4 transforms a PTR name representing an IPv4 address to said address.
|
||||
// Such names are IPv4 labels in reverse order followed by .in-addr.arpa.
|
||||
// For example,
|
||||
// 4.3.2.1.in-addr.arpa
|
||||
//
|
||||
// 4.3.2.1.in-addr.arpa
|
||||
//
|
||||
// is transformed to
|
||||
// 1.2.3.4
|
||||
//
|
||||
// 1.2.3.4
|
||||
func rdnsNameToIPv4(name dnsname.FQDN) (ip netip.Addr, ok bool) {
|
||||
s := strings.TrimSuffix(name.WithTrailingDot(), rdnsv4Suffix)
|
||||
ip, err := netip.ParseAddr(s)
|
||||
@@ -1082,9 +1085,12 @@ func rdnsNameToIPv4(name dnsname.FQDN) (ip netip.Addr, ok bool) {
|
||||
// ptrNameToIPv6 transforms a PTR name representing an IPv6 address to said address.
|
||||
// Such names are dot-separated nibbles in reverse order followed by .ip6.arpa.
|
||||
// For example,
|
||||
// b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
|
||||
//
|
||||
// b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
|
||||
//
|
||||
// is transformed to
|
||||
// 2001:db8::567:89ab
|
||||
//
|
||||
// 2001:db8::567:89ab
|
||||
func rdnsNameToIPv6(name dnsname.FQDN) (ip netip.Addr, ok bool) {
|
||||
var b [32]byte
|
||||
var ipb [16]byte
|
||||
@@ -1118,7 +1124,7 @@ func rdnsNameToIPv6(name dnsname.FQDN) (ip netip.Addr, ok bool) {
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
return netaddr.IPFrom16(ipb), true
|
||||
return netip.AddrFrom16(ipb), true
|
||||
}
|
||||
|
||||
// respondReverse returns a DNS response to a PTR query.
|
||||
@@ -1215,7 +1221,7 @@ func unARPA(a string) (ipStr string, ok bool) {
|
||||
}
|
||||
}
|
||||
hex.Decode(a16[:], hx[:])
|
||||
return netaddr.IPFrom16(a16).String(), true
|
||||
return netip.AddrFrom16(a16).Unmap().String(), true
|
||||
}
|
||||
return "", false
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user