mirror of
https://github.com/tailscale/tailscale.git
synced 2026-10-23 03:37:19 +09:00
ipn, ipn/ipnlocal: remove Notify.NetMap, size initial status to subscription
Nothing reads ipn.Notify.NetMap anymore. The previous commit removed its runtime (non-initial) emission, and every first-party client is also off the initial one: the Win32 and WinUI GUIs and the Apple bridge subscribe with InitialStatus or InitialState plus peer deltas, Android no longer uses it, and the remaining in-tree subscribers that set NotifyInitialNetMap (sniproxy and the kube helpers) only did so to get the initial Notify.SelfChange and discarded the netmap that tailscaled built, encoded, and shipped for them. Delete the Notify.NetMap field and the NotifyInitialNetMap bit. The bit value stays reserved under the name ObsoleteNotifyInitialNetMap and ValidateNotifyWatchOpt rejects subscriptions that set it, like the NotifyRateLimit bit removed in the previous commit. NotifyNoNetMap remains accepted as a no-op because shipping GUIs still set it. The blessed way to seed a watcher's view is NotifyInitialStatus, but it unconditionally built O(peers) status entries, which is exactly the waste this series is deleting for watchers that only care about the self node. Size the initial status to the subscription instead: Status.Peer is only populated when the watcher also set NotifyPeerChanges or NotifyPeerPatches, since only peer-delta subscribers need a peer baseline to apply deltas to. That matches its existing first-party users (containerboot and the WinUI GUI both pair InitialStatus with peer bits). Migrate sniproxy and the kube helpers to NotifyInitialStatus: they seed from InitialStatus.Self and react to the (ungated) runtime Notify.SelfChange messages after that, so their initial message now carries one PeerStatus instead of a full netmap. Also drop doc comment references to LocalClient.NetMap, a method that does not exist; on-demand fetches go through other LocalAPI methods such as LocalClient.Status. Updates #12542 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: I242992a744c0ffd0be6f27e8c735aa69d5b23b5e
This commit is contained in:
1 parent
1b0f7b3c6c
commit
ab4262680b
13 files changed
+191
-94
No files matched your search
@@ -139,7 +139,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/types/key from tailscale.com/client/local+
|
||||
tailscale.com/types/lazy from tailscale.com/version+
|
||||
tailscale.com/types/logger from tailscale.com/cmd/derper+
|
||||
tailscale.com/types/netmap from tailscale.com/ipn+
|
||||
tailscale.com/types/netmap from tailscale.com/net/routecheck
|
||||
tailscale.com/types/opt from tailscale.com/envknob+
|
||||
tailscale.com/types/persist from tailscale.com/ipn+
|
||||
tailscale.com/types/preftype from tailscale.com/ipn
|
||||
|
||||
@@ -138,10 +138,9 @@ func run(ctx context.Context, ts *tsnet.Server, wgPort int, hostname string, pro
|
||||
}
|
||||
|
||||
// Finally, start mainloop to configure app connector based on information
|
||||
// in the self node's CapMap. We set NotifyInitialNetMap so the first
|
||||
// Notify carries the current self node (now via Notify.SelfChange);
|
||||
// subsequent self changes wake us up too.
|
||||
bus, err := lc.WatchIPNBus(ctx, ipn.NotifyWatchEngineUpdates|ipn.NotifyInitialNetMap)
|
||||
// in the self node's CapMap. The initial status carries the current
|
||||
// self node; subsequent self changes arrive via Notify.SelfChange.
|
||||
bus, err := lc.WatchIPNBus(ctx, ipn.NotifyWatchEngineUpdates|ipn.NotifyInitialStatus)
|
||||
if err != nil {
|
||||
log.Fatalf("watching IPN bus: %v", err)
|
||||
}
|
||||
@@ -155,13 +154,16 @@ func run(ctx context.Context, ts *tsnet.Server, wgPort int, hostname string, pro
|
||||
log.Fatalf("reading IPN bus: %v", err)
|
||||
}
|
||||
|
||||
self := msg.SelfChange
|
||||
if self == nil {
|
||||
var capMap tailcfg.NodeCapMap
|
||||
if self := msg.SelfChange; self != nil {
|
||||
capMap = self.CapMap
|
||||
} else if st := msg.InitialStatus; st != nil && st.Self != nil {
|
||||
capMap = st.Self.CapMap
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
var c appctype.AppConnectorConfig
|
||||
// View() lets us reuse the existing CapView decoder.
|
||||
nmConf, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.AppConnectorConfig](self.View().CapMap(), configCapKey)
|
||||
nmConf, err := tailcfg.UnmarshalNodeCapJSON[appctype.AppConnectorConfig](capMap, configCapKey)
|
||||
if err != nil {
|
||||
log.Printf("failed to read app connector configuration from coordination server: %v", err)
|
||||
} else if len(nmConf) > 0 {
|
||||
|
||||
@@ -275,7 +275,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/types/key from tailscale.com/client/local+
|
||||
tailscale.com/types/lazy from tailscale.com/util/testenv+
|
||||
tailscale.com/types/logger from tailscale.com/client/web+
|
||||
tailscale.com/types/netmap from tailscale.com/ipn+
|
||||
tailscale.com/types/netmap from tailscale.com/net/routecheck
|
||||
tailscale.com/types/nettype from tailscale.com/net/netcheck+
|
||||
tailscale.com/types/opt from tailscale.com/client/tailscale+
|
||||
tailscale.com/types/persist from tailscale.com/ipn+
|
||||
|
||||
+42
-34
@@ -17,7 +17,6 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/empty"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/structs"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
@@ -86,9 +85,19 @@ const (
|
||||
// each one via Engine.RequestStatus.
|
||||
NotifyWatchEngineUpdates NotifyWatchOpt = 1 << 0
|
||||
|
||||
NotifyInitialState NotifyWatchOpt = 1 << 1 // if set, the first Notify message (sent immediately) will contain the current State + BrowseToURL + SessionID
|
||||
NotifyInitialPrefs NotifyWatchOpt = 1 << 2 // if set, the first Notify message (sent immediately) will contain the current Prefs
|
||||
NotifyInitialNetMap NotifyWatchOpt = 1 << 3 // if set, the first Notify message (sent immediately) will contain the current NetMap
|
||||
NotifyInitialState NotifyWatchOpt = 1 << 1 // if set, the first Notify message (sent immediately) will contain the current State + BrowseToURL + SessionID
|
||||
NotifyInitialPrefs NotifyWatchOpt = 1 << 2 // if set, the first Notify message (sent immediately) will contain the current Prefs
|
||||
|
||||
// ObsoleteNotifyInitialNetMap is a formerly valid bit (previously named
|
||||
// NotifyInitialNetMap) that asked for the first Notify message to
|
||||
// carry the full netmap in the since-removed Notify.NetMap field.
|
||||
// Subscription requests that set it are now rejected by
|
||||
// [ValidateNotifyWatchOpt]. Watchers seed their view from
|
||||
// [NotifyInitialStatus] instead, and those that need more than the
|
||||
// status fetch what they need via other LocalAPI methods. The bit
|
||||
// value remains reserved so it is never reused with a different
|
||||
// meaning.
|
||||
ObsoleteNotifyInitialNetMap NotifyWatchOpt = 1 << 3
|
||||
|
||||
NotifyNoPrivateKeys NotifyWatchOpt = 1 << 4 // (no-op) it used to redact private keys; now they always are and this does nothing
|
||||
NotifyInitialDriveShares NotifyWatchOpt = 1 << 5 // if set, the first Notify message (sent immediately) will contain the current Taildrive Shares
|
||||
@@ -128,27 +137,29 @@ const (
|
||||
// observes every per-peer mutation; it just receives them as full
|
||||
// Nodes rather than narrow patches. The cost is bus bandwidth.
|
||||
//
|
||||
// It is permitted to combine this with [NotifyInitialNetMap] for
|
||||
// backwards compatibility. New code should pair this with
|
||||
// [NotifyInitialStatus] instead.
|
||||
// Watchers typically pair this with [NotifyInitialStatus] to seed
|
||||
// their initial state.
|
||||
NotifyPeerChanges NotifyWatchOpt = 1 << 12
|
||||
|
||||
// NotifyNoNetMap historically suppressed the legacy [Notify.NetMap]
|
||||
// NotifyNoNetMap historically suppressed the legacy Notify.NetMap
|
||||
// field on runtime (non-initial) Notify messages on platforms where
|
||||
// tailscaled still emitted it by default. tailscaled no longer emits
|
||||
// NetMap on runtime messages on any platform, so this bit is now a
|
||||
// no-op. It remains accepted for compatibility with clients that set
|
||||
// it. The initial-state NetMap (sent when [NotifyInitialNetMap] is
|
||||
// set) was never affected by this bit.
|
||||
// tailscaled still emitted it by default. That field no longer
|
||||
// exists, so this bit is now a no-op. It remains accepted for
|
||||
// compatibility with clients that set it.
|
||||
NotifyNoNetMap NotifyWatchOpt = 1 << 13
|
||||
|
||||
// NotifyInitialStatus, if set, causes the first Notify message (sent
|
||||
// immediately) to contain the current [ipnstate.Status] in
|
||||
// [Notify.InitialStatus]. Together with [Notify.SelfChange] and
|
||||
// [Notify.PeersChanged] on subsequent messages, it lets a watcher
|
||||
// stitch together a continuous view of the local node's state without
|
||||
// fetching the netmap directly. Prefer this over [LocalClient.NetMap]
|
||||
// for new code that wants a stable, client-facing snapshot type.
|
||||
// stitch together a continuous view of the local node's state as a
|
||||
// stable, client-facing snapshot type, without needing the netmap.
|
||||
//
|
||||
// The status is sized to the subscription: its per-peer entries
|
||||
// (Status.Peer) are only populated if the watcher also set
|
||||
// [NotifyPeerChanges] or [NotifyPeerPatches], since building them is
|
||||
// O(peers) and only peer-delta subscribers need a peer baseline.
|
||||
// Self-only watchers get Status.Self and the scalar fields.
|
||||
NotifyInitialStatus NotifyWatchOpt = 1 << 14
|
||||
|
||||
// NotifyPeerPatches, if set, opts the watcher into narrow per-field
|
||||
@@ -226,7 +237,7 @@ func (o NotifyWatchOpt) String() string {
|
||||
try(NotifyWatchEngineUpdates, "NotifyWatchEngineUpdates")
|
||||
try(NotifyInitialState, "NotifyInitialState")
|
||||
try(NotifyInitialPrefs, "NotifyInitialPrefs")
|
||||
try(NotifyInitialNetMap, "NotifyInitialNetMap")
|
||||
try(ObsoleteNotifyInitialNetMap, "ObsoleteNotifyInitialNetMap")
|
||||
try(NotifyNoPrivateKeys, "NotifyNoPrivateKeys")
|
||||
try(NotifyInitialDriveShares, "NotifyInitialDriveShares")
|
||||
try(NotifyInitialOutgoingFiles, "NotifyInitialOutgoingFiles")
|
||||
@@ -283,6 +294,9 @@ func ValidateNotifyWatchOpt(mask NotifyWatchOpt) error {
|
||||
if mask&ObsoleteNotifyRateLimit != 0 {
|
||||
return errors.New("the NotifyRateLimit IPN bus subscription bit is no longer supported")
|
||||
}
|
||||
if mask&ObsoleteNotifyInitialNetMap != 0 {
|
||||
return errors.New("the NotifyInitialNetMap IPN bus subscription bit is no longer supported; seed from NotifyInitialStatus instead")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -318,9 +332,15 @@ type Notify struct {
|
||||
// sniproxy, etc.) can read the current self state without watching the
|
||||
// full netmap.
|
||||
//
|
||||
// Consumers that need additional state (peers, DNS config, packet
|
||||
// filter) should react to SelfChange by fetching the full netmap on
|
||||
// demand via [LocalClient.NetMap].
|
||||
// Runtime self changes are not gated by any subscription bit: every
|
||||
// watcher receives them. There is no initial SelfChange; watchers
|
||||
// that need the current self node at the start of a session should
|
||||
// set [NotifyInitialStatus] and seed from InitialStatus.Self.
|
||||
//
|
||||
// Consumers that need additional state (peers, DNS config, etc.)
|
||||
// should react to SelfChange by fetching what they need via other
|
||||
// LocalAPI methods, such as LocalClient.Status or
|
||||
// LocalClient.DNSConfig.
|
||||
SelfChange *tailcfg.Node `json:",omitzero"`
|
||||
|
||||
// InitialStatus, if non-nil, is the current [ipnstate.Status]. It is
|
||||
@@ -330,18 +350,6 @@ type Notify struct {
|
||||
// a continuous view of node state without fetching the netmap.
|
||||
InitialStatus *ipnstate.Status `json:",omitzero"`
|
||||
|
||||
// NetMap, if non-nil, is the full network map. It is only delivered
|
||||
// on the initial Notify of a session, and only when the watcher
|
||||
// requested [NotifyInitialNetMap]; it is always nil on subsequent
|
||||
// messages. New consumers should prefer [LocalClient.NetMap] for
|
||||
// one-shot fetches and [Notify.SelfChange] / [Notify.PeerChanges]
|
||||
// for incremental reactive updates.
|
||||
//
|
||||
// Deprecated: this field is slated for removal in favor of
|
||||
// [Notify.InitialStatus] + [Notify.SelfChange] /
|
||||
// [Notify.PeerChanges], etc, as this field doesn't scale.
|
||||
NetMap *netmap.NetworkMap
|
||||
|
||||
// PeerChangedPatch, if non-empty, lists narrow per-field peer patches
|
||||
// since the last Notify (currently Online, LastSeen, DERPHome,
|
||||
// Endpoints). It mirrors [tailcfg.MapResponse.PeersChangedPatch].
|
||||
@@ -392,8 +400,8 @@ type Notify struct {
|
||||
// The producer guarantees that any UserID referenced by a peer in
|
||||
// a [Notify.PeersChanged] / [Notify.PeerChangedPatch] entry will
|
||||
// have its profile delivered either earlier on this same session
|
||||
// (e.g. via the initial NetMap or via an earlier Notify carrying
|
||||
// UserProfiles) or in this same Notify. A consumer that sees a
|
||||
// (via an earlier Notify carrying UserProfiles) or in this same
|
||||
// Notify. A consumer that sees a
|
||||
// UserID it doesn't recognize on a session that opted in to
|
||||
// peer-change notifications can treat it as a bug; the
|
||||
// [LocalClient.UserProfile] LocalAPI fallback exists for sessions
|
||||
|
||||
@@ -110,6 +110,11 @@ func TestValidateNotifyWatchOpt(t *testing.T) {
|
||||
mask: ObsoleteNotifyRateLimit | NotifyPeerChanges,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "obsolete-initial-netmap",
|
||||
mask: ObsoleteNotifyInitialNetMap,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
+9
-14
@@ -2071,7 +2071,7 @@ func (b *LocalBackend) setControlClientStatusLocked(c controlclient.Client, st c
|
||||
|
||||
// Notify watchers that the self node may have changed. Reactive
|
||||
// consumers (containerboot, kube agents, sniproxy, etc.) listen on
|
||||
// this signal and re-fetch peers/DNS via [LocalClient.NetMap] if
|
||||
// this signal and re-fetch peers/DNS via other LocalAPI methods if
|
||||
// they need more than self info.
|
||||
var selfChange *tailcfg.Node
|
||||
if st.NetMap.SelfNode.Valid() {
|
||||
@@ -3741,7 +3741,13 @@ func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauth.A
|
||||
// channel before InitialStatus is delivered.
|
||||
var statusSB *ipnstate.StatusBuilder
|
||||
if mask&ipn.NotifyInitialStatus != 0 {
|
||||
statusSB = &ipnstate.StatusBuilder{WantPeers: true}
|
||||
// The initial status is sized to the subscription: building the
|
||||
// per-peer status entries is O(peers), so only do it for watchers
|
||||
// that subscribed to peer deltas and thus need a peer baseline to
|
||||
// apply them to. Self-only watchers get Status.Self and the
|
||||
// scalar fields.
|
||||
wantPeers := mask&(ipn.NotifyPeerChanges|ipn.NotifyPeerPatches) != 0
|
||||
statusSB = &ipnstate.StatusBuilder{WantPeers: wantPeers}
|
||||
b.e.UpdateStatus(statusSB)
|
||||
}
|
||||
if mask&ipn.NotifyPeerWireGuardState != 0 {
|
||||
@@ -3756,11 +3762,10 @@ func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauth.A
|
||||
|
||||
var policyUID string
|
||||
const initialBits = ipn.NotifyInitialState | ipn.NotifyInitialPrefs |
|
||||
ipn.NotifyInitialNetMap | ipn.NotifyInitialStatus |
|
||||
ipn.NotifyInitialStatus |
|
||||
ipn.NotifyInitialDriveShares | ipn.NotifyInitialSuggestedExitNode |
|
||||
ipn.NotifyInitialClientVersion | ipn.NotifySysPolicyChanges | ipn.NotifyPeerWireGuardState
|
||||
if mask&initialBits != 0 {
|
||||
cn := b.currentNode()
|
||||
ini = &ipn.Notify{Version: version.Long()}
|
||||
if mask&ipn.NotifyInitialState != 0 {
|
||||
ini.SessionID = sessionID
|
||||
@@ -3772,16 +3777,6 @@ func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauth.A
|
||||
if mask&ipn.NotifyInitialPrefs != 0 {
|
||||
ini.Prefs = new(b.sanitizedPrefsLocked())
|
||||
}
|
||||
if mask&ipn.NotifyInitialNetMap != 0 {
|
||||
if nm := cn.NetMap(); nm != nil && nm.SelfNode.Valid() {
|
||||
ini.SelfChange = nm.SelfNode.AsStruct()
|
||||
}
|
||||
// The legacy initial NetMap is delivered cross-platform: it
|
||||
// is what watchers asked for by setting NotifyInitialNetMap
|
||||
// and is always a one-shot, so the cost of building it is
|
||||
// paid once per bus subscription.
|
||||
ini.NetMap = cn.netMapWithPeers()
|
||||
}
|
||||
if statusSB != nil {
|
||||
b.updateStatusLocked(statusSB)
|
||||
ini.InitialStatus = statusSB.Status()
|
||||
|
||||
@@ -2470,9 +2470,6 @@ func TestSetControlClientStatusSendsFullNetmapAsPeerChanges(t *testing.T) {
|
||||
if n.SelfChange == nil {
|
||||
return false
|
||||
}
|
||||
if n.NetMap != nil {
|
||||
t.Errorf("NetMap was delivered to NotifyNoNetMap watcher")
|
||||
}
|
||||
if got, want := len(n.PeersChanged), 2; got != want {
|
||||
t.Errorf("PeersChanged len = %d; want %d", got, want)
|
||||
return false
|
||||
@@ -2510,6 +2507,55 @@ func TestSetControlClientStatusSendsFullNetmapAsPeerChanges(t *testing.T) {
|
||||
nw.check()
|
||||
}
|
||||
|
||||
// TestWatchNotificationsInitialStatusPeers verifies that the initial
|
||||
// status is sized to the subscription: Status.Peer entries are only
|
||||
// populated for watchers that subscribed to peer deltas, while
|
||||
// Status.Self is populated either way.
|
||||
func TestWatchNotificationsInitialStatusPeers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mask ipn.NotifyWatchOpt
|
||||
wantPeers bool
|
||||
}{
|
||||
{"self-only", ipn.NotifyInitialStatus, false},
|
||||
{"peer-changes", ipn.NotifyInitialStatus | ipn.NotifyPeerChanges, true},
|
||||
{"peer-patches", ipn.NotifyInitialStatus | ipn.NotifyPeerPatches, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b := newTestLocalBackend(t)
|
||||
b.currentNode().SetNetMap(&netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
ID: 1,
|
||||
User: 1,
|
||||
Key: makeNodeKeyFromID(1),
|
||||
}).View(),
|
||||
Peers: []tailcfg.NodeView{
|
||||
(&tailcfg.Node{ID: 10, User: 1, Key: makeNodeKeyFromID(10)}).View(),
|
||||
},
|
||||
})
|
||||
|
||||
nw := newNotificationWatcher(t, b, ipnauth.Self)
|
||||
nw.watch(tt.mask, []wantedNotification{{
|
||||
name: "initial status",
|
||||
cond: func(t testing.TB, _ ipnauth.Actor, n *ipn.Notify) bool {
|
||||
if n.InitialStatus == nil {
|
||||
return false
|
||||
}
|
||||
if n.InitialStatus.Self == nil {
|
||||
t.Errorf("InitialStatus.Self = nil; want non-nil")
|
||||
}
|
||||
if got := len(n.InitialStatus.Peer); (got > 0) != tt.wantPeers {
|
||||
t.Errorf("len(InitialStatus.Peer) = %d; wantPeers = %v", got, tt.wantPeers)
|
||||
}
|
||||
return true
|
||||
},
|
||||
}})
|
||||
nw.check()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type expiryCallbackClock struct {
|
||||
tstime.StdClock
|
||||
now time.Time
|
||||
@@ -5248,6 +5294,11 @@ func TestDriveManageShares(t *testing.T) {
|
||||
0,
|
||||
func() { wg.Done() },
|
||||
func(n *ipn.Notify) bool {
|
||||
if n.DriveShares.IsNil() {
|
||||
// Skip unrelated notifications, such as the
|
||||
// initial SelfChange sent to every watcher.
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case result <- n.DriveShares:
|
||||
default:
|
||||
|
||||
@@ -154,8 +154,8 @@ type nodeBackend struct {
|
||||
// by mergeUserProfiles as deltas arrive. It parallels the peers map:
|
||||
// netMap.UserProfiles is the frozen snapshot from the last full install,
|
||||
// while this field reflects incremental updates. Readers that need a
|
||||
// snapshot (e.g. the initial-Notify NetMap or [LocalClient.NetMap]
|
||||
// paths) must clone this map.
|
||||
// snapshot (e.g. [LocalBackend.NetMapWithPeers] callers such as the
|
||||
// c2n and debug netmap handlers) must clone this map.
|
||||
userProfiles map[tailcfg.UserID]tailcfg.UserProfileView
|
||||
|
||||
// packetFilterRules and packetFilter are the live packet filter state,
|
||||
|
||||
@@ -910,10 +910,6 @@ func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// NotifyInitialNetMap is permitted alongside NotifyPeerChanges /
|
||||
// NotifyPeerPatches for backwards compatibility with clients that
|
||||
// set both (e.g. the Apple client). The initial netmap is a one-shot;
|
||||
// peer-change subscribers simply receive deltas after that point.
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
ctx := r.Context()
|
||||
|
||||
+5
-4
@@ -207,10 +207,11 @@ func (cm *CertManager) runCertLoop(ctx context.Context, domain string) {
|
||||
}
|
||||
|
||||
// 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].
|
||||
// only as a wake-up trigger (the initial status and subsequent
|
||||
// Notify.SelfChange messages) 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)
|
||||
w, err := cm.lc.WatchIPNBus(ctx, ipn.NotifyInitialStatus)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error watching IPN bus: %w", err)
|
||||
}
|
||||
@@ -221,7 +222,7 @@ func (cm *CertManager) waitForCertDomain(ctx context.Context, domain string) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n.SelfChange == nil {
|
||||
if n.SelfChange == nil && n.InitialStatus == nil {
|
||||
continue
|
||||
}
|
||||
domains, err := cm.lc.CertDomains(ctx)
|
||||
|
||||
@@ -60,7 +60,7 @@ func (h *Healthz) Update(healthy bool) {
|
||||
}
|
||||
|
||||
func (h *Healthz) MonitorHealth(ctx context.Context, lc *local.Client) error {
|
||||
w, err := lc.WatchIPNBus(ctx, ipn.NotifyInitialNetMap)
|
||||
w, err := lc.WatchIPNBus(ctx, ipn.NotifyInitialStatus)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to watch IPN bus: %w", err)
|
||||
}
|
||||
@@ -73,6 +73,8 @@ func (h *Healthz) MonitorHealth(ctx context.Context, lc *local.Client) error {
|
||||
|
||||
if self := n.SelfChange; self != nil {
|
||||
h.Update(len(self.Addresses) != 0)
|
||||
} else if st := n.InitialStatus; st != nil && st.Self != nil {
|
||||
h.Update(len(st.Self.TailscaleIPs) != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+58
-21
@@ -14,12 +14,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/kube/kubetypes"
|
||||
klc "tailscale.com/kube/localclient"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/deephash"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -44,43 +46,39 @@ 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. 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.
|
||||
// cancelled or it hits an error. It seeds the self node from the initial
|
||||
// status and then watches the IPN bus for SelfChange notifications, which
|
||||
// fire whenever the self node changes.
|
||||
func KeepKeysUpdated(ctx context.Context, store ipn.StateStore, lc klc.LocalClient) error {
|
||||
w, err := lc.WatchIPNBus(ctx, ipn.NotifyInitialNetMap)
|
||||
w, err := lc.WatchIPNBus(ctx, ipn.NotifyInitialStatus)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error watching IPN bus: %w", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
var currentDeviceID, currentDeviceIPs, currentDeviceFQDN deephash.Sum
|
||||
for {
|
||||
n, err := w.Next() // Blocks on a streaming LocalAPI HTTP call.
|
||||
if err != nil {
|
||||
if err == ctx.Err() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
self := n.SelfChange
|
||||
if self == nil {
|
||||
continue
|
||||
}
|
||||
var prevDeviceID tailcfg.StableNodeID
|
||||
var prevFQDN string
|
||||
var prevAddrs []netip.Prefix
|
||||
|
||||
if deviceID := self.StableID; deephash.Update(¤tDeviceID, &deviceID) {
|
||||
// storeSelf writes the device ID, FQDN, and IP state keys derived
|
||||
// from the given self node, skipping any whose value is unchanged
|
||||
// since the last write.
|
||||
storeSelf := func(self tailcfg.NodeView) error {
|
||||
if deviceID := self.StableID(); deviceID != prevDeviceID {
|
||||
if err := store.WriteState(keyDeviceID, []byte(deviceID)); err != nil {
|
||||
return fmt.Errorf("failed to store device ID in state: %w", err)
|
||||
}
|
||||
prevDeviceID = deviceID
|
||||
}
|
||||
|
||||
if fqdn := self.Name; deephash.Update(¤tDeviceFQDN, &fqdn) {
|
||||
if fqdn := self.Name(); fqdn != prevFQDN {
|
||||
if err := store.WriteState(keyDeviceFQDN, []byte(fqdn)); err != nil {
|
||||
return fmt.Errorf("failed to store device FQDN in state: %w", err)
|
||||
}
|
||||
prevFQDN = fqdn
|
||||
}
|
||||
|
||||
if addrs := self.Addresses; deephash.Update(¤tDeviceIPs, &addrs) {
|
||||
if addrs := self.Addresses().AsSlice(); !slices.Equal(addrs, prevAddrs) {
|
||||
var deviceIPs []string
|
||||
for _, addr := range addrs {
|
||||
deviceIPs = append(deviceIPs, addr.Addr().String())
|
||||
@@ -92,6 +90,45 @@ func KeepKeysUpdated(ctx context.Context, store ipn.StateStore, lc klc.LocalClie
|
||||
if err := store.WriteState(keyDeviceIPs, deviceIPsValue); err != nil {
|
||||
return fmt.Errorf("failed to store device IPs in state: %w", err)
|
||||
}
|
||||
prevAddrs = addrs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
n, err := w.Next() // Blocks on a streaming LocalAPI HTTP call.
|
||||
if err != nil {
|
||||
if err == ctx.Err() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var self tailcfg.NodeView
|
||||
switch {
|
||||
case n.SelfChange != nil:
|
||||
self = n.SelfChange.View()
|
||||
case n.InitialStatus != nil && n.InitialStatus.Self != nil:
|
||||
self = selfNodeFromPeerStatus(n.InitialStatus.Self)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if err := storeSelf(self); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// selfNodeFromPeerStatus converts the subset of ps that KeepKeysUpdated
|
||||
// reads into a [tailcfg.NodeView], so the initial status seed and
|
||||
// subsequent [ipn.Notify.SelfChange] updates can share one code path.
|
||||
func selfNodeFromPeerStatus(ps *ipnstate.PeerStatus) tailcfg.NodeView {
|
||||
n := &tailcfg.Node{
|
||||
StableID: ps.ID,
|
||||
Name: ps.DNSName,
|
||||
}
|
||||
for _, ip := range ps.TailscaleIPs {
|
||||
n.Addresses = append(n.Addresses, netip.PrefixFrom(ip, ip.BitLen()))
|
||||
}
|
||||
return n.View()
|
||||
}
|
||||
+1
-1
@@ -1182,7 +1182,7 @@ func setUpServiceState(t *testing.T, name, ip string, host, client *Server,
|
||||
}
|
||||
waitForLatestNetmap := func(t *testing.T, s *Server) {
|
||||
t.Helper()
|
||||
w := must.Get(s.localClient.WatchIPNBus(t.Context(), ipn.NotifyInitialNetMap))
|
||||
w := must.Get(s.localClient.WatchIPNBus(t.Context(), ipn.NotifyInitialStatus))
|
||||
defer w.Close()
|
||||
for {
|
||||
must.Get(w.Next())
|
||||
|
||||
Reference in new issue
Block a user