Compare commits

...

29 Commits

Author SHA1 Message Date
Brad Fitzpatrick
698defd54b syncs, all: move to using Go's new atomic types instead of ours
Fixes #5185

Change-Id: I850dd532559af78c3895e2924f8237ccc328449d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-03 21:51:42 -07:00
Brad Fitzpatrick
c378a9900c logtail, net/portmapper, wgengine/magicsock: use fmt.Appendf
Fixes #5206

Change-Id: I490bb92e774ce7c044040537e2cd864fcf1dbe5a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-03 21:29:11 -07:00
Mihai Parparita
4aa88bc2c0 cmd/tsconnect,util/precompress: move precompression to its own package
We have very similar code in corp, moving it to util/precompress allows
it to be reused.

Updates #5133

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-03 17:44:57 -07:00
Mihai Parparita
dfcef3382e cmd/tsconnect: add README with instructions
Outlines basic development, build and serving workflows.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-03 17:17:42 -07:00
Brad Fitzpatrick
9e5954c598 control/controlclient: fix crash in tests elsewhere when GetNLPublicKey is nil
4001d0bf25 caused tests in another repo to fail with a crash, calling
a nil func. This might not be the right fix, but fixes the build.

Change-Id: I67263f883c298f307abdd22bc2a30b3393f062e6
Co-authored-by: Maisem Ali <maisem@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-03 16:39:06 -07:00
Tom DNetto
8cfd775885 tka,types/key: implement direct node-key signatures
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-03 15:42:27 -07:00
Tom DNetto
c13fab2a67 tka: add attack-scenario unit tests, defensive checks, resolve TODOs
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-03 15:42:09 -07:00
Tom DNetto
4001d0bf25 assorted: plumb tka initialization & network-lock key into tailscaled
- A network-lock key is generated if it doesn't already exist, and stored in the StateStore. The public component is communicated to control during registration.
 - If TKA state exists on the filesystem, a tailnet key authority is initialized (but nothing is done with it for now).

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-03 14:51:47 -07:00
Tom DNetto
8d45d7e312 types/key: make NLPublic complement to NLPrivate
Forgot that I would need that in control. Oops.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-03 14:51:47 -07:00
Maisem Ali
be5eadbecc tsnet: log out ephemeral nodes on Close()
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-03 14:27:04 -07:00
Maisem Ali
95d43c54bf cmd/{cloner,viewer}: add support for map values with pointers
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-03 13:02:17 -07:00
Maisem Ali
26f103473c cmd/viewer: add support for map of structs without pointers
This adds support for fields like `map[string]netaddr.IPPrefix`.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-03 13:02:17 -07:00
Mihai Parparita
adc5ffea99 cmd/tsconnect: make PeerAPI work
JS -> native nodes worked already, tested by exposing a fetch() method
to JS (it's Promise-based to be consistent with the native fetch() API).

Native nodes -> JS almost worked, we just needed to set the LocalBackend
on the userspace netstack.

Fixes #5130

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-03 10:39:47 -07:00
Brad Fitzpatrick
5f6abcfa6f all: migrate code from netaddr.FromStdAddr to Go 1.18
With caveat https://github.com/golang/go/issues/53607#issuecomment-1203466984
that then requires a new wrapper. But a simpler one at least.

Updates #5162

Change-Id: I0a5265065bfcd7f21e8dd65b2bd74cae90d76090
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-02 22:25:07 -07:00
Brad Fitzpatrick
7c7e23d87a control/controlclient, tailcfg: add 6 more patchable Node fields [capver 36]
Change-Id: Iae997a9a98a5dd841bc41fa91227d5a7dd476a25
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-02 21:14:00 -07:00
Mihai Parparita
52d769d35c cmd/tsconnect: prefetch main.wasm when serving
Avoids waterfalling of requests from the file (its load is triggered
from JavaScript).

Also has other cleanups to index.html, adding a <title> and moving the
<script> to being loaded sooner (but still not delaying page rendering
by using the defer attribute).

Fixes #5141
Fixes #5135

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-02 16:52:06 -07:00
Mihai Parparita
f04bc31820 cmd/tsconnect: add -fast-compression option
Changes Gzip and Brotli to optimize for speed instead of size. This
signficantly speeds up Brotli, and is useful when iterating locally
or running the build during a CI job (where we just care that it
can successfully build).

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-02 16:52:06 -07:00
Mihai Parparita
9a2171e4ea cmd/tsconnect: make terminal resizable
Makes the terminal container DOM node as large as the window (except for
the header) via flexbox. The xterm.js terminal is then sized to fit via
xterm-addon-fit. Once we have a computed rows/columns size, and we can
tell the SSH session of the computed size.

Required introducing an IPNSSHSession type to allow the JS to control
the SSH session once opened. That alse allows us to programatically
close it, which we do when the user closes the window with the session
still active.

I initially wanted to open the terminal in a new window instead (so that
it could be resizable independently of the main window), but xterm.js
does not appear to work well in that mode (possibly because it adds an
IntersectionObserver to pause rendering when the window is not visible,
and it ends up doing that when the parent window is hidden -- see
xtermjs/xterm.js@87dca56dee)

Fixes #5150

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-02 15:30:40 -07:00
Brad Fitzpatrick
8725b14056 all: migrate more code code to net/netip directly
Instead of going through the tailscale.com/net/netaddr transitional
wrappers.

Updates #5162

Change-Id: I3dafd1c2effa1a6caa9b7151ecf6edd1a3fda3dd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-02 13:59:57 -07:00
Maisem Ali
eb32847d85 tailcfg: add CapabilityFileSharingTarget to identify FileTargets
This adds the inverse to CapabilityFileSharingSend so that senders can
identify who they can Taildrop to.

Updates #2101

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-02 13:52:10 -07:00
Mihai Parparita
9dfcdbf478 .github/workflows: put back CLI in cross-wasm GitHub action
Since we're keeping the JS support in the CLI (see https://github.com/tailscale/tailscale/pull/5265#issuecomment-1203078243),
make sure it keeps building.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-02 12:39:35 -07:00
Mihai Parparita
e846481731 cmd/tailscale/cli: use printf and outln consistently
Fix some fmt.Println and fmt.Printf calls that crept in since
5df7ac70d6.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-02 12:37:45 -07:00
Maisem Ali
02a765743e ssh/tailssh: fix deadlock in expandDelegateURL
Also rename it to expandDelegateURLLocked, previously it was trying
to acquire the mutex while holding the mutex.

Fixes #5235

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-02 12:25:09 -07:00
Brad Fitzpatrick
e1309e1323 all: require Go 1.19
Updates #5210

Change-Id: I2e950b4776636b4ea89b6566b60e4a87596a3a43
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-02 11:49:01 -07:00
Brad Fitzpatrick
fb82299f5a wgengine/magicsock: avoid RebindingUDPConn mutex in common read/write case
Change-Id: I209fac567326f2e926bace2582dbc67a8bc94c78
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-02 11:27:10 -07:00
Brad Fitzpatrick
116f55ff66 all: gofmt for Go 1.19
Updates #5210

Change-Id: Ib02cd5e43d0a8db60c1f09755a8ac7b140b670be
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-02 10:08:05 -07:00
Maisem Ali
a029989aff types/dnstype: use viewer instead of cloner
This was missed when I did the initial viewer work.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-02 09:58:53 -07:00
Joe Tsai
57275a4912 tsweb: add HTTPError.Header (#5251)
The Header field allows the server to specify specific headers to set.
Example use case: server returns 429 with the "Retry-After" header set.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-01 22:57:04 -07:00
Joe Tsai
a794963e2f tsweb: mark AccessLogRecord fields as omitempty (#5250)
If the field is the zero value, then avoid serializing the field.
This reduces verbosity in server logs.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-01 21:17:38 -07:00
151 changed files with 1746 additions and 861 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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 (

View File

@@ -20,6 +20,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"text/tabwriter"
@@ -29,7 +30,6 @@ import (
"tailscale.com/ipn"
"tailscale.com/paths"
"tailscale.com/safesocket"
"tailscale.com/syncs"
"tailscale.com/version/distro"
)
@@ -230,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)

View File

@@ -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
}

View File

@@ -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())
}
}
}
@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
@@ -570,9 +571,9 @@ 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(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
@@ -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))
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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+

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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"

