Compare commits

..

10 Commits

Author SHA1 Message Date
Shayne Sweeney
1dc9edde90 cmd/tailscale/cli: [web] update JS in web.html for Unraid support
Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
2023-05-16 20:34:30 -04:00
Derek Kaser
b15d8525d0 cmd/tailscale: allow Tailscale to work with Unraid web interface
Updates tailscale/tailscale#8026

Signed-off-by: Derek Kaser <derek.kaser@gmail.com>
2023-05-06 22:35:02 -04:00
Derek Kaser
0d7303b798 various: add detection and Taildrop for Unraid
Updates tailscale/tailscale#8025

Signed-off-by: Derek Kaser <derek.kaser@gmail.com>
2023-05-04 13:40:13 -07:00
Flakes Updater
d1ce7a9b5e go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
2023-05-04 12:55:01 -07:00
James Tucker
5def4f4a1c go.mod: bump goreleaser deps
Periodic update for start of cycle. goreleaser is not updated to v2 yet,
but indirects updated.

Updates #8043

Signed-off-by: James Tucker <james@tailscale.com>
2023-05-04 12:32:24 -07:00
Flakes Updater
1c6ff310ae go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
2023-05-04 12:31:31 -07:00
James Tucker
48605226dd go.mod: bump gvisor
Periodic update for start of cycle.

Updates #8043

Signed-off-by: James Tucker <james@tailscale.com>
2023-05-04 12:30:27 -07:00
Maisem Ali
f46c1aede0 go.mod: bump k8s libs
The key is to update sigs.k8s.io/controller-runtime and let it update others.

Updates #8043

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-05-04 12:00:03 -07:00
Brad Fitzpatrick
73d128238e envknob: support tailscaled-env.txt on macOS too
Updates #3707

Co-authored-by: Marwan Sulaiman <marwan@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-04 10:27:59 -07:00
Anton Tolchanov
787fc41fa4 scripts/installer.sh: check connectivity with pkgs.tailscale.com
Installer script relies on pkgs.tailscale.com being reachable, both for
checking what Linux distros are supported, but also for actually
downloading repo configuration files, gpg keys and packages themselves.

This change adds a simple reachability check which will print an error
message when pkgs.tailscale.com is not reachable.

Fixes https://github.com/tailscale/corp/issues/8952

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-05-04 16:49:56 +02:00
21 changed files with 268 additions and 487 deletions

View File

