feature/featuretags, all: add build features, use existing ones in more places

Saves 270 KB.

Updates #12614

Change-Id: I4c3fe06d32c49edb3a4bb0758a8617d83f291cf5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2025-09-30 14:47:40 -07:00
committed by Brad Fitzpatrick
parent aa5b2ce83b
commit c45f8813b4
35 changed files with 407 additions and 166 deletions

View File

@@ -112,7 +112,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
github.com/tailscale/hujson from tailscale.com/ipn/conffile+
github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
LD github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
github.com/tailscale/web-client-prebuilt from tailscale.com/client/web
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/conn/winrio from github.com/tailscale/wireguard-go/conn

View File

@@ -22,9 +22,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/mdlayher/genetlink from tailscale.com/net/tstun
💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
💣 github.com/mdlayher/socket from github.com/mdlayher/netlink+
💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/safchain/ethtool from tailscale.com/net/netkernelconf
github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/device from tailscale.com/net/tstun+
github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device
@@ -221,10 +220,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/net/internal/httpcommon from golang.org/x/net/http2
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
golang.org/x/net/internal/socks from golang.org/x/net/proxy
golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/conn+
golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/conn+
golang.org/x/net/proxy from tailscale.com/net/netns
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
golang.org/x/sys/cpu from github.com/tailscale/wireguard-go/tun+
golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+

View File

@@ -28,7 +28,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/mdlayher/genetlink from tailscale.com/net/tstun
💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
💣 github.com/mdlayher/socket from github.com/mdlayher/netlink+
💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli+
github.com/peterbourgon/ff/v3/ffcli from tailscale.com/cmd/tailscale/cli+
github.com/peterbourgon/ff/v3/internal from github.com/peterbourgon/ff/v3
@@ -36,7 +36,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/skip2/go-qrcode from tailscale.com/cmd/tailscale/cli
github.com/skip2/go-qrcode/bitset from github.com/skip2/go-qrcode+
github.com/skip2/go-qrcode/reedsolomon from github.com/skip2/go-qrcode
github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/device from tailscale.com/net/tstun+
github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device
@@ -248,10 +247,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/net/internal/httpcommon from golang.org/x/net/http2
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
golang.org/x/net/internal/socks from golang.org/x/net/proxy
golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/conn+
golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/conn+
golang.org/x/net/proxy from tailscale.com/net/netns
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
golang.org/x/sys/cpu from github.com/tailscale/wireguard-go/tun+
golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+

View File

@@ -174,7 +174,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/tailscale/hujson from tailscale.com/ipn/conffile
L 💣 github.com/tailscale/netlink from tailscale.com/net/routetable+
L 💣 github.com/tailscale/netlink/nl from github.com/tailscale/netlink
github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
LD github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
github.com/tailscale/web-client-prebuilt from tailscale.com/client/web
W 💣 github.com/tailscale/wf from tailscale.com/wf
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+

View File

