ipn/local: add the suggested exit node to the ipn bus (#16748)
fixes tailscale/corp#26369 The suggested exit node is currently only calculated during a localAPI request. For older UIs, this wasn't a bad choice - we could just fetch it on-demand when a menu presented itself. For newer incarnations however, this is an always-visible field that needs to react to changes in the suggested exit node's value. This change recalculates the suggested exit node ID on netmap updates and broadcasts it on the IPN bus. The localAPI version of this remains intact for the time being. Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
This commit is contained in:
+2
-1
@@ -156,5 +156,6 @@ func isNotableNotify(n *ipn.Notify) bool {
|
||||
n.Health != nil ||
|
||||
len(n.IncomingFiles) > 0 ||
|
||||
len(n.OutgoingFiles) > 0 ||
|
||||
n.FilesWaiting != nil
|
||||
n.FilesWaiting != nil ||
|
||||
n.SuggestedExitNode != nil
|
||||
}
|
||||
|
||||
+56
-1
@@ -1737,6 +1737,10 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
|
||||
|
||||
b.send(ipn.Notify{NetMap: st.NetMap})
|
||||
|
||||
// The error here is unimportant as is the result. This will recalculate the suggested exit node
|
||||
// cache the value and push any changes to the IPN bus.
|
||||
b.SuggestExitNode()
|
||||
|
||||
// Check and update the exit node if needed, now that we have a new netmap.
|
||||
//
|
||||
// This must happen after the netmap change is sent via [ipn.Notify],
|
||||
@@ -2033,7 +2037,13 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
||||
}
|
||||
}
|
||||
|
||||
if cn.NetMap() != nil && mutationsAreWorthyOfRecalculatingSuggestedExitNode(muts, cn, b.lastSuggestedExitNode) {
|
||||
// Recompute the suggested exit node
|
||||
b.suggestExitNodeLocked()
|
||||
}
|
||||
|
||||
if cn.NetMap() != nil && mutationsAreWorthyOfTellingIPNBus(muts) {
|
||||
|
||||
nm := cn.netMapWithPeers()
|
||||
notify = &ipn.Notify{NetMap: nm}
|
||||
} else if testenv.InTest() {
|
||||
@@ -2045,6 +2055,41 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
||||
return true
|
||||
}
|
||||
|
||||
// mustationsAreWorthyOfRecalculatingSuggestedExitNode reports whether any mutation type in muts is
|
||||
// worthy of recalculating the suggested exit node.
|
||||
func mutationsAreWorthyOfRecalculatingSuggestedExitNode(muts []netmap.NodeMutation, cn *nodeBackend, sid tailcfg.StableNodeID) bool {
|
||||
for _, m := range muts {
|
||||
n, ok := cn.NodeByID(m.NodeIDBeingMutated())
|
||||
if !ok {
|
||||
// The node being mutated is not in the netmap.
|
||||
continue
|
||||
}
|
||||
|
||||
// The previously suggested exit node itself is being mutated.
|
||||
if sid != "" && n.StableID() == sid {
|
||||
return true
|
||||
}
|
||||
|
||||
allowed := n.AllowedIPs().AsSlice()
|
||||
isExitNode := slices.Contains(allowed, tsaddr.AllIPv4()) || slices.Contains(allowed, tsaddr.AllIPv6())
|
||||
// The node being mutated is not an exit node. We don't care about it - unless
|
||||
// it was our previously suggested exit node which we catch above.
|
||||
if !isExitNode {
|
||||
continue
|
||||
}
|
||||
|
||||
// Some exit node is being mutated. We care about it if it's online
|
||||
// or offline state has changed. We *might* eventually care about it for other reasons
|
||||
// but for the sake of finding a "better" suggested exit node, this is probably
|
||||
// sufficient.
|
||||
switch m.(type) {
|
||||
case netmap.NodeMutationOnline:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// mutationsAreWorthyOfTellingIPNBus reports whether any mutation type in muts is
|
||||
// worthy of spamming the IPN bus (the Windows & Mac GUIs, basically) to tell them
|
||||
// about the update.
|
||||
@@ -3063,7 +3108,7 @@ func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauth.A
|
||||
|
||||
b.mu.Lock()
|
||||
|
||||
const initialBits = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap | ipn.NotifyInitialDriveShares
|
||||
const initialBits = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap | ipn.NotifyInitialDriveShares | ipn.NotifyInitialSuggestedExitNode
|
||||
if mask&initialBits != 0 {
|
||||
cn := b.currentNode()
|
||||
ini = &ipn.Notify{Version: version.Long()}
|
||||
@@ -3086,6 +3131,11 @@ func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauth.A
|
||||
if mask&ipn.NotifyInitialHealthState != 0 {
|
||||
ini.Health = b.HealthTracker().CurrentState()
|
||||
}
|
||||
if mask&ipn.NotifyInitialSuggestedExitNode != 0 {
|
||||
if en, err := b.SuggestExitNode(); err != nil {
|
||||
ini.SuggestedExitNode = &en.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
@@ -7716,7 +7766,12 @@ func (b *LocalBackend) suggestExitNodeLocked() (response apitype.ExitNodeSuggest
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
if prevSuggestion != res.ID {
|
||||
// Notify the clients via the IPN bus if the exit node suggestion has changed.
|
||||
b.sendToLocked(ipn.Notify{SuggestedExitNode: &res.ID}, allClients)
|
||||
}
|
||||
b.lastSuggestedExitNode = res.ID
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user