@@ -1,177 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Equaler is a tool to automate the creation of an Equals method.
//
// This tool assumes that if a type you give it contains another named struct
// type, that type will also have an Equal method, and that all fields are
// comparable unless explicitly excluded.
package main
import (
"bytes"
"flag"
"fmt"
"go/token"
"go/types"
"log"
"os"
"strings"
"golang.org/x/exp/slices"
"tailscale.com/util/codegen"
)
var (
flagTypes = flag.String("type", "", "comma-separated list of types; required")
flagBuildTags = flag.String("tags", "", "compiler build tags to apply")
)
func main() {
log.SetFlags(0)
log.SetPrefix("equaler: ")
flag.Parse()
if len(*flagTypes) == 0 {
flag.Usage()
os.Exit(2)
}
typeNames := strings.Split(*flagTypes, ",")
pkg, namedTypes, err := codegen.LoadTypes(*flagBuildTags, ".")
if err != nil {
log.Fatal(err)
}
it := codegen.NewImportTracker(pkg.Types)
buf := new(bytes.Buffer)
for _, typeName := range typeNames {
typ, ok := namedTypes[typeName]
if !ok {
log.Fatalf("could not find type %s", typeName)
}
gen(buf, it, typ, typeNames)
}
cloneOutput := pkg.Name + "_equal.go"
if err := codegen.WritePackageFile("tailscale.com/cmd/equaler", pkg, cloneOutput, it, buf); err != nil {
log.Fatal(err)
}
}
func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, typeNames []string) {
t, ok := typ.Underlying().(*types.Struct)
if !ok {
return
}
name := typ.Obj().Name()
fmt.Fprintf(buf, "// Equal reports whether a and b are equal.\n")
fmt.Fprintf(buf, "func (a *%s) Equal(b *%s) bool {\n", name, name)
writef := func(format string, args ...any) {
fmt.Fprintf(buf, "\t"+format+"\n", args...)
}
writef("if a == b {")
writef("\treturn true")
writef("}")
writef("return a != nil && b != nil &&")
for i := 0; i < t.NumFields(); i++ {
fname := t.Field(i).Name()
ft := t.Field(i).Type()
// Fields which are explicitly ignored are skipped.
if codegen.HasNoEqual(t.Tag(i)) {
writef("\t// Skipping %s because of codegen:noequal", fname)
continue
}
// Fields which are named types that have an Equal() method, get that method used
if named, _ := ft.(*types.Named); named != nil {
if implementsEqual(ft) || slices.Contains(typeNames, named.Obj().Name()) {
writef("\ta.%s.Equal(b.%s) &&", fname, fname)
continue
}
}
// Fields which are just values are directly compared, unless they have an Equal() method.
if !codegen.ContainsPointers(ft) {
writef("\ta.%s == b.%s &&", fname, fname)
continue
}
switch ft := ft.Underlying().(type) {
case *types.Pointer:
if named, _ := ft.Elem().(*types.Named); named != nil {
if slices.Contains(typeNames, named.Obj().Name()) || implementsEqual(ft) {
writef("\t((a.%s == nil) == (b.%s == nil)) && (a.%s == nil || a.%s.Equal(b.%s)) &&", fname, fname, fname, fname, fname)
continue
}
if implementsEqual(ft.Elem()) {
writef("\t((a.%s == nil) == (b.%s == nil)) && (a.%s == nil || a.%s.Equal(*b.%s)) &&", fname, fname, fname, fname, fname)
continue
}
}
if !codegen.ContainsPointers(ft.Elem()) {
writef("\t((a.%s == nil) == (b.%s == nil)) && (a.%s == nil || *a.%s == *b.%s) &&", fname, fname, fname, fname, fname)
continue
}
log.Fatalf("unimplemented: %s (%T)", fname, ft)
case *types.Slice:
// Empty slices and nil slices are different.
writef("\t((a.%s == nil) == (b.%s == nil)) &&", fname, fname)
if named, _ := ft.Elem().(*types.Named); named != nil {
if implementsEqual(ft.Elem()) {
it.Import("golang.org/x/exp/slices")
writef("\tslices.EqualFunc(a.%s, b.%s, func(aa %s, bb %s) bool {return aa.Equal(bb)}) &&", fname, fname, named.Obj().Name(), named.Obj().Name())
continue
}
if slices.Contains(typeNames, named.Obj().Name()) || implementsEqual(types.NewPointer(ft.Elem())) {
it.Import("golang.org/x/exp/slices")
writef("\tslices.EqualFunc(a.%s, b.%s, func(aa %s, bb %s) bool {return aa.Equal(&bb)}) &&", fname, fname, named.Obj().Name(), named.Obj().Name())
continue
}
}
if !codegen.ContainsPointers(ft.Elem()) {
it.Import("golang.org/x/exp/slices")
writef("\tslices.Equal(a.%s, b.%s) &&", fname, fname)
continue
}
log.Fatalf("unimplemented: %s (%T)", fname, ft)
case *types.Map:
if !codegen.ContainsPointers(ft.Elem()) {
it.Import("golang.org/x/exp/maps")
writef("\tmaps.Equal(a.%s, b.%s) &&", fname, fname)
continue
}
log.Fatalf("unimplemented: %s (%T)", fname, ft)
default:
log.Fatalf("unimplemented: %s (%T)", fname, ft)
}
}
writef("\ttrue")
fmt.Fprintf(buf, "}\n\n")
buf.Write(codegen.AssertStructUnchanged(t, name, "Equal", it))
}
// hasBasicUnderlying reports true when typ.Underlying() is a slice or a map.
func hasBasicUnderlying(typ types.Type) bool {
switch typ.Underlying().(type) {
case *types.Slice, *types.Map:
return true
default:
return false
}
}
// implementsEqual reports whether typ has an Equal(typ) bool method.
func implementsEqual(typ types.Type) bool {
return types.Implements(typ, types.NewInterfaceType(
[]*types.Func{types.NewFunc(
token.NoPos, nil, "Equal", types.NewSignatureType(
types.NewVar(token.NoPos, nil, "a", typ),
nil, nil,
types.NewTuple(types.NewVar(token.NoPos, nil, "b", typ)),
types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.Bool])), false))},
[]types.Type{},
))
}

View File

@@ -66,7 +66,7 @@ func isSystemdSystem() bool {
return false
}
switch distro.Get() {
case distro.QNAP, distro.Gokrazy, distro.Synology:
case distro.QNAP, distro.Gokrazy, distro.Synology, distro.Unraid:
return false
}
_, err := exec.LookPath("systemctl")

View File

@@ -61,6 +61,8 @@ type tmplData struct {
TUNMode bool
IsSynology bool
DSMVersion int // 6 or 7, if IsSynology=true
IsUnraid bool
UnraidToken string
IPNVersion string
}
@@ -440,6 +442,8 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
LicensesURL: licensesURL(),
TUNMode: st.TUN,
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
IsUnraid: distro.Get() == distro.Unraid,
UnraidToken: os.Getenv("UNRAID_CSRF_TOKEN"),
DSMVersion: distro.DSMVersion(),
IPNVersion: versionShort,
}

View File

