ipn/ipnext, feature/routecheck: subscribe routecheck to the IPN bus

This patch adds a new ipnext.NotifyWatcher interface that exposes
ipn.LocalBackend.WatchNotifications so that extensions inside
tailscaled can subscribe to the IPN bus, much like how the GUI
clients subscribe to it through the Local API.

This interface is used by the new feature/routecheck.RouterTracker to
watch for changes in the peer map that affect routers. RouterTracker
uses dead reckoning to incrementally maintain the set of routers. We
do this to avoid looping over the peer map repeatedly. See #17366.

RouterTracker supports two hooks:

- OnNetMapAvailable signals that the initial netmap has been received,
  so that the routecheck.Client can wake up goroutines that are
  waiting for it.

- OnRoutersChange signals that the set of routers has changed, so that
  the routecheck.Client can decide to probe a subset of the routers
  instead of all of them. Currently, this optimization hasn’t been
  implemented yet.

Updates #17366
Updates #20062
Updates tailscale/corp#33033

Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Signed-off-by: Simon Law <sfllaw@tailscale.com>
This commit is contained in:
Simon Law
2026-07-02 20:26:27 -07:00
committed by Simon Law
co-authored by Brad Fitzpatrick
parent 8d830599b1
commit d8ee47d1cf
6 changed files with 605 additions and 10 deletions
+2 -2
View File
@@ -138,8 +138,8 @@ func NewClient(logf logger.Logf, nb NodeBackender, nm NetMapper, pinger Pinger)
// NotifyNetMapAvailable wakes up goroutines that have been waiting for the
// non-nil network map that the control plane sends after reconnecting.
func (c *Client) NotifyNetMapAvailable(nm *netmap.NetworkMap) {
if nm == nil {
func (c *Client) NotifyNetMapAvailable() {
if nm := c.nm.NetMapNoPeers(); nm == nil {
return // client disconnected
}
var nextCh *chan struct{}
+1 -1
View File
@@ -153,7 +153,7 @@ func TestRefresh(t *testing.T) {
if tt.init {
// This callback simulates the delay between
// connecting to the backend and receiving the NetMap.
donef := func() { c.NotifyNetMapAvailable(b.NetMapWithPeers()) }
donef := func() { c.NotifyNetMapAvailable() }
b.donef.Store(&donef)
}