@ -1191,7 +1191,13 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
prefsChanged := false
prefsChanged := false
prefs := b . pm . CurrentPrefs ( ) . AsStruct ( )
prefs := b . pm . CurrentPrefs ( ) . AsStruct ( )
netMap := b . netMap
oldNetMap := b . netMap
curNetMap := st . NetMap
if curNetMap == nil {
// The status didn't include a netmap update, so the old one is still
// current.
curNetMap = oldNetMap
}
if prefs . ControlURL == "" {
if prefs . ControlURL == "" {
// Once we get a message from the control plane, set
// Once we get a message from the control plane, set
@ -1222,7 +1228,14 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
prefs . WantRunning = true
prefs . WantRunning = true
prefs . LoggedOut = false
prefs . LoggedOut = false
}
}
if setExitNodeID ( prefs , st . NetMap , b . lastSuggestedExitNode ) {
if shouldAutoExitNode ( ) {
// Re-evaluate exit node suggestion in case circumstances have changed.
_ , err := b . suggestExitNodeLocked ( curNetMap )
if err != nil && ! errors . Is ( err , ErrNoPreferredDERP ) {
b . logf ( "SetControlClientStatus failed to select auto exit node: %v" , err )
}
}
if setExitNodeID ( prefs , curNetMap , b . lastSuggestedExitNode ) {
prefsChanged = true
prefsChanged = true
}
}
if applySysPolicy ( prefs ) {
if applySysPolicy ( prefs ) {
@ -1239,8 +1252,8 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
if prefsChanged {
if prefsChanged {
// Prefs will be written out if stale; this is not safe unless locked or cloned.
// Prefs will be written out if stale; this is not safe unless locked or cloned.
if err := b . pm . SetPrefs ( prefs . View ( ) , ipn . NetworkProfile {
if err := b . pm . SetPrefs ( prefs . View ( ) , ipn . NetworkProfile {
MagicDNSName : st. NetMap. MagicDNSSuffix ( ) ,
MagicDNSName : cur NetMap. MagicDNSSuffix ( ) ,
DomainName : st. NetMap. DomainName ( ) ,
DomainName : cur NetMap. DomainName ( ) ,
} ) ; err != nil {
} ) ; err != nil {
b . logf ( "Failed to save new controlclient state: %v" , err )
b . logf ( "Failed to save new controlclient state: %v" , err )
}
}
@ -1307,8 +1320,8 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
b . send ( ipn . Notify { ErrMessage : & msg , Prefs : & p } )
b . send ( ipn . Notify { ErrMessage : & msg , Prefs : & p } )
return
return
}
}
if n etMap != nil {
if oldN etMap != nil {
diff := st . NetMap . ConciseDiffFrom ( n etMap)
diff := st . NetMap . ConciseDiffFrom ( oldN etMap)
if strings . TrimSpace ( diff ) == "" {
if strings . TrimSpace ( diff ) == "" {
b . logf ( "[v1] netmap diff: (none)" )
b . logf ( "[v1] netmap diff: (none)" )
} else {
} else {
@ -4891,7 +4904,7 @@ func (b *LocalBackend) setAutoExitNodeIDLockedOnEntry(unlock unlockOnce) {
return
return
}
}
prefsClone := prefs . AsStruct ( )
prefsClone := prefs . AsStruct ( )
newSuggestion , err := b . suggestExitNodeLocked ( )
newSuggestion , err := b . suggestExitNodeLocked ( nil )
if err != nil {
if err != nil {
b . logf ( "setAutoExitNodeID: %v" , err )
b . logf ( "setAutoExitNodeID: %v" , err )
return
return
@ -6573,7 +6586,6 @@ func mayDeref[T any](p *T) (v T) {
}
}
var ErrNoPreferredDERP = errors . New ( "no preferred DERP, try again later" )
var ErrNoPreferredDERP = errors . New ( "no preferred DERP, try again later" )
var ErrCannotSuggestExitNode = errors . New ( "unable to suggest an exit node, try again later" )
// suggestExitNodeLocked computes a suggestion based on the current netmap and last netcheck report. If
// suggestExitNodeLocked computes a suggestion based on the current netmap and last netcheck report. If
// there are multiple equally good options, one is selected at random, so the result is not stable. To be
// there are multiple equally good options, one is selected at random, so the result is not stable. To be
@ -6582,10 +6594,17 @@ var ErrCannotSuggestExitNode = errors.New("unable to suggest an exit node, try a
// Currently, peers with a DERP home are preferred over those without (typically this means Mullvad).
// Currently, peers with a DERP home are preferred over those without (typically this means Mullvad).
// Peers are selected based on having a DERP home that is the lowest latency to this device. For peers
// Peers are selected based on having a DERP home that is the lowest latency to this device. For peers
// without a DERP home, we look for geographic proximity to this device's DERP home.
// without a DERP home, we look for geographic proximity to this device's DERP home.
//
// netMap is an optional netmap to use that overrides b.netMap (needed for SetControlClientStatus before b.netMap is updated).
// If netMap is nil, then b.netMap is used.
//
// b.mu.lock() must be held.
// b.mu.lock() must be held.
func ( b * LocalBackend ) suggestExitNodeLocked ( ) ( response apitype . ExitNodeSuggestionResponse , err error ) {
func ( b * LocalBackend ) suggestExitNodeLocked ( netMap * netmap . NetworkMap ) ( response apitype . ExitNodeSuggestionResponse , err error ) {
// netMap is an optional netmap to use that overrides b.netMap (needed for SetControlClientStatus before b.netMap is updated). If netMap is nil, then b.netMap is used.
if netMap == nil {
netMap = b . netMap
}
lastReport := b . MagicConn ( ) . GetLastNetcheckReport ( b . ctx )
lastReport := b . MagicConn ( ) . GetLastNetcheckReport ( b . ctx )
netMap := b . netMap
prevSuggestion := b . lastSuggestedExitNode
prevSuggestion := b . lastSuggestedExitNode
res , err := suggestExitNode ( lastReport , netMap , prevSuggestion , randomRegion , randomNode , getAllowedSuggestions ( ) )
res , err := suggestExitNode ( lastReport , netMap , prevSuggestion , randomRegion , randomNode , getAllowedSuggestions ( ) )
@ -6599,7 +6618,7 @@ func (b *LocalBackend) suggestExitNodeLocked() (response apitype.ExitNodeSuggest
func ( b * LocalBackend ) SuggestExitNode ( ) ( response apitype . ExitNodeSuggestionResponse , err error ) {
func ( b * LocalBackend ) SuggestExitNode ( ) ( response apitype . ExitNodeSuggestionResponse , err error ) {
b . mu . Lock ( )
b . mu . Lock ( )
defer b . mu . Unlock ( )
defer b . mu . Unlock ( )
return b . suggestExitNodeLocked ( )
return b . suggestExitNodeLocked ( nil )
}
}
// selectRegionFunc returns a DERP region from the slice of candidate regions.
// selectRegionFunc returns a DERP region from the slice of candidate regions.