@@ -116,10 +116,12 @@
<a class="text-xs text-gray-500 hover:text-gray-600" href="{{ .LicensesURL }}">Open Source Licenses</a>
</footer>
<script>(function () {
const advertiseExitNode = {{.AdvertiseExitNode}};
const advertiseExitNode = {{ .AdvertiseExitNode }};
const isUnraid = {{ .IsUnraid }};
const unraidCsrfToken = "{{ .UnraidToken }}";
let fetchingUrl = false;
var data = {
AdvertiseRoutes: "{{.AdvertiseRoutes}}",
AdvertiseRoutes: "{{ .AdvertiseRoutes }}",
AdvertiseExitNode: advertiseExitNode,
Reauthenticate: false,
ForceLogout: false
@@ -141,15 +143,25 @@ function postData(e) {
}
const nextUrl = new URL(window.location);
nextUrl.search = nextParams.toString()
const url = nextUrl.toString();
let contentType = "application/json";
let body = JSON.stringify(data);
if (isUnraid) {
const params = new URLSearchParams();
params.append("csrf_token", unraidCsrfToken);
params.append("ts_data", JSON.stringify(data));
body = params.toString();
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
}
const url = nextUrl.toString();
fetch(url, {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Content-Type": contentType,
},
body: JSON.stringify(data)
body: body
}).then(res => res.json()).then(res => {
fetchingUrl = false;
const err = res["error"];
@@ -158,7 +170,11 @@ function postData(e) {
}
const url = res["url"];
if (url) {
document.location.href = url;
if(isUnraid) {
window.open(url, "_blank");
} else {
document.location.href = url;
}
} else {
location.reload();
}

View File

@@ -18,7 +18,7 @@ import (
func configureTaildrop(logf logger.Logf, lb *ipnlocal.LocalBackend) {
dg := distro.Get()
switch dg {
case distro.Synology, distro.TrueNAS, distro.QNAP:
case distro.Synology, distro.TrueNAS, distro.QNAP, distro.Unraid:
// See if they have a "Taildrop" share.
// See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319
path, err := findTaildropDir(dg)
@@ -42,6 +42,8 @@ func findTaildropDir(dg distro.Distro) (string, error) {
return findTrueNASTaildropDir(name)
case distro.QNAP:
return findQnapTaildropDir(name)
case distro.Unraid:
return findUnraidTaildropDir(name)
}
return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg)
}
@@ -103,3 +105,25 @@ func findQnapTaildropDir(name string) (string, error) {
}
return "", fmt.Errorf("shared folder %q not found", name)
}
// findUnraidTaildropDir looks for a directory linked at
// /var/lib/tailscale/Taildrop. This is a symlink to the
// path specified by the user in the Unraid Web UI
func findUnraidTaildropDir(name string) (string, error) {
dir := fmt.Sprintf("/var/lib/tailscale/%s", name)
_, err := os.Stat(dir)
if err != nil {
return "", fmt.Errorf("symlink %q not found", name)
}
fullpath, err := filepath.EvalSymlinks(dir)
if err != nil {
return "", fmt.Errorf("symlink %q to shared folder not valid", name)
}
fi, err := os.Stat(fullpath)
if err == nil && fi.IsDir() {
return dir, nil // return the symlink
}
return "", fmt.Errorf("shared folder %q not found", name)
}

View File

@@ -457,13 +457,24 @@ var applyDiskConfigErr error
// ApplyDiskConfigError returns the most recent result of ApplyDiskConfig.
func ApplyDiskConfigError() error { return applyDiskConfigErr }
// ApplyDiskConfig returns a platform-specific config file of environment keys/values and
// applies them. On Linux and Unix operating systems, it's a no-op and always returns nil.
// If no platform-specific config file is found, it also returns nil.
// ApplyDiskConfig returns a platform-specific config file of environment
// keys/values and applies them. On Linux and Unix operating systems, it's a
// no-op and always returns nil. If no platform-specific config file is found,
// it also returns nil.
//
// It exists primarily for Windows and macOS to make it easy to apply
// environment variables to a running service in a way similar to modifying
// /etc/default/tailscaled on Linux.
//
// It exists primarily for Windows to make it easy to apply environment variables to
// a running service in a way similar to modifying /etc/default/tailscaled on Linux.
// On Windows, you use %ProgramData%\Tailscale\tailscaled-env.txt instead.
//
// On macOS, use one of:
//
// - ~/Library/Containers/io.tailscale.ipn.macsys/Data/tailscaled-env.txt
// for standalone macOS GUI builds
// - ~/Library/Containers/io.tailscale.ipn.macos.network-extension/Data/tailscaled-env.txt
// for App Store builds
// - /etc/tailscale/tailscaled-env.txt for tailscaled-on-macOS (homebrew, etc)
func ApplyDiskConfig() (err error) {
var f *os.File
defer func() {
@@ -512,9 +523,15 @@ func getPlatformEnvFile() string {
return "/etc/tailscale/tailscaled-env.txt"
}
case "darwin":
// TODO(bradfitz): figure this out. There are three ways to run
// Tailscale on macOS (tailscaled, GUI App Store, GUI System Extension)
// and we should deal with all three.
if version.IsSandboxedMacOS() { // the two GUI variants (App Store or separate download)
// This will be user-visible as ~/Library/Containers/$VARIANT/Data/tailscaled-env.txt
// where $VARIANT is "io.tailscale.ipn.macsys" for macsys (downloadable mac GUI builds)
// or "io.tailscale.ipn.macos.network-extension" for App Store builds.
return filepath.Join(os.Getenv("HOME"), "tailscaled-env.txt")
} else {
// Open source / homebrew variable, running tailscaled-on-macOS.
return "/etc/tailscale/tailscaled-env.txt"
}
}
return ""
}

View File

@@ -115,4 +115,4 @@
in
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
}
# nix-direnv cache busting line: sha256-ZQ6aE+9PfAxfeNQeDzwcOCXpztLORVriHkEw51lbeHM=
# nix-direnv cache busting line: sha256-7L+dvS++UNfMVcPUCbK/xuBPwtrzW4RpZTtcl7VCwQs=

21
go.mod
View File

@@ -78,23 +78,23 @@ require (
golang.org/x/mod v0.10.0
golang.org/x/net v0.9.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.7.0
golang.org/x/sync v0.2.0
golang.org/x/sys v0.8.0
golang.org/x/term v0.7.0
golang.org/x/time v0.3.0
golang.org/x/tools v0.8.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard/windows v0.5.3
gvisor.dev/gvisor v0.0.0-20230328175328-162ed5ef888d
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f
honnef.co/go/tools v0.4.3
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
inet.af/wf v0.0.0-20221017222439-36129f591884
k8s.io/api v0.25.0
k8s.io/apimachinery v0.25.0
k8s.io/client-go v0.25.0
k8s.io/api v0.26.1
k8s.io/apimachinery v0.26.1
k8s.io/client-go v0.26.1
nhooyr.io/websocket v1.8.7
sigs.k8s.io/controller-runtime v0.13.1
sigs.k8s.io/controller-runtime v0.14.6
sigs.k8s.io/yaml v1.3.0
software.sslmate.com/src/go-pkcs12 v0.2.0
)
@@ -203,7 +203,7 @@ require (
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect
github.com/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
github.com/goreleaser/chglog v0.1.2 // indirect
github.com/goreleaser/chglog v0.4.2 // indirect
github.com/goreleaser/fileglob v0.3.1 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
@@ -328,6 +328,7 @@ require (
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.2.0 // indirect
gitlab.com/bosi/decorder v0.2.3 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect
@@ -342,8 +343,8 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
k8s.io/apiextensions-apiserver v0.25.0 // indirect
k8s.io/component-base v0.25.0 // indirect
k8s.io/apiextensions-apiserver v0.26.1 // indirect
k8s.io/component-base v0.26.1 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect

View File

@@ -1 +1 @@
sha256-ZQ6aE+9PfAxfeNQeDzwcOCXpztLORVriHkEw51lbeHM=
sha256-7L+dvS++UNfMVcPUCbK/xuBPwtrzW4RpZTtcl7VCwQs=

50
go.sum
View File

@@ -526,11 +526,13 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U=
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/goreleaser/chglog v0.1.2 h1:tdzAb/ILeMnphzI9zQ7Nkq+T8R9qyXli8GydD8plFRY=
github.com/goreleaser/chglog v0.1.2/go.mod h1:tTZsFuSZK4epDXfjMkxzcGbrIOXprf0JFp47BjIr3B8=
github.com/goreleaser/chglog v0.4.2 h1:afmbT1d7lX/q+GF8wv3a1Dofs2j/Y9YkiCpGemWR6mI=
github.com/goreleaser/chglog v0.4.2/go.mod h1:u/F03un4hMCQrp65qSWCkkC6T+G7YLKZ+AM2mITE47s=
github.com/goreleaser/fileglob v0.3.1 h1:OTFDWqUUHjQazk2N5GdUqEbqT/grBnRARaAXsV07q1Y=
github.com/goreleaser/fileglob v0.3.1/go.mod h1:kNcPrPzjCp+Ox3jmXLU5QEsjhqrtLBm6OnXAif8KRl8=
github.com/goreleaser/nfpm v1.10.3 h1:NzpWKKzSFr7JOn55XN0SskyFOjP6BkvRt3JujoX8fws=
@@ -646,6 +648,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
@@ -821,14 +824,13 @@ github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3L
github.com/nunnatsa/ginkgolinter v0.11.2 h1:xzQpAsEyZe5F1RMy2Z5kn8UFCGiWfKqJOUd2ZzBXA4M=
github.com/nunnatsa/ginkgolinter v0.11.2/go.mod h1:dJIGXYXbkBswqa/pIzG0QlVTTDSBMxDoCFwhsl4Uras=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
@@ -984,7 +986,9 @@ github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXi
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00=
@@ -1153,6 +1157,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/bosi/decorder v0.2.3 h1:gX4/RgK16ijY8V+BRQHAySfQAb354T7/xQpDB2n10P0=
gitlab.com/bosi/decorder v0.2.3/go.mod h1:9K1RB5+VPNQYtXtTDAzd2OEftsZb1oV0IrJrzChSdGE=
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -1166,7 +1172,7 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -1336,8 +1342,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1425,8 +1432,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1686,7 +1693,6 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
@@ -1708,8 +1714,8 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gvisor.dev/gvisor v0.0.0-20230328175328-162ed5ef888d h1:pzPNKWBCLdzu7KGiFnFGuvKsLf+pNkOzHSK6zJ9tMKY=
gvisor.dev/gvisor v0.0.0-20230328175328-162ed5ef888d/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f h1:8GE2MRjGiFmfpon8dekPI08jEuNMQzSffVHgdupcO4E=
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1728,16 +1734,16 @@ inet.af/tcpproxy v0.0.0-20221017015627-91f861402626 h1:2dMP3Ox/Wh5BiItwOt4jxRsfz
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk=
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0=
k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk=
k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY=
k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E=
k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU=
k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0=
k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E=
k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8=
k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y=
k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk=
k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI=
k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM=
k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4=
k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU=
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
@@ -1761,8 +1767,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/controller-runtime v0.13.1 h1:tUsRCSJVM1QQOOeViGeX3GMT3dQF1eePPw6sEE3xSlg=
sigs.k8s.io/controller-runtime v0.13.1/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI=
sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA=
sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=

View File

@@ -95,6 +95,8 @@ func linuxVersionMeta() (meta versionMeta) {
propFile = "/etc.defaults/VERSION"
case distro.OpenWrt:
propFile = "/etc/openwrt_release"
case distro.Unraid:
propFile = "/etc/unraid-version"
case distro.WDMyCloud:
slurp, _ := os.ReadFile("/etc/version")
meta.DistroVersion = string(bytes.TrimSpace(slurp))
@@ -153,6 +155,8 @@ func linuxVersionMeta() (meta versionMeta) {
meta.DistroVersion = m["productversion"]
case distro.OpenWrt:
meta.DistroVersion = m["DISTRIB_RELEASE"]
case distro.Unraid:
meta.DistroVersion = m["version"]
}
return
}

View File

@@ -49,6 +49,7 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/util/clientmetric"
"tailscale.com/util/multierr"
"tailscale.com/version/distro"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
)
@@ -1090,6 +1091,10 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
http.Error(w, errNoTaildrop.Error(), http.StatusInternalServerError)
return
}
if distro.Get() == distro.Unraid && !h.ps.directFileMode {
http.Error(w, "Taildrop folder not configured or accessible", http.StatusInternalServerError)
return
}
rawPath := r.URL.EscapedPath()
suffix, ok := strings.CutPrefix(rawPath, "/v0/put/")
if !ok {

View File

@@ -321,6 +321,17 @@ main() {
exit 1
fi
TEST_URL="https://pkgs.tailscale.com/"
RC=0
TEST_OUT=$($CURL "$TEST_URL" 2>&1) || RC=$?
if [ $RC != 0 ]; then
echo "The installer cannot reach $TEST_URL"
echo "Please make sure that your machine has internet access."
echo "Test output:"
echo $TEST_OUT
exit 1
fi
# Step 2: having detected an OS we support, is it one of the
# versions we support?
OS_UNSUPPORTED=

View File

@@ -16,4 +16,4 @@
) {
src = ./.;
}).shellNix
# nix-direnv cache busting line: sha256-ZQ6aE+9PfAxfeNQeDzwcOCXpztLORVriHkEw51lbeHM=
# nix-direnv cache busting line: sha256-7L+dvS++UNfMVcPUCbK/xuBPwtrzW4RpZTtcl7VCwQs=

View File

@@ -5,9 +5,8 @@ package tailcfg
//go:generate go run tailscale.com/cmd/viewer --type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode,SSHRule,SSHAction,SSHPrincipal,ControlDialPlan --clonefunc
//go:generage go run tailscale.com/cmd/equaler --type Node,Hostinfo,NetInfo,Service
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
@@ -498,7 +497,7 @@ const (
// Service represents a service running on a node.
type Service struct {
_ structs.Incomparable `codegen:"noequal"`
_ structs.Incomparable
// Proto is the type of service. It's usually the constant TCP
// or UDP ("tcp" or "udp"), but it can also be one of the
@@ -583,6 +582,9 @@ type Hostinfo struct {
Cloud string `json:",omitempty"`
Userspace opt.Bool `json:",omitempty"` // if the client is running in userspace (netstack) mode
UserspaceRouter opt.Bool `json:",omitempty"` // if the client's subnet router is running in userspace (netstack) mode
// NOTE: any new fields containing pointers in this type
// require changes to Hostinfo.Equal.
}
// TailscaleSSHEnabled reports whether or not this node is acting as a
@@ -662,7 +664,9 @@ type NetInfo struct {
// This should only be updated rarely, or when there's a
// material change, as any change here also gets uploaded to
// the control plane.
DERPLatency map[string]float64 `json:",omitempty" codegen:"noequal"`
DERPLatency map[string]float64 `json:",omitempty"`
// Update BasicallyEqual when adding fields.
}
func (ni *NetInfo) String() string {
@@ -700,6 +704,40 @@ func conciseOptBool(b opt.Bool, trueVal string) string {
return ""
}
// BasicallyEqual reports whether ni and ni2 are basically equal, ignoring
// changes in DERP ServerLatency & RegionLatency.
func (ni *NetInfo) BasicallyEqual(ni2 *NetInfo) bool {
if (ni == nil) != (ni2 == nil) {
return false
}
if ni == nil {
return true
}
return ni.MappingVariesByDestIP == ni2.MappingVariesByDestIP &&
ni.HairPinning == ni2.HairPinning &&
ni.WorkingIPv6 == ni2.WorkingIPv6 &&
ni.OSHasIPv6 == ni2.OSHasIPv6 &&
ni.WorkingUDP == ni2.WorkingUDP &&
ni.WorkingICMPv4 == ni2.WorkingICMPv4 &&
ni.HavePortMap == ni2.HavePortMap &&
ni.UPnP == ni2.UPnP &&
ni.PMP == ni2.PMP &&
ni.PCP == ni2.PCP &&
ni.PreferredDERP == ni2.PreferredDERP &&
ni.LinkType == ni2.LinkType
}
// Equal reports whether h and h2 are equal.
func (h *Hostinfo) Equal(h2 *Hostinfo) bool {
if h == nil && h2 == nil {
return true
}
if (h == nil) != (h2 == nil) {
return false
}
return reflect.DeepEqual(h, h2)
}
// HowUnequal returns a list of paths through Hostinfo where h and h2 differ.
// If they differ in nil-ness, the path is "nil", otherwise the path is like
// "ShieldsUp" or "NetInfo.nil" or "NetInfo.PCP".
@@ -1651,6 +1689,82 @@ func (id UserID) String() string { return fmt.Sprintf("userid:%x", int64(id)) }
func (id LoginID) String() string { return fmt.Sprintf("loginid:%x", int64(id)) }
func (id NodeID) String() string { return fmt.Sprintf("nodeid:%x", int64(id)) }
// Equal reports whether n and n2 are equal.
func (n *Node) Equal(n2 *Node) bool {
if n == nil && n2 == nil {
return true
}
return n != nil && n2 != nil &&
n.ID == n2.ID &&
n.StableID == n2.StableID &&
n.Name == n2.Name &&
n.User == n2.User &&
n.Sharer == n2.Sharer &&
n.UnsignedPeerAPIOnly == n2.UnsignedPeerAPIOnly &&
n.Key == n2.Key &&
n.KeyExpiry.Equal(n2.KeyExpiry) &&
bytes.Equal(n.KeySignature, n2.KeySignature) &&
n.Machine == n2.Machine &&
n.DiscoKey == n2.DiscoKey &&
eqPtr(n.Online, n2.Online) &&
eqCIDRs(n.Addresses, n2.Addresses) &&
eqCIDRs(n.AllowedIPs, n2.AllowedIPs) &&
eqCIDRs(n.PrimaryRoutes, n2.PrimaryRoutes) &&
eqStrings(n.Endpoints, n2.Endpoints) &&
n.DERP == n2.DERP &&
n.Cap == n2.Cap &&
n.Hostinfo.Equal(n2.Hostinfo) &&
n.Created.Equal(n2.Created) &&
eqTimePtr(n.LastSeen, n2.LastSeen) &&
n.MachineAuthorized == n2.MachineAuthorized &&
eqStrings(n.Capabilities, n2.Capabilities) &&
n.ComputedName == n2.ComputedName &&
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
n.ComputedNameWithHost == n2.ComputedNameWithHost &&
eqStrings(n.Tags, n2.Tags) &&
n.Expired == n2.Expired &&
eqPtr(n.SelfNodeV4MasqAddrForThisPeer, n2.SelfNodeV4MasqAddrForThisPeer) &&
n.IsWireGuardOnly == n2.IsWireGuardOnly
}
func eqPtr[T comparable](a, b *T) bool {
if a == b { // covers nil
return true
}
if a == nil || b == nil {
return false
}
return *a == *b
}
func eqStrings(a, b []string) bool {
if len(a) != len(b) || ((a == nil) != (b == nil)) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func eqCIDRs(a, b []netip.Prefix) bool {
if len(a) != len(b) || ((a == nil) != (b == nil)) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func eqTimePtr(a, b *time.Time) bool {
return ((a == nil) == (b == nil)) && (a == nil || a.Equal(*b))
}
// Oauth2Token is a copy of golang.org/x/oauth2.Token, to avoid the
// go.mod dependency on App Engine and grpc, which was causing problems.
// All we actually needed was this struct on the client side.

View File

@@ -1,244 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by tailscale.com/cmd/equaler; DO NOT EDIT.
package tailcfg
import (
"net/netip"
"time"
"golang.org/x/exp/slices"
"tailscale.com/types/key"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
"tailscale.com/types/tkatype"
)
// Equal reports whether a and b are equal.
func (a *Node) Equal(b *Node) bool {
if a == b {
return true
}
return a != nil && b != nil &&
a.ID == b.ID &&
a.StableID == b.StableID &&
a.Name == b.Name &&
a.User == b.User &&
a.Sharer == b.Sharer &&
a.Key == b.Key &&
a.KeyExpiry.Equal(b.KeyExpiry) &&
((a.KeySignature == nil) == (b.KeySignature == nil)) &&
slices.Equal(a.KeySignature, b.KeySignature) &&
a.Machine == b.Machine &&
a.DiscoKey == b.DiscoKey &&
((a.Addresses == nil) == (b.Addresses == nil)) &&
slices.Equal(a.Addresses, b.Addresses) &&
((a.AllowedIPs == nil) == (b.AllowedIPs == nil)) &&
slices.Equal(a.AllowedIPs, b.AllowedIPs) &&
((a.Endpoints == nil) == (b.Endpoints == nil)) &&
slices.Equal(a.Endpoints, b.Endpoints) &&
a.DERP == b.DERP &&
a.Hostinfo.Equal(b.Hostinfo) &&
a.Created.Equal(b.Created) &&
a.Cap == b.Cap &&
((a.Tags == nil) == (b.Tags == nil)) &&
slices.Equal(a.Tags, b.Tags) &&
((a.PrimaryRoutes == nil) == (b.PrimaryRoutes == nil)) &&
slices.Equal(a.PrimaryRoutes, b.PrimaryRoutes) &&
((a.LastSeen == nil) == (b.LastSeen == nil)) && (a.LastSeen == nil || a.LastSeen.Equal(*b.LastSeen)) &&
((a.Online == nil) == (b.Online == nil)) && (a.Online == nil || *a.Online == *b.Online) &&
a.KeepAlive == b.KeepAlive &&
a.MachineAuthorized == b.MachineAuthorized &&
((a.Capabilities == nil) == (b.Capabilities == nil)) &&
slices.Equal(a.Capabilities, b.Capabilities) &&
a.UnsignedPeerAPIOnly == b.UnsignedPeerAPIOnly &&
a.ComputedName == b.ComputedName &&
a.computedHostIfDifferent == b.computedHostIfDifferent &&
a.ComputedNameWithHost == b.ComputedNameWithHost &&
a.DataPlaneAuditLogID == b.DataPlaneAuditLogID &&
a.Expired == b.Expired &&
((a.SelfNodeV4MasqAddrForThisPeer == nil) == (b.SelfNodeV4MasqAddrForThisPeer == nil)) && (a.SelfNodeV4MasqAddrForThisPeer == nil || *a.SelfNodeV4MasqAddrForThisPeer == *b.SelfNodeV4MasqAddrForThisPeer) &&
a.IsWireGuardOnly == b.IsWireGuardOnly &&
true
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _NodeEqualNeedsRegeneration = Node(struct {
ID NodeID
StableID StableNodeID
Name string
User UserID
Sharer UserID
Key key.NodePublic
KeyExpiry time.Time
KeySignature tkatype.MarshaledSignature
Machine key.MachinePublic
DiscoKey key.DiscoPublic
Addresses []netip.Prefix
AllowedIPs []netip.Prefix
Endpoints []string
DERP string
Hostinfo HostinfoView
Created time.Time
Cap CapabilityVersion
Tags []string
PrimaryRoutes []netip.Prefix
LastSeen *time.Time
Online *bool
KeepAlive bool
MachineAuthorized bool
Capabilities []string
UnsignedPeerAPIOnly bool
ComputedName string
computedHostIfDifferent string
ComputedNameWithHost string
DataPlaneAuditLogID string
Expired bool
SelfNodeV4MasqAddrForThisPeer *netip.Addr
IsWireGuardOnly bool
}{})
// Equal reports whether a and b are equal.
func (a *Hostinfo) Equal(b *Hostinfo) bool {
if a == b {
return true
}
return a != nil && b != nil &&
a.IPNVersion == b.IPNVersion &&
a.FrontendLogID == b.FrontendLogID &&
a.BackendLogID == b.BackendLogID &&
a.OS == b.OS &&
a.OSVersion == b.OSVersion &&
a.Container == b.Container &&
a.Env == b.Env &&
a.Distro == b.Distro &&
a.DistroVersion == b.DistroVersion &&
a.DistroCodeName == b.DistroCodeName &&
a.App == b.App &&
a.Desktop == b.Desktop &&
a.Package == b.Package &&
a.DeviceModel == b.DeviceModel &&
a.PushDeviceToken == b.PushDeviceToken &&
a.Hostname == b.Hostname &&
a.ShieldsUp == b.ShieldsUp &&
a.ShareeNode == b.ShareeNode &&
a.NoLogsNoSupport == b.NoLogsNoSupport &&
a.WireIngress == b.WireIngress &&
a.AllowsUpdate == b.AllowsUpdate &&
a.Machine == b.Machine &&
a.GoArch == b.GoArch &&
a.GoArchVar == b.GoArchVar &&
a.GoVersion == b.GoVersion &&
((a.RoutableIPs == nil) == (b.RoutableIPs == nil)) &&
slices.Equal(a.RoutableIPs, b.RoutableIPs) &&
((a.RequestTags == nil) == (b.RequestTags == nil)) &&
slices.Equal(a.RequestTags, b.RequestTags) &&
((a.Services == nil) == (b.Services == nil)) &&
slices.EqualFunc(a.Services, b.Services, func(aa Service, bb Service) bool { return aa.Equal(&bb) }) &&
((a.NetInfo == nil) == (b.NetInfo == nil)) && (a.NetInfo == nil || a.NetInfo.Equal(b.NetInfo)) &&
((a.SSH_HostKeys == nil) == (b.SSH_HostKeys == nil)) &&
slices.Equal(a.SSH_HostKeys, b.SSH_HostKeys) &&
a.Cloud == b.Cloud &&
a.Userspace == b.Userspace &&
a.UserspaceRouter == b.UserspaceRouter &&
true
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _HostinfoEqualNeedsRegeneration = Hostinfo(struct {
IPNVersion string
FrontendLogID string
BackendLogID string
OS string
OSVersion string
Container opt.Bool
Env string
Distro string
DistroVersion string
DistroCodeName string
App string
Desktop opt.Bool
Package string
DeviceModel string
PushDeviceToken string
Hostname string
ShieldsUp bool
ShareeNode bool
NoLogsNoSupport bool
WireIngress bool
AllowsUpdate bool
Machine string
GoArch string
GoArchVar string
GoVersion string
RoutableIPs []netip.Prefix
RequestTags []string
Services []Service
NetInfo *NetInfo
SSH_HostKeys []string
Cloud string
Userspace opt.Bool
UserspaceRouter opt.Bool
}{})
// Equal reports whether a and b are equal.
func (a *NetInfo) Equal(b *NetInfo) bool {
if a == b {
return true
}
return a != nil && b != nil &&
a.MappingVariesByDestIP == b.MappingVariesByDestIP &&
a.HairPinning == b.HairPinning &&
a.WorkingIPv6 == b.WorkingIPv6 &&
a.OSHasIPv6 == b.OSHasIPv6 &&
a.WorkingUDP == b.WorkingUDP &&
a.WorkingICMPv4 == b.WorkingICMPv4 &&
a.HavePortMap == b.HavePortMap &&
a.UPnP == b.UPnP &&
a.PMP == b.PMP &&
a.PCP == b.PCP &&
a.PreferredDERP == b.PreferredDERP &&
a.LinkType == b.LinkType &&
// Skipping DERPLatency because of codegen:noequal
true
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _NetInfoEqualNeedsRegeneration = NetInfo(struct {
MappingVariesByDestIP opt.Bool
HairPinning opt.Bool
WorkingIPv6 opt.Bool
OSHasIPv6 opt.Bool
WorkingUDP opt.Bool
WorkingICMPv4 opt.Bool
HavePortMap bool
UPnP opt.Bool
PMP opt.Bool
PCP opt.Bool
PreferredDERP int
LinkType string
DERPLatency map[string]float64
}{})
// Equal reports whether a and b are equal.
func (a *Service) Equal(b *Service) bool {
if a == b {
return true
}
return a != nil && b != nil &&
// Skipping _ because of codegen:noequal
a.Proto == b.Proto &&
a.Port == b.Port &&
a.Description == b.Description &&
true
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _ServiceEqualNeedsRegeneration = Service(struct {
_ structs.Incomparable
Proto ServiceProto
Port uint16
Description string
}{})

View File

@@ -572,7 +572,7 @@ func TestNetInfoFields(t *testing.T) {
"DERPLatency",
}
if have := fieldsOf(reflect.TypeOf(NetInfo{})); !reflect.DeepEqual(have, handled) {
t.Errorf("NetInfo.Clone/Equal check might be out of sync\nfields: %q\nhandled: %q\n",
t.Errorf("NetInfo.Clone/BasicallyEqually check might be out of sync\nfields: %q\nhandled: %q\n",
have, handled)
}
}

View File

@@ -402,7 +402,6 @@ func (v NetInfoView) LinkType() string { return v.ж.LinkType }
func (v NetInfoView) DERPLatency() views.Map[string, float64] { return views.MapOf(v.ж.DERPLatency) }
func (v NetInfoView) String() string { return v.ж.String() }
func (v NetInfoView) Equal(v2 NetInfoView) bool { return v.ж.Equal(v2.ж) }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _NetInfoViewNeedsRegeneration = NetInfo(struct {

View File

@@ -16,7 +16,6 @@ import (
"reflect"
"strings"
"golang.org/x/exp/slices"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/imports"
"tailscale.com/util/mak"
@@ -48,13 +47,12 @@ func LoadTypes(buildTags string, pkgName string) (*packages.Package, map[string]
// HasNoClone reports whether the provided tag has `codegen:noclone`.
func HasNoClone(structTag string) bool {
val := reflect.StructTag(structTag).Get("codegen")
return slices.Contains(strings.Split(val, ","), "noclone")
}
// HasNoEqual reports whether the provided tag has `codegen:noequal`.
func HasNoEqual(structTag string) bool {
val := reflect.StructTag(structTag).Get("codegen")
return slices.Contains(strings.Split(val, ","), "noequal")
for _, v := range strings.Split(val, ",") {
if v == "noclone" {
return true
}
}
return false
}
const copyrightHeader = `// Copyright (c) Tailscale Inc & AUTHORS

View File

@@ -29,6 +29,7 @@ const (
TrueNAS = Distro("truenas")
Gokrazy = Distro("gokrazy")
WDMyCloud = Distro("wdmycloud")
Unraid = Distro("unraid")
)
var distro lazy.SyncValue[Distro]
@@ -90,6 +91,8 @@ func linuxDistro() Distro {
return WDMyCloud
case have("/usr/sbin/wd_crontab.sh"): // Western Digital MyCloud OS5
return WDMyCloud
case have("/etc/unraid-version"):
return Unraid
}
return ""
}

View File

@@ -951,7 +951,7 @@ func (c *Conn) pickDERPFallback() int {
func (c *Conn) callNetInfoCallback(ni *tailcfg.NetInfo) {
c.mu.Lock()
defer c.mu.Unlock()
if ni.Equal(c.netInfoLast) {
if ni.BasicallyEqual(c.netInfoLast) {
return
}
c.callNetInfoCallbackLocked(ni)