all: migrate code off Notify.NetMap to Notify.SelfChange

Move tailscaled's in-tree reactive users from of IPN bus Notify.NetMap
updates to the narrower Notify.SelfChange signal introduced earlier in
this series. Consumers that need additional state (peers, DNS config,
etc.) fetch it on demand via the LocalAPI.

It is a step toward the larger goal of not fanning Notify.NetMap out
to every bus watcher on Linux/non-GUI hosts.

A future change stops sending Notify.NetMap entirely on Linux and
non-GUI platforms. (eventually once macOS/iOS/Windows migrate to the
upcoming new Notify APIs, we'll remove ipn.Notify.NetMap entirely)

Updates #12542

Change-Id: I51ea9d86bdca1909d6ac0e7d5bd3934a3a4e8516
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-05-01 03:47:18 +00:00
committed by Brad Fitzpatrick
parent ff9c3f0e00
commit 4c3ed5ab32
14 changed files with 498 additions and 410 deletions
+4 -9
View File
@@ -171,14 +171,9 @@ func (cm *CertManager) runCertLoop(ctx context.Context, domain string) {
}
}
// waitForCertDomain ensures the requested domain is in the list of allowed
// domains before issuing the cert for the first time.
// It uses the IPN bus only as a wake-up trigger and queries the current cert
// domains explicitly via [LocalClient.CertDomains].
//
// TODO(bradfitz): once Notify.SelfChange lands upstream, switch this to
// watch for SelfChange events instead of NotifyInitialNetMap, and drop the
// netmap dependency on the bus entirely.
// domains before issuing the cert for the first time. It uses the IPN bus
// only as a wake-up trigger (Notify.SelfChange) and queries the current
// cert domains explicitly via [LocalClient.CertDomains].
func (cm *CertManager) waitForCertDomain(ctx context.Context, domain string) error {
w, err := cm.lc.WatchIPNBus(ctx, ipn.NotifyInitialNetMap)
if err != nil {
@@ -191,7 +186,7 @@ func (cm *CertManager) waitForCertDomain(ctx context.Context, domain string) err
if err != nil {
return err
}
if n.NetMap == nil {
if n.SelfChange == nil {
continue
}
domains, err := cm.lc.CertDomains(ctx)
+5 -6
View File
@@ -12,7 +12,6 @@ import (
"tailscale.com/ipn"
"tailscale.com/kube/localclient"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
)
// TestEnsureCertLoops tests that the certManager correctly starts and stops
@@ -201,12 +200,12 @@ func TestEnsureCertLoops(t *testing.T) {
notifyChan := make(chan ipn.Notify)
go func() {
// Drive waitForCertDomain by sending notifications
// with empty netmaps as wake-up triggers; the cert
// manager queries CertDomains via the local
// client and not by reading the bus payload.
// SelfChange wakes the cert manager; cert domains are
// then fetched via FakeLocalClient.CertDomainsResult.
for {
notifyChan <- ipn.Notify{NetMap: &netmap.NetworkMap{}}
notifyChan <- ipn.Notify{
SelfChange: &tailcfg.Node{StableID: "test"},
}
}
}()
cm := &CertManager{
+2 -2
View File
@@ -65,8 +65,8 @@ func (h *Healthz) MonitorHealth(ctx context.Context, lc *local.Client) error {
return err
}
if n.NetMap != nil {
h.Update(n.NetMap.SelfNode.Addresses().Len() != 0)
if self := n.SelfChange; self != nil {
h.Update(len(self.Addresses) != 0)
}
}
}
+9 -8
View File
@@ -44,9 +44,9 @@ func SetInitialKeys(store ipn.StateStore, podUID string) error {
// KeepKeysUpdated sets state store keys consistent with containerboot to
// signal proxy readiness to the operator. It runs until its context is
// cancelled or it hits an error. The passed in next function is expected to be
// from a local.IPNBusWatcher that is at least subscribed to
// ipn.NotifyInitialNetMap.
// cancelled or it hits an error. It watches the IPN bus for SelfChange
// notifications (which fire whenever the self node changes) and reads
// the new self node directly from the notify.
func KeepKeysUpdated(ctx context.Context, store ipn.StateStore, lc klc.LocalClient) error {
w, err := lc.WatchIPNBus(ctx, ipn.NotifyInitialNetMap)
if err != nil {
@@ -63,25 +63,26 @@ func KeepKeysUpdated(ctx context.Context, store ipn.StateStore, lc klc.LocalClie
}
return err
}
if n.NetMap == nil {
self := n.SelfChange
if self == nil {
continue
}
if deviceID := n.NetMap.SelfNode.StableID(); deephash.Update(&currentDeviceID, &deviceID) {
if deviceID := self.StableID; deephash.Update(&currentDeviceID, &deviceID) {
if err := store.WriteState(keyDeviceID, []byte(deviceID)); err != nil {
return fmt.Errorf("failed to store device ID in state: %w", err)
}
}
if fqdn := n.NetMap.SelfNode.Name(); deephash.Update(&currentDeviceFQDN, &fqdn) {
if fqdn := self.Name; deephash.Update(&currentDeviceFQDN, &fqdn) {
if err := store.WriteState(keyDeviceFQDN, []byte(fqdn)); err != nil {
return fmt.Errorf("failed to store device FQDN in state: %w", err)
}
}
if addrs := n.NetMap.SelfNode.Addresses(); deephash.Update(&currentDeviceIPs, &addrs) {
if addrs := self.Addresses; deephash.Update(&currentDeviceIPs, &addrs) {
var deviceIPs []string
for _, addr := range addrs.AsSlice() {
for _, addr := range addrs {
deviceIPs = append(deviceIPs, addr.Addr().String())
}
deviceIPsValue, err := json.Marshal(deviceIPs)
+8 -13
View File
@@ -18,7 +18,6 @@ import (
klc "tailscale.com/kube/localclient"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
)
func TestSetInitialStateKeys(t *testing.T) {
@@ -133,12 +132,10 @@ func TestKeepStateKeysUpdated(t *testing.T) {
{
name: "authed",
notify: ipn.Notify{
NetMap: &netmap.NetworkMap{
SelfNode: (&tailcfg.Node{
StableID: "TESTCTRL00000001",
Name: "test-node.test.ts.net",
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32"), netip.MustParsePrefix("fd7a:115c:a1e0:ab12:4843:cd96:0:1/128")},
}).View(),
SelfChange: &tailcfg.Node{
StableID: "TESTCTRL00000001",
Name: "test-node.test.ts.net",
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32"), netip.MustParsePrefix("fd7a:115c:a1e0:ab12:4843:cd96:0:1/128")},
},
},
expected: []string{
@@ -150,12 +147,10 @@ func TestKeepStateKeysUpdated(t *testing.T) {
{
name: "updated_fields",
notify: ipn.Notify{
NetMap: &netmap.NetworkMap{
SelfNode: (&tailcfg.Node{
StableID: "TESTCTRL00000001",
Name: "updated.test.ts.net",
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.250/32")},
}).View(),
SelfChange: &tailcfg.Node{
StableID: "TESTCTRL00000001",
Name: "updated.test.ts.net",
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.250/32")},
},
},
expected: []string{