Compare commits

...

19 Commits

Author SHA1 Message Date
Nick Khyl 63242007ae
VERSION.txt: this is v1.90.5
Signed-off-by: Nick Khyl <nickk@tailscale.com>
1 month ago
Brad Fitzpatrick 300e6062bf cmd/k8s-operator/generate: skip tests if no network or Helm is down
Updates helm/helm#31434

Change-Id: I5eb20e97ff543f883d5646c9324f50f54180851d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit d5a40c01ab)
1 month ago
Brad Fitzpatrick 1a6c31538e sessionrecording: fix regression in recent http2 package change
In 3f5c560fd4 I changed to use std net/http's HTTP/2 support,
instead of pulling in x/net/http2.

But I forgot to update DialTLSContext to DialContext, which meant it
was falling back to using the std net.Dialer for its dials, instead
of the passed-in one.

The tests only passed because they were using localhost addresses, so
the std net.Dialer worked. But in prod, where a tsnet Dialer would be
needed, it didn't work, and would time out for 10 seconds before
resorting to the old protocol.

So this fixes the tests to use an isolated in-memory network to prevent
that class of problem in the future. With the test change, the old code
fails and the new code passes.

Thanks to @jasonodonnell for debugging!

Updates #17304
Updates 3f5c560fd4

Change-Id: I3602bafd07dc6548e2c62985af9ac0afb3a0e967
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 8996254647)
1 month ago
Nick Khyl 68cba300e4
VERSION.txt: this is v1.90.4
Signed-off-by: Nick Khyl <nickk@tailscale.com>
1 month ago
M. J. Fromberger 2dd72f6ec2
Revert "logtail: avoid racing eventbus subscriptions with Shutdown (#17639)" (#17684)
This reverts commit 4346615d77.
We averted the shutdown race, but will need to service the subscriber even when
we are not waiting for a change so that we do not delay the bus as a whole.

Updates #17638

Change-Id: I5488466ed83f5ad1141c95267f5ae54878a24657
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
(cherry picked from commit db5815fb97)
1 month ago
Brad Fitzpatrick 53004dded1 wgengine/magicsock: fix js/wasm crash regression loading non-existent portmapper
Thanks for the report, @Need-an-AwP!

Fixes #17681
Updates #9394

Change-Id: I2e0b722ef9b460bd7e79499192d1a315504ca84c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit edb11e0e60)
1 month ago
srwareham 033adc398c cmd/tailscale/cli: move JetKVM scripts to /userdata/init.d for persistence (#17610)
Updates #16524
Updates jetkvm/rv1106-system#34

Signed-off-by: srwareham <ebriouscoding@gmail.com>
(cherry picked from commit f4e2720821)
1 month ago
Max Coulombe bad03eefa1 feature/identityfederation: strip query params on clientID (#17666)
Updates #9192

Change-Id: I35c88df8a0242ecc19a23265d392ef78ac176b9d
Signed-off-by: mcoulombe <max@tailscale.com>
(cherry picked from commit 34e992f59d)
1 month ago
Patrick O'Doherty dc3c15b4c6
control/controlclient: back out HW key attestation (#17664)
Temporarily back out the TPM-based hw attestation code while we debug
Windows exceptions.

Updates tailscale/corp#31269

Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
(cherry picked from commit a760cbe33f)
1 month ago
Nick Khyl c50fe71822
VERSION.txt: this is v1.90.3
Signed-off-by: Nick Khyl <nickk@tailscale.com>
1 month ago
M. J. Fromberger 597acd8663
logtail: avoid racing eventbus subscriptions with Shutdown (#17639)
When the eventbus is enabled, set up the subscription for change deltas at the
beginning when the client is created, rather than waiting for the first
awaitInternetUp check.

Otherwise, it is possible for a check to race with the client close in
Shutdown, which triggers a panic.

Updates #17638

Change-Id: I461c07939eca46699072b14b1814ecf28eec750c
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
(cherry picked from commit 4346615d77)
1 month ago
Claus Lensbøl e6a3669277
net/tsdial: do not panic if setting the same eventbus twice (#17640)
Updates #17638

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
(cherry picked from commit fd0e541e5d)
1 month ago
Nick Khyl 8bcd44ecf0
VERSION.txt: this is v1.90.2
Signed-off-by: Nick Khyl <nickk@tailscale.com>
1 month ago
Claus Lensbøl b0f0bce928 health: compare warnable codes to avoid errors on release branch (#17637)
This compares the warnings we actually care about and skips the unstable
warnings and the changes with no warnings.

Fixes #17635

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
(cherry picked from commit 7418583e47)
1 month ago
Brad Fitzpatrick c81ef9055b util/linuxfw: fix 32-bit arm regression with iptables
This fixes a regression from dd615c8fdd that moved the
newIPTablesRunner constructor from a any-Linux-GOARCH file to one that
was only amd64 and arm64, thus breaking iptables on other platforms
(notably 32-bit "arm", as seen on older Pis running Buster with
iptables)

Tested by hand on a Raspberry Pi 2 w/ Buster + iptables for now, for
lack of automated 32-bit arm tests at the moment. But filed #17629.

Fixes #17623
Updates #17629

Change-Id: Iac1a3d78f35d8428821b46f0fed3f3717891c1bd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 8576a802ca)
1 month ago
Patrick O'Doherty 9fe44b3718 feature/tpm: use withSRK to probe TPM availability (#17627)
On some platforms e.g. ChromeOS the owner hierarchy might not always be
available to us. To avoid stale sealing exceptions later we probe to
confirm it's working rather than rely solely on family indicator status.

Updates #17622

Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
(cherry picked from commit 672b1f0e76)
1 month ago
Patrick O'Doherty a8ae316858 feature/tpm: check TPM family data for compatibility (#17624)
Check that the TPM we have opened is advertised as a 2.0 family device
before using it for state sealing / hardware attestation.

Updates #17622

Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
(cherry picked from commit 36ad24b20f)
1 month ago
Nick Khyl 75b0c6f164 VERSION.txt: this is v1.90.1
Signed-off-by: Nick Khyl <nickk@tailscale.com>
1 month ago
Nick Khyl 3c78146ece VERSION.txt: this is v1.90.0
Signed-off-by: Nick Khyl <nickk@tailscale.com>
1 month ago

@ -1 +1 @@
1.89.0
1.90.5

@ -596,6 +596,19 @@ func (lc *Client) DebugResultJSON(ctx context.Context, action string) (any, erro
return x, nil
}
// QueryOptionalFeatures queries the optional features supported by the Tailscale daemon.
func (lc *Client) QueryOptionalFeatures(ctx context.Context) (*apitype.OptionalFeatures, error) {
body, err := lc.send(ctx, "POST", "/localapi/v0/debug-optional-features", 200, nil)
if err != nil {
return nil, fmt.Errorf("error %w: %s", err, body)
}
var x apitype.OptionalFeatures
if err := json.Unmarshal(body, &x); err != nil {
return nil, err
}
return &x, nil
}
// SetDevStoreKeyValue set a statestore key/value. It's only meant for development.
// The schema (including when keys are re-read) is not a stable interface.
func (lc *Client) SetDevStoreKeyValue(ctx context.Context, key, value string) error {

@ -94,3 +94,13 @@ type DNSQueryResponse struct {
// Resolvers is the list of resolvers that the forwarder deemed able to resolve the query.
Resolvers []*dnstype.Resolver
}
// OptionalFeatures describes which optional features are enabled in the build.
type OptionalFeatures struct {
// Features is the map of optional feature names to whether they are
// enabled.
//
// Disabled features may be absent from the map. (That is, false values
// are not guaranteed to be present.)
Features map[string]bool
}

@ -116,7 +116,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/syncs from tailscale.com/cmd/derper+
tailscale.com/tailcfg from tailscale.com/client/local+
tailscale.com/tka from tailscale.com/client/local+
LW tailscale.com/tsconst from tailscale.com/net/netmon+
tailscale.com/tsconst from tailscale.com/net/netmon+
tailscale.com/tstime from tailscale.com/derp+
tailscale.com/tstime/mono from tailscale.com/tstime/rate
tailscale.com/tstime/rate from tailscale.com/derp/derpserver

@ -144,7 +144,7 @@ func generate(baseDir string) error {
if _, err := file.Write([]byte(helmConditionalEnd)); err != nil {
return fmt.Errorf("error writing helm if-statement end: %w", err)
}
return nil
return file.Close()
}
for _, crd := range []struct {
crdPath, templatePath string

@ -7,26 +7,50 @@ package main
import (
"bytes"
"context"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"tailscale.com/tstest/nettest"
"tailscale.com/util/cibuild"
)
func Test_generate(t *testing.T) {
nettest.SkipIfNoNetwork(t)
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
if _, err := net.DefaultResolver.LookupIPAddr(ctx, "get.helm.sh"); err != nil {
// https://github.com/helm/helm/issues/31434
t.Skipf("get.helm.sh seems down or unreachable; skipping test")
}
base, err := os.Getwd()
base = filepath.Join(base, "../../../")
if err != nil {
t.Fatalf("error getting current working directory: %v", err)
}
defer cleanup(base)
helmCLIPath := filepath.Join(base, "tool/helm")
if out, err := exec.Command(helmCLIPath, "version").CombinedOutput(); err != nil && cibuild.On() {
// It's not just DNS. Azure is generating bogus certs within GitHub Actions at least for
// helm. So try to run it and see if we can even fetch it.
//
// https://github.com/helm/helm/issues/31434
t.Skipf("error fetching helm; skipping test in CI: %v, %s", err, out)
}
if err := generate(base); err != nil {
t.Fatalf("CRD template generation: %v", err)
}
tempDir := t.TempDir()
helmCLIPath := filepath.Join(base, "tool/helm")
helmChartTemplatesPath := filepath.Join(base, "cmd/k8s-operator/deploy/chart")
helmPackageCmd := exec.Command(helmCLIPath, "package", helmChartTemplatesPath, "--destination", tempDir, "--version", "0.0.1")
helmPackageCmd.Stderr = os.Stderr

@ -48,9 +48,12 @@ func runConfigureJetKVM(ctx context.Context, args []string) error {
if runtime.GOOS != "linux" || distro.Get() != distro.JetKVM {
return errors.New("only implemented on JetKVM")
}
err := os.WriteFile("/etc/init.d/S22tailscale", bytes.TrimLeft([]byte(`
if err := os.MkdirAll("/userdata/init.d", 0755); err != nil {
return errors.New("unable to create /userdata/init.d")
}
err := os.WriteFile("/userdata/init.d/S22tailscale", bytes.TrimLeft([]byte(`
#!/bin/sh
# /etc/init.d/S22tailscale
# /userdata/init.d/S22tailscale
# Start/stop tailscaled
case "$1" in

@ -116,7 +116,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
tailscale.com/tka from tailscale.com/control/controlclient+
tailscale.com/tsconst from tailscale.com/net/netns
tailscale.com/tsconst from tailscale.com/net/netns+
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
tailscale.com/tstime from tailscale.com/control/controlclient+
tailscale.com/tstime/mono from tailscale.com/net/tstun+

@ -7,8 +7,6 @@ import (
"bytes"
"cmp"
"context"
"crypto"
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
@ -948,26 +946,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
ConnectionHandleForTest: connectionHandleForTest,
}
// If we have a hardware attestation key, sign the node key with it and send
// the key & signature in the map request.
if buildfeatures.HasTPM {
if k := persist.AsStruct().AttestationKey; k != nil && !k.IsZero() {
hwPub := key.HardwareAttestationPublicFromPlatformKey(k)
request.HardwareAttestationKey = hwPub
t := c.clock.Now()
msg := fmt.Sprintf("%d|%s", t.Unix(), nodeKey.String())
digest := sha256.Sum256([]byte(msg))
sig, err := k.Sign(nil, digest[:], crypto.SHA256)
if err != nil {
c.logf("failed to sign node key with hardware attestation key: %v", err)
} else {
request.HardwareAttestationKeySignature = sig
request.HardwareAttestationKeySignatureTimestamp = t
}
}
}
var extraDebugFlags []string
if buildfeatures.HasAdvertiseRoutes && hi != nil && c.netMon != nil && !c.skipIPForwardingCheck &&
ipForwardingBroken(hi.RoutableIPs, c.netMon.InterfaceState()) {

@ -13,6 +13,12 @@ var ErrUnavailable = errors.New("feature not included in this build")
var in = map[string]bool{}
// Registered reports the set of registered features.
//
// The returned map should not be modified by the caller,
// not accessed concurrently with calls to Register.
func Registered() map[string]bool { return in }
// Register notes that the named feature is linked into the binary.
func Register(name string) {
if _, ok := in[name]; ok {

@ -42,12 +42,12 @@ func resolveAuthKey(ctx context.Context, baseURL, clientID, idToken string, tags
baseURL = ipn.DefaultControlURL
}
ephemeral, preauth, err := parseOptionalAttributes(clientID)
strippedID, ephemeral, preauth, err := parseOptionalAttributes(clientID)
if err != nil {
return "", fmt.Errorf("failed to parse optional config attributes: %w", err)
}
accessToken, err := exchangeJWTForToken(ctx, baseURL, clientID, idToken)
accessToken, err := exchangeJWTForToken(ctx, baseURL, strippedID, idToken)
if err != nil {
return "", fmt.Errorf("failed to exchange JWT for access token: %w", err)
}
@ -79,15 +79,15 @@ func resolveAuthKey(ctx context.Context, baseURL, clientID, idToken string, tags
return authkey, nil
}
func parseOptionalAttributes(clientID string) (ephemeral bool, preauthorized bool, err error) {
_, attrs, found := strings.Cut(clientID, "?")
func parseOptionalAttributes(clientID string) (strippedID string, ephemeral bool, preauthorized bool, err error) {
strippedID, attrs, found := strings.Cut(clientID, "?")
if !found {
return true, false, nil
return clientID, true, false, nil
}
parsed, err := url.ParseQuery(attrs)
if err != nil {
return false, false, fmt.Errorf("failed to parse optional config attributes: %w", err)
return "", false, false, fmt.Errorf("failed to parse optional config attributes: %w", err)
}
for k := range parsed {
@ -97,11 +97,14 @@ func parseOptionalAttributes(clientID string) (ephemeral bool, preauthorized boo
case "preauthorized":
preauthorized, err = strconv.ParseBool(parsed.Get(k))
default:
return false, false, fmt.Errorf("unknown optional config attribute %q", k)
return "", false, false, fmt.Errorf("unknown optional config attribute %q", k)
}
}
if err != nil {
return "", false, false, err
}
return ephemeral, preauthorized, err
return strippedID, ephemeral, preauthorized, nil
}
// exchangeJWTForToken exchanges a JWT for a Tailscale access token.

@ -87,6 +87,7 @@ func TestParseOptionalAttributes(t *testing.T) {
tests := []struct {
name string
clientID string
wantClientID string
wantEphemeral bool
wantPreauth bool
wantErr string
@ -94,6 +95,7 @@ func TestParseOptionalAttributes(t *testing.T) {
{
name: "default values",
clientID: "client-123",
wantClientID: "client-123",
wantEphemeral: true,
wantPreauth: false,
wantErr: "",
@ -101,6 +103,7 @@ func TestParseOptionalAttributes(t *testing.T) {
{
name: "custom values",
clientID: "client-123?ephemeral=false&preauthorized=true",
wantClientID: "client-123",
wantEphemeral: false,
wantPreauth: true,
wantErr: "",
@ -108,6 +111,7 @@ func TestParseOptionalAttributes(t *testing.T) {
{
name: "unknown attribute",
clientID: "client-123?unknown=value",
wantClientID: "",
wantEphemeral: false,
wantPreauth: false,
wantErr: `unknown optional config attribute "unknown"`,
@ -115,6 +119,7 @@ func TestParseOptionalAttributes(t *testing.T) {
{
name: "invalid value",
clientID: "client-123?ephemeral=invalid",
wantClientID: "",
wantEphemeral: false,
wantPreauth: false,
wantErr: `strconv.ParseBool: parsing "invalid": invalid syntax`,
@ -123,7 +128,7 @@ func TestParseOptionalAttributes(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ephemeral, preauth, err := parseOptionalAttributes(tt.clientID)
strippedID, ephemeral, preauth, err := parseOptionalAttributes(tt.clientID)
if tt.wantErr != "" {
if err == nil {
t.Errorf("parseOptionalAttributes() error = nil, want %q", tt.wantErr)
@ -138,6 +143,9 @@ func TestParseOptionalAttributes(t *testing.T) {
return
}
}
if strippedID != tt.wantClientID {
t.Errorf("parseOptionalAttributes() strippedID = %v, want %v", strippedID, tt.wantClientID)
}
if ephemeral != tt.wantEphemeral {
t.Errorf("parseOptionalAttributes() ephemeral = %v, want %v", ephemeral, tt.wantEphemeral)
}

@ -6,6 +6,7 @@
package portmapper
import (
"tailscale.com/feature"
"tailscale.com/net/netmon"
"tailscale.com/net/portmapper"
"tailscale.com/net/portmapper/portmappertype"
@ -14,6 +15,7 @@ import (
)
func init() {
feature.Register("portmapper")
portmappertype.HookNewPortMapper.Set(newPortMapper)
}

@ -55,11 +55,25 @@ func init() {
}
func tpmSupported() bool {
hi := infoOnce()
if hi == nil {
return false
}
if hi.FamilyIndicator != "2.0" {
return false
}
tpm, err := open()
if err != nil {
return false
}
tpm.Close()
defer tpm.Close()
if err := withSRK(logger.Discard, tpm, func(srk tpm2.AuthHandle) error {
return nil
}); err != nil {
return false
}
return true
}
@ -104,6 +118,7 @@ func info() *tailcfg.TPMInfo {
{tpm2.TPMPTVendorTPMType, func(info *tailcfg.TPMInfo, value uint32) { info.Model = int(value) }},
{tpm2.TPMPTFirmwareVersion1, func(info *tailcfg.TPMInfo, value uint32) { info.FirmwareVersion += uint64(value) << 32 }},
{tpm2.TPMPTFirmwareVersion2, func(info *tailcfg.TPMInfo, value uint32) { info.FirmwareVersion += uint64(value) }},
{tpm2.TPMPTFamilyIndicator, toStr(&info.FamilyIndicator)},
} {
resp, err := tpm2.GetCapability{
Capability: tpm2.TPMCapTPMProperties,

@ -133,6 +133,31 @@ func TestStore(t *testing.T) {
})
}
func BenchmarkInfo(b *testing.B) {
b.StopTimer()
skipWithoutTPM(b)
b.StartTimer()
for i := 0; i < b.N; i++ {
hi := info()
if hi == nil {
b.Fatalf("tpm info error")
}
}
b.StopTimer()
}
func BenchmarkTPMSupported(b *testing.B) {
b.StopTimer()
skipWithoutTPM(b)
b.StartTimer()
for i := 0; i < b.N; i++ {
if !tpmSupported() {
b.Fatalf("tpmSupported returned false")
}
}
b.StopTimer()
}
func BenchmarkStore(b *testing.B) {
skipWithoutTPM(b)
b.StopTimer()

@ -19,6 +19,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"tailscale.com/metrics"
"tailscale.com/tailcfg"
"tailscale.com/tsconst"
"tailscale.com/tstest"
"tailscale.com/tstime"
"tailscale.com/types/opt"
@ -739,21 +740,27 @@ func TestControlHealthNotifies(t *testing.T) {
ht.SetIPNState("NeedsLogin", true)
ht.GotStreamedMapResponse()
// Expect events at starup, before doing anything else
// Expect events at starup, before doing anything else, skip unstable
// event and no warning event as they show up at different times.
synctest.Wait()
if err := eventbustest.ExpectExactly(tw,
eventbustest.Type[Change](), // warming-up
eventbustest.Type[Change](), // is-using-unstable-version
eventbustest.Type[Change](), // not-in-map-poll
if err := eventbustest.Expect(tw,
CompareWarnableCode(t, tsconst.HealthWarnableWarmingUp),
CompareWarnableCode(t, tsconst.HealthWarnableNotInMapPoll),
CompareWarnableCode(t, tsconst.HealthWarnableWarmingUp),
); err != nil {
t.Errorf("startup error: %v", err)
}
// Only set initial state if we need to
if len(test.initialState) != 0 {
t.Log("Setting initial state")
ht.SetControlHealth(test.initialState)
synctest.Wait()
if err := eventbustest.ExpectExactly(tw, eventbustest.Type[Change]()); err != nil {
if err := eventbustest.Expect(tw,
CompareWarnableCode(t, tsconst.HealthWarnableMagicsockReceiveFuncError),
// Skip event with no warnable
CompareWarnableCode(t, tsconst.HealthWarnableNoDERPHome),
); err != nil {
t.Errorf("initial state error: %v", err)
}
}
@ -771,6 +778,22 @@ func TestControlHealthNotifies(t *testing.T) {
}
}
func CompareWarnableCode(t *testing.T, code string) func(Change) bool {
t.Helper()
return func(c Change) bool {
t.Helper()
if c.Warnable != nil {
t.Logf("Warnable code: %s", c.Warnable.Code)
if string(c.Warnable.Code) == code {
return true
}
} else {
t.Log("No Warnable")
}
return false
}
}
func TestControlHealthIgnoredOutsideMapPoll(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
bus := eventbustest.NewBus(t)

@ -9,6 +9,7 @@ import (
"time"
"tailscale.com/feature/buildfeatures"
"tailscale.com/tsconst"
"tailscale.com/version"
)
@ -26,7 +27,7 @@ This file contains definitions for the Warnables maintained within this `health`
// updateAvailableWarnable is a Warnable that warns the user that an update is available.
var updateAvailableWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "update-available",
Code: tsconst.HealthWarnableUpdateAvailable,
Title: "Update available",
Severity: SeverityLow,
Text: func(args Args) string {
@ -42,7 +43,7 @@ var updateAvailableWarnable = condRegister(func() *Warnable {
// securityUpdateAvailableWarnable is a Warnable that warns the user that an important security update is available.
var securityUpdateAvailableWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "security-update-available",
Code: tsconst.HealthWarnableSecurityUpdateAvailable,
Title: "Security update available",
Severity: SeverityMedium,
Text: func(args Args) string {
@ -59,7 +60,7 @@ var securityUpdateAvailableWarnable = condRegister(func() *Warnable {
// so they won't be surprised by all the issues that may arise.
var unstableWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "is-using-unstable-version",
Code: tsconst.HealthWarnableIsUsingUnstableVersion,
Title: "Using an unstable version",
Severity: SeverityLow,
Text: StaticMessage("This is an unstable version of Tailscale meant for testing and development purposes. Please report any issues to Tailscale."),
@ -69,7 +70,7 @@ var unstableWarnable = condRegister(func() *Warnable {
// NetworkStatusWarnable is a Warnable that warns the user that the network is down.
var NetworkStatusWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "network-status",
Code: tsconst.HealthWarnableNetworkStatus,
Title: "Network down",
Severity: SeverityMedium,
Text: StaticMessage("Tailscale cannot connect because the network is down. Check your Internet connection."),
@ -81,7 +82,7 @@ var NetworkStatusWarnable = condRegister(func() *Warnable {
// IPNStateWarnable is a Warnable that warns the user that Tailscale is stopped.
var IPNStateWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "wantrunning-false",
Code: tsconst.HealthWarnableWantRunningFalse,
Title: "Tailscale off",
Severity: SeverityLow,
Text: StaticMessage("Tailscale is stopped."),
@ -91,7 +92,7 @@ var IPNStateWarnable = condRegister(func() *Warnable {
// localLogWarnable is a Warnable that warns the user that the local log is misconfigured.
var localLogWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "local-log-config-error",
Code: tsconst.HealthWarnableLocalLogConfigError,
Title: "Local log misconfiguration",
Severity: SeverityLow,
Text: func(args Args) string {
@ -104,7 +105,7 @@ var localLogWarnable = condRegister(func() *Warnable {
// and provides the last login error if available.
var LoginStateWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "login-state",
Code: tsconst.HealthWarnableLoginState,
Title: "Logged out",
Severity: SeverityMedium,
Text: func(args Args) string {
@ -121,7 +122,7 @@ var LoginStateWarnable = condRegister(func() *Warnable {
// notInMapPollWarnable is a Warnable that warns the user that we are using a stale network map.
var notInMapPollWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "not-in-map-poll",
Code: tsconst.HealthWarnableNotInMapPoll,
Title: "Out of sync",
Severity: SeverityMedium,
DependsOn: []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
@ -134,7 +135,7 @@ var notInMapPollWarnable = condRegister(func() *Warnable {
// noDERPHomeWarnable is a Warnable that warns the user that Tailscale doesn't have a home DERP.
var noDERPHomeWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "no-derp-home",
Code: tsconst.HealthWarnableNoDERPHome,
Title: "No home relay server",
Severity: SeverityMedium,
DependsOn: []*Warnable{NetworkStatusWarnable},
@ -147,7 +148,7 @@ var noDERPHomeWarnable = condRegister(func() *Warnable {
// noDERPConnectionWarnable is a Warnable that warns the user that Tailscale couldn't connect to a specific DERP server.
var noDERPConnectionWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "no-derp-connection",
Code: tsconst.HealthWarnableNoDERPConnection,
Title: "Relay server unavailable",
Severity: SeverityMedium,
DependsOn: []*Warnable{
@ -177,7 +178,7 @@ var noDERPConnectionWarnable = condRegister(func() *Warnable {
// heard from the home DERP region for a while.
var derpTimeoutWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "derp-timed-out",
Code: tsconst.HealthWarnableDERPTimedOut,
Title: "Relay server timed out",
Severity: SeverityMedium,
DependsOn: []*Warnable{
@ -198,7 +199,7 @@ var derpTimeoutWarnable = condRegister(func() *Warnable {
// derpRegionErrorWarnable is a Warnable that warns the user that a DERP region is reporting an issue.
var derpRegionErrorWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "derp-region-error",
Code: tsconst.HealthWarnableDERPRegionError,
Title: "Relay server error",
Severity: SeverityLow,
DependsOn: []*Warnable{NetworkStatusWarnable},
@ -211,7 +212,7 @@ var derpRegionErrorWarnable = condRegister(func() *Warnable {
// noUDP4BindWarnable is a Warnable that warns the user that Tailscale couldn't listen for incoming UDP connections.
var noUDP4BindWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "no-udp4-bind",
Code: tsconst.HealthWarnableNoUDP4Bind,
Title: "NAT traversal setup failure",
Severity: SeverityMedium,
DependsOn: []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
@ -223,7 +224,7 @@ var noUDP4BindWarnable = condRegister(func() *Warnable {
// mapResponseTimeoutWarnable is a Warnable that warns the user that Tailscale hasn't received a network map from the coordination server in a while.
var mapResponseTimeoutWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "mapresponse-timeout",
Code: tsconst.HealthWarnableMapResponseTimeout,
Title: "Network map response timeout",
Severity: SeverityMedium,
DependsOn: []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
@ -236,7 +237,7 @@ var mapResponseTimeoutWarnable = condRegister(func() *Warnable {
// tlsConnectionFailedWarnable is a Warnable that warns the user that Tailscale could not establish an encrypted connection with a server.
var tlsConnectionFailedWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "tls-connection-failed",
Code: tsconst.HealthWarnableTLSConnectionFailed,
Title: "Encrypted connection failed",
Severity: SeverityMedium,
DependsOn: []*Warnable{NetworkStatusWarnable},
@ -249,7 +250,7 @@ var tlsConnectionFailedWarnable = condRegister(func() *Warnable {
// magicsockReceiveFuncWarnable is a Warnable that warns the user that one of the Magicsock functions is not running.
var magicsockReceiveFuncWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "magicsock-receive-func-error",
Code: tsconst.HealthWarnableMagicsockReceiveFuncError,
Title: "MagicSock function not running",
Severity: SeverityMedium,
Text: func(args Args) string {
@ -261,7 +262,7 @@ var magicsockReceiveFuncWarnable = condRegister(func() *Warnable {
// testWarnable is a Warnable that is used within this package for testing purposes only.
var testWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "test-warnable",
Code: tsconst.HealthWarnableTestWarnable,
Title: "Test warnable",
Severity: SeverityLow,
Text: func(args Args) string {
@ -273,7 +274,7 @@ var testWarnable = condRegister(func() *Warnable {
// applyDiskConfigWarnable is a Warnable that warns the user that there was an error applying the envknob config stored on disk.
var applyDiskConfigWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "apply-disk-config",
Code: tsconst.HealthWarnableApplyDiskConfig,
Title: "Could not apply configuration",
Severity: SeverityMedium,
Text: func(args Args) string {
@ -291,7 +292,7 @@ const warmingUpWarnableDuration = 5 * time.Second
// the backend is fully started.
var warmingUpWarnable = condRegister(func() *Warnable {
return &Warnable{
Code: "warming-up",
Code: tsconst.HealthWarnableWarmingUp,
Title: "Tailscale is starting",
Severity: SeverityLow,
Text: StaticMessage("Tailscale is starting. Please wait."),

@ -384,6 +384,7 @@ func TestRedactNetmapPrivateKeys(t *testing.T) {
f(tailcfg.Service{}, "Port"): false,
f(tailcfg.Service{}, "Proto"): false,
f(tailcfg.Service{}, "_"): false,
f(tailcfg.TPMInfo{}, "FamilyIndicator"): false,
f(tailcfg.TPMInfo{}, "FirmwareVersion"): false,
f(tailcfg.TPMInfo{}, "Manufacturer"): false,
f(tailcfg.TPMInfo{}, "Model"): false,

@ -1,48 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tpm
package ipnlocal
import (
"errors"
"tailscale.com/feature"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/persist"
)
func init() {
feature.HookGenerateAttestationKeyIfEmpty.Set(generateAttestationKeyIfEmpty)
}
// generateAttestationKeyIfEmpty generates a new hardware attestation key if
// none exists. It returns true if a new key was generated and stored in
// p.AttestationKey.
func generateAttestationKeyIfEmpty(p *persist.Persist, logf logger.Logf) (bool, error) {
// attempt to generate a new hardware attestation key if none exists
var ak key.HardwareAttestationKey
if p != nil {
ak = p.AttestationKey
}
if ak == nil || ak.IsZero() {
var err error
ak, err = key.NewHardwareAttestationKey()
if err != nil {
if !errors.Is(err, key.ErrUnsupported) {
logf("failed to create hardware attestation key: %v", err)
}
} else if ak != nil {
logf("using new hardware attestation key: %v", ak.Public())
if p == nil {
p = &persist.Persist{}
}
p.AttestationKey = ak
return true, nil
}
}
return false, nil
}

@ -1216,7 +1216,6 @@ func stripKeysFromPrefs(p ipn.PrefsView) ipn.PrefsView {
p2.Persist.PrivateNodeKey = key.NodePrivate{}
p2.Persist.OldPrivateNodeKey = key.NodePrivate{}
p2.Persist.NetworkLockKey = key.NLPrivate{}
p2.Persist.AttestationKey = nil
return p2.View()
}

@ -19,9 +19,7 @@ import (
"tailscale.com/ipn"
"tailscale.com/ipn/ipnext"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/persist"
"tailscale.com/util/clientmetric"
"tailscale.com/util/eventbus"
)
@ -656,14 +654,6 @@ func (pm *profileManager) loadSavedPrefs(k ipn.StateKey) (ipn.PrefsView, error)
return ipn.PrefsView{}, err
}
savedPrefs := ipn.NewPrefs()
// if supported by the platform, create an empty hardware attestation key to use when deserializing
// to avoid type exceptions from json.Unmarshaling into an interface{}.
hw, _ := key.NewEmptyHardwareAttestationKey()
savedPrefs.Persist = &persist.Persist{
AttestationKey: hw,
}
if err := ipn.PrefsFromBytes(bs, savedPrefs); err != nil {
return ipn.PrefsView{}, fmt.Errorf("parsing saved prefs: %v", err)
}

@ -151,7 +151,6 @@ func TestProfileDupe(t *testing.T) {
ID: tailcfg.UserID(user),
LoginName: fmt.Sprintf("user%d@example.com", user),
},
AttestationKey: nil,
}
}
user1Node1 := newPersist(1, 1)

@ -19,6 +19,7 @@ import (
"sync"
"time"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/feature"
"tailscale.com/feature/buildfeatures"
"tailscale.com/ipn"
@ -39,6 +40,7 @@ func init() {
Register("debug-packet-filter-matches", (*Handler).serveDebugPacketFilterMatches)
Register("debug-packet-filter-rules", (*Handler).serveDebugPacketFilterRules)
Register("debug-peer-endpoint-changes", (*Handler).serveDebugPeerEndpointChanges)
Register("debug-optional-features", (*Handler).serveDebugOptionalFeatures)
}
func (h *Handler) serveDebugPeerEndpointChanges(w http.ResponseWriter, r *http.Request) {
@ -463,3 +465,11 @@ func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
func (h *Handler) serveDebugOptionalFeatures(w http.ResponseWriter, r *http.Request) {
of := &apitype.OptionalFeatures{
Features: feature.Registered(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(of)
}

@ -501,7 +501,7 @@ func TestPrefsPretty(t *testing.T) {
},
},
"linux",
`Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist{o=, n=[B1VKl] u="" ak=-}}`,
`Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist{o=, n=[B1VKl] u=""}}`,
},
{
Prefs{

@ -96,6 +96,7 @@ type Dialer struct {
dnsCache *dnscache.MessageCache // nil until first non-empty SetExitDNSDoH
nextSysConnID int
activeSysConns map[int]net.Conn // active connections not yet closed
bus *eventbus.Bus // only used for comparison with already set bus.
eventClient *eventbus.Client
eventBusSubs eventbus.Monitor
}
@ -226,14 +227,17 @@ func (d *Dialer) NetMon() *netmon.Monitor {
func (d *Dialer) SetBus(bus *eventbus.Bus) {
d.mu.Lock()
defer d.mu.Unlock()
if d.eventClient != nil {
panic("eventbus has already been set")
if d.bus == bus {
return
} else if d.bus != nil {
panic("different eventbus has already been set")
}
// Having multiple watchers could lead to problems,
// so unregister the callback if it exists.
if d.netMonUnregister != nil {
d.netMonUnregister()
}
d.bus = bus
d.eventClient = bus.Client("tsdial.Dialer")
d.eventBusSubs = d.eventClient.Monitor(d.linkChangeWatcher(d.eventClient))
}

@ -405,10 +405,7 @@ func clientHTTP2(dialCtx context.Context, dial netx.DialFunc) *http.Client {
return &http.Client{
Transport: &http.Transport{
Protocols: &p,
// Pretend like we're using TLS, but actually use the provided
// DialFunc underneath. This is necessary to convince the transport
// to actually dial.
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
perAttemptCtx, cancel := context.WithTimeout(ctx, perDialAttemptTimeout)
defer cancel()
go func() {

@ -21,6 +21,7 @@ import (
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"tailscale.com/net/memnet"
)
func TestConnectToRecorder(t *testing.T) {
@ -145,7 +146,14 @@ func TestConnectToRecorder(t *testing.T) {
t.Run(tt.desc, func(t *testing.T) {
mux, uploadHash := tt.setup(t)
srv := httptest.NewUnstartedServer(mux)
memNet := &memnet.Network{}
ln := memNet.NewLocalTCPListener()
srv := &httptest.Server{
Config: &http.Server{Handler: mux},
Listener: ln,
}
if tt.http2 {
// Wire up h2c-compatible HTTP/2 server. This is optional
// because the v1 recorder didn't support HTTP/2 and we try to
@ -159,10 +167,8 @@ func TestConnectToRecorder(t *testing.T) {
srv.Start()
t.Cleanup(srv.Close)
d := new(net.Dialer)
ctx := context.Background()
w, _, errc, err := ConnectToRecorder(ctx, []netip.AddrPort{netip.MustParseAddrPort(srv.Listener.Addr().String())}, d.DialContext)
w, _, errc, err := ConnectToRecorder(ctx, []netip.AddrPort{netip.MustParseAddrPort(ln.Addr().String())}, memNet.Dial)
if err != nil {
t.Fatalf("ConnectToRecorder: %v", err)
}

@ -928,6 +928,10 @@ type TPMInfo struct {
// https://trustedcomputinggroup.org/resource/tpm-library-specification/.
// Before revision 184, TCG used the "01.83" format for revision 183.
SpecRevision int `json:",omitempty"`
// FamilyIndicator is the TPM spec family, like "2.0".
// Read from TPM_PT_FAMILY_INDICATOR.
FamilyIndicator string `json:",omitempty"`
}
// Present reports whether a TPM device is present on this machine.

@ -0,0 +1,26 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tsconst
const (
HealthWarnableUpdateAvailable = "update-available"
HealthWarnableSecurityUpdateAvailable = "security-update-available"
HealthWarnableIsUsingUnstableVersion = "is-using-unstable-version"
HealthWarnableNetworkStatus = "network-status"
HealthWarnableWantRunningFalse = "wantrunning-false"
HealthWarnableLocalLogConfigError = "local-log-config-error"
HealthWarnableLoginState = "login-state"
HealthWarnableNotInMapPoll = "not-in-map-poll"
HealthWarnableNoDERPHome = "no-derp-home"
HealthWarnableNoDERPConnection = "no-derp-connection"
HealthWarnableDERPTimedOut = "derp-timed-out"
HealthWarnableDERPRegionError = "derp-region-error"
HealthWarnableNoUDP4Bind = "no-udp4-bind"
HealthWarnableMapResponseTimeout = "mapresponse-timeout"
HealthWarnableTLSConnectionFailed = "tls-connection-failed"
HealthWarnableMagicsockReceiveFuncError = "magicsock-receive-func-error"
HealthWarnableTestWarnable = "test-warnable"
HealthWarnableApplyDiskConfig = "apply-disk-config"
HealthWarnableWarmingUp = "warming-up"
)

@ -175,6 +175,28 @@ func TestControlKnobs(t *testing.T) {
}
}
func TestExpectedFeaturesLinked(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
d1 := n1.StartDaemon()
n1.AwaitResponding()
lc := n1.LocalClient()
got, err := lc.QueryOptionalFeatures(t.Context())
if err != nil {
t.Fatal(err)
}
if !got.Features["portmapper"] {
t.Errorf("optional feature portmapper unexpectedly not found: got %v", got.Features)
}
d1.MustCleanShutdown(t)
t.Logf("number of HTTP logcatcher requests: %v", env.LogCatcher.numRequests())
}
func TestCollectPanic(t *testing.T) {
flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/15865")
tstest.Shard(t)

@ -26,7 +26,6 @@ type Persist struct {
UserProfile tailcfg.UserProfile
NetworkLockKey key.NLPrivate
NodeID tailcfg.StableNodeID
AttestationKey key.HardwareAttestationKey `json:",omitempty"`
// DisallowedTKAStateIDs stores the tka.State.StateID values which
// this node will not operate network lock on. This is used to
@ -85,20 +84,11 @@ func (p *Persist) Equals(p2 *Persist) bool {
return false
}
var pub, p2Pub key.HardwareAttestationPublic
if p.AttestationKey != nil && !p.AttestationKey.IsZero() {
pub = key.HardwareAttestationPublicFromPlatformKey(p.AttestationKey)
}
if p2.AttestationKey != nil && !p2.AttestationKey.IsZero() {
p2Pub = key.HardwareAttestationPublicFromPlatformKey(p2.AttestationKey)
}
return p.PrivateNodeKey.Equal(p2.PrivateNodeKey) &&
p.OldPrivateNodeKey.Equal(p2.OldPrivateNodeKey) &&
p.UserProfile.Equal(&p2.UserProfile) &&
p.NetworkLockKey.Equal(p2.NetworkLockKey) &&
p.NodeID == p2.NodeID &&
pub.Equal(p2Pub) &&
reflect.DeepEqual(nilIfEmpty(p.DisallowedTKAStateIDs), nilIfEmpty(p2.DisallowedTKAStateIDs))
}
@ -106,16 +96,12 @@ func (p *Persist) Pretty() string {
var (
ok, nk key.NodePublic
)
akString := "-"
if !p.OldPrivateNodeKey.IsZero() {
ok = p.OldPrivateNodeKey.Public()
}
if !p.PrivateNodeKey.IsZero() {
nk = p.PublicNodeKey()
}
if p.AttestationKey != nil && !p.AttestationKey.IsZero() {
akString = fmt.Sprintf("%v", p.AttestationKey.Public())
}
return fmt.Sprintf("Persist{o=%v, n=%v u=%#v ak=%s}",
ok.ShortString(), nk.ShortString(), p.UserProfile.LoginName, akString)
return fmt.Sprintf("Persist{o=%v, n=%v u=%#v}",
ok.ShortString(), nk.ShortString(), p.UserProfile.LoginName)
}

@ -19,9 +19,6 @@ func (src *Persist) Clone() *Persist {
}
dst := new(Persist)
*dst = *src
if src.AttestationKey != nil {
dst.AttestationKey = src.AttestationKey.Clone()
}
dst.DisallowedTKAStateIDs = append(src.DisallowedTKAStateIDs[:0:0], src.DisallowedTKAStateIDs...)
return dst
}
@ -34,6 +31,5 @@ var _PersistCloneNeedsRegeneration = Persist(struct {
UserProfile tailcfg.UserProfile
NetworkLockKey key.NLPrivate
NodeID tailcfg.StableNodeID
AttestationKey key.HardwareAttestationKey
DisallowedTKAStateIDs []string
}{})

@ -21,7 +21,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
}
func TestPersistEqual(t *testing.T) {
persistHandles := []string{"PrivateNodeKey", "OldPrivateNodeKey", "UserProfile", "NetworkLockKey", "NodeID", "AttestationKey", "DisallowedTKAStateIDs"}
persistHandles := []string{"PrivateNodeKey", "OldPrivateNodeKey", "UserProfile", "NetworkLockKey", "NodeID", "DisallowedTKAStateIDs"}
if have := fieldsOf(reflect.TypeFor[Persist]()); !reflect.DeepEqual(have, persistHandles) {
t.Errorf("Persist.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
have, persistHandles)

@ -93,7 +93,6 @@ func (v PersistView) OldPrivateNodeKey() key.NodePrivate { return v.ж.OldPriv
func (v PersistView) UserProfile() tailcfg.UserProfile { return v.ж.UserProfile }
func (v PersistView) NetworkLockKey() key.NLPrivate { return v.ж.NetworkLockKey }
func (v PersistView) NodeID() tailcfg.StableNodeID { return v.ж.NodeID }
func (v PersistView) AttestationKey() tailcfg.StableNodeID { panic("unsupported") }
// DisallowedTKAStateIDs stores the tka.State.StateID values which
// this node will not operate network lock on. This is used to
@ -111,6 +110,5 @@ var _PersistViewNeedsRegeneration = Persist(struct {
UserProfile tailcfg.UserProfile
NetworkLockKey key.NLPrivate
NodeID tailcfg.StableNodeID
AttestationKey key.HardwareAttestationKey
DisallowedTKAStateIDs []string
}{})

@ -1,9 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux && (arm64 || amd64) && !ts_omit_iptables
// TODO(#8502): add support for more architectures
//go:build linux && !ts_omit_iptables
package linuxfw

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build (linux && !(arm64 || amd64)) || ts_omit_iptables
//go:build linux && ts_omit_iptables
package linuxfw

@ -719,9 +719,13 @@ func NewConn(opts Options) (*Conn, error) {
newPortMapper, ok := portmappertype.HookNewPortMapper.GetOk()
if ok {
c.portMapper = newPortMapper(portmapperLogf, opts.EventBus, opts.NetMon, disableUPnP, c.onlyTCP443.Load)
} else if !testenv.InTest() {
panic("unexpected: HookNewPortMapper not set")
}
// If !ok, the HookNewPortMapper hook is not set (so feature/portmapper
// isn't linked), but the build tag to explicitly omit the portmapper
// isn't set either. This should only happen to js/wasm builds, where
// the portmapper is a no-op even if linked (but it's no longer linked,
// since the move to feature/portmapper), or if people are wiring up
// their own Tailscale build from pieces.
}
c.netMon = opts.NetMon

Loading…
Cancel
Save