Compare commits
22 Commits
cloner
...
coral-gito
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c684ca7a0c | ||
|
|
1116602d4c | ||
|
|
be67b8e75b | ||
|
|
8047dfa2dc | ||
|
|
ebbf5c57b3 | ||
|
|
39efba528f | ||
|
|
69c0b7e712 | ||
|
|
673b3d8dbd | ||
|
|
10eec37cd9 | ||
|
|
8f2bc0708b | ||
|
|
0088c5ddc0 | ||
|
|
907f85cd67 | ||
|
|
8724aa254f | ||
|
|
c4e262a0fc | ||
|
|
eafbf8886d | ||
|
|
b2b8e62476 | ||
|
|
91e64ca74f | ||
|
|
d72575eaaa | ||
|
|
b2c55e62c8 | ||
|
|
467ace7d0c | ||
|
|
aad6830df0 | ||
|
|
ea70aa3d98 |
32
README.md
32
README.md
@@ -6,27 +6,41 @@ Private WireGuard® networks made easy
|
||||
|
||||
## Overview
|
||||
|
||||
This repository contains all the open source Tailscale client code and
|
||||
the `tailscaled` daemon and `tailscale` CLI tool. The `tailscaled`
|
||||
daemon runs on Linux, Windows and [macOS](https://tailscale.com/kb/1065/macos-variants/), and to varying degrees on FreeBSD, OpenBSD, and Darwin. (The Tailscale iOS and Android apps use this repo's code, but this repo doesn't contain the mobile GUI code.)
|
||||
This repository contains the majority of Tailscale's open source code.
|
||||
Notably, it includes the `tailscaled` daemon and
|
||||
the `tailscale` CLI tool. The `tailscaled` daemon runs on Linux, Windows,
|
||||
[macOS](https://tailscale.com/kb/1065/macos-variants/), and to varying degrees
|
||||
on FreeBSD and OpenBSD. The Tailscale iOS and Android apps use this repo's
|
||||
code, but this repo doesn't contain the mobile GUI code.
|
||||
|
||||
The Android app is at https://github.com/tailscale/tailscale-android
|
||||
Other [Tailscale repos](https://github.com/orgs/tailscale/repositories) of note:
|
||||
|
||||
The Synology package is at https://github.com/tailscale/tailscale-synology
|
||||
* the Android app is at https://github.com/tailscale/tailscale-android
|
||||
* the Synology package is at https://github.com/tailscale/tailscale-synology
|
||||
* the QNAP package is at https://github.com/tailscale/tailscale-qpkg
|
||||
* the Chocolatey packaging is at https://github.com/tailscale/tailscale-chocolatey
|
||||
|
||||
For background on which parts of Tailscale are open source and why,
|
||||
see [https://tailscale.com/opensource/](https://tailscale.com/opensource/).
|
||||
|
||||
## Using
|
||||
|
||||
We serve packages for a variety of distros at
|
||||
https://pkgs.tailscale.com .
|
||||
We serve packages for a variety of distros and platforms at
|
||||
[https://pkgs.tailscale.com](https://pkgs.tailscale.com/).
|
||||
|
||||
## Other clients
|
||||
|
||||
The [macOS, iOS, and Windows clients](https://tailscale.com/download)
|
||||
use the code in this repository but additionally include small GUI
|
||||
wrappers that are not open source.
|
||||
wrappers. The GUI wrappers on non-open source platforms are themselves
|
||||
not open source.
|
||||
|
||||
## Building
|
||||
|
||||
We always require the latest Go release, currently Go 1.19. (While we build
|
||||
releases with our [Go fork](https://github.com/tailscale/go/), its use is not
|
||||
required.)
|
||||
|
||||
```
|
||||
go install tailscale.com/cmd/tailscale{,d}
|
||||
```
|
||||
@@ -43,8 +57,6 @@ 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 require the latest Go release, currently Go 1.19.
|
||||
|
||||
## Bugs
|
||||
|
||||
Please file any issues about this code or the hosted service on
|
||||
|
||||
@@ -80,7 +80,7 @@ func main() {
|
||||
w("}")
|
||||
}
|
||||
cloneOutput := pkg.Name + "_clone.go"
|
||||
if err := codegen.WritePackageFile("tailscale.com/cmd/cloner", pkg, cloneOutput, it, buf); err != nil {
|
||||
if err := codegen.WritePackageFile("tailscale.com/cmd/cloner", pkg, cloneOutput, codegen.CopyrightYear("."), it, buf); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// As with most container things, configuration is passed through environment
|
||||
// variables. All configuration is optional.
|
||||
//
|
||||
// - TS_AUTH_KEY: the authkey to use for login.
|
||||
// - TS_AUTHKEY: the authkey to use for login.
|
||||
// - TS_ROUTES: subnet routes to advertise.
|
||||
// - TS_DEST_IP: proxy all incoming Tailscale traffic to the given
|
||||
// destination.
|
||||
@@ -42,7 +42,7 @@
|
||||
// TS_KUBE_SECRET="" and TS_STATE_DIR=/path/to/storage/dir. The state dir should
|
||||
// be persistent storage.
|
||||
//
|
||||
// Additionally, if TS_AUTH_KEY is not set and the TS_KUBE_SECRET contains an
|
||||
// Additionally, if TS_AUTHKEY is not set and the TS_KUBE_SECRET contains an
|
||||
// "authkey" field, that key is used as the tailscale authkey.
|
||||
package main
|
||||
|
||||
@@ -73,7 +73,7 @@ func main() {
|
||||
tailscale.I_Acknowledge_This_API_Is_Unstable = true
|
||||
|
||||
cfg := &settings{
|
||||
AuthKey: defaultEnv("TS_AUTH_KEY", ""),
|
||||
AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
|
||||
Routes: defaultEnv("TS_ROUTES", ""),
|
||||
ProxyTo: defaultEnv("TS_DEST_IP", ""),
|
||||
DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
|
||||
@@ -548,6 +548,15 @@ func defaultEnv(name, defVal string) string {
|
||||
return defVal
|
||||
}
|
||||
|
||||
func defaultEnvs(names []string, defVal string) string {
|
||||
for _, name := range names {
|
||||
if v, ok := os.LookupEnv(name); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return defVal
|
||||
}
|
||||
|
||||
// defaultBool returns the boolean value of the given envvar name, or
|
||||
// defVal if unset or not a bool.
|
||||
func defaultBool(name string, defVal bool) bool {
|
||||
|
||||
@@ -146,6 +146,24 @@ func TestContainerBoot(t *testing.T) {
|
||||
{
|
||||
// Userspace mode, ephemeral storage, authkey provided on every run.
|
||||
Name: "authkey",
|
||||
Env: map[string]string{
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
},
|
||||
Phases: []phase{
|
||||
{
|
||||
WantCmds: []string{
|
||||
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
|
||||
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key",
|
||||
},
|
||||
},
|
||||
{
|
||||
Notify: runningNotify,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Userspace mode, ephemeral storage, authkey provided on every run.
|
||||
Name: "authkey-old-flag",
|
||||
Env: map[string]string{
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
},
|
||||
@@ -164,7 +182,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
{
|
||||
Name: "authkey_disk_state",
|
||||
Env: map[string]string{
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
"TS_STATE_DIR": filepath.Join(d, "tmp"),
|
||||
},
|
||||
Phases: []phase{
|
||||
@@ -182,8 +200,8 @@ func TestContainerBoot(t *testing.T) {
|
||||
{
|
||||
Name: "routes",
|
||||
Env: map[string]string{
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_ROUTES": "1.2.3.0/24,10.20.30.0/24",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
"TS_ROUTES": "1.2.3.0/24,10.20.30.0/24",
|
||||
},
|
||||
Phases: []phase{
|
||||
{
|
||||
@@ -204,7 +222,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
{
|
||||
Name: "routes_kernel_ipv4",
|
||||
Env: map[string]string{
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
"TS_ROUTES": "1.2.3.0/24,10.20.30.0/24",
|
||||
"TS_USERSPACE": "false",
|
||||
},
|
||||
@@ -227,7 +245,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
{
|
||||
Name: "routes_kernel_ipv6",
|
||||
Env: map[string]string{
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
"TS_ROUTES": "::/64,1::/64",
|
||||
"TS_USERSPACE": "false",
|
||||
},
|
||||
@@ -250,7 +268,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
{
|
||||
Name: "routes_kernel_all_families",
|
||||
Env: map[string]string{
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
"TS_ROUTES": "::/64,1.2.3.0/24",
|
||||
"TS_USERSPACE": "false",
|
||||
},
|
||||
@@ -273,7 +291,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
{
|
||||
Name: "proxy",
|
||||
Env: map[string]string{
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
"TS_DEST_IP": "1.2.3.4",
|
||||
"TS_USERSPACE": "false",
|
||||
},
|
||||
@@ -295,7 +313,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
{
|
||||
Name: "authkey_once",
|
||||
Env: map[string]string{
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
"TS_AUTH_ONCE": "true",
|
||||
},
|
||||
Phases: []phase{
|
||||
@@ -354,7 +372,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
// Explicitly set to an empty value, to override the default of "tailscale".
|
||||
"TS_KUBE_SECRET": "",
|
||||
"TS_STATE_DIR": filepath.Join(d, "tmp"),
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
},
|
||||
KubeSecret: map[string]string{},
|
||||
Phases: []phase{
|
||||
@@ -376,7 +394,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
Env: map[string]string{
|
||||
"KUBERNETES_SERVICE_HOST": kube.Host,
|
||||
"KUBERNETES_SERVICE_PORT_HTTPS": kube.Port,
|
||||
"TS_AUTH_KEY": "tskey-key",
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
},
|
||||
KubeSecret: map[string]string{},
|
||||
KubeDenyPatch: true,
|
||||
|
||||
@@ -134,6 +134,7 @@ var debugCmd = &ffcli.Command{
|
||||
fs := newFlagSet("watch-ipn")
|
||||
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
|
||||
fs.BoolVar(&watchIPNArgs.initial, "initial", false, "include initial status")
|
||||
fs.BoolVar(&watchIPNArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
@@ -319,8 +320,9 @@ func runPrefs(ctx context.Context, args []string) error {
|
||||
}
|
||||
|
||||
var watchIPNArgs struct {
|
||||
netmap bool
|
||||
initial bool
|
||||
netmap bool
|
||||
initial bool
|
||||
showPrivateKey bool
|
||||
}
|
||||
|
||||
func runWatchIPN(ctx context.Context, args []string) error {
|
||||
@@ -328,6 +330,9 @@ func runWatchIPN(ctx context.Context, args []string) error {
|
||||
if watchIPNArgs.initial {
|
||||
mask = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap
|
||||
}
|
||||
if !watchIPNArgs.showPrivateKey {
|
||||
mask |= ipn.NotifyNoPrivateKeys
|
||||
}
|
||||
watcher, err := localClient.WatchIPNBus(ctx, mask)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
@@ -99,6 +100,22 @@ func runNetworkLockInit(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Common mistake: Not specifying the current node's key as one of the trusted keys.
|
||||
foundSelfKey := false
|
||||
for _, k := range keys {
|
||||
keyID, err := k.ID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(keyID, st.PublicKey.KeyID()) {
|
||||
foundSelfKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundSelfKey {
|
||||
return errors.New("the tailnet lock key of the current node must be one of the trusted keys during initialization")
|
||||
}
|
||||
|
||||
fmt.Println("You are initializing tailnet lock with the following trusted signing keys:")
|
||||
for _, k := range keys {
|
||||
fmt.Printf(" - tlpub:%x (%s key)\n", k.Public, k.Kind.String())
|
||||
@@ -196,7 +213,7 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
|
||||
line.WriteString(fmt.Sprint(k.Votes))
|
||||
line.WriteString("\t")
|
||||
if k.Key == st.PublicKey {
|
||||
line.WriteString("(us)")
|
||||
line.WriteString("(self)")
|
||||
}
|
||||
fmt.Println(line.String())
|
||||
}
|
||||
@@ -444,8 +461,13 @@ func nlDescribeUpdate(update ipnstate.NetworkLockUpdate, color bool) (string, er
|
||||
var stanza strings.Builder
|
||||
printKey := func(key *tka.Key, prefix string) {
|
||||
fmt.Fprintf(&stanza, "%sType: %s\n", prefix, key.Kind.String())
|
||||
fmt.Fprintf(&stanza, "%sKeyID: %x\n", prefix, key.ID())
|
||||
fmt.Fprintf(&stanza, "%sVotes: %d\n", prefix, key.Votes)
|
||||
if keyID, err := key.ID(); err == nil {
|
||||
fmt.Fprintf(&stanza, "%sKeyID: %x\n", prefix, keyID)
|
||||
} else {
|
||||
// Older versions of the client shouldn't explode when they encounter an
|
||||
// unknown key type.
|
||||
fmt.Fprintf(&stanza, "%sKeyID: <Error: %v>\n", prefix, err)
|
||||
}
|
||||
if key.Meta != nil {
|
||||
fmt.Fprintf(&stanza, "%sMetadata: %+v\n", prefix, key.Meta)
|
||||
}
|
||||
|
||||
@@ -223,8 +223,8 @@ func qnapAuthnQtoken(r *http.Request, user, token string) (string, *qnapAuthResp
|
||||
"user": []string{user},
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: r.URL.Scheme,
|
||||
Host: r.URL.Host,
|
||||
Scheme: "http",
|
||||
Host: "127.0.0.1:8080",
|
||||
Path: "/cgi-bin/authLogin.cgi",
|
||||
RawQuery: query.Encode(),
|
||||
}
|
||||
@@ -237,8 +237,8 @@ func qnapAuthnSid(r *http.Request, user, sid string) (string, *qnapAuthResponse,
|
||||
"sid": []string{sid},
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: r.URL.Scheme,
|
||||
Host: r.URL.Host,
|
||||
Scheme: "http",
|
||||
Host: "127.0.0.1:8080",
|
||||
Path: "/cgi-bin/authLogin.cgi",
|
||||
RawQuery: query.Encode(),
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/tsaddr from tailscale.com/ipn+
|
||||
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/tstun from tailscale.com/net/dns+
|
||||
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/net/wsconn from tailscale.com/control/controlhttp+
|
||||
tailscale.com/paths from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || darwin || freebsd
|
||||
//go:build linux || darwin || freebsd || openbsd
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -526,6 +526,9 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logid string) (_ *ip
|
||||
return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err)
|
||||
}
|
||||
lb.SetVarRoot(opts.VarRoot)
|
||||
if logPol != nil {
|
||||
lb.SetLogFlusher(logPol.Logtail.StartFlush)
|
||||
}
|
||||
if root := lb.TailscaleVarRoot(); root != "" {
|
||||
dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json"))
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ func main() {
|
||||
genView(buf, it, typ, pkg.Types)
|
||||
}
|
||||
out := pkg.Name + "_view.go"
|
||||
if err := codegen.WritePackageFile("tailscale/cmd/viewer", pkg, out, it, buf); err != nil {
|
||||
if err := codegen.WritePackageFile("tailscale/cmd/viewer", pkg, out, codegen.CopyrightYear("."), it, buf); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if runCloner {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
@@ -40,6 +41,7 @@ type mapSession struct {
|
||||
lastDNSConfig *tailcfg.DNSConfig
|
||||
lastDERPMap *tailcfg.DERPMap
|
||||
lastUserProfile map[tailcfg.UserID]tailcfg.UserProfile
|
||||
lastPacketFilterRules views.Slice[tailcfg.FilterRule]
|
||||
lastParsedPacketFilter []filter.Match
|
||||
lastSSHPolicy *tailcfg.SSHPolicy
|
||||
collectServices bool
|
||||
@@ -96,6 +98,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
|
||||
|
||||
if pf := resp.PacketFilter; pf != nil {
|
||||
var err error
|
||||
ms.lastPacketFilterRules = views.SliceOf(pf)
|
||||
ms.lastParsedPacketFilter, err = filter.MatchesFromFilterRules(pf)
|
||||
if err != nil {
|
||||
ms.logf("parsePacketFilter: %v", err)
|
||||
@@ -147,21 +150,22 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
|
||||
}
|
||||
|
||||
nm := &netmap.NetworkMap{
|
||||
NodeKey: ms.privateNodeKey.Public(),
|
||||
PrivateKey: ms.privateNodeKey,
|
||||
MachineKey: ms.machinePubKey,
|
||||
Peers: resp.Peers,
|
||||
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile),
|
||||
Domain: ms.lastDomain,
|
||||
DomainAuditLogID: ms.lastDomainAuditLogID,
|
||||
DNS: *ms.lastDNSConfig,
|
||||
PacketFilter: ms.lastParsedPacketFilter,
|
||||
SSHPolicy: ms.lastSSHPolicy,
|
||||
CollectServices: ms.collectServices,
|
||||
DERPMap: ms.lastDERPMap,
|
||||
Debug: debug,
|
||||
ControlHealth: ms.lastHealth,
|
||||
TKAEnabled: ms.lastTKAInfo != nil && !ms.lastTKAInfo.Disabled,
|
||||
NodeKey: ms.privateNodeKey.Public(),
|
||||
PrivateKey: ms.privateNodeKey,
|
||||
MachineKey: ms.machinePubKey,
|
||||
Peers: resp.Peers,
|
||||
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile),
|
||||
Domain: ms.lastDomain,
|
||||
DomainAuditLogID: ms.lastDomainAuditLogID,
|
||||
DNS: *ms.lastDNSConfig,
|
||||
PacketFilter: ms.lastParsedPacketFilter,
|
||||
PacketFilterRules: ms.lastPacketFilterRules,
|
||||
SSHPolicy: ms.lastSSHPolicy,
|
||||
CollectServices: ms.collectServices,
|
||||
DERPMap: ms.lastDERPMap,
|
||||
Debug: debug,
|
||||
ControlHealth: ms.lastHealth,
|
||||
TKAEnabled: ms.lastTKAInfo != nil && !ms.lastTKAInfo.Disabled,
|
||||
}
|
||||
ms.netMapBuilding = nm
|
||||
|
||||
|
||||
5
go.mod
5
go.mod
@@ -64,7 +64,7 @@ require (
|
||||
github.com/tc-hib/winres v0.1.6
|
||||
github.com/tcnksm/go-httpstat v0.2.0
|
||||
github.com/toqueteos/webbrowser v1.2.0
|
||||
github.com/u-root/u-root v0.9.1-0.20221111022710-6e9699743f5d
|
||||
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
|
||||
go.uber.org/zap v1.21.0
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94
|
||||
@@ -104,7 +104,7 @@ require (
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
@@ -134,6 +134,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/charithe/durationcheck v0.0.9 // indirect
|
||||
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
|
||||
github.com/daixiang0/gci v0.2.9 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
||||
13
go.sum
13
go.sum
@@ -90,8 +90,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 h1:XcF0cTDJeiuZ5NU8w7WUDge0HRwwNRmxj/GGk6KSA6g=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
@@ -195,6 +195,7 @@ github.com/breml/bidichk v0.2.1 h1:SRNtZuLdfkxtocj+xyHXKC1Uv3jVi6EPYx+NHSTNQvE=
|
||||
github.com/breml/bidichk v0.2.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso=
|
||||
github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY=
|
||||
github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc=
|
||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -212,6 +213,8 @@ 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/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
|
||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
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=
|
||||
@@ -1194,8 +1197,9 @@ github.com/tommy-muehle/go-mnd/v2 v2.4.0 h1:1t0f8Uiaq+fqKteUR4N9Umr6E99R+lDnLnq7
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/u-root/u-root v0.9.1-0.20221111022710-6e9699743f5d h1:sT5Q2xFrqgm/3yrCkVLkVuEFpG07UXz9ALqxxN1SmZc=
|
||||
github.com/u-root/u-root v0.9.1-0.20221111022710-6e9699743f5d/go.mod h1:jMbuI3nENTNzHW9mYwQ57b8/DSuJTq+joYY18x/WGxE=
|
||||
github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90 h1:zTk5683I9K62wtZ6eUa6vu6IWwVHXPnoKK5n2unAwv0=
|
||||
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad h1:0lEUXaz1vhlAtoMpu18vhb16s5rGRpNCl2trxc2/Qbg=
|
||||
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
|
||||
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww=
|
||||
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
@@ -1564,6 +1568,7 @@ golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@@ -70,6 +70,9 @@ const (
|
||||
|
||||
// SysDNSManager is the name of the net/dns manager subsystem.
|
||||
SysDNSManager = Subsystem("dns-manager")
|
||||
|
||||
// SysTKA is the name of the tailnet key authority subsystem.
|
||||
SysTKA = Subsystem("tailnet-lock")
|
||||
)
|
||||
|
||||
// NewWarnable returns a new warnable item that the caller can mark
|
||||
@@ -194,6 +197,12 @@ func SetDNSManagerHealth(err error) { setErr(SysDNSManager, err) }
|
||||
// DNSOSHealth returns the net/dns.OSConfigurator error state.
|
||||
func DNSOSHealth() error { return get(SysDNSOS) }
|
||||
|
||||
// SetTKAHealth sets the health of the tailnet key authority.
|
||||
func SetTKAHealth(err error) { setErr(SysTKA, err) }
|
||||
|
||||
// TKAHealth returns the tailnet key authority error state.
|
||||
func TKAHealth() error { return get(SysTKA) }
|
||||
|
||||
// SetLocalLogConfigHealth sets the error state of this client's local log configuration.
|
||||
func SetLocalLogConfigHealth(err error) {
|
||||
mu.Lock()
|
||||
|
||||
@@ -65,6 +65,8 @@ const (
|
||||
NotifyInitialState // if set, the first Notify message (sent immediately) will contain the current State + BrowseToURL
|
||||
NotifyInitialPrefs // if set, the first Notify message (sent immediately) will contain the current Prefs
|
||||
NotifyInitialNetMap // if set, the first Notify message (sent immediately) will contain the current NetMap
|
||||
|
||||
NotifyNoPrivateKeys // if set, private keys that would normally be sent in updates are zeroed out
|
||||
)
|
||||
|
||||
// Notify is a communication from a backend (e.g. tailscaled) to a frontend
|
||||
|
||||
@@ -26,6 +26,16 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
|
||||
// Test handler.
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
w.Write(body)
|
||||
case "/logtail/flush":
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "bad method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if b.TryFlushLogs() {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
http.Error(w, "no log flusher wired up", http.StatusInternalServerError)
|
||||
}
|
||||
case "/debug/goroutines":
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write(goroutines.ScrubbedGoroutineDump())
|
||||
|
||||
@@ -140,6 +140,7 @@ type LocalBackend struct {
|
||||
gotPortPollRes chan struct{} // closed upon first readPoller result
|
||||
newDecompressor func() (controlclient.Decompressor, error)
|
||||
varRoot string // or empty if SetVarRoot never called
|
||||
logFlushFunc func() // or nil if SetLogFlusher wasn't called
|
||||
sshAtomicBool atomic.Bool
|
||||
shutdownCalled bool // if Shutdown has been called
|
||||
|
||||
@@ -1742,6 +1743,24 @@ func (b *LocalBackend) readPoller() {
|
||||
func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWatchOpt, fn func(roNotify *ipn.Notify) (keepGoing bool)) {
|
||||
ch := make(chan *ipn.Notify, 128)
|
||||
|
||||
origFn := fn
|
||||
if mask&ipn.NotifyNoPrivateKeys != 0 {
|
||||
fn = func(n *ipn.Notify) bool {
|
||||
if n.NetMap == nil || n.NetMap.PrivateKey.IsZero() {
|
||||
return origFn(n)
|
||||
}
|
||||
|
||||
// The netmap in n is shared across all watchers, so to mutate it for a
|
||||
// single watcher we have to clone the notify and the netmap. We can
|
||||
// make shallow clones, at least.
|
||||
nm2 := *n.NetMap
|
||||
n2 := *n
|
||||
n2.NetMap = &nm2
|
||||
n2.NetMap.PrivateKey = key.NodePrivate{}
|
||||
return origFn(&n2)
|
||||
}
|
||||
}
|
||||
|
||||
var ini *ipn.Notify
|
||||
|
||||
b.mu.Lock()
|
||||
@@ -2395,7 +2414,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
|
||||
if !envknob.UseWIPCode() {
|
||||
return errors.New("The Tailscale SSH server is disabled on macOS tailscaled by default. To try, set env TAILSCALE_USE_WIP_CODE=1")
|
||||
}
|
||||
case "freebsd":
|
||||
case "freebsd", "openbsd":
|
||||
default:
|
||||
return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS)
|
||||
}
|
||||
@@ -2997,6 +3016,25 @@ func (b *LocalBackend) SetVarRoot(dir string) {
|
||||
b.varRoot = dir
|
||||
}
|
||||
|
||||
// SetLogFlusher sets a func to be called to flush log uploads.
|
||||
//
|
||||
// It should only be called before the LocalBackend is used.
|
||||
func (b *LocalBackend) SetLogFlusher(flushFunc func()) {
|
||||
b.logFlushFunc = flushFunc
|
||||
}
|
||||
|
||||
// TryFlushLogs calls the log flush function. It returns false if a log flush
|
||||
// function was never initialized with SetLogFlusher.
|
||||
//
|
||||
// TryFlushLogs should not block.
|
||||
func (b *LocalBackend) TryFlushLogs() bool {
|
||||
if b.logFlushFunc == nil {
|
||||
return false
|
||||
}
|
||||
b.logFlushFunc()
|
||||
return true
|
||||
}
|
||||
|
||||
// TailscaleVarRoot returns the root directory of Tailscale's writable
|
||||
// storage area. (e.g. "/var/lib/tailscale")
|
||||
//
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/tsaddr"
|
||||
@@ -60,9 +61,11 @@ func (b *LocalBackend) permitTKAInitLocked() bool {
|
||||
func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
|
||||
// TODO(tom): Remove this guard for 1.35 and later.
|
||||
if b.tka == nil && !b.permitTKAInitLocked() {
|
||||
health.SetTKAHealth(nil)
|
||||
return
|
||||
}
|
||||
if b.tka == nil {
|
||||
health.SetTKAHealth(nil)
|
||||
return // TKA not enabled.
|
||||
}
|
||||
|
||||
@@ -111,6 +114,13 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
|
||||
} else {
|
||||
b.tka.filtered = nil
|
||||
}
|
||||
|
||||
// Check that we ourselves are not locked out, report a health issue if so.
|
||||
if nm.SelfNode != nil && b.tka.authority.NodeKeyAuthorized(nm.SelfNode.Key, nm.SelfNode.KeySignature) != nil {
|
||||
health.SetTKAHealth(errors.New("this node is locked out; it will not have connectivity until it is signed. For more info, see https://tailscale.com/s/locked-out"))
|
||||
} else {
|
||||
health.SetTKAHealth(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// tkaSyncIfNeeded examines TKA info reported from the control plane,
|
||||
@@ -177,6 +187,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
|
||||
b.logf("Disablement failed, leaving TKA enabled. Error: %v", err)
|
||||
} else {
|
||||
isEnabled = false
|
||||
health.SetTKAHealth(nil)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("[bug] unreachable invariant of wantEnabled /w isEnabled")
|
||||
@@ -666,7 +677,11 @@ func (b *LocalBackend) NetworkLockModify(addKeys, removeKeys []tka.Key) (err err
|
||||
}
|
||||
}
|
||||
for _, removeKey := range removeKeys {
|
||||
if err := updater.RemoveKey(removeKey.ID()); err != nil {
|
||||
keyID, err := removeKey.ID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updater.RemoveKey(keyID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ func TestTKASync(t *testing.T) {
|
||||
name: "control has an update",
|
||||
controlAUMs: func(t *testing.T, a *tka.Authority, storage tka.Chonk, signer tka.Signer) []tka.AUM {
|
||||
b := a.NewUpdater(signer)
|
||||
if err := b.RemoveKey(someKey.ID()); err != nil {
|
||||
if err := b.RemoveKey(someKey.MustID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
aums, err := b.Finalize(storage)
|
||||
@@ -336,7 +336,7 @@ func TestTKASync(t *testing.T) {
|
||||
name: "node has an update",
|
||||
nodeAUMs: func(t *testing.T, a *tka.Authority, storage tka.Chonk, signer tka.Signer) []tka.AUM {
|
||||
b := a.NewUpdater(signer)
|
||||
if err := b.RemoveKey(someKey.ID()); err != nil {
|
||||
if err := b.RemoveKey(someKey.MustID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
aums, err := b.Finalize(storage)
|
||||
@@ -351,7 +351,7 @@ func TestTKASync(t *testing.T) {
|
||||
name: "node and control diverge",
|
||||
controlAUMs: func(t *testing.T, a *tka.Authority, storage tka.Chonk, signer tka.Signer) []tka.AUM {
|
||||
b := a.NewUpdater(signer)
|
||||
if err := b.SetKeyMeta(someKey.ID(), map[string]string{"ye": "swiggity"}); err != nil {
|
||||
if err := b.SetKeyMeta(someKey.MustID(), map[string]string{"ye": "swiggity"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
aums, err := b.Finalize(storage)
|
||||
@@ -362,7 +362,7 @@ func TestTKASync(t *testing.T) {
|
||||
},
|
||||
nodeAUMs: func(t *testing.T, a *tka.Authority, storage tka.Chonk, signer tka.Signer) []tka.AUM {
|
||||
b := a.NewUpdater(signer)
|
||||
if err := b.SetKeyMeta(someKey.ID(), map[string]string{"ye": "swooty"}); err != nil {
|
||||
if err := b.SetKeyMeta(someKey.MustID(), map[string]string{"ye": "swooty"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
aums, err := b.Finalize(storage)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/strs"
|
||||
"tailscale.com/util/winutil"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
@@ -322,7 +324,7 @@ func (pm *profileManager) setAsUserSelectedProfileLocked() error {
|
||||
func (pm *profileManager) loadSavedPrefs(key ipn.StateKey) (ipn.PrefsView, error) {
|
||||
bs, err := pm.store.ReadState(key)
|
||||
if err == ipn.ErrStateNotExist || len(bs) == 0 {
|
||||
return emptyPrefs, nil
|
||||
return defaultPrefs, nil
|
||||
}
|
||||
if err != nil {
|
||||
return ipn.PrefsView{}, err
|
||||
@@ -394,15 +396,29 @@ func (pm *profileManager) writeKnownProfiles() error {
|
||||
func (pm *profileManager) NewProfile() {
|
||||
metricNewProfile.Add(1)
|
||||
|
||||
pm.prefs = emptyPrefs
|
||||
pm.prefs = defaultPrefs
|
||||
pm.isNewProfile = true
|
||||
pm.currentProfile = &ipn.LoginProfile{}
|
||||
}
|
||||
|
||||
// emptyPrefs is the default prefs for a new profile.
|
||||
var emptyPrefs = func() ipn.PrefsView {
|
||||
// defaultPrefs is the default prefs for a new profile.
|
||||
var defaultPrefs = func() ipn.PrefsView {
|
||||
prefs := ipn.NewPrefs()
|
||||
prefs.WantRunning = false
|
||||
|
||||
prefs.ControlURL = winutil.GetPolicyString("LoginURL", "")
|
||||
|
||||
if exitNode := winutil.GetPolicyString("ExitNodeIP", ""); exitNode != "" {
|
||||
if ip, err := netip.ParseAddr(exitNode); err == nil {
|
||||
prefs.ExitNodeIP = ip
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Incoming (used by the UI) is the negation of ShieldsUp (used by the
|
||||
// backend), so this has to convert between the two conventions.
|
||||
prefs.ShieldsUp = winutil.GetPolicyString("AllowIncomingConnections", "") == "never"
|
||||
prefs.ForceDaemon = winutil.GetPolicyString("UnattendedMode", "") == "always"
|
||||
|
||||
return prefs.View()
|
||||
}()
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestProfileCurrentUserSwitch(t *testing.T) {
|
||||
} else if pm.currentProfile.ID != "" {
|
||||
t.Fatalf("currentProfile.ID = %q, want empty", pm.currentProfile.ID)
|
||||
}
|
||||
if !pm.CurrentPrefs().Equals(emptyPrefs) {
|
||||
if !pm.CurrentPrefs().Equals(defaultPrefs) {
|
||||
t.Fatalf("CurrentPrefs() = %v, want emptyPrefs", pm.CurrentPrefs().Pretty())
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestProfileCurrentUserSwitch(t *testing.T) {
|
||||
} else if pm.currentProfile.ID != "" {
|
||||
t.Fatalf("currentProfile.ID = %q, want empty", pm.currentProfile.ID)
|
||||
}
|
||||
if !pm.CurrentPrefs().Equals(emptyPrefs) {
|
||||
if !pm.CurrentPrefs().Equals(defaultPrefs) {
|
||||
t.Fatalf("CurrentPrefs() = %v, want emptyPrefs", pm.CurrentPrefs().Pretty())
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func TestProfileManagement(t *testing.T) {
|
||||
}
|
||||
wantCurProfile := ""
|
||||
wantProfiles := map[string]ipn.PrefsView{
|
||||
"": emptyPrefs,
|
||||
"": defaultPrefs,
|
||||
}
|
||||
checkProfiles := func(t *testing.T) {
|
||||
t.Helper()
|
||||
@@ -237,7 +237,7 @@ func TestProfileManagement(t *testing.T) {
|
||||
t.Logf("Create new profile")
|
||||
pm.NewProfile()
|
||||
wantCurProfile = ""
|
||||
wantProfiles[""] = emptyPrefs
|
||||
wantProfiles[""] = defaultPrefs
|
||||
checkProfiles(t)
|
||||
|
||||
{
|
||||
@@ -276,7 +276,7 @@ func TestProfileManagement(t *testing.T) {
|
||||
t.Logf("Create new profile - 2")
|
||||
pm.NewProfile()
|
||||
wantCurProfile = ""
|
||||
wantProfiles[""] = emptyPrefs
|
||||
wantProfiles[""] = defaultPrefs
|
||||
checkProfiles(t)
|
||||
|
||||
t.Logf("Login with the existing profile")
|
||||
@@ -310,7 +310,7 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
}
|
||||
wantCurProfile := ""
|
||||
wantProfiles := map[string]ipn.PrefsView{
|
||||
"": emptyPrefs,
|
||||
"": defaultPrefs,
|
||||
}
|
||||
checkProfiles := func(t *testing.T) {
|
||||
t.Helper()
|
||||
@@ -363,7 +363,7 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
t.Logf("Create new profile")
|
||||
pm.NewProfile()
|
||||
wantCurProfile = ""
|
||||
wantProfiles[""] = emptyPrefs
|
||||
wantProfiles[""] = defaultPrefs
|
||||
checkProfiles(t)
|
||||
|
||||
t.Logf("Save as test profile")
|
||||
@@ -380,7 +380,7 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantCurProfile = ""
|
||||
wantProfiles[""] = emptyPrefs
|
||||
wantProfiles[""] = defaultPrefs
|
||||
checkProfiles(t)
|
||||
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || (darwin && !ios) || freebsd
|
||||
//go:build linux || (darwin && !ios) || freebsd || openbsd
|
||||
|
||||
package ipnlocal
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ios || (!linux && !darwin && !freebsd)
|
||||
//go:build ios || (!linux && !darwin && !freebsd && !openbsd)
|
||||
|
||||
package ipnlocal
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
@@ -61,39 +61,41 @@ var handler = map[string]localAPIHandler{
|
||||
|
||||
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
|
||||
// without a trailing slash:
|
||||
"bugreport": (*Handler).serveBugReport,
|
||||
"check-ip-forwarding": (*Handler).serveCheckIPForwarding,
|
||||
"check-prefs": (*Handler).serveCheckPrefs,
|
||||
"component-debug-logging": (*Handler).serveComponentDebugLogging,
|
||||
"debug": (*Handler).serveDebug,
|
||||
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
||||
"derpmap": (*Handler).serveDERPMap,
|
||||
"dev-set-state-store": (*Handler).serveDevSetStateStore,
|
||||
"dial": (*Handler).serveDial,
|
||||
"file-targets": (*Handler).serveFileTargets,
|
||||
"goroutines": (*Handler).serveGoroutines,
|
||||
"id-token": (*Handler).serveIDToken,
|
||||
"login-interactive": (*Handler).serveLoginInteractive,
|
||||
"logout": (*Handler).serveLogout,
|
||||
"metrics": (*Handler).serveMetrics,
|
||||
"ping": (*Handler).servePing,
|
||||
"prefs": (*Handler).servePrefs,
|
||||
"pprof": (*Handler).servePprof,
|
||||
"serve-config": (*Handler).serveServeConfig,
|
||||
"set-dns": (*Handler).serveSetDNS,
|
||||
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
||||
"start": (*Handler).serveStart,
|
||||
"status": (*Handler).serveStatus,
|
||||
"tka/init": (*Handler).serveTKAInit,
|
||||
"tka/log": (*Handler).serveTKALog,
|
||||
"tka/modify": (*Handler).serveTKAModify,
|
||||
"tka/sign": (*Handler).serveTKASign,
|
||||
"tka/status": (*Handler).serveTKAStatus,
|
||||
"tka/disable": (*Handler).serveTKADisable,
|
||||
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
|
||||
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
|
||||
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
|
||||
"whois": (*Handler).serveWhoIs,
|
||||
"bugreport": (*Handler).serveBugReport,
|
||||
"check-ip-forwarding": (*Handler).serveCheckIPForwarding,
|
||||
"check-prefs": (*Handler).serveCheckPrefs,
|
||||
"component-debug-logging": (*Handler).serveComponentDebugLogging,
|
||||
"debug": (*Handler).serveDebug,
|
||||
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
||||
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
|
||||
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
|
||||
"derpmap": (*Handler).serveDERPMap,
|
||||
"dev-set-state-store": (*Handler).serveDevSetStateStore,
|
||||
"dial": (*Handler).serveDial,
|
||||
"file-targets": (*Handler).serveFileTargets,
|
||||
"goroutines": (*Handler).serveGoroutines,
|
||||
"id-token": (*Handler).serveIDToken,
|
||||
"login-interactive": (*Handler).serveLoginInteractive,
|
||||
"logout": (*Handler).serveLogout,
|
||||
"metrics": (*Handler).serveMetrics,
|
||||
"ping": (*Handler).servePing,
|
||||
"prefs": (*Handler).servePrefs,
|
||||
"pprof": (*Handler).servePprof,
|
||||
"serve-config": (*Handler).serveServeConfig,
|
||||
"set-dns": (*Handler).serveSetDNS,
|
||||
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
||||
"start": (*Handler).serveStart,
|
||||
"status": (*Handler).serveStatus,
|
||||
"tka/init": (*Handler).serveTKAInit,
|
||||
"tka/log": (*Handler).serveTKALog,
|
||||
"tka/modify": (*Handler).serveTKAModify,
|
||||
"tka/sign": (*Handler).serveTKASign,
|
||||
"tka/status": (*Handler).serveTKAStatus,
|
||||
"tka/disable": (*Handler).serveTKADisable,
|
||||
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
|
||||
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
|
||||
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
|
||||
"whois": (*Handler).serveWhoIs,
|
||||
}
|
||||
|
||||
func randHex(n int) string {
|
||||
@@ -290,6 +292,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
defer h.b.TryFlushLogs() // kick off upload after bugreport's done logging
|
||||
|
||||
logMarker := func() string {
|
||||
return fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, time.Now().UTC().Format("20060102150405Z"), randHex(8))
|
||||
@@ -506,6 +509,40 @@ func (h *Handler) serveDevSetStateStore(w http.ResponseWriter, r *http.Request)
|
||||
io.WriteString(w, "done\n")
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugPacketFilterRules(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", "\t")
|
||||
enc.Encode(nm.PacketFilterRules)
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugPacketFilterMatches(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", "\t")
|
||||
enc.Encode(nm.PacketFilter)
|
||||
}
|
||||
|
||||
func (h *Handler) serveComponentDebugLogging(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
|
||||
@@ -57,12 +57,12 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [golang.org/x/exp/shiny](https://pkg.go.dev/golang.org/x/exp/shiny) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/334a2380:shiny/LICENSE))
|
||||
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/062f8c9f:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.2.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/886fb937:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.2.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/2204b661:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.2.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.4.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/846276b3dbc5/LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/703fd9b7fbc0/LICENSE))
|
||||
- [inet.af/netaddr](https://pkg.go.dev/inet.af/netaddr) ([BSD-3-Clause](https://github.com/inetaf/netaddr/blob/097006376321/LICENSE))
|
||||
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||
|
||||
@@ -69,7 +69,7 @@ Some packages may only be included on certain architectures or operating systems
|
||||
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/4fa124729667/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/toqueteos/webbrowser](https://pkg.go.dev/github.com/toqueteos/webbrowser) ([MIT](https://github.com/toqueteos/webbrowser/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/6e9699743f5d/LICENSE))
|
||||
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/948a78c969ad/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/c3537552635f/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
|
||||
|
||||
@@ -66,13 +66,12 @@ type Config struct {
|
||||
// that's safe to embed in a JSON string literal without further escaping.
|
||||
MetricsDelta func() string
|
||||
|
||||
// FlushDelay is how long to wait to accumulate logs before
|
||||
// uploading them.
|
||||
// FlushDelayFn, if non-nil is a func that returns how long to wait to
|
||||
// accumulate logs before uploading them. 0 or negative means to upload
|
||||
// immediately.
|
||||
//
|
||||
// If zero, a default value is used. (currently 2 seconds)
|
||||
//
|
||||
// Negative means to upload immediately.
|
||||
FlushDelay time.Duration
|
||||
// If nil, a default value is used. (currently 2 seconds)
|
||||
FlushDelayFn func() time.Duration
|
||||
|
||||
// IncludeProcID, if true, results in an ephemeral process identifier being
|
||||
// included in logs. The ID is random and not guaranteed to be globally
|
||||
@@ -118,13 +117,13 @@ func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
|
||||
}
|
||||
}
|
||||
if s := envknob.String("TS_DEBUG_LOGTAIL_FLUSHDELAY"); s != "" {
|
||||
var err error
|
||||
cfg.FlushDelay, err = time.ParseDuration(s)
|
||||
if err != nil {
|
||||
if delay, err := time.ParseDuration(s); err == nil {
|
||||
cfg.FlushDelayFn = func() time.Duration { return delay }
|
||||
} else {
|
||||
log.Fatalf("invalid TS_DEBUG_LOGTAIL_FLUSHDELAY: %v", err)
|
||||
}
|
||||
} else if cfg.FlushDelay == 0 && !envknob.Bool("IN_TS_TEST") {
|
||||
cfg.FlushDelay = defaultFlushDelay
|
||||
} else if cfg.FlushDelayFn == nil && envknob.Bool("IN_TS_TEST") {
|
||||
cfg.FlushDelayFn = func() time.Duration { return 0 }
|
||||
}
|
||||
|
||||
stdLogf := func(f string, a ...any) {
|
||||
@@ -145,7 +144,7 @@ func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
|
||||
skipClientTime: cfg.SkipClientTime,
|
||||
drainWake: make(chan struct{}, 1),
|
||||
sentinel: make(chan int32, 16),
|
||||
flushDelay: cfg.FlushDelay,
|
||||
flushDelayFn: cfg.FlushDelayFn,
|
||||
timeNow: cfg.TimeNow,
|
||||
bo: backoff.NewBackoff("logtail", stdLogf, 30*time.Second),
|
||||
metricsDelta: cfg.MetricsDelta,
|
||||
@@ -179,8 +178,8 @@ type Logger struct {
|
||||
skipClientTime bool
|
||||
linkMonitor *monitor.Mon
|
||||
buffer Buffer
|
||||
drainWake chan struct{} // signal to speed up drain
|
||||
flushDelay time.Duration // negative or zero to upload agressively, or >0 to batch at this delay
|
||||
drainWake chan struct{} // signal to speed up drain
|
||||
flushDelayFn func() time.Duration // negative or zero return value to upload aggressively, or >0 to batch at this delay
|
||||
flushPending atomic.Bool
|
||||
sentinel chan int32
|
||||
timeNow func() time.Time
|
||||
@@ -462,12 +461,24 @@ func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Flush uploads all logs to the server.
|
||||
// It blocks until complete or there is an unrecoverable error.
|
||||
// Flush uploads all logs to the server. It blocks until complete or there is an
|
||||
// unrecoverable error.
|
||||
//
|
||||
// TODO(bradfitz): this apparently just returns nil, as of tailscale/corp@9c2ec35.
|
||||
// Finish cleaning this up.
|
||||
func (l *Logger) Flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartFlush starts a log upload, if anything is pending.
|
||||
//
|
||||
// If l is nil, StartFlush is a no-op.
|
||||
func (l *Logger) StartFlush() {
|
||||
if l != nil {
|
||||
l.tryDrainWake()
|
||||
}
|
||||
}
|
||||
|
||||
// logtailDisabled is whether logtail uploads to logcatcher are disabled.
|
||||
var logtailDisabled atomic.Bool
|
||||
|
||||
@@ -500,12 +511,16 @@ func (l *Logger) sendLocked(jsonBlob []byte) (int, error) {
|
||||
|
||||
n, err := l.buffer.Write(jsonBlob)
|
||||
|
||||
if l.flushDelay > 0 {
|
||||
flushDelay := defaultFlushDelay
|
||||
if l.flushDelayFn != nil {
|
||||
flushDelay = l.flushDelayFn()
|
||||
}
|
||||
if flushDelay > 0 {
|
||||
if l.flushPending.CompareAndSwap(false, true) {
|
||||
if l.flushTimer == nil {
|
||||
l.flushTimer = time.AfterFunc(l.flushDelay, l.tryDrainWake)
|
||||
l.flushTimer = time.AfterFunc(flushDelay, l.tryDrainWake)
|
||||
} else {
|
||||
l.flushTimer.Reset(l.flushDelay)
|
||||
l.flushTimer.Reset(flushDelay)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -19,41 +19,21 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns/resolver"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/net/tstun"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
var (
|
||||
magicDNSIP = tsaddr.TailscaleServiceIP()
|
||||
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
|
||||
)
|
||||
|
||||
var (
|
||||
errFullQueue = errors.New("request queue full")
|
||||
)
|
||||
|
||||
// maxActiveQueries returns the maximal number of DNS requests that be
|
||||
// can running.
|
||||
// If EnqueueRequest is called when this many requests are already pending,
|
||||
// the request will be dropped to avoid blocking the caller.
|
||||
func maxActiveQueries() int32 {
|
||||
if runtime.GOOS == "ios" {
|
||||
// For memory paranoia reasons on iOS, match the
|
||||
// historical Tailscale 1.x..1.8 behavior for now
|
||||
// (just before the 1.10 release).
|
||||
return 64
|
||||
}
|
||||
// But for other platforms, allow more burstiness:
|
||||
return 256
|
||||
}
|
||||
// maxActiveQueries returns the maximal number of DNS requests that can
|
||||
// be running.
|
||||
const maxActiveQueries = 256
|
||||
|
||||
// We use file-ignore below instead of ignore because on some platforms,
|
||||
// the lint exception is necessary and on others it is not,
|
||||
@@ -75,13 +55,6 @@ type response struct {
|
||||
type Manager struct {
|
||||
logf logger.Logf
|
||||
|
||||
// When netstack is not used, Manager implements magic DNS.
|
||||
// In this case, responses tracks completed DNS requests
|
||||
// which need a response, and NextPacket() synthesizes a
|
||||
// fake IP+UDP header to finish assembling the response.
|
||||
//
|
||||
// TODO(tom): Rip out once all platforms use netstack.
|
||||
responses chan response
|
||||
activeQueriesAtomic int32
|
||||
|
||||
ctx context.Context // good until Down
|
||||
@@ -98,10 +71,9 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon, di
|
||||
}
|
||||
logf = logger.WithPrefix(logf, "dns: ")
|
||||
m := &Manager{
|
||||
logf: logf,
|
||||
resolver: resolver.New(logf, linkMon, linkSel, dialer),
|
||||
os: oscfg,
|
||||
responses: make(chan response),
|
||||
logf: logf,
|
||||
resolver: resolver.New(logf, linkMon, linkSel, dialer),
|
||||
os: oscfg,
|
||||
}
|
||||
m.ctx, m.ctxCancel = context.WithCancel(context.Background())
|
||||
m.logf("using %T", m.os)
|
||||
@@ -316,89 +288,6 @@ func toIPsOnly(resolvers []*dnstype.Resolver) (ret []netip.Addr) {
|
||||
return ret
|
||||
}
|
||||
|
||||
// EnqueuePacket is the legacy path for handling magic DNS traffic, and is
|
||||
// called with a DNS request payload.
|
||||
//
|
||||
// TODO(tom): Rip out once all platforms use netstack.
|
||||
func (m *Manager) EnqueuePacket(bs []byte, proto ipproto.Proto, from, to netip.AddrPort) error {
|
||||
if to.Port() != 53 || proto != ipproto.UDP {
|
||||
return nil
|
||||
}
|
||||
|
||||
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries() {
|
||||
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
||||
metricDNSQueryErrorQueue.Add(1)
|
||||
return errFullQueue
|
||||
}
|
||||
|
||||
go func() {
|
||||
resp, err := m.resolver.Query(m.ctx, bs, from)
|
||||
if err != nil {
|
||||
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
||||
m.logf("dns query: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
return
|
||||
case m.responses <- response{resp, from}:
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextPacket is the legacy path for obtaining DNS results in response to
|
||||
// magic DNS queries. It blocks until a response is available.
|
||||
//
|
||||
// TODO(tom): Rip out once all platforms use netstack.
|
||||
func (m *Manager) NextPacket() ([]byte, error) {
|
||||
var resp response
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
return nil, net.ErrClosed
|
||||
case resp = <-m.responses:
|
||||
// continue
|
||||
}
|
||||
|
||||
// Unused space is needed further down the stack. To avoid extra
|
||||
// allocations/copying later on, we allocate such space here.
|
||||
const offset = tstun.PacketStartOffset
|
||||
|
||||
var buf []byte
|
||||
switch {
|
||||
case resp.to.Addr().Is4():
|
||||
h := packet.UDP4Header{
|
||||
IP4Header: packet.IP4Header{
|
||||
Src: magicDNSIP,
|
||||
Dst: resp.to.Addr(),
|
||||
},
|
||||
SrcPort: 53,
|
||||
DstPort: resp.to.Port(),
|
||||
}
|
||||
hlen := h.Len()
|
||||
buf = make([]byte, offset+hlen+len(resp.pkt))
|
||||
copy(buf[offset+hlen:], resp.pkt)
|
||||
h.Marshal(buf[offset:])
|
||||
case resp.to.Addr().Is6():
|
||||
h := packet.UDP6Header{
|
||||
IP6Header: packet.IP6Header{
|
||||
Src: magicDNSIPv6,
|
||||
Dst: resp.to.Addr(),
|
||||
},
|
||||
SrcPort: 53,
|
||||
DstPort: resp.to.Port(),
|
||||
}
|
||||
hlen := h.Len()
|
||||
buf = make([]byte, offset+hlen+len(resp.pkt))
|
||||
copy(buf[offset+hlen:], resp.pkt)
|
||||
h.Marshal(buf[offset:])
|
||||
}
|
||||
|
||||
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Query executes a DNS query received from the given address. The query is
|
||||
// provided in bs as a wire-encoded DNS query without any transport header.
|
||||
// This method is called for requests arriving over UDP and TCP.
|
||||
@@ -410,7 +299,7 @@ func (m *Manager) Query(ctx context.Context, bs []byte, from netip.AddrPort) ([]
|
||||
// continue
|
||||
}
|
||||
|
||||
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries() {
|
||||
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries {
|
||||
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
||||
metricDNSQueryErrorQueue.Add(1)
|
||||
return nil, errFullQueue
|
||||
|
||||
@@ -83,17 +83,26 @@ func Parse(r io.Reader) (*Config, error) {
|
||||
}
|
||||
|
||||
if s, ok := strs.CutPrefix(line, "search"); ok {
|
||||
domain := strings.TrimSpace(s)
|
||||
if len(domain) == len(s) {
|
||||
domains := strings.TrimSpace(s)
|
||||
if len(domains) == len(s) {
|
||||
// No leading space?!
|
||||
return nil, fmt.Errorf("missing space after \"domain\" in %q", line)
|
||||
return nil, fmt.Errorf("missing space after \"search\" in %q", line)
|
||||
}
|
||||
fqdn, err := dnsname.ToFQDN(domain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing search domains %q: %w", line, err)
|
||||
for len(domains) > 0 {
|
||||
domain := domains
|
||||
i := strings.IndexAny(domain, " \t")
|
||||
if i != -1 {
|
||||
domain = domain[:i]
|
||||
domains = strings.TrimSpace(domains[i+1:])
|
||||
} else {
|
||||
domains = ""
|
||||
}
|
||||
fqdn, err := dnsname.ToFQDN(domain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing search domain %q in %q: %w", domain, line, err)
|
||||
}
|
||||
config.SearchDomains = append(config.SearchDomains, fqdn)
|
||||
}
|
||||
config.SearchDomains = append(config.SearchDomains, fqdn)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
|
||||
@@ -57,6 +57,31 @@ func TestParse(t *testing.T) {
|
||||
},
|
||||
{in: `searchtailsacle.com`, wantErr: true},
|
||||
{in: `search`, wantErr: true},
|
||||
|
||||
// Issue 6875: there can be multiple search domains, and even if they're
|
||||
// over 253 bytes long total.
|
||||
{
|
||||
in: "search search-01.example search-02.example search-03.example search-04.example search-05.example search-06.example search-07.example search-08.example search-09.example search-10.example search-11.example search-12.example search-13.example search-14.example search-15.example\n",
|
||||
want: &Config{
|
||||
SearchDomains: []dnsname.FQDN{
|
||||
"search-01.example.",
|
||||
"search-02.example.",
|
||||
"search-03.example.",
|
||||
"search-04.example.",
|
||||
"search-05.example.",
|
||||
"search-06.example.",
|
||||
"search-07.example.",
|
||||
"search-08.example.",
|
||||
"search-09.example.",
|
||||
"search-10.example.",
|
||||
"search-11.example.",
|
||||
"search-12.example.",
|
||||
"search-13.example.",
|
||||
"search-14.example.",
|
||||
"search-15.example.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestFallbackRootWorks(t *testing.T) {
|
||||
crtFile := filepath.Join(d, "tlsdial.test.crt")
|
||||
keyFile := filepath.Join(d, "tlsdial.test.key")
|
||||
caFile := filepath.Join(d, "rootCA.pem")
|
||||
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"),
|
||||
cmd := exec.Command("go",
|
||||
"run", "filippo.io/mkcert",
|
||||
"--cert-file="+crtFile,
|
||||
"--key-file="+keyFile,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
check_file() {
|
||||
got=$1
|
||||
|
||||
for year in `seq 2019 2022`; do
|
||||
for year in `seq 2019 2023`; do
|
||||
want=$(cat <<EOF
|
||||
// Copyright (c) $year Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// and groups to the specified `--uid`, `--gid` and `--groups`, and
|
||||
// then launches the requested `--cmd`.
|
||||
|
||||
//go:build linux || (darwin && !ios) || freebsd
|
||||
//go:build linux || (darwin && !ios) || freebsd || openbsd
|
||||
|
||||
package tailssh
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -33,6 +34,7 @@ import (
|
||||
"github.com/u-root/u-root/pkg/termios"
|
||||
"go4.org/mem"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/cmd/tailscaled/childproc"
|
||||
"tailscale.com/envknob"
|
||||
@@ -693,3 +695,59 @@ func acceptEnvPair(kv string) bool {
|
||||
}
|
||||
return k == "TERM" || k == "LANG" || strings.HasPrefix(k, "LC_")
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (ia *incubatorArgs) loginArgs() []string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
if distro.Get() == distro.Arch && !fileExists("/etc/pam.d/remote") {
|
||||
// See https://github.com/tailscale/tailscale/issues/4924
|
||||
//
|
||||
// Arch uses a different login binary that makes the -h flag set the PAM
|
||||
// service to "remote". So if they don't have that configured, don't
|
||||
// pass -h.
|
||||
return []string{ia.loginCmdPath, "-f", ia.localUser, "-p"}
|
||||
}
|
||||
return []string{ia.loginCmdPath, "-f", ia.localUser, "-h", ia.remoteIP, "-p"}
|
||||
case "darwin", "freebsd", "openbsd":
|
||||
return []string{ia.loginCmdPath, "-fp", "-h", ia.remoteIP, ia.localUser}
|
||||
}
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func setGroups(groupIDs []int) error {
|
||||
if runtime.GOOS == "darwin" && len(groupIDs) > 16 {
|
||||
// darwin returns "invalid argument" if more than 16 groups are passed to syscall.Setgroups
|
||||
// some info can be found here:
|
||||
// https://opensource.apple.com/source/samba/samba-187.8/patches/support-darwin-initgroups-syscall.auto.html
|
||||
// this fix isn't great, as anyone reading this has probably just wasted hours figuring out why
|
||||
// some permissions thing isn't working, due to some arbitrary group ordering, but it at least allows
|
||||
// this to work for more things than it previously did.
|
||||
groupIDs = groupIDs[:16]
|
||||
}
|
||||
|
||||
err := syscall.Setgroups(groupIDs)
|
||||
if err != nil && os.Geteuid() != 0 && groupsMatchCurrent(groupIDs) {
|
||||
// If we're not root, ignore a Setgroups failure if all groups are the same.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func groupsMatchCurrent(groupIDs []int) bool {
|
||||
existing, err := syscall.Getgroups()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if len(existing) != len(groupIDs) {
|
||||
return false
|
||||
}
|
||||
groupIDs = slices.Clone(groupIDs)
|
||||
sort.Ints(groupIDs)
|
||||
sort.Ints(existing)
|
||||
return slices.Equal(groupIDs, existing)
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tailssh
|
||||
|
||||
import "syscall"
|
||||
|
||||
func (ia *incubatorArgs) loginArgs() []string {
|
||||
return []string{ia.loginCmdPath, "-fp", "-h", ia.remoteIP, ia.localUser}
|
||||
}
|
||||
|
||||
func setGroups(groupIDs []int) error {
|
||||
// darwin returns "invalid argument" if more than 16 groups are passed to syscall.Setgroups
|
||||
// some info can be found here:
|
||||
// https://opensource.apple.com/source/samba/samba-187.8/patches/support-darwin-initgroups-syscall.auto.html
|
||||
// this fix isn't great, as anyone reading this has probably just wasted hours figuring out why
|
||||
// some permissions thing isn't working, due to some arbitrary group ordering, but it at least allows
|
||||
// this to work for more things than it previously did.
|
||||
return syscall.Setgroups(groupIDs[:16])
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tailssh
|
||||
|
||||
import "syscall"
|
||||
|
||||
func (ia *incubatorArgs) loginArgs() []string {
|
||||
return []string{ia.loginCmdPath, "-fp", "-h", ia.remoteIP, ia.localUser}
|
||||
}
|
||||
|
||||
func setGroups(groupIDs []int) error {
|
||||
return syscall.Setgroups(groupIDs)
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -173,24 +172,3 @@ func maybeStartLoginSessionLinux(logf logger.Logf, ia incubatorArgs) (func() err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (ia *incubatorArgs) loginArgs() []string {
|
||||
if distro.Get() == distro.Arch && !fileExists("/etc/pam.d/remote") {
|
||||
// See https://github.com/tailscale/tailscale/issues/4924
|
||||
//
|
||||
// Arch uses a different login binary that makes the -h flag set the PAM
|
||||
// service to "remote". So if they don't have that configured, don't
|
||||
// pass -h.
|
||||
return []string{ia.loginCmdPath, "-f", ia.localUser, "-p"}
|
||||
}
|
||||
return []string{ia.loginCmdPath, "-f", ia.localUser, "-h", ia.remoteIP, "-p"}
|
||||
}
|
||||
|
||||
func setGroups(groupIDs []int) error {
|
||||
return syscall.Setgroups(groupIDs)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || (darwin && !ios) || freebsd
|
||||
//go:build linux || (darwin && !ios) || freebsd || openbsd
|
||||
|
||||
// Package tailssh is an SSH server integrated into Tailscale.
|
||||
package tailssh
|
||||
|
||||
@@ -88,7 +88,8 @@ type CapabilityVersion int
|
||||
// - 49: 2022-11-03: Client understands EarlyNoise
|
||||
// - 50: 2022-11-14: Client understands CapabilityIngress
|
||||
// - 51: 2022-11-30: Client understands CapabilityTailnetLockAlpha
|
||||
const CurrentCapabilityVersion CapabilityVersion = 51
|
||||
// - 52: 2023-01-05: client can handle c2n POST /logtail/flush
|
||||
const CurrentCapabilityVersion CapabilityVersion = 52
|
||||
|
||||
type StableID string
|
||||
|
||||
|
||||
21
tka/aum.go
21
tka/aum.go
@@ -150,7 +150,7 @@ func (a *AUM) StaticValidate() error {
|
||||
return errors.New("absent parent must be represented by a nil slice")
|
||||
}
|
||||
for i, sig := range a.Signatures {
|
||||
if len(sig.KeyID) == 0 || len(sig.Signature) != ed25519.SignatureSize {
|
||||
if len(sig.KeyID) != 32 || len(sig.Signature) != ed25519.SignatureSize {
|
||||
return fmt.Errorf("signature %d has missing keyID or malformed signature", i)
|
||||
}
|
||||
}
|
||||
@@ -196,8 +196,13 @@ func (a *AUM) StaticValidate() error {
|
||||
|
||||
case AUMNoOp:
|
||||
default:
|
||||
// TODO(tom): Ignore unknown AUMs for GA.
|
||||
return fmt.Errorf("unknown AUM kind: %v", a.MessageKind)
|
||||
// An AUM with an unknown message kind was received! That means
|
||||
// that a future version of tailscaled added some feature we don't
|
||||
// understand.
|
||||
//
|
||||
// The future-compatibility contract for AUM message types is that
|
||||
// they must only add new features, not change the semantics of existing
|
||||
// mechanisms or features. As such, old clients can safely ignore them.
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -276,14 +281,20 @@ func (a *AUM) Parent() (h AUMHash, ok bool) {
|
||||
return h, false
|
||||
}
|
||||
|
||||
func (a *AUM) sign25519(priv ed25519.PrivateKey) {
|
||||
func (a *AUM) sign25519(priv ed25519.PrivateKey) error {
|
||||
key := Key{Kind: Key25519, Public: priv.Public().(ed25519.PublicKey)}
|
||||
sigHash := a.SigHash()
|
||||
|
||||
keyID, err := key.ID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Signatures = append(a.Signatures, tkatype.Signature{
|
||||
KeyID: key.ID(),
|
||||
KeyID: keyID,
|
||||
Signature: ed25519.Sign(priv, sigHash[:]),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Weight computes the 'signature weight' of the AUM
|
||||
|
||||
@@ -189,7 +189,7 @@ func TestAUMWeight(t *testing.T) {
|
||||
{
|
||||
"Unary key",
|
||||
AUM{
|
||||
Signatures: []tkatype.Signature{{KeyID: key.ID()}},
|
||||
Signatures: []tkatype.Signature{{KeyID: key.MustID()}},
|
||||
},
|
||||
State{
|
||||
Keys: []Key{key},
|
||||
@@ -199,7 +199,7 @@ func TestAUMWeight(t *testing.T) {
|
||||
{
|
||||
"Multiple keys",
|
||||
AUM{
|
||||
Signatures: []tkatype.Signature{{KeyID: key.ID()}, {KeyID: key2.ID()}},
|
||||
Signatures: []tkatype.Signature{{KeyID: key.MustID()}, {KeyID: key2.MustID()}},
|
||||
},
|
||||
State{
|
||||
Keys: []Key{key, key2},
|
||||
@@ -209,7 +209,7 @@ func TestAUMWeight(t *testing.T) {
|
||||
{
|
||||
"Double use",
|
||||
AUM{
|
||||
Signatures: []tkatype.Signature{{KeyID: key.ID()}, {KeyID: key.ID()}},
|
||||
Signatures: []tkatype.Signature{{KeyID: key.MustID()}, {KeyID: key.MustID()}},
|
||||
},
|
||||
State{
|
||||
Keys: []Key{key},
|
||||
|
||||
@@ -60,7 +60,12 @@ func (b *UpdateBuilder) mkUpdate(update AUM) error {
|
||||
|
||||
// AddKey adds a new key to the authority.
|
||||
func (b *UpdateBuilder) AddKey(key Key) error {
|
||||
if _, err := b.state.GetKey(key.ID()); err == nil {
|
||||
keyID, err := key.ID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := b.state.GetKey(keyID); err == nil {
|
||||
return fmt.Errorf("cannot add key %v: already exists", key)
|
||||
}
|
||||
return b.mkUpdate(AUM{MessageKind: AUMAddKey, Key: &key})
|
||||
|
||||
@@ -19,7 +19,7 @@ func (s signer25519) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, e
|
||||
key := Key{Kind: Key25519, Public: priv.Public().(ed25519.PublicKey)}
|
||||
|
||||
return []tkatype.Signature{{
|
||||
KeyID: key.ID(),
|
||||
KeyID: key.MustID(),
|
||||
Signature: ed25519.Sign(priv, sigHash[:]),
|
||||
}}, nil
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func TestAuthorityBuilderAddKey(t *testing.T) {
|
||||
if err := a.Inform(storage, updates); err != nil {
|
||||
t.Fatalf("could not apply generated updates: %v", err)
|
||||
}
|
||||
if _, err := a.state.GetKey(key2.ID()); err != nil {
|
||||
if _, err := a.state.GetKey(key2.MustID()); err != nil {
|
||||
t.Errorf("could not read new key: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) {
|
||||
}
|
||||
|
||||
b := a.NewUpdater(signer25519(priv))
|
||||
if err := b.RemoveKey(key2.ID()); err != nil {
|
||||
if err := b.RemoveKey(key2.MustID()); err != nil {
|
||||
t.Fatalf("RemoveKey(%v) failed: %v", key2, err)
|
||||
}
|
||||
updates, err := b.Finalize(storage)
|
||||
@@ -88,7 +88,7 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) {
|
||||
if err := a.Inform(storage, updates); err != nil {
|
||||
t.Fatalf("could not apply generated updates: %v", err)
|
||||
}
|
||||
if _, err := a.state.GetKey(key2.ID()); err != ErrNoSuchKey {
|
||||
if _, err := a.state.GetKey(key2.MustID()); err != ErrNoSuchKey {
|
||||
t.Errorf("GetKey(key2).err = %v, want %v", err, ErrNoSuchKey)
|
||||
}
|
||||
}
|
||||
@@ -107,8 +107,8 @@ func TestAuthorityBuilderSetKeyVote(t *testing.T) {
|
||||
}
|
||||
|
||||
b := a.NewUpdater(signer25519(priv))
|
||||
if err := b.SetKeyVote(key.ID(), 5); err != nil {
|
||||
t.Fatalf("SetKeyVote(%v) failed: %v", key.ID(), err)
|
||||
if err := b.SetKeyVote(key.MustID(), 5); err != nil {
|
||||
t.Fatalf("SetKeyVote(%v) failed: %v", key.MustID(), err)
|
||||
}
|
||||
updates, err := b.Finalize(storage)
|
||||
if err != nil {
|
||||
@@ -120,7 +120,7 @@ func TestAuthorityBuilderSetKeyVote(t *testing.T) {
|
||||
if err := a.Inform(storage, updates); err != nil {
|
||||
t.Fatalf("could not apply generated updates: %v", err)
|
||||
}
|
||||
k, err := a.state.GetKey(key.ID())
|
||||
k, err := a.state.GetKey(key.MustID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func TestAuthorityBuilderSetKeyMeta(t *testing.T) {
|
||||
}
|
||||
|
||||
b := a.NewUpdater(signer25519(priv))
|
||||
if err := b.SetKeyMeta(key.ID(), map[string]string{"b": "c"}); err != nil {
|
||||
if err := b.SetKeyMeta(key.MustID(), map[string]string{"b": "c"}); err != nil {
|
||||
t.Fatalf("SetKeyMeta(%v) failed: %v", key, err)
|
||||
}
|
||||
updates, err := b.Finalize(storage)
|
||||
@@ -156,7 +156,7 @@ func TestAuthorityBuilderSetKeyMeta(t *testing.T) {
|
||||
if err := a.Inform(storage, updates); err != nil {
|
||||
t.Fatalf("could not apply generated updates: %v", err)
|
||||
}
|
||||
k, err := a.state.GetKey(key.ID())
|
||||
k, err := a.state.GetKey(key.MustID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -185,10 +185,10 @@ func TestAuthorityBuilderMultiple(t *testing.T) {
|
||||
if err := b.AddKey(key2); err != nil {
|
||||
t.Fatalf("AddKey(%v) failed: %v", key2, err)
|
||||
}
|
||||
if err := b.SetKeyVote(key2.ID(), 42); err != nil {
|
||||
if err := b.SetKeyVote(key2.MustID(), 42); err != nil {
|
||||
t.Fatalf("SetKeyVote(%v) failed: %v", key2, err)
|
||||
}
|
||||
if err := b.RemoveKey(key.ID()); err != nil {
|
||||
if err := b.RemoveKey(key.MustID()); err != nil {
|
||||
t.Fatalf("RemoveKey(%v) failed: %v", key, err)
|
||||
}
|
||||
updates, err := b.Finalize(storage)
|
||||
@@ -201,14 +201,14 @@ func TestAuthorityBuilderMultiple(t *testing.T) {
|
||||
if err := a.Inform(storage, updates); err != nil {
|
||||
t.Fatalf("could not apply generated updates: %v", err)
|
||||
}
|
||||
k, err := a.state.GetKey(key2.ID())
|
||||
k, err := a.state.GetKey(key2.MustID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := k.Votes, uint(42); got != want {
|
||||
t.Errorf("key.Votes = %d, want %d", got, want)
|
||||
}
|
||||
if _, err := a.state.GetKey(key.ID()); err != ErrNoSuchKey {
|
||||
if _, err := a.state.GetKey(key.MustID()); err != ErrNoSuchKey {
|
||||
t.Errorf("GetKey(key).err = %v, want %v", err, ErrNoSuchKey)
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,7 @@ func TestAuthorityBuilderCheckpointsAfterXUpdates(t *testing.T) {
|
||||
if err := a.Inform(storage, updates); err != nil {
|
||||
t.Fatalf("could not apply generated updates: %v", err)
|
||||
}
|
||||
if _, err := a.state.GetKey(key2.ID()); err != nil {
|
||||
if _, err := a.state.GetKey(key2.MustID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ func (c *testChain) makeAUM(v *testchainNode) AUM {
|
||||
sigHash := aum.SigHash()
|
||||
for _, key := range c.SignAllKeys {
|
||||
aum.Signatures = append(aum.Signatures, tkatype.Signature{
|
||||
KeyID: c.Key[key].ID(),
|
||||
KeyID: c.Key[key].MustID(),
|
||||
Signature: ed25519.Sign(c.KeyPrivs[key], sigHash[:]),
|
||||
})
|
||||
}
|
||||
@@ -276,7 +276,7 @@ func (c *testChain) makeAUM(v *testchainNode) AUM {
|
||||
// sign it using that key.
|
||||
if key := v.SignedWith; key != "" {
|
||||
aum.Signatures = append(aum.Signatures, tkatype.Signature{
|
||||
KeyID: c.Key[key].ID(),
|
||||
KeyID: c.Key[key].MustID(),
|
||||
Signature: ed25519.Sign(c.KeyPrivs[key], sigHash[:]),
|
||||
})
|
||||
}
|
||||
|
||||
17
tka/key.go
17
tka/key.go
@@ -74,14 +74,25 @@ func (k Key) Clone() Key {
|
||||
return out
|
||||
}
|
||||
|
||||
func (k Key) ID() tkatype.KeyID {
|
||||
// MustID returns the KeyID of the key, panicking if an error is
|
||||
// encountered. This must only be used for tests.
|
||||
func (k Key) MustID() tkatype.KeyID {
|
||||
id, err := k.ID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// ID returns the KeyID of the key.
|
||||
func (k Key) ID() (tkatype.KeyID, error) {
|
||||
switch k.Kind {
|
||||
// Because 25519 public keys are so short, we just use the 32-byte
|
||||
// public as their 'key ID'.
|
||||
case Key25519:
|
||||
return tkatype.KeyID(k.Public)
|
||||
return tkatype.KeyID(k.Public), nil
|
||||
default:
|
||||
panic("unsupported key kind")
|
||||
return nil, fmt.Errorf("unknown key kind: %v", k.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestVerify25519(t *testing.T) {
|
||||
sigHash := aum.SigHash()
|
||||
aum.Signatures = []tkatype.Signature{
|
||||
{
|
||||
KeyID: key.ID(),
|
||||
KeyID: key.MustID(),
|
||||
Signature: ed25519.Sign(priv, sigHash[:]),
|
||||
},
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func TestNLPrivate(t *testing.T) {
|
||||
|
||||
// We manually compute the keyID, so make sure its consistent with
|
||||
// tka.Key.ID().
|
||||
if !bytes.Equal(k.ID(), p.KeyID()) {
|
||||
t.Errorf("private.KeyID() & tka KeyID differ: %x != %x", k.ID(), p.KeyID())
|
||||
if !bytes.Equal(k.MustID(), p.KeyID()) {
|
||||
t.Errorf("private.KeyID() & tka KeyID differ: %x != %x", k.MustID(), p.KeyID())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ func TestForkingPropagation(t *testing.T) {
|
||||
F1.template = removeKey1`,
|
||||
optSignAllUsing("key2"),
|
||||
optKey("key2", key, priv),
|
||||
optTemplate("removeKey1", AUM{MessageKind: AUMRemoveKey, KeyID: s.defaultKey.ID()})),
|
||||
optTemplate("removeKey1", AUM{MessageKind: AUMRemoveKey, KeyID: s.defaultKey.MustID()})),
|
||||
})
|
||||
s.testSyncsBetween(control, n2)
|
||||
s.checkHaveConsensus(control, n2)
|
||||
@@ -282,10 +282,10 @@ func TestForkingPropagation(t *testing.T) {
|
||||
s.testSyncsBetween(control, n1)
|
||||
s.checkHaveConsensus(n1, n2)
|
||||
|
||||
if _, err := n1.A.state.GetKey(s.defaultKey.ID()); err != ErrNoSuchKey {
|
||||
if _, err := n1.A.state.GetKey(s.defaultKey.MustID()); err != ErrNoSuchKey {
|
||||
t.Error("default key was still present")
|
||||
}
|
||||
if _, err := n1.A.state.GetKey(key.ID()); err != nil {
|
||||
if _, err := n1.A.state.GetKey(key.MustID()); err != nil {
|
||||
t.Errorf("key2 was not trusted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -305,7 +305,9 @@ func TestInvalidAUMPropagationRejected(t *testing.T) {
|
||||
l3 := n1.AUMs["L3"]
|
||||
l3H := l3.Hash()
|
||||
l4 := AUM{MessageKind: AUMAddKey, PrevAUMHash: l3H[:]}
|
||||
l4.sign25519(s.defaultPriv)
|
||||
if err := l4.sign25519(s.defaultPriv); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l4H := l4.Hash()
|
||||
n1.storage.CommitVerifiedAUMs([]AUM{l4})
|
||||
n1.A.state.LastAUMHash = &l4H
|
||||
@@ -371,7 +373,9 @@ func TestBadSigAUMPropagationRejected(t *testing.T) {
|
||||
l3 := n1.AUMs["L3"]
|
||||
l3H := l3.Hash()
|
||||
l4 := AUM{MessageKind: AUMNoOp, PrevAUMHash: l3H[:]}
|
||||
l4.sign25519(s.defaultPriv)
|
||||
if err := l4.sign25519(s.defaultPriv); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l4.Signatures[0].Signature[3] = 42
|
||||
l4H := l4.Hash()
|
||||
n1.storage.CommitVerifiedAUMs([]AUM{l4})
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestSigDirect(t *testing.T) {
|
||||
|
||||
sig := NodeKeySignature{
|
||||
SigKind: SigDirect,
|
||||
KeyID: k.ID(),
|
||||
KeyID: k.MustID(),
|
||||
Pubkey: nodeKeyPub,
|
||||
}
|
||||
sigHash := sig.SigHash()
|
||||
@@ -65,7 +65,7 @@ func TestSigNested(t *testing.T) {
|
||||
// the network-lock key.
|
||||
nestedSig := NodeKeySignature{
|
||||
SigKind: SigDirect,
|
||||
KeyID: k.ID(),
|
||||
KeyID: k.MustID(),
|
||||
Pubkey: oldPub,
|
||||
WrappingPubkey: rPub,
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func TestSigNested_DeepNesting(t *testing.T) {
|
||||
// the network-lock key.
|
||||
nestedSig := NodeKeySignature{
|
||||
SigKind: SigDirect,
|
||||
KeyID: k.ID(),
|
||||
KeyID: k.MustID(),
|
||||
Pubkey: oldPub,
|
||||
WrappingPubkey: rPub,
|
||||
}
|
||||
@@ -204,7 +204,7 @@ func TestSigCredential(t *testing.T) {
|
||||
// public key.
|
||||
nestedSig := NodeKeySignature{
|
||||
SigKind: SigCredential,
|
||||
KeyID: k.ID(),
|
||||
KeyID: k.MustID(),
|
||||
WrappingPubkey: cPub,
|
||||
}
|
||||
sigHash := nestedSig.SigHash()
|
||||
@@ -280,11 +280,11 @@ func TestSigSerializeUnserialize(t *testing.T) {
|
||||
key := Key{Kind: Key25519, Public: pub, Votes: 2}
|
||||
sig := NodeKeySignature{
|
||||
SigKind: SigDirect,
|
||||
KeyID: key.ID(),
|
||||
KeyID: key.MustID(),
|
||||
Pubkey: nodeKeyPub,
|
||||
Nested: &NodeKeySignature{
|
||||
SigKind: SigDirect,
|
||||
KeyID: key.ID(),
|
||||
KeyID: key.MustID(),
|
||||
Pubkey: nodeKeyPub,
|
||||
},
|
||||
}
|
||||
|
||||
52
tka/state.go
52
tka/state.go
@@ -29,9 +29,6 @@ type State struct {
|
||||
|
||||
// DisablementSecrets are KDF-derived values which can be used
|
||||
// to turn off the TKA in the event of a consensus-breaking bug.
|
||||
//
|
||||
// TODO(tom): This is an alpha feature, remove this mechanism once
|
||||
// we have confidence in our implementation.
|
||||
DisablementSecrets [][]byte `cbor:"2,keyasint"`
|
||||
|
||||
// Keys are the public keys currently trusted by the TKA.
|
||||
@@ -48,7 +45,12 @@ type State struct {
|
||||
// GetKey returns the trusted key with the specified KeyID.
|
||||
func (s State) GetKey(key tkatype.KeyID) (Key, error) {
|
||||
for _, k := range s.Keys {
|
||||
if bytes.Equal(k.ID(), key) {
|
||||
keyID, err := k.ID()
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
|
||||
if bytes.Equal(keyID, key) {
|
||||
return k, nil
|
||||
}
|
||||
}
|
||||
@@ -172,7 +174,11 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
|
||||
if update.Key == nil {
|
||||
return State{}, errors.New("no key to add provided")
|
||||
}
|
||||
if _, err := s.GetKey(update.Key.ID()); err == nil {
|
||||
keyID, err := update.Key.ID()
|
||||
if err != nil {
|
||||
return State{}, err
|
||||
}
|
||||
if _, err := s.GetKey(keyID); err == nil {
|
||||
return State{}, errors.New("key already exists")
|
||||
}
|
||||
out := s.cloneForUpdate(&update)
|
||||
@@ -195,7 +201,11 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
|
||||
}
|
||||
out := s.cloneForUpdate(&update)
|
||||
for i := range out.Keys {
|
||||
if bytes.Equal(out.Keys[i].ID(), update.KeyID) {
|
||||
keyID, err := out.Keys[i].ID()
|
||||
if err != nil {
|
||||
return State{}, err
|
||||
}
|
||||
if bytes.Equal(keyID, update.KeyID) {
|
||||
out.Keys[i] = k
|
||||
}
|
||||
}
|
||||
@@ -204,7 +214,11 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
|
||||
case AUMRemoveKey:
|
||||
idx := -1
|
||||
for i := range s.Keys {
|
||||
if bytes.Equal(update.KeyID, s.Keys[i].ID()) {
|
||||
keyID, err := s.Keys[i].ID()
|
||||
if err != nil {
|
||||
return State{}, err
|
||||
}
|
||||
if bytes.Equal(update.KeyID, keyID) {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
@@ -217,9 +231,15 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
|
||||
return out, nil
|
||||
|
||||
default:
|
||||
// TODO(tom): Instead of erroring, update lastHash and
|
||||
// continue (to preserve future compatibility).
|
||||
return State{}, fmt.Errorf("unhandled message: %v", update.MessageKind)
|
||||
// An AUM with an unknown message kind was received! That means
|
||||
// that a future version of tailscaled added some feature we don't
|
||||
// understand.
|
||||
//
|
||||
// The future-compatibility contract for AUM message types is that
|
||||
// they must only add new features, not change the semantics of existing
|
||||
// mechanisms or features. As such, old clients can safely ignore them.
|
||||
out := s.cloneForUpdate(&update)
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +294,17 @@ func (s *State) staticValidateCheckpoint() error {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(k.ID(), k2.ID()) {
|
||||
|
||||
id1, err := k.ID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("key[%d]: %w", i, err)
|
||||
}
|
||||
id2, err := k2.ID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("key[%d]: %w", j, err)
|
||||
}
|
||||
|
||||
if bytes.Equal(id1, id2) {
|
||||
return fmt.Errorf("key[%d]: duplicates key[%d]", i, j)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ func TestForkResolutionMessageType(t *testing.T) {
|
||||
L3.hashSeed = 18
|
||||
`,
|
||||
optTemplate("addKey", AUM{MessageKind: AUMAddKey, Key: &key}),
|
||||
optTemplate("removeKey", AUM{MessageKind: AUMRemoveKey, KeyID: key.ID()}))
|
||||
optTemplate("removeKey", AUM{MessageKind: AUMRemoveKey, KeyID: key.MustID()}))
|
||||
|
||||
l1H := c.AUMHashes["L1"]
|
||||
l2H := c.AUMHashes["L2"]
|
||||
@@ -165,7 +165,7 @@ func TestComputeStateAt(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("computeStateAt(G1) failed: %v", err)
|
||||
}
|
||||
if _, err := state.GetKey(key.ID()); err != ErrNoSuchKey {
|
||||
if _, err := state.GetKey(key.MustID()); err != ErrNoSuchKey {
|
||||
t.Errorf("expected key to be missing: err = %v", err)
|
||||
}
|
||||
if *state.LastAUMHash != c.AUMHashes["G1"] {
|
||||
@@ -182,7 +182,7 @@ func TestComputeStateAt(t *testing.T) {
|
||||
if *state.LastAUMHash != wantHash {
|
||||
t.Errorf("LastAUMHash = %x, want %x", *state.LastAUMHash, wantHash)
|
||||
}
|
||||
if _, err := state.GetKey(key.ID()); err != nil {
|
||||
if _, err := state.GetKey(key.MustID()); err != nil {
|
||||
t.Errorf("expected key to be present at state: err = %v", err)
|
||||
}
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func TestOpenAuthority(t *testing.T) {
|
||||
|
||||
i2, i2H := fakeAUM(t, 2, &i1H)
|
||||
i3, i3H := fakeAUM(t, 5, &i2H)
|
||||
l2, l2H := fakeAUM(t, AUM{MessageKind: AUMNoOp, KeyID: []byte{7}, Signatures: []tkatype.Signature{{KeyID: key.ID()}}}, &i3H)
|
||||
l2, l2H := fakeAUM(t, AUM{MessageKind: AUMNoOp, KeyID: []byte{7}, Signatures: []tkatype.Signature{{KeyID: key.MustID()}}}, &i3H)
|
||||
l3, l3H := fakeAUM(t, 4, &i3H)
|
||||
|
||||
g2, g2H := fakeAUM(t, 8, nil)
|
||||
@@ -266,7 +266,7 @@ func TestOpenAuthority(t *testing.T) {
|
||||
t.Fatalf("New() failed: %v", err)
|
||||
}
|
||||
// Should include the key added in G1
|
||||
if _, err := a.state.GetKey(key.ID()); err != nil {
|
||||
if _, err := a.state.GetKey(key.MustID()); err != nil {
|
||||
t.Errorf("missing G1 key: %v", err)
|
||||
}
|
||||
// The head of the chain should be L2.
|
||||
@@ -338,10 +338,10 @@ func TestCreateBootstrapAuthority(t *testing.T) {
|
||||
}
|
||||
|
||||
// Both authorities should trust the key laid down in the genesis state.
|
||||
if !a1.KeyTrusted(key.ID()) {
|
||||
if !a1.KeyTrusted(key.MustID()) {
|
||||
t.Error("a1 did not trust genesis key")
|
||||
}
|
||||
if !a2.KeyTrusted(key.ID()) {
|
||||
if !a2.KeyTrusted(key.MustID()) {
|
||||
t.Error("a2 did not trust genesis key")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -53,7 +51,7 @@ func TestInQemu(t *testing.T) {
|
||||
}
|
||||
t.Logf("using %v", look)
|
||||
}
|
||||
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"),
|
||||
cmd := exec.Command("go",
|
||||
"test",
|
||||
"--exec="+execVia,
|
||||
"-v",
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
_ "tailscale.com/paths"
|
||||
_ "tailscale.com/safesocket"
|
||||
_ "tailscale.com/smallzstd"
|
||||
_ "tailscale.com/ssh/tailssh"
|
||||
_ "tailscale.com/syncs"
|
||||
_ "tailscale.com/tailcfg"
|
||||
_ "tailscale.com/tsweb"
|
||||
|
||||
@@ -13,13 +13,11 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "list", "-json", ".")
|
||||
cmd := exec.Command("go", "list", "-json", ".")
|
||||
cmd.Env = append(os.Environ(), "GOOS=ios", "GOARCH=arm64")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
|
||||
@@ -13,13 +13,11 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "list", "-json", ".")
|
||||
cmd := exec.Command("go", "list", "-json", ".")
|
||||
cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
@@ -38,9 +39,10 @@ type NetworkMap struct {
|
||||
Peers []*tailcfg.Node // sorted by Node.ID
|
||||
DNS tailcfg.DNSConfig
|
||||
// TODO(maisem) : replace with View.
|
||||
Hostinfo tailcfg.Hostinfo
|
||||
PacketFilter []filter.Match
|
||||
SSHPolicy *tailcfg.SSHPolicy // or nil, if not enabled/allowed
|
||||
Hostinfo tailcfg.Hostinfo
|
||||
PacketFilter []filter.Match
|
||||
PacketFilterRules views.Slice[tailcfg.FilterRule]
|
||||
SSHPolicy *tailcfg.SSHPolicy // or nil, if not enabled/allowed
|
||||
|
||||
// CollectServices reports whether this node's Tailnet has
|
||||
// requested that info about services be included in HostInfo.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package codegen
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
@@ -13,13 +14,16 @@ import (
|
||||
"go/types"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/imports"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
// LoadTypes returns all named types in pkgName, keyed by their type name.
|
||||
@@ -54,11 +58,13 @@ func HasNoClone(structTag string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
const header = `// Copyright (c) %d Tailscale Inc & AUTHORS All rights reserved.
|
||||
const copyrightHeader = `// Copyright (c) %d Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Code generated by %v; DO NOT EDIT.
|
||||
`
|
||||
|
||||
const genAndPackageHeader = `// Code generated by %v; DO NOT EDIT.
|
||||
|
||||
package %s
|
||||
`
|
||||
@@ -104,15 +110,18 @@ func (it *ImportTracker) Write(w io.Writer) {
|
||||
fmt.Fprintf(w, ")\n\n")
|
||||
}
|
||||
|
||||
func writeHeader(w io.Writer, tool, pkg string) {
|
||||
fmt.Fprintf(w, header, time.Now().Year(), tool, pkg)
|
||||
func writeHeader(w io.Writer, tool, pkg string, copyrightYear int) {
|
||||
if copyrightYear != 0 {
|
||||
fmt.Fprintf(w, copyrightHeader, copyrightYear)
|
||||
}
|
||||
fmt.Fprintf(w, genAndPackageHeader, tool, pkg)
|
||||
}
|
||||
|
||||
// WritePackageFile adds a file with the provided imports and contents to package.
|
||||
// The tool param is used to identify the tool that generated package file.
|
||||
func WritePackageFile(tool string, pkg *packages.Package, path string, it *ImportTracker, contents *bytes.Buffer) error {
|
||||
func WritePackageFile(tool string, pkg *packages.Package, path string, copyrightYear int, it *ImportTracker, contents *bytes.Buffer) error {
|
||||
buf := new(bytes.Buffer)
|
||||
writeHeader(buf, tool, pkg.Name)
|
||||
writeHeader(buf, tool, pkg.Name, copyrightYear)
|
||||
it.Write(buf)
|
||||
if _, err := buf.Write(contents.Bytes()); err != nil {
|
||||
return err
|
||||
@@ -263,3 +272,51 @@ func IsViewType(typ types.Type) bool {
|
||||
}
|
||||
return t.Field(0).Name() == "ж"
|
||||
}
|
||||
|
||||
// CopyrightYear reports the greatest copyright year in non-generated *.go files
|
||||
// in the current directory, for use in the copyright line of generated code.
|
||||
//
|
||||
// It panics on I/O error, as it's assumed this is only being used by "go
|
||||
// generate" or GitHub actions.
|
||||
//
|
||||
// TODO(bradfitz,dgentry): determine what heuristic to use for all this: latest
|
||||
// year, earliest, none? don't list years at all? IANAL. Get advice of others.
|
||||
// For now we just want to unbreak the tree. See Issue 6865.
|
||||
func CopyrightYear(dir string) (year int) {
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rxYear := regexp.MustCompile(`^// Copyright \(c\) (20\d{2}) `)
|
||||
rxGenerated := regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
|
||||
Files:
|
||||
for _, f := range files {
|
||||
name := f.Name()
|
||||
if !f.Type().IsRegular() ||
|
||||
strings.HasPrefix(name, ".") || // includes emacs noise
|
||||
!strings.HasSuffix(name, ".go") ||
|
||||
strings.HasSuffix(name, "_clone.go") ||
|
||||
strings.HasSuffix(name, "_view.go") ||
|
||||
strings.HasSuffix(name, "_test.go") {
|
||||
continue
|
||||
}
|
||||
src, err := os.ReadFile(filepath.Join(dir, name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bs := bufio.NewScanner(bytes.NewReader(src))
|
||||
for bs.Scan() {
|
||||
line := bs.Bytes()
|
||||
if m := rxYear.FindSubmatch(line); m != nil {
|
||||
if y := must.Get(strconv.Atoi(string(m[1]))); y > year {
|
||||
year = y
|
||||
}
|
||||
continue
|
||||
}
|
||||
if rxGenerated.Match(line) {
|
||||
continue Files
|
||||
}
|
||||
}
|
||||
}
|
||||
return year
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ var (
|
||||
func TestFindModuleInfo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
name := filepath.Join(dir, "tailscaled-version-test")
|
||||
goTool := filepath.Join(runtime.GOROOT(), "bin", "go"+exe())
|
||||
out, err := exec.Command(goTool, "build", "-o", name, "tailscale.com/cmd/tailscaled").CombinedOutput()
|
||||
out, err := exec.Command("go", "build", "-o", name, "tailscale.com/cmd/tailscaled").CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build tailscaled: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -56,13 +55,6 @@ import (
|
||||
"tailscale.com/wgengine/wglog"
|
||||
)
|
||||
|
||||
const magicDNSPort = 53
|
||||
|
||||
var (
|
||||
magicDNSIP = tsaddr.TailscaleServiceIP()
|
||||
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
|
||||
)
|
||||
|
||||
// Lazy wireguard-go configuration parameters.
|
||||
const (
|
||||
// lazyPeerIdleThreshold is the idle duration after
|
||||
@@ -462,8 +454,6 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
e.logf("Starting link monitor...")
|
||||
e.linkMon.Start()
|
||||
|
||||
go e.pollResolver()
|
||||
|
||||
e.logf("Engine created.")
|
||||
return e, nil
|
||||
}
|
||||
@@ -491,19 +481,6 @@ func echoRespondToAll(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
|
||||
// tailscaled directly. Other packets are allowed to proceed into the
|
||||
// main ACL filter.
|
||||
func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
|
||||
// Handle traffic to the service IP.
|
||||
// TODO(tom): Netstack handles this when it is installed. Rip all
|
||||
// this out once netstack is used on all platforms.
|
||||
switch p.Dst.Addr() {
|
||||
case magicDNSIP, magicDNSIPv6:
|
||||
err := e.dns.EnqueuePacket(append([]byte(nil), p.Payload()...), p.IPProto, p.Src, p.Dst)
|
||||
if err != nil {
|
||||
e.logf("dns: enqueue: %v", err)
|
||||
}
|
||||
metricMagicDNSPacketIn.Add(1)
|
||||
return filter.Drop
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
|
||||
isLocalAddr, ok := e.isLocalAddr.LoadOk()
|
||||
if !ok {
|
||||
@@ -523,27 +500,6 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper)
|
||||
return filter.Accept
|
||||
}
|
||||
|
||||
// pollResolver reads packets from the DNS resolver and injects them inbound.
|
||||
//
|
||||
// TODO(tom): Remove this fallback path (via NextPacket()) once all
|
||||
// platforms use netstack.
|
||||
func (e *userspaceEngine) pollResolver() {
|
||||
for {
|
||||
bs, err := e.dns.NextPacket()
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
e.logf("dns: error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// The leading empty space required by the semantics of
|
||||
// InjectInboundDirect is allocated in NextPacket().
|
||||
e.tundev.InjectInboundDirect(bs, tstun.PacketStartOffset)
|
||||
}
|
||||
}
|
||||
|
||||
var debugTrimWireguard = envknob.RegisterOptBool("TS_DEBUG_TRIM_WIREGUARD")
|
||||
|
||||
// forceFullWireguardConfig reports whether we should give wireguard our full
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user