View File

@@ -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
View 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.

View File

@@ -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)
}

View File

@@ -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"`
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -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()

View File

@@ -73,3 +73,7 @@
background-color: currentColor;
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
}
body.ssh-active #ssh-form {
@apply hidden;
}

View File

@@ -52,3 +52,7 @@ function handleGoPanic(err?: string) {
}
let panicNode: HTMLDivElement | undefined
export function getContentNode(): HTMLDivElement {
return document.querySelector("#content") as HTMLDivElement
}

View File

@@ -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() {

View File

@@ -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 ?? ""
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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() {

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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.

View File

@@ -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]

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -1 +1 @@
effe2d16a9ed6c4fd97361e8090bb22acc221075
6dca83b256c7decd3dd6706ee47e04f21a0b935c

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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()
}

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -21,7 +21,6 @@ import (
"time"
"tailscale.com/envknob"
"tailscale.com/net/netaddr"
"tailscale.com/util/cloudenv"
"tailscale.com/util/singleflight"
)
@@ -312,7 +311,7 @@ func (r *Resolver) lookupIP(host string) (ip, ip6 net.IP, allIPs []net.IPAddr, e
}
func (r *Resolver) addIPCache(host string, ip, ip6 net.IP, allIPs []net.IPAddr, d time.Duration) {
if naIP, _ := netaddr.FromStdIP(ip); naIP.IsPrivate() {
if ip.IsPrivate() {
// Don't cache obviously wrong entries from captive portals.
// TODO: use DoH or DoT for the forwarding resolver?
if debug {
@@ -400,16 +399,16 @@ func (d *dialer) DialContext(ctx context.Context, network, address string) (retC
if debug {
log.Printf("dnscache: dialing %s, %s for %s", network, ip, address)
}
ipNA, ok := netaddr.FromStdIP(ip)
ipNA, ok := netip.AddrFromSlice(ip)
if !ok {
return nil, fmt.Errorf("invalid IP %q", ip)
}
c, err := dc.dialOne(ctx, ipNA)
c, err := dc.dialOne(ctx, ipNA.Unmap())
if err == nil || ctx.Err() != nil {
return c, err
}
// Fall back to trying IPv6, if any.
ip6NA, ok := netaddr.FromStdIP(ip6)
ip6NA, ok := netip.AddrFromSlice(ip6)
if !ok {
return nil, err
}
@@ -583,7 +582,9 @@ func (dc *dialCall) raceDial(ctx context.Context, ips []netip.Addr) (net.Conn, e
func v4addrs(aa []net.IPAddr) (ret []netip.Addr) {
for _, a := range aa {
if ip, ok := netaddr.FromStdIP(a.IP); ok && ip.Is4() {
ip, ok := netip.AddrFromSlice(a.IP)
ip = ip.Unmap()
if ok {
ret = append(ret, ip)
}
}
@@ -592,7 +593,7 @@ func v4addrs(aa []net.IPAddr) (ret []netip.Addr) {
func v6addrs(aa []net.IPAddr) (ret []netip.Addr) {
for _, a := range aa {
if ip, ok := netaddr.FromStdIP(a.IP); ok && ip.Is6() {
if ip, ok := netip.AddrFromSlice(a.IP); ok && ip.Is6() {
ret = append(ret, ip)
}
}

View File

@@ -44,7 +44,8 @@ func Tailscale() ([]netip.Addr, *net.Interface, error) {
var tsIPs []netip.Addr
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok {
nip, ok := netaddr.FromStdIP(ipnet.IP)
nip, ok := netip.AddrFromSlice(ipnet.IP)
nip = nip.Unmap()
if ok && tsaddr.IsTailscaleIP(nip) {
tsIPs = append(tsIPs, nip)
}
@@ -110,10 +111,11 @@ func LocalAddresses() (regular, loopback []netip.Addr, err error) {
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
ip, ok := netaddr.FromStdIP(v.IP)
ip, ok := netip.AddrFromSlice(v.IP)
if !ok {
continue
}
ip = ip.Unmap()
// TODO(apenwarr): don't special case cgNAT.
// In the general wireguard case, it might
// very well be something we can route to

View File

@@ -39,7 +39,6 @@ default link#14 UCSI utun2
10/16 link#4 UCS en0 !
10.0.0.1/32 link#4 UCS en0 !
...
*/
func likelyHomeRouterIPDarwinExec() (ret netip.Addr, ok bool) {
if version.IsMobile() {

View File

@@ -17,13 +17,13 @@ import (
"os/exec"
"runtime"
"strings"
"sync/atomic"
"github.com/jsimonetti/rtnetlink"
"github.com/mdlayher/netlink"
"go4.org/mem"
"golang.org/x/sys/unix"
"tailscale.com/net/netaddr"
"tailscale.com/syncs"
"tailscale.com/util/lineread"
)
@@ -31,7 +31,7 @@ func init() {
likelyHomeRouterIP = likelyHomeRouterIPLinux
}
var procNetRouteErr syncs.AtomicBool
var procNetRouteErr atomic.Bool
// errStopReading is a sentinel error value used internally by
// lineread.File callers to stop reading. It doesn't escape to
@@ -47,7 +47,7 @@ ens18 00000000 0100000A 0003 0 0 0 00000000
ens18 0000000A 00000000 0001 0 0 0 0000FFFF 0 0 0
*/
func likelyHomeRouterIPLinux() (ret netip.Addr, ok bool) {
if procNetRouteErr.Get() {
if procNetRouteErr.Load() {
// If we failed to read /proc/net/route previously, don't keep trying.
// But if we're on Android, go into the Android path.
if runtime.GOOS == "android" {
@@ -93,7 +93,7 @@ func likelyHomeRouterIPLinux() (ret netip.Addr, ok bool) {
err = nil
}
if err != nil {
procNetRouteErr.Set(true)
procNetRouteErr.Store(true)
if runtime.GOOS == "android" {
return likelyHomeRouterIPAndroid()
}

View File

@@ -15,7 +15,6 @@ import (
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/net/netaddr"
"tailscale.com/tsconst"
)
@@ -65,11 +64,12 @@ func likelyHomeRouterIPWindows() (ret netip.Addr, ok bool) {
continue
}
ip, ok := netaddr.FromStdIP(r.NextHop.IP())
ip, ok := netip.AddrFromSlice(r.NextHop.IP())
if !ok {
// Not a valid gateway, so skip (won't happen though)
continue
}
ip = ip.Unmap()
if best == nil {
best = r

View File

@@ -10,7 +10,6 @@
package netaddr
import (
"math"
"net"
"net/netip"
)
@@ -20,42 +19,21 @@ func IPv4(a, b, c, d uint8) netip.Addr {
return netip.AddrFrom4([4]byte{a, b, c, d})
}
// IPFrom16 returns the IP address given by the bytes in addr, unmapping any
// v6-mapped IPv4 address.
// Unmap returns the provided AddrPort with its Addr IP component Unmap'ed.
//
// It is equivalent to calling IPv6Raw(addr).Unmap().
func IPFrom16(a [16]byte) netip.Addr {
return netip.AddrFrom16(a).Unmap()
}
// FromStdIP returns an IP from the standard library's IP type.
//
// If std is invalid, ok is false.
//
// FromStdIP implicitly unmaps IPv6-mapped IPv4 addresses. That is, if
// len(std) == 16 and contains an IPv4 address, only the IPv4 part is
// returned, without the IPv6 wrapper. This is the common form returned by
// the standard library's ParseIP: https://play.golang.org/p/qdjylUkKWxl.
// To convert a standard library IP without the implicit unmapping, use
// netip.AddrFromSlice.
func FromStdIP(std net.IP) (ip netip.Addr, ok bool) {
ret, ok := netip.AddrFromSlice(std)
if !ok {
return ret, false
}
if ret.Is4In6() {
return ret.Unmap(), true
}
return ret, true
// See https://github.com/golang/go/issues/53607#issuecomment-1203466984
func Unmap(ap netip.AddrPort) netip.AddrPort {
return netip.AddrPortFrom(ap.Addr().Unmap(), ap.Port())
}
// FromStdIPNet returns an IPPrefix from the standard library's IPNet type.
// If std is invalid, ok is false.
func FromStdIPNet(std *net.IPNet) (prefix netip.Prefix, ok bool) {
ip, ok := FromStdIP(std.IP)
ip, ok := netip.AddrFromSlice(std.IP)
if !ok {
return netip.Prefix{}, false
}
ip = ip.Unmap()
if l := len(std.Mask); l != net.IPv4len && l != net.IPv6len {
// Invalid mask.
@@ -70,21 +48,3 @@ func FromStdIPNet(std *net.IPNet) (prefix netip.Prefix, ok bool) {
return netip.PrefixFrom(ip, ones), true
}
// FromStdAddr maps the components of a standard library TCPAddr or
// UDPAddr into an IPPort.
func FromStdAddr(stdIP net.IP, port int, zone string) (_ netip.AddrPort, ok bool) {
ip, ok := FromStdIP(stdIP)
if !ok || port < 0 || port > math.MaxUint16 {
return
}
ip = ip.Unmap()
if zone != "" {
if ip.Is4() {
ok = false
return
}
ip = ip.WithZone(zone)
}
return netip.AddrPortFrom(ip, uint16(port)), true
}

View File

@@ -273,7 +273,8 @@ func (c *Client) ReceiveSTUNPacket(pkt []byte, src netip.AddrPort) {
}
rs.mu.Unlock()
if ok {
if ipp, ok := netaddr.FromStdAddr(addr, int(port), ""); ok {
ta := net.TCPAddr{IP: addr, Port: int(port)}
if ipp := netaddr.Unmap(ta.AddrPort()); ipp.IsValid() {
onDone(ipp)
}
}
@@ -516,8 +517,8 @@ func (c *Client) readPackets(ctx context.Context, pc net.PacketConn) {
if !stun.Is(pkt) {
continue
}
if ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone); ok {
c.ReceiveSTUNPacket(pkt, ipp)
if ap := netaddr.Unmap(ua.AddrPort()); ap.IsValid() {
c.ReceiveSTUNPacket(pkt, ap)
}
}
}
@@ -1031,7 +1032,8 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
defer tcpConn.Close()
if ta, ok := tlsConn.RemoteAddr().(*net.TCPAddr); ok {
ip, _ = netaddr.FromStdIP(ta.IP)
ip, _ = netip.AddrFromSlice(ta.IP)
ip = ip.Unmap()
}
if ip == (netip.Addr{}) {
return 0, ip, fmt.Errorf("no unexpected RemoteAddr %#v", tlsConn.RemoteAddr())
@@ -1328,8 +1330,8 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
addrs, _ := net.DefaultResolver.LookupIPAddr(ctx, n.HostName)
for _, a := range addrs {
if (a.IP.To4() != nil) == (proto == probeIPv4) {
na, _ := netaddr.FromStdIP(a.IP.To4())
return netip.AddrPortFrom(na, uint16(port))
na, _ := netip.AddrFromSlice(a.IP.To4())
return netip.AddrPortFrom(na.Unmap(), uint16(port))
}
}
return

View File

@@ -18,25 +18,25 @@ import (
"context"
"net"
"net/netip"
"sync/atomic"
"tailscale.com/net/netknob"
"tailscale.com/syncs"
"tailscale.com/types/logger"
)
var disabled syncs.AtomicBool
var disabled atomic.Bool
// SetEnabled enables or disables netns for the process.
// It defaults to being enabled.
func SetEnabled(on bool) {
disabled.Set(!on)
disabled.Store(!on)
}
// Listener returns a new net.Listener with its Control hook func
// initialized as necessary to run in logical network namespace that
// doesn't route back into Tailscale.
func Listener(logf logger.Logf) *net.ListenConfig {
if disabled.Get() {
if disabled.Load() {
return new(net.ListenConfig)
}
return &net.ListenConfig{Control: control(logf)}
@@ -57,7 +57,7 @@ func NewDialer(logf logger.Logf) Dialer {
// handles using a SOCKS if configured in the environment with
// ALL_PROXY.
func FromDialer(logf logger.Logf, d *net.Dialer) Dialer {
if disabled.Get() {
if disabled.Load() {
return d
}
d.Control = control(logf)

View File

@@ -164,7 +164,7 @@ func ipport4(addr uint32, port uint16) netip.AddrPort {
}
func ipport6(addr [16]byte, scope uint32, port uint16) netip.AddrPort {
ip := netaddr.IPFrom16(addr)
ip := netip.AddrFrom16(addr).Unmap()
if scope != 0 {
// TODO: something better here?
ip = ip.WithZone(fmt.Sprint(scope))

View File

@@ -250,8 +250,8 @@ func (q *Parsed) decode6(b []byte) {
// okay to ignore `ok` here, because IPs pulled from packets are
// always well-formed stdlib IPs.
srcIP, _ := netaddr.FromStdIP(net.IP(b[8:24]))
dstIP, _ := netaddr.FromStdIP(net.IP(b[24:40]))
srcIP, _ := netip.AddrFromSlice(net.IP(b[8:24]))
dstIP, _ := netip.AddrFromSlice(net.IP(b[24:40]))
q.Src = withIP(q.Src, srcIP)
q.Dst = withIP(q.Dst, dstIP)

View File

@@ -25,13 +25,13 @@ import (
// TCP RST, this includes a reason.
//
// On the wire, after the IP header, it's currently 7 or 8 bytes:
// * '!'
// * IPProto byte (IANA protocol number: TCP or UDP)
// * 'A' or 'S' (RejectedDueToACLs, RejectedDueToShieldsUp)
// * srcPort big endian uint16
// * dstPort big endian uint16
// * [optional] byte of flag bits:
// lowest bit (0x1): MaybeBroken
// - '!'
// - IPProto byte (IANA protocol number: TCP or UDP)
// - 'A' or 'S' (RejectedDueToACLs, RejectedDueToShieldsUp)
// - srcPort big endian uint16
// - dstPort big endian uint16
// - [optional] byte of flag bits:
// lowest bit (0x1): MaybeBroken
//
// In the future it might also accept 16 byte IP flow src/dst IPs
// after the header, if they're different than the IP-level ones.
@@ -205,8 +205,8 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
// TSMPPingRequest is a TSMP message that's like an ICMP ping request.
//
// On the wire, after the IP header, it's currently 9 bytes:
// * 'p' (TSMPTypePing)
// * 8 opaque ping bytes to copy back in the response
// - 'p' (TSMPTypePing)
// - 8 opaque ping bytes to copy back in the response
type TSMPPingRequest struct {
Data [8]byte
}

View File

@@ -12,10 +12,10 @@ import (
"net/http/httptest"
"net/netip"
"sync"
"sync/atomic"
"testing"
"tailscale.com/net/netaddr"
"tailscale.com/syncs"
"tailscale.com/types/logger"
)
@@ -26,7 +26,7 @@ type TestIGD struct {
pxpConn net.PacketConn // for NAT-PMP and/or PCP
ts *httptest.Server
logf logger.Logf
closed syncs.AtomicBool
closed atomic.Bool
// do* will log which packets are sent, but will not reply to unexpected packets.
@@ -143,7 +143,7 @@ func (d *TestIGD) serveUPnPDiscovery() {
pkt := buf[:n]
if bytes.Equal(pkt, uPnPPacket) { // a super lazy "parse"
d.inc(&d.counters.numUPnPDiscoRecv)
resPkt := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=120\r\nST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nUSN: uuid:bee7052b-49e8-3597-b545-55a1e38ac11::urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nEXT:\r\nSERVER: Tailscale-Test/1.0 UPnP/1.1 MiniUPnPd/2.2.1\r\nLOCATION: %s\r\nOPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n01-NLS: 1627958564\r\nBOOTID.UPNP.ORG: 1627958564\r\nCONFIGID.UPNP.ORG: 1337\r\n\r\n", d.ts.URL+"/rootDesc.xml"))
resPkt := fmt.Appendf(nil, "HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=120\r\nST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nUSN: uuid:bee7052b-49e8-3597-b545-55a1e38ac11::urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nEXT:\r\nSERVER: Tailscale-Test/1.0 UPnP/1.1 MiniUPnPd/2.2.1\r\nLOCATION: %s\r\nOPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n01-NLS: 1627958564\r\nBOOTID.UPNP.ORG: 1627958564\r\nCONFIGID.UPNP.ORG: 1337\r\n\r\n", d.ts.URL+"/rootDesc.xml")
if d.doUPnP {
_, err = d.upnpConn.WriteTo(resPkt, src)
if err != nil {
@@ -167,9 +167,8 @@ func (d *TestIGD) servePxP() {
}
return
}
ua := a.(*net.UDPAddr)
src, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone)
if !ok {
src := netaddr.Unmap(a.(*net.UDPAddr).AddrPort())
if !src.IsValid() {
panic("bogus addr")
}
pkt := buf[:n]
@@ -214,7 +213,7 @@ func (d *TestIGD) handlePCPQuery(pkt []byte, src netip.AddrPort) {
op := pkt[1]
pktSrcBytes := [16]byte{}
copy(pktSrcBytes[:], pkt[8:24])
pktSrc := netaddr.IPFrom16(pktSrcBytes)
pktSrc := netip.AddrFrom16(pktSrcBytes).Unmap()
if pktSrc != src.Addr() {
// TODO this error isn't fatal but should be rejected by server.
// Since it's a test it's difficult to get them the same though.

View File

@@ -11,8 +11,6 @@ import (
"fmt"
"net/netip"
"time"
"tailscale.com/net/netaddr"
)
// References:
@@ -126,7 +124,7 @@ func parsePCPMapResponse(resp []byte) (*pcpMapping, error) {
externalPort := binary.BigEndian.Uint16(resp[42:44])
externalIPBytes := [16]byte{}
copy(externalIPBytes[:], resp[44:])
externalIP := netaddr.IPFrom16(externalIPBytes)
externalIP := netip.AddrFrom16(externalIPBytes).Unmap()
external := netip.AddrPortFrom(externalIP, externalPort)

Some files were not shown because too many files have changed in this diff Show More