ipn/ipnlocal: refactor resolveExitNodeInPrefsLocked, setExitNodeID and resolveExitNodeIP

Now that resolveExitNodeInPrefsLocked is the only caller of setExitNodeID,
and setExitNodeID is the only caller of resolveExitNodeIP, we can restructure
the code with resolveExitNodeInPrefsLocked now calling both
resolveAutoExitNodeLocked and resolveExitNodeIPLocked directly.

This prepares for factoring out resolveAutoExitNodeLocked and related
auto-exit-node logic into an ipnext extension in a future commit.

While there, we also update exit node by IP lookup to use (*nodeBackend).NodeByAddr
and (*nodeBackend).NodeByID instead of iterating over all peers in the most recent netmap.

Updates tailscale/corp#29969

Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
Nick Khyl 2025-07-07 19:02:10 -05:00 committed by Nick Khyl
parent 1fe82d6ef5
commit 9bf99741dd

View File

@ -2030,14 +2030,21 @@ func mutationsAreWorthyOfTellingIPNBus(muts []netmap.NodeMutation) bool {
return false return false
} }
// setExitNodeID updates prefs to either use the suggestedExitNodeID if AutoExitNode is enabled, // resolveAutoExitNodeLocked computes a suggested exit node and updates prefs
// or resolve ExitNodeIP to an ID and use that. It returns whether prefs was mutated. // to use it if AutoExitNode is enabled, and reports whether prefs was mutated.
func setExitNodeID(prefs *ipn.Prefs, suggestedExitNodeID tailcfg.StableNodeID, nm *netmap.NetworkMap) (prefsChanged bool) { //
if prefs.AutoExitNode.IsSet() { // b.mu must be held.
func (b *LocalBackend) resolveAutoExitNodeLocked(prefs *ipn.Prefs) (prefsChanged bool) {
if !prefs.AutoExitNode.IsSet() {
return false
}
if _, err := b.suggestExitNodeLocked(); err != nil && !errors.Is(err, ErrNoPreferredDERP) {
b.logf("failed to select auto exit node: %v", err) // non-fatal, see below
}
var newExitNodeID tailcfg.StableNodeID var newExitNodeID tailcfg.StableNodeID
if !suggestedExitNodeID.IsZero() { if !b.lastSuggestedExitNode.IsZero() {
// If we have a suggested exit node, use it. // If we have a suggested exit node, use it.
newExitNodeID = suggestedExitNodeID newExitNodeID = b.lastSuggestedExitNode
} else if isAllowedAutoExitNodeID(prefs.ExitNodeID) { } else if isAllowedAutoExitNodeID(prefs.ExitNodeID) {
// If we don't have a suggested exit node, but the prefs already // If we don't have a suggested exit node, but the prefs already
// specify an allowed auto exit node ID, retain it. // specify an allowed auto exit node ID, retain it.
@ -2057,20 +2064,14 @@ func setExitNodeID(prefs *ipn.Prefs, suggestedExitNodeID tailcfg.StableNodeID, n
prefsChanged = true prefsChanged = true
} }
return prefsChanged return prefsChanged
}
return resolveExitNodeIP(prefs, nm)
} }
// resolveExitNodeIP updates prefs to reference an exit node by ID, rather // resolveExitNodeIPLocked updates prefs to reference an exit node by ID, rather
// than by IP. It returns whether prefs was mutated. // than by IP. It returns whether prefs was mutated.
func resolveExitNodeIP(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool) { //
if nm == nil { // b.mu must be held.
// No netmap, can't resolve anything. func (b *LocalBackend) resolveExitNodeIPLocked(prefs *ipn.Prefs) (prefsChanged bool) {
return false // If we have a desired IP on file, try to find the corresponding node.
}
// If we have a desired IP on file, try to find the corresponding
// node.
if !prefs.ExitNodeIP.IsValid() { if !prefs.ExitNodeIP.IsValid() {
return false return false
} }
@ -2081,20 +2082,19 @@ func resolveExitNodeIP(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bo
prefsChanged = true prefsChanged = true
} }
oldExitNodeID := prefs.ExitNodeID cn := b.currentNode()
for _, peer := range nm.Peers { if nid, ok := cn.NodeByAddr(prefs.ExitNodeIP); ok {
for _, addr := range peer.Addresses().All() { if node, ok := cn.NodeByID(nid); ok {
if !addr.IsSingleIP() || addr.Addr() != prefs.ExitNodeIP {
continue
}
// Found the node being referenced, upgrade prefs to // Found the node being referenced, upgrade prefs to
// reference it directly for next time. // reference it directly for next time.
prefs.ExitNodeID = peer.StableID() prefs.ExitNodeID = node.StableID()
prefs.ExitNodeIP = netip.Addr{} prefs.ExitNodeIP = netip.Addr{}
return prefsChanged || oldExitNodeID != prefs.ExitNodeID // Cleared ExitNodeIP, so prefs changed
} // even if the ID stayed the same.
} prefsChanged = true
}
}
return prefsChanged return prefsChanged
} }
@ -6042,17 +6042,13 @@ func (b *LocalBackend) reconcilePrefsLocked(prefs *ipn.Prefs) (changed bool) {
// //
// b.mu must be held. // b.mu must be held.
func (b *LocalBackend) resolveExitNodeInPrefsLocked(prefs *ipn.Prefs) (changed bool) { func (b *LocalBackend) resolveExitNodeInPrefsLocked(prefs *ipn.Prefs) (changed bool) {
if prefs.AutoExitNode.IsSet() { if b.resolveAutoExitNodeLocked(prefs) {
_, err := b.suggestExitNodeLocked() changed = true
if err != nil && !errors.Is(err, ErrNoPreferredDERP) {
b.logf("failed to select auto exit node: %v", err)
} }
if b.resolveExitNodeIPLocked(prefs) {
changed = true
} }
if setExitNodeID(prefs, b.lastSuggestedExitNode, b.currentNode().NetMap()) { return changed
b.logf("exit node resolved: %v", prefs.ExitNodeID)
return true
}
return false
} }
// setNetMapLocked updates the LocalBackend state to reflect the newly // setNetMapLocked updates the LocalBackend state to reflect the newly