@@ -237,16 +237,22 @@ func minTags() string {
}
func TestMinTailscaledNoCLI(t *testing.T) {
badSubstrs := []string{
"cbor",
"regexp",
"golang.org/x/net/proxy",
"internal/socks",
"github.com/tailscale/peercred",
}
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: minTags(),
OnDep: func(dep string) {
if strings.Contains(dep, "regexp") {
t.Errorf("unexpected dep: %q", dep)
}
if strings.Contains(dep, "cbor") {
t.Errorf("unexpected dep: %q", dep)
for _, bad := range badSubstrs {
if strings.Contains(dep, bad) {
t.Errorf("unexpected dep: %q", dep)
}
}
},
}.Check(t)

View File

@@ -58,7 +58,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
github.com/tailscale/hujson from tailscale.com/ipn/conffile
github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
LD github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
github.com/tailscale/web-client-prebuilt from tailscale.com/client/web
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/conn/winrio from github.com/tailscale/wireguard-go/conn

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build ts_omit_bakedroots
package buildfeatures
// HasBakedRoots is whether the binary was built with support for modular feature "Embed CA (LetsEncrypt) x509 roots to use as fallback".
// Specifically, it's whether the binary was NOT built with the "ts_omit_bakedroots" build tag.
// It's a const so it can be used for dead code elimination.
const HasBakedRoots = false

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build !ts_omit_bakedroots
package buildfeatures
// HasBakedRoots is whether the binary was built with support for modular feature "Embed CA (LetsEncrypt) x509 roots to use as fallback".
// Specifically, it's whether the binary was NOT built with the "ts_omit_bakedroots" build tag.
// It's a const so it can be used for dead code elimination.
const HasBakedRoots = true

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build ts_omit_cloud
package buildfeatures
// HasCloud is whether the binary was built with support for modular feature "detect cloud environment to learn instances IPs and DNS servers".
// Specifically, it's whether the binary was NOT built with the "ts_omit_cloud" build tag.
// It's a const so it can be used for dead code elimination.
const HasCloud = false

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build !ts_omit_cloud
package buildfeatures
// HasCloud is whether the binary was built with support for modular feature "detect cloud environment to learn instances IPs and DNS servers".
// Specifically, it's whether the binary was NOT built with the "ts_omit_cloud" build tag.
// It's a const so it can be used for dead code elimination.
const HasCloud = true

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build ts_omit_listenrawdisco
package buildfeatures
// HasListenRawDisco is whether the binary was built with support for modular feature "Use raw sockets for more robust disco (NAT traversal) message receiving (Linux only)".
// Specifically, it's whether the binary was NOT built with the "ts_omit_listenrawdisco" build tag.
// It's a const so it can be used for dead code elimination.
const HasListenRawDisco = false

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build !ts_omit_listenrawdisco
package buildfeatures
// HasListenRawDisco is whether the binary was built with support for modular feature "Use raw sockets for more robust disco (NAT traversal) message receiving (Linux only)".
// Specifically, it's whether the binary was NOT built with the "ts_omit_listenrawdisco" build tag.
// It's a const so it can be used for dead code elimination.
const HasListenRawDisco = true

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build ts_omit_unixsocketidentity
package buildfeatures
// HasUnixSocketIdentity is whether the binary was built with support for modular feature "differentiate between users accessing the LocalAPI over unix sockets (if omitted, all users have full access)".
// Specifically, it's whether the binary was NOT built with the "ts_omit_unixsocketidentity" build tag.
// It's a const so it can be used for dead code elimination.
const HasUnixSocketIdentity = false

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build !ts_omit_unixsocketidentity
package buildfeatures
// HasUnixSocketIdentity is whether the binary was built with support for modular feature "differentiate between users accessing the LocalAPI over unix sockets (if omitted, all users have full access)".
// Specifically, it's whether the binary was NOT built with the "ts_omit_unixsocketidentity" build tag.
// It's a const so it can be used for dead code elimination.
const HasUnixSocketIdentity = true

View File

@@ -87,41 +87,47 @@ type FeatureMeta struct {
// Features are the known Tailscale features that can be selectively included or
// excluded via build tags, and a description of each.
var Features = map[FeatureTag]FeatureMeta{
"acme": {"ACME", "ACME TLS certificate management", nil},
"appconnectors": {"AppConnectors", "App Connectors support", nil},
"aws": {"AWS", "AWS integration", nil},
"bird": {"Bird", "Bird BGP integration", nil},
"captiveportal": {"CaptivePortal", "Captive portal detection", nil},
"capture": {"Capture", "Packet capture", nil},
"cli": {"CLI", "embed the CLI into the tailscaled binary", nil},
"cliconndiag": {"CLIConnDiag", "CLI connection error diagnostics", nil},
"clientupdate": {"ClientUpdate", "Client auto-update support", nil},
"completion": {"Completion", "CLI shell completion", nil},
"dbus": {"DBus", "Linux DBus support", nil},
"debug": {"Debug", "various debug support, for things that don't have or need their own more specific feature", nil},
"debugeventbus": {"DebugEventBus", "eventbus debug support", nil},
"acme": {Sym: "ACME", Desc: "ACME TLS certificate management"},
"appconnectors": {Sym: "AppConnectors", Desc: "App Connectors support"},
"aws": {Sym: "AWS", Desc: "AWS integration"},
"bakedroots": {Sym: "BakedRoots", Desc: "Embed CA (LetsEncrypt) x509 roots to use as fallback"},
"bird": {Sym: "Bird", Desc: "Bird BGP integration"},
"captiveportal": {Sym: "CaptivePortal", Desc: "Captive portal detection"},
"capture": {Sym: "Capture", Desc: "Packet capture"},
"cloud": {Sym: "Cloud", Desc: "detect cloud environment to learn instances IPs and DNS servers"},
"cli": {Sym: "CLI", Desc: "embed the CLI into the tailscaled binary"},
"cliconndiag": {Sym: "CLIConnDiag", Desc: "CLI connection error diagnostics"},
"clientupdate": {Sym: "ClientUpdate", Desc: "Client auto-update support"},
"completion": {Sym: "Completion", Desc: "CLI shell completion"},
"dbus": {Sym: "DBus", Desc: "Linux DBus support"},
"debug": {Sym: "Debug", Desc: "various debug support, for things that don't have or need their own more specific feature"},
"debugeventbus": {Sym: "DebugEventBus", Desc: "eventbus debug support"},
"debugportmapper": {
Sym: "DebugPortMapper",
Desc: "portmapper debug support",
Deps: []FeatureTag{"portmapper"},
},
"desktop_sessions": {"DesktopSessions", "Desktop sessions support", nil},
"doctor": {"Doctor", "Diagnose possible issues with Tailscale and its host environment", nil},
"drive": {"Drive", "Tailscale Drive (file server) support", nil},
"desktop_sessions": {Sym: "DesktopSessions", Desc: "Desktop sessions support"},
"doctor": {Sym: "Doctor", Desc: "Diagnose possible issues with Tailscale and its host environment"},
"drive": {Sym: "Drive", Desc: "Tailscale Drive (file server) support"},
"gro": {
Sym: "GRO",
Desc: "Generic Receive Offload support (performance)",
Deps: []FeatureTag{"netstack"},
},
"hujsonconf": {"HuJSONConf", "HuJSON config file support", nil},
"iptables": {"IPTables", "Linux iptables support", nil},
"kube": {"Kube", "Kubernetes integration", nil},
"linuxdnsfight": {"LinuxDNSFight", "Linux support for detecting DNS fights (inotify watching of /etc/resolv.conf)", nil},
"hujsonconf": {Sym: "HuJSONConf", Desc: "HuJSON config file support"},
"iptables": {Sym: "IPTables", Desc: "Linux iptables support"},
"kube": {Sym: "Kube", Desc: "Kubernetes integration"},
"linuxdnsfight": {Sym: "LinuxDNSFight", Desc: "Linux support for detecting DNS fights (inotify watching of /etc/resolv.conf)"},
"listenrawdisco": {
Sym: "ListenRawDisco",
Desc: "Use raw sockets for more robust disco (NAT traversal) message receiving (Linux only)",
},
"logtail": {
Sym: "LogTail",
Desc: "upload logs to log.tailscale.com (debug logs for bug reports and also by network flow logs if enabled)",
},
"oauthkey": {"OAuthKey", "OAuth secret-to-authkey resolution support", nil},
"oauthkey": {Sym: "OAuthKey", Desc: "OAuth secret-to-authkey resolution support"},
"outboundproxy": {
Sym: "OutboundProxy",
Desc: "Support running an outbound localhost HTTP/SOCK5 proxy support that sends traffic over Tailscale",
@@ -137,9 +143,9 @@ var Features = map[FeatureTag]FeatureMeta{
// by some other feature are missing, then it's an error by default unless you accept
// that it's okay to proceed without that meta feature.
},
"portlist": {"PortList", "Optionally advertise listening service ports", nil},
"portmapper": {"PortMapper", "NAT-PMP/PCP/UPnP port mapping support", nil},
"posture": {"Posture", "Device posture checking support", nil},
"portlist": {Sym: "PortList", Desc: "Optionally advertise listening service ports"},
"portmapper": {Sym: "PortMapper", Desc: "NAT-PMP/PCP/UPnP port mapping support"},
"posture": {Sym: "Posture", Desc: "Device posture checking support"},
"dns": {
Sym: "DNS",
Desc: "MagicDNS and system DNS configuration support",
@@ -149,13 +155,13 @@ var Features = map[FeatureTag]FeatureMeta{
Desc: "Network flow logging support",
Deps: []FeatureTag{"logtail"},
},
"netstack": {"Netstack", "gVisor netstack (userspace networking) support", nil},
"netstack": {Sym: "Netstack", Desc: "gVisor netstack (userspace networking) support"},
"networkmanager": {
Sym: "NetworkManager",
Desc: "Linux NetworkManager integration",
Deps: []FeatureTag{"dbus"},
},
"relayserver": {"RelayServer", "Relay server", nil},
"relayserver": {Sym: "RelayServer", Desc: "Relay server"},
"resolved": {
Sym: "Resolved",
Desc: "Linux systemd-resolved integration",
@@ -179,21 +185,25 @@ var Features = map[FeatureTag]FeatureMeta{
Sym: "Synology",
Desc: "Synology NAS integration (applies to Linux builds only)",
},
"syspolicy": {"SystemPolicy", "System policy configuration (MDM) support", nil},
"syspolicy": {Sym: "SystemPolicy", Desc: "System policy configuration (MDM) support"},
"systray": {
Sym: "SysTray",
Desc: "Linux system tray",
Deps: []FeatureTag{"dbus"},
},
"taildrop": {"Taildrop", "Taildrop (file sending) support", nil},
"tailnetlock": {"TailnetLock", "Tailnet Lock support", nil},
"tap": {"Tap", "Experimental Layer 2 (ethernet) support", nil},
"tpm": {"TPM", "TPM support", nil},
"taildrop": {Sym: "Taildrop", Desc: "Taildrop (file sending) support"},
"tailnetlock": {Sym: "TailnetLock", Desc: "Tailnet Lock support"},
"tap": {Sym: "Tap", Desc: "Experimental Layer 2 (ethernet) support"},
"tpm": {Sym: "TPM", Desc: "TPM support"},
"unixsocketidentity": {
Sym: "UnixSocketIdentity",
Desc: "differentiate between users accessing the LocalAPI over unix sockets (if omitted, all users have full access)",
},
"useproxy": {
Sym: "UseProxy",
Desc: "Support using system proxies as specified by env vars or the system configuration to reach Tailscale servers.",
},
"wakeonlan": {"WakeOnLAN", "Wake-on-LAN support", nil},
"wakeonlan": {Sym: "WakeOnLAN", Desc: "Wake-on-LAN support"},
"webclient": {
Sym: "WebClient", Desc: "Web client support",
Deps: []FeatureTag{"serve"},

View File

@@ -14,7 +14,6 @@ import (
"runtime"
"strconv"
"github.com/tailscale/peercred"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/safesocket"
@@ -63,8 +62,8 @@ type ConnIdentity struct {
notWindows bool // runtime.GOOS != "windows"
// Fields used when NotWindows:
isUnixSock bool // Conn is a *net.UnixConn
creds *peercred.Creds // or nil if peercred.Get was not implemented on this OS
isUnixSock bool // Conn is a *net.UnixConn
creds PeerCreds // or nil if peercred.Get was not implemented on this OS
// Used on Windows:
// TODO(bradfitz): merge these into the peercreds package and
@@ -97,9 +96,18 @@ func (ci *ConnIdentity) WindowsUserID() ipn.WindowsUserID {
return ""
}
func (ci *ConnIdentity) Pid() int { return ci.pid }
func (ci *ConnIdentity) IsUnixSock() bool { return ci.isUnixSock }
func (ci *ConnIdentity) Creds() *peercred.Creds { return ci.creds }
func (ci *ConnIdentity) Pid() int { return ci.pid }
func (ci *ConnIdentity) IsUnixSock() bool { return ci.isUnixSock }
func (ci *ConnIdentity) Creds() PeerCreds { return ci.creds }
// PeerCreds is the interface for a github.com/tailscale/peercred.Creds,
// if linked into the binary.
//
// (It's not used on some platforms, or if ts_omit_unixsocketidentity is set.)
type PeerCreds interface {
UserID() (uid string, ok bool)
PID() (pid int, ok bool)
}
var metricIssue869Workaround = clientmetric.NewCounter("issue_869_workaround")

View File

@@ -0,0 +1,25 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !windows && ts_omit_unixsocketidentity
package ipnauth
import (
"net"
"tailscale.com/types/logger"
)
// GetConnIdentity extracts the identity information from the connection
// based on the user who owns the other end of the connection.
// and couldn't. The returned connIdentity has NotWindows set to true.
func GetConnIdentity(_ logger.Logf, c net.Conn) (ci *ConnIdentity, err error) {
return &ConnIdentity{conn: c, notWindows: true}, nil
}
// WindowsToken is unsupported when GOOS != windows and always returns
// ErrNotImplemented.
func (ci *ConnIdentity) WindowsToken() (WindowsToken, error) {
return nil, ErrNotImplemented
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !windows
//go:build !windows && !ts_omit_unixsocketidentity
package ipnauth

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"path"
"reflect"
"runtime"
"strconv"
"strings"
"time"
@@ -33,26 +34,34 @@ import (
// exists for that, a map entry with an empty method is used as a fallback.
var c2nHandlers = map[methodAndPath]c2nHandler{
// Debug.
req("/echo"): handleC2NEcho,
req("/debug/goroutines"): handleC2NDebugGoroutines,
req("/debug/prefs"): handleC2NDebugPrefs,
req("/debug/metrics"): handleC2NDebugMetrics,
req("/debug/component-logging"): handleC2NDebugComponentLogging,
req("/debug/logheap"): handleC2NDebugLogHeap,
req("/debug/netmap"): handleC2NDebugNetMap,
req("/echo"): handleC2NEcho,
}
// PPROF - We only expose a subset of typical pprof endpoints for security.
req("/debug/pprof/heap"): handleC2NPprof,
req("/debug/pprof/allocs"): handleC2NPprof,
func init() {
if buildfeatures.HasSSH {
RegisterC2N("/ssh/usernames", handleC2NSSHUsernames)
}
if buildfeatures.HasLogTail {
RegisterC2N("POST /logtail/flush", handleC2NLogtailFlush)
}
if buildfeatures.HasDebug {
RegisterC2N("POST /sockstats", handleC2NSockStats)
req("POST /logtail/flush"): handleC2NLogtailFlush,
req("POST /sockstats"): handleC2NSockStats,
// pprof:
// we only expose a subset of typical pprof endpoints for security.
RegisterC2N("/debug/pprof/heap", handleC2NPprof)
RegisterC2N("/debug/pprof/allocs", handleC2NPprof)
// SSH
req("/ssh/usernames"): handleC2NSSHUsernames,
// Linux netfilter.
req("POST /netfilter-kind"): handleC2NSetNetfilterKind,
RegisterC2N("/debug/goroutines", handleC2NDebugGoroutines)
RegisterC2N("/debug/prefs", handleC2NDebugPrefs)
RegisterC2N("/debug/metrics", handleC2NDebugMetrics)
RegisterC2N("/debug/component-logging", handleC2NDebugComponentLogging)
RegisterC2N("/debug/logheap", handleC2NDebugLogHeap)
RegisterC2N("/debug/netmap", handleC2NDebugNetMap)
}
if runtime.GOOS == "linux" && buildfeatures.HasOSRouter {
RegisterC2N("POST /netfilter-kind", handleC2NSetNetfilterKind)
}
}
// RegisterC2N registers a new c2n handler for the given pattern.
@@ -265,6 +274,10 @@ func handleC2NPprof(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
}
func handleC2NSSHUsernames(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
if !buildfeatures.HasSSH {
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
return
}
var req tailcfg.C2NSSHUsernamesRequest
if r.Method == "POST" {
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {

View File

@@ -1442,7 +1442,7 @@ func (b *LocalBackend) WhoIs(proto string, ipp netip.AddrPort) (n tailcfg.NodeVi
cn := b.currentNode()
nid, ok := cn.NodeByAddr(ipp.Addr())
if !ok {
if !ok && buildfeatures.HasNetstack {
var ip netip.Addr
if ipp.Port() != 0 {
var protos []string
@@ -5015,6 +5015,9 @@ func (b *LocalBackend) SetVarRoot(dir string) {
//
// It should only be called before the LocalBackend is used.
func (b *LocalBackend) SetLogFlusher(flushFunc func()) {
if !buildfeatures.HasLogTail {
return
}
b.logFlushFunc = flushFunc
}
@@ -5023,7 +5026,7 @@ func (b *LocalBackend) SetLogFlusher(flushFunc func()) {
//
// TryFlushLogs should not block.
func (b *LocalBackend) TryFlushLogs() bool {
if b.logFlushFunc == nil {
if !buildfeatures.HasLogTail || b.logFlushFunc == nil {
return false
}
b.logFlushFunc()

View File

@@ -354,33 +354,35 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
}
if strings.HasPrefix(r.URL.Path, "/dns-query") {
if buildfeatures.HasDNS && strings.HasPrefix(r.URL.Path, "/dns-query") {
metricDNSCalls.Add(1)
h.handleDNSQuery(w, r)
return
}
switch r.URL.Path {
case "/v0/goroutines":
h.handleServeGoroutines(w, r)
return
case "/v0/env":
h.handleServeEnv(w, r)
return
case "/v0/metrics":
h.handleServeMetrics(w, r)
return
case "/v0/magicsock":
h.handleServeMagicsock(w, r)
return
case "/v0/dnsfwd":
h.handleServeDNSFwd(w, r)
return
case "/v0/interfaces":
h.handleServeInterfaces(w, r)
return
case "/v0/sockstats":
h.handleServeSockStats(w, r)
return
if buildfeatures.HasDebug {
switch r.URL.Path {
case "/v0/goroutines":
h.handleServeGoroutines(w, r)
return
case "/v0/env":
h.handleServeEnv(w, r)
return
case "/v0/metrics":
h.handleServeMetrics(w, r)
return
case "/v0/magicsock":
h.handleServeMagicsock(w, r)
return
case "/v0/dnsfwd":
h.handleServeDNSFwd(w, r)
return
case "/v0/interfaces":
h.handleServeInterfaces(w, r)
return
case "/v0/sockstats":
h.handleServeSockStats(w, r)
return
}
}
if ph, ok := peerAPIHandlers[r.URL.Path]; ok {
ph(h, w, r)

View File

@@ -12,6 +12,7 @@ import (
"runtime"
"time"
"tailscale.com/feature/buildfeatures"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnauth"
"tailscale.com/types/logger"
@@ -237,6 +238,11 @@ func connIsLocalAdmin(logf logger.Logf, ci *ipnauth.ConnIdentity, operatorUID st
// Linux.
fallthrough
case "linux":
if !buildfeatures.HasUnixSocketIdentity {
// Everybody is an admin if support for unix socket identities
// is omitted for the build.
return true
}
uid, ok := ci.Creds().UserID()
if !ok {
return false

View File

@@ -10,6 +10,8 @@ import (
"net"
"net/http"
"tailscale.com/feature"
"tailscale.com/feature/buildfeatures"
"tailscale.com/logpolicy"
)
@@ -23,6 +25,10 @@ import (
// precludes that from working and instead the GUI fails to dial out.
// So, go through tailscaled (with a CONNECT request) instead.
func (s *Server) handleProxyConnectConn(w http.ResponseWriter, r *http.Request) {
if !buildfeatures.HasOutboundProxy {
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
return
}
ctx := r.Context()
if r.Method != "CONNECT" {
panic("[unexpected] miswired")

View File

@@ -15,6 +15,7 @@ import (
"net"
"net/http"
"os/user"
"runtime"
"strconv"
"strings"
"sync"
@@ -24,6 +25,7 @@ import (
"tailscale.com/client/tailscale/apitype"
"tailscale.com/envknob"
"tailscale.com/feature"
"tailscale.com/feature/buildfeatures"
"tailscale.com/ipn/ipnauth"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/localapi"
@@ -120,6 +122,10 @@ func (s *Server) awaitBackend(ctx context.Context) (_ *ipnlocal.LocalBackend, ok
// This is primarily for the Windows GUI, because wintun can take awhile to
// come up. See https://github.com/tailscale/tailscale/issues/6522.
func (s *Server) serveServerStatus(w http.ResponseWriter, r *http.Request) {
if !buildfeatures.HasDebug && runtime.GOOS != "windows" {
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotFound)
return
}
ctx := r.Context()
w.Header().Set("Content-Type", "application/json")
@@ -382,6 +388,9 @@ func isAllDigit(s string) bool {
// connection. It's intended to give your non-root webserver access
// (www-data, caddy, nginx, etc) to certs.
func (a *actor) CanFetchCerts() bool {
if !buildfeatures.HasACME {
return false
}
if a.ci.IsUnixSock() && a.ci.Creds() != nil {
connUID, ok := a.ci.Creds().UserID()
if ok && connUID == userIDFromString(envknob.String("TS_PERMIT_CERT_UID")) {
@@ -398,6 +407,10 @@ func (a *actor) CanFetchCerts() bool {
//
// onDone must be called when the HTTP request is done.
func (s *Server) addActiveHTTPRequest(req *http.Request, actor ipnauth.Actor) (onDone func(), err error) {
if runtime.GOOS != "windows" && !buildfeatures.HasUnixSocketIdentity {
return func() {}, nil
}
if actor == nil {
return nil, errors.New("internal error: nil actor")
}
@@ -538,6 +551,10 @@ func (s *Server) Run(ctx context.Context, ln net.Listener) error {
// Windows and via $DEBUG_LISTENER/debug/ipn when tailscaled's --debug flag
// is used to run a debug server.
func (s *Server) ServeHTMLStatus(w http.ResponseWriter, r *http.Request) {
if !buildfeatures.HasDebug {
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotFound)
return
}
lb := s.lb.Load()
if lb == nil {
http.Error(w, "no LocalBackend", http.StatusServiceUnavailable)

View File

@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ios && !js && !android
//go:build !ios && !js && !android && !ts_omit_useproxy
package netns

View File

@@ -9,13 +9,19 @@ package blockblame
import (
"crypto/x509"
"strings"
"sync"
"tailscale.com/feature/buildfeatures"
)
// VerifyCertificate checks if the given certificate c is issued by a firewall manufacturer
// that is known to block Tailscale connections. It returns true and the Manufacturer of
// the equipment if it is, or false and nil if it is not.
func VerifyCertificate(c *x509.Certificate) (m *Manufacturer, ok bool) {
for _, m := range Manufacturers {
if !buildfeatures.HasDebug {
return nil, false
}
for _, m := range manufacturers() {
if m.match != nil && m.match(c) {
return m, true
}
@@ -33,46 +39,56 @@ type Manufacturer struct {
match matchFunc
}
var Manufacturers = []*Manufacturer{
{
Name: "Aruba Networks",
match: issuerContains("Aruba"),
},
{
Name: "Cisco",
match: issuerContains("Cisco"),
},
{
Name: "Fortinet",
match: matchAny(
issuerContains("Fortinet"),
certEmail("support@fortinet.com"),
),
},
{
Name: "Huawei",
match: certEmail("mobile@huawei.com"),
},
{
Name: "Palo Alto Networks",
match: matchAny(
issuerContains("Palo Alto Networks"),
issuerContains("PAN-FW"),
),
},
{
Name: "Sophos",
match: issuerContains("Sophos"),
},
{
Name: "Ubiquiti",
match: matchAny(
issuerContains("UniFi"),
issuerContains("Ubiquiti"),
),
},
func manufacturers() []*Manufacturer {
manufacturersOnce.Do(func() {
manufacturersList = []*Manufacturer{
{
Name: "Aruba Networks",
match: issuerContains("Aruba"),
},
{
Name: "Cisco",
match: issuerContains("Cisco"),
},
{
Name: "Fortinet",
match: matchAny(
issuerContains("Fortinet"),
certEmail("support@fortinet.com"),
),
},
{
Name: "Huawei",
match: certEmail("mobile@huawei.com"),
},
{
Name: "Palo Alto Networks",
match: matchAny(
issuerContains("Palo Alto Networks"),
issuerContains("PAN-FW"),
),
},
{
Name: "Sophos",
match: issuerContains("Sophos"),
},
{
Name: "Ubiquiti",
match: matchAny(
issuerContains("UniFi"),
issuerContains("Ubiquiti"),
),
},
}
})
return manufacturersList
}
var (
manufacturersOnce sync.Once
manufacturersList []*Manufacturer
)
type matchFunc func(*x509.Certificate) bool
func issuerContains(s string) matchFunc {

View File

@@ -28,6 +28,7 @@ import (
"tailscale.com/derp/derpconst"
"tailscale.com/envknob"
"tailscale.com/feature/buildfeatures"
"tailscale.com/health"
"tailscale.com/hostinfo"
"tailscale.com/net/bakedroots"
@@ -36,12 +37,6 @@ import (
var counterFallbackOK int32 // atomic
// If SSLKEYLOGFILE is set, it's a file to which we write our TLS private keys
// in a way that WireShark can read.
//
// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
var sslKeyLogFile = os.Getenv("SSLKEYLOGFILE")
var debug = envknob.RegisterBool("TS_DEBUG_TLS_DIAL")
// tlsdialWarningPrinted tracks whether we've printed a warning about a given
@@ -80,13 +75,19 @@ func Config(ht *health.Tracker, base *tls.Config) *tls.Config {
// the real TCP connection) because host is the ultimate hostname, but this
// tls.Config is used for both the proxy and the ultimate target.
if n := sslKeyLogFile; n != "" {
f, err := os.OpenFile(n, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
if buildfeatures.HasDebug {
// If SSLKEYLOGFILE is set, it's a file to which we write our TLS private keys
// in a way that WireShark can read.
//
// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
if n := os.Getenv("SSLKEYLOGFILE"); n != "" {
f, err := os.OpenFile(n, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
}
log.Printf("WARNING: writing to SSLKEYLOGFILE %v", n)
conf.KeyLogWriter = f
}
log.Printf("WARNING: writing to SSLKEYLOGFILE %v", n)
conf.KeyLogWriter = f
}
if conf.InsecureSkipVerify {
@@ -164,10 +165,12 @@ func Config(ht *health.Tracker, base *tls.Config) *tls.Config {
if debug() {
log.Printf("tlsdial(sys %q): %v", dialedHost, errSys)
}
if !buildfeatures.HasBakedRoots || (errSys == nil && !debug()) {
return errSys
}
// Always verify with our baked-in Let's Encrypt certificate,
// so we can log an informational message. This is useful for
// detecting SSL MiTM.
// If we have baked-in LetsEncrypt roots and we either failed above, or
// debug logging is enabled, also verify with LetsEncrypt.
opts.Roots = bakedroots.Get()
_, bakedErr := cs.PeerCertificates[0].Verify(opts)
if debug() {
@@ -239,8 +242,8 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
if debug() {
log.Printf("tlsdial(sys %q/%q): %v", c.ServerName, certDNSName, errSys)
}
if errSys == nil {
return nil
if !buildfeatures.HasBakedRoots || errSys == nil {
return errSys
}
opts.Roots = bakedroots.Get()
_, err := certs[0].Verify(opts)

View File

@@ -13,6 +13,7 @@ import (
"time"
"tailscale.com/feature"
"tailscale.com/feature/buildfeatures"
)
type closeable interface {
@@ -108,7 +109,12 @@ func LocalTCPPortAndToken() (port int, token string, err error) {
// PlatformUsesPeerCreds reports whether the current platform uses peer credentials
// to authenticate connections.
func PlatformUsesPeerCreds() bool { return GOOSUsesPeerCreds(runtime.GOOS) }
func PlatformUsesPeerCreds() bool {
if !buildfeatures.HasUnixSocketIdentity {
return false
}
return GOOSUsesPeerCreds(runtime.GOOS)
}
// GOOSUsesPeerCreds is like PlatformUsesPeerCreds but takes a
// runtime.GOOS value instead of using the current one.

View File

@@ -58,7 +58,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
LDW github.com/tailscale/hujson from tailscale.com/ipn/conffile
github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
LDAI github.com/tailscale/peercred from tailscale.com/ipn/ipnauth
LDW github.com/tailscale/web-client-prebuilt from tailscale.com/client/web
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/conn/winrio from github.com/tailscale/wireguard-go/conn

View File

@@ -18,6 +18,7 @@ import (
"sync/atomic"
"time"
"tailscale.com/feature/buildfeatures"
"tailscale.com/util/set"
)
@@ -130,15 +131,17 @@ func (m *Metric) Publish() {
metrics[m.name] = m
sortedDirty = true
if m.f != nil {
lastLogVal = append(lastLogVal, scanEntry{f: m.f})
} else {
if len(valFreeList) == 0 {
valFreeList = make([]int64, 256)
if buildfeatures.HasLogTail {
if m.f != nil {
lastLogVal = append(lastLogVal, scanEntry{f: m.f})
} else {
if len(valFreeList) == 0 {
valFreeList = make([]int64, 256)
}
m.v = &valFreeList[0]
valFreeList = valFreeList[1:]
lastLogVal = append(lastLogVal, scanEntry{v: m.v})
}
m.v = &valFreeList[0]
valFreeList = valFreeList[1:]
lastLogVal = append(lastLogVal, scanEntry{v: m.v})
}
m.regIdx = len(unsorted)
@@ -319,6 +322,9 @@ const (
// - increment a metric: (decrements if negative)
// 'I' + hex(varint(wireid)) + hex(varint(value))
func EncodeLogTailMetricsDelta() string {
if !buildfeatures.HasLogTail {
return ""
}
mu.Lock()
defer mu.Unlock()

View File

@@ -16,6 +16,7 @@ import (
"strings"
"time"
"tailscale.com/feature/buildfeatures"
"tailscale.com/syncs"
"tailscale.com/types/lazy"
)
@@ -51,6 +52,9 @@ const (
// ResolverIP returns the cloud host's recursive DNS server or the
// empty string if not available.
func (c Cloud) ResolverIP() string {
if !buildfeatures.HasCloud {
return ""
}
switch c {
case GCP:
return GoogleMetadataAndDNSIP
@@ -92,6 +96,9 @@ var cloudAtomic syncs.AtomicValue[Cloud]
// Get returns the current cloud, or the empty string if unknown.
func Get() Cloud {
if !buildfeatures.HasCloud {
return ""
}
if c, ok := cloudAtomic.LoadOk(); ok {
return c
}

View File

@@ -17,6 +17,7 @@ import (
"strings"
"time"
"tailscale.com/feature/buildfeatures"
"tailscale.com/types/logger"
"tailscale.com/util/cloudenv"
)
@@ -34,6 +35,9 @@ type cloudInfo struct {
}
func newCloudInfo(logf logger.Logf) *cloudInfo {
if !buildfeatures.HasCloud {
return nil
}
tr := &http.Transport{
DisableKeepAlives: true,
Dial: (&net.Dialer{
@@ -53,6 +57,9 @@ func newCloudInfo(logf logger.Logf) *cloudInfo {
// if the tailscaled process is running in a known cloud and there are any such
// IPs present.
func (ci *cloudInfo) GetPublicIPs(ctx context.Context) ([]netip.Addr, error) {
if !buildfeatures.HasCloud {
return nil, nil
}
switch ci.cloud {
case cloudenv.AWS:
ret, err := ci.getAWS(ctx)

View File

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

View File

@@ -1,6 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux && !ts_omit_listenrawdisco
package magicsock
import (

View File

@@ -435,7 +435,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
}
e.tundev.PreFilterPacketOutboundToWireGuardEngineIntercept = e.handleLocalPackets
if envknob.BoolDefaultTrue("TS_DEBUG_CONNECT_FAILURES") {
if buildfeatures.HasDebug && envknob.BoolDefaultTrue("TS_DEBUG_CONNECT_FAILURES") {
if e.tundev.PreFilterPacketInboundFromWireGuard != nil {
return nil, errors.New("unexpected PreFilterIn already set")
}