Jelajahi Sumber

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 <[email protected]>
Brad Fitzpatrick 5 bulan lalu
induk
melakukan
c45f8813b4
35 mengubah file dengan 410 tambahan dan 169 penghapusan
  1. 1 1
      cmd/k8s-operator/depaware.txt
  2. 1 4
      cmd/tailscaled/depaware-min.txt
  3. 1 4
      cmd/tailscaled/depaware-minbox.txt
  4. 1 1
      cmd/tailscaled/depaware.txt
  5. 11 5
      cmd/tailscaled/deps_test.go
  6. 1 1
      cmd/tsidp/depaware.txt
  7. 13 0
      feature/buildfeatures/feature_bakedroots_disabled.go
  8. 13 0
      feature/buildfeatures/feature_bakedroots_enabled.go
  9. 13 0
      feature/buildfeatures/feature_cloud_disabled.go
  10. 13 0
      feature/buildfeatures/feature_cloud_enabled.go
  11. 13 0
      feature/buildfeatures/feature_listenrawdisco_disabled.go
  12. 13 0
      feature/buildfeatures/feature_listenrawdisco_enabled.go
  13. 13 0
      feature/buildfeatures/feature_unixsocketidentity_disabled.go
  14. 13 0
      feature/buildfeatures/feature_unixsocketidentity_enabled.go
  15. 42 32
      feature/featuretags/featuretags.go
  16. 14 6
      ipn/ipnauth/ipnauth.go
  17. 25 0
      ipn/ipnauth/ipnauth_omit_unixsocketidentity.go
  18. 1 1
      ipn/ipnauth/ipnauth_unix_creds.go
  19. 33 20
      ipn/ipnlocal/c2n.go
  20. 5 2
      ipn/ipnlocal/local.go
  21. 25 23
      ipn/ipnlocal/peerapi.go
  22. 6 0
      ipn/ipnserver/actor.go
  23. 6 0
      ipn/ipnserver/proxyconnect.go
  24. 17 0
      ipn/ipnserver/server.go
  25. 1 1
      net/netns/socks.go
  26. 55 39
      net/tlsdial/blockblame/blockblame.go
  27. 20 17
      net/tlsdial/tlsdial.go
  28. 7 1
      safesocket/safesocket.go
  29. 1 1
      tsnet/depaware.txt
  30. 14 8
      util/clientmetric/clientmetric.go
  31. 7 0
      util/cloudenv/cloudenv.go
  32. 7 0
      wgengine/magicsock/cloudinfo.go
  33. 1 1
      wgengine/magicsock/magicsock_default.go
  34. 2 0
      wgengine/magicsock/magicsock_linux.go
  35. 1 1
      wgengine/userspace.go

+ 1 - 1
cmd/k8s-operator/depaware.txt

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

+ 1 - 4
cmd/tailscaled/depaware-min.txt

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

+ 1 - 4
cmd/tailscaled/depaware-minbox.txt

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

+ 1 - 1
cmd/tailscaled/depaware.txt

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

+ 11 - 5
cmd/tailscaled/deps_test.go

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

+ 1 - 1
cmd/tsidp/depaware.txt

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

+ 13 - 0
feature/buildfeatures/feature_bakedroots_disabled.go

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

+ 13 - 0
feature/buildfeatures/feature_bakedroots_enabled.go

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

+ 13 - 0
feature/buildfeatures/feature_cloud_disabled.go

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

+ 13 - 0
feature/buildfeatures/feature_cloud_enabled.go

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

+ 13 - 0
feature/buildfeatures/feature_listenrawdisco_disabled.go

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

+ 13 - 0
feature/buildfeatures/feature_listenrawdisco_enabled.go

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

+ 13 - 0
feature/buildfeatures/feature_unixsocketidentity_disabled.go

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

+ 13 - 0
feature/buildfeatures/feature_unixsocketidentity_enabled.go

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

+ 42 - 32
feature/featuretags/featuretags.go

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

+ 14 - 6
ipn/ipnauth/ipnauth.go

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

+ 25 - 0
ipn/ipnauth/ipnauth_omit_unixsocketidentity.go

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

+ 1 - 1
ipn/ipnauth/ipnauth_notwindows.go → ipn/ipnauth/ipnauth_unix_creds.go

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

+ 33 - 20
ipn/ipnlocal/c2n.go

@@ -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,
-
-	// PPROF - We only expose a subset of typical pprof endpoints for security.
-	req("/debug/pprof/heap"):   handleC2NPprof,
-	req("/debug/pprof/allocs"): handleC2NPprof,
-
-	req("POST /logtail/flush"): handleC2NLogtailFlush,
-	req("POST /sockstats"):     handleC2NSockStats,
-
-	// SSH
-	req("/ssh/usernames"): handleC2NSSHUsernames,
-
-	// Linux netfilter.
-	req("POST /netfilter-kind"): handleC2NSetNetfilterKind,
+	req("/echo"): handleC2NEcho,
+}
+
+func init() {
+	if buildfeatures.HasSSH {
+		RegisterC2N("/ssh/usernames", handleC2NSSHUsernames)
+	}
+	if buildfeatures.HasLogTail {
+		RegisterC2N("POST /logtail/flush", handleC2NLogtailFlush)
+	}
+	if buildfeatures.HasDebug {
+		RegisterC2N("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)
+
+		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 {

+ 5 - 2
ipn/ipnlocal/local.go

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

+ 25 - 23
ipn/ipnlocal/peerapi.go

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

+ 6 - 0
ipn/ipnserver/actor.go

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

+ 6 - 0
ipn/ipnserver/proxyconnect.go

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

+ 17 - 0
ipn/ipnserver/server.go

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

+ 1 - 1
net/netns/socks.go

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

+ 55 - 39
net/tlsdial/blockblame/blockblame.go

@@ -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("[email protected]"),
-		),
-	},
-	{
-		Name:  "Huawei",
-		match: certEmail("[email protected]"),
-	},
-	{
-		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("[email protected]"),
+				),
+			},
+			{
+				Name:  "Huawei",
+				match: certEmail("[email protected]"),
+			},
+			{
+				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 {

+ 20 - 17
net/tlsdial/tlsdial.go

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

+ 7 - 1
safesocket/safesocket.go

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

+ 1 - 1
tsnet/depaware.txt

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

+ 14 - 8
util/clientmetric/clientmetric.go

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

+ 7 - 0
util/cloudenv/cloudenv.go

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

+ 7 - 0
wgengine/magicsock/cloudinfo.go

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

+ 1 - 1
wgengine/magicsock/magicsock_default.go

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

+ 2 - 0
wgengine/magicsock/magicsock_linux.go

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

+ 1 - 1
wgengine/userspace.go

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