@ -4436,6 +4436,14 @@ func deterministicRegionForTest(t testing.TB, want views.Slice[int], use int) se
}
}
}
}
// deterministicNodeForTest returns a deterministic selectNodeFunc, which
// allows us to make stable assertions about which exit node will be chosen
// from a list of possible candidates.
//
// When given a list of candidates, it checks that `use` is in the list and
// returns that.
//
// It verifies that `wantLast` was passed to `selectNode(…, want)`.
func deterministicNodeForTest ( t testing . TB , want views . Slice [ tailcfg . StableNodeID ] , wantLast tailcfg . StableNodeID , use tailcfg . StableNodeID ) selectNodeFunc {
func deterministicNodeForTest ( t testing . TB , want views . Slice [ tailcfg . StableNodeID ] , wantLast tailcfg . StableNodeID , use tailcfg . StableNodeID ) selectNodeFunc {
t . Helper ( )
t . Helper ( )
@ -4444,6 +4452,16 @@ func deterministicNodeForTest(t testing.TB, want views.Slice[tailcfg.StableNodeI
}
}
return func ( got views . Slice [ tailcfg . NodeView ] , last tailcfg . StableNodeID ) tailcfg . NodeView {
return func ( got views . Slice [ tailcfg . NodeView ] , last tailcfg . StableNodeID ) tailcfg . NodeView {
// In the tests, we choose nodes deterministically so we can get
// stable results, but in the real code, we choose nodes randomly.
//
// Call the randomNode function anyway, and ensure it returns
// a sensible result.
view := randomNode ( got , last )
if ! views . SliceContains ( got , view ) {
t . Fatalf ( "randomNode returns an unexpected node" )
}
var ret tailcfg . NodeView
var ret tailcfg . NodeView
gotIDs := make ( [ ] tailcfg . StableNodeID , got . Len ( ) )
gotIDs := make ( [ ] tailcfg . StableNodeID , got . Len ( ) )
@ -4529,6 +4547,7 @@ func TestSuggestExitNode(t *testing.T) {
Longitude : - 97.3325 ,
Longitude : - 97.3325 ,
Priority : 100 ,
Priority : 100 ,
}
}
var emptyLocation * tailcfg . Location
peer1 := makePeer ( 1 ,
peer1 := makePeer ( 1 ,
withExitRoutes ( ) ,
withExitRoutes ( ) ,
@ -4568,6 +4587,18 @@ func TestSuggestExitNode(t *testing.T) {
withExitRoutes ( ) ,
withExitRoutes ( ) ,
withSuggest ( ) ,
withSuggest ( ) ,
withLocation ( fortWorthLowPriority . View ( ) ) )
withLocation ( fortWorthLowPriority . View ( ) ) )
emptyLocationPeer9 := makePeer ( 9 ,
withoutDERP ( ) ,
withExitRoutes ( ) ,
withSuggest ( ) ,
withLocation ( emptyLocation . View ( ) ) ,
)
emptyLocationPeer10 := makePeer ( 10 ,
withoutDERP ( ) ,
withExitRoutes ( ) ,
withSuggest ( ) ,
withLocation ( emptyLocation . View ( ) ) ,
)
selfNode := tailcfg . Node {
selfNode := tailcfg . Node {
Addresses : [ ] netip . Prefix {
Addresses : [ ] netip . Prefix {
@ -4898,6 +4929,31 @@ func TestSuggestExitNode(t *testing.T) {
wantName : "San Jose" ,
wantName : "San Jose" ,
wantLocation : sanJose . View ( ) ,
wantLocation : sanJose . View ( ) ,
} ,
} ,
{
// Regression test for https://github.com/tailscale/tailscale/issues/17661
name : "exit nodes with no home DERP, randomly selected" ,
lastReport : & netcheck . Report {
RegionLatency : map [ int ] time . Duration {
1 : 10 ,
2 : 20 ,
3 : 10 ,
} ,
PreferredDERP : 1 ,
} ,
netMap : & netmap . NetworkMap {
SelfNode : selfNode . View ( ) ,
DERPMap : defaultDERPMap ,
Peers : [ ] tailcfg . NodeView {
emptyLocationPeer9 ,
emptyLocationPeer10 ,
} ,
} ,
wantRegions : [ ] int { 1 , 2 } ,
wantName : "peer9" ,
wantNodes : [ ] tailcfg . StableNodeID { "stable9" , "stable10" } ,
wantID : "stable9" ,
useRegion : 1 ,
} ,
}
}
for _ , tt := range tests {
for _ , tt := range tests {