From 1b5b43787c33a197977a92d108297cac9b090ddf Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 7 Apr 2026 14:48:57 +0000 Subject: [PATCH] ipn/localapi, cli, clientmetric: add ipnbus feature tag; fix omit.go stub Add a new "ipnbus" build feature tag so the watch-ipn-bus LocalAPI endpoint can be independently controlled, rather than being gated behind HasDebug || HasServe. Minimal/embedded builds that omit both debug and serve were getting 404s on watch-ipn-bus, breaking "tailscale up --authkey=..." and other CLI flows that depend on WatchIPNBus. In the CLI, check buildfeatures.HasIPNBus before attempting to watch the IPN bus in "tailscale up"/"tailscale login", and exit early with an informational message when the feature is omitted. Also add the missing NewCounterFunc stub to clientmetric/omit.go, which caused compilation errors when building with ts_omit_clientmetrics and netstack enabled. Fixes #19240 Change-Id: I2e3c69a72fc50fa02542b91b8a54859618a463d1 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/up.go | 5 +++++ feature/buildfeatures/feature_ipnbus_disabled.go | 13 +++++++++++++ feature/buildfeatures/feature_ipnbus_enabled.go | 13 +++++++++++++ feature/featuretags/featuretags.go | 1 + ipn/localapi/localapi.go | 2 +- util/clientmetric/omit.go | 7 ++++--- 6 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 feature/buildfeatures/feature_ipnbus_disabled.go create mode 100644 feature/buildfeatures/feature_ipnbus_enabled.go diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index ba87739fc..586df07bb 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -599,6 +599,11 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE } }() + if !buildfeatures.HasIPNBus { + fmt.Fprintln(Stderr, "binary built with ts_omit_ipnbus; not waiting for completion") + return nil + } + // Start watching the IPN bus before we call Start() or StartLoginInteractive(), // or we could miss IPN notifications. // diff --git a/feature/buildfeatures/feature_ipnbus_disabled.go b/feature/buildfeatures/feature_ipnbus_disabled.go new file mode 100644 index 000000000..b71dbda62 --- /dev/null +++ b/feature/buildfeatures/feature_ipnbus_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_ipnbus + +package buildfeatures + +// HasIPNBus is whether the binary was built with support for modular feature "IPN notification bus (watch-ipn-bus) support, used by GUIs, debugging, and nicer 'tailscale up' support". +// Specifically, it's whether the binary was NOT built with the "ts_omit_ipnbus" build tag. +// It's a const so it can be used for dead code elimination. +const HasIPNBus = false diff --git a/feature/buildfeatures/feature_ipnbus_enabled.go b/feature/buildfeatures/feature_ipnbus_enabled.go new file mode 100644 index 000000000..74d954729 --- /dev/null +++ b/feature/buildfeatures/feature_ipnbus_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_ipnbus + +package buildfeatures + +// HasIPNBus is whether the binary was built with support for modular feature "IPN notification bus (watch-ipn-bus) support, used by GUIs, debugging, and nicer 'tailscale up' support". +// Specifically, it's whether the binary was NOT built with the "ts_omit_ipnbus" build tag. +// It's a const so it can be used for dead code elimination. +const HasIPNBus = true diff --git a/feature/featuretags/featuretags.go b/feature/featuretags/featuretags.go index 634c736e0..4f34acbe8 100644 --- a/feature/featuretags/featuretags.go +++ b/feature/featuretags/featuretags.go @@ -168,6 +168,7 @@ var Features = map[FeatureTag]FeatureMeta{ "health": {Sym: "Health", Desc: "Health checking support"}, "hujsonconf": {Sym: "HuJSONConf", Desc: "HuJSON config file support"}, "identityfederation": {Sym: "IdentityFederation", Desc: "Auth key generation via identity federation support"}, + "ipnbus": {Sym: "IPNBus", Desc: "IPN notification bus (watch-ipn-bus) support, used by GUIs, debugging, and nicer 'tailscale up' support"}, "iptables": {Sym: "IPTables", Desc: "Linux iptables support"}, "kube": {Sym: "Kube", Desc: "Kubernetes integration"}, "lazywg": {Sym: "LazyWG", Desc: "Lazy WireGuard configuration for memory-constrained devices with large netmaps"}, diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 5eec66e64..43942c52f 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -115,7 +115,7 @@ func init() { Register("bugreport", (*Handler).serveBugReport) Register("pprof", (*Handler).servePprof) } - if buildfeatures.HasDebug || buildfeatures.HasServe { + if buildfeatures.HasIPNBus { Register("watch-ipn-bus", (*Handler).serveWatchIPNBus) } if buildfeatures.HasDNS { diff --git a/util/clientmetric/omit.go b/util/clientmetric/omit.go index 725b18fe4..380205eeb 100644 --- a/util/clientmetric/omit.go +++ b/util/clientmetric/omit.go @@ -26,6 +26,7 @@ func WritePrometheusExpositionFormat(any) {} var zeroMetric Metric -func NewCounter(string) *Metric { return &zeroMetric } -func NewGauge(string) *Metric { return &zeroMetric } -func NewAggregateCounter(string) *Metric { return &zeroMetric } +func NewCounter(string) *Metric { return &zeroMetric } +func NewGauge(string) *Metric { return &zeroMetric } +func NewAggregateCounter(string) *Metric { return &zeroMetric } +func NewCounterFunc(string, func() int64) *Metric { return &zeroMetric }