Files
tailscale/wgengine/magicsock/derp_test.go
T
Claus Lensbøl 78627c132f wgengine/magicsock,ipn/ipnlocal: store and load homeDERP from cache (#19491)
With netmap caching, the home DERP of the self node was neither saved to
the cache or loaded from it, making nodes not stick to a DERP when
starting without a connection to control.

Instead, make sure that when a cache is available, load that cache,
before looking for DERP servers. This is implemented by allowing a skip
of ReSTUN in setting the DERP map (we must have a DERP map before
setting the home DERP), so the DERP from cache will set itself and be
sticky until a connection to control is established.

Making DERP only change when connected to control is handled by existing
code from f072d017bd.

Updates #19490

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-04-29 10:24:09 -04:00

131 lines
3.2 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package magicsock
import (
"fmt"
"testing"
"tailscale.com/health"
"tailscale.com/net/netcheck"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
"tailscale.com/util/eventbus"
"tailscale.com/util/eventbus/eventbustest"
)
func CheckDERPHeuristicTimes(t *testing.T) {
if netcheck.PreferredDERPFrameTime <= frameReceiveRecordRate {
t.Errorf("PreferredDERPFrameTime too low; should be at least frameReceiveRecordRate")
}
}
func TestForceSetNearestDERP(t *testing.T) {
derpMap := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
7: {
RegionID: 7,
RegionCode: "test",
Nodes: []*tailcfg.DERPNode{
{
Name: "7a",
RegionID: 7,
HostName: "derp7.test.unused",
IPv4: "127.0.0.1",
IPv6: "none",
},
},
},
},
}
// Force the real control health check so we can verify force=true bypasses it.
tstest.Replace(t, &checkControlHealthDuringNearestDERPInTests, true)
bus := eventbustest.NewBus(t)
ht := health.NewTracker(bus)
c := newConn(t.Logf)
ec := bus.Client("magicsock.Conn.Test")
c.eventClient = ec
c.homeDERPChangedPub = eventbus.Publish[HomeDERPChanged](ec)
c.eventBus = bus
c.derpMap = derpMap
c.health = ht
ht.SetOutOfPollNetMap()
tw := eventbustest.NewWatcher(t, bus)
got := c.ForceSetNearestDERP(7)
if got != 7 {
t.Fatalf("ForceSetNearestDERP(7) = %d, want 7", got)
}
if c.myDerp != 7 {
t.Errorf("c.myDerp = %d after ForceSetNearestDERP, want 7", c.myDerp)
}
if err := eventbustest.Expect(tw, func(e HomeDERPChanged) error {
if e.Old != 0 || e.New != 7 {
return fmt.Errorf("got HomeDERPChanged{Old:%d, New:%d}, want {Old:0, New:7}", e.Old, e.New)
}
return nil
}); err != nil {
t.Errorf("expected HomeDERPChanged event: %v", err)
}
}
func TestSetDERPMapDoReStun(t *testing.T) {
derpMap1 := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
1: {
RegionID: 1,
RegionCode: "cph",
Nodes: []*tailcfg.DERPNode{
{Name: "1a", RegionID: 1, HostName: "cph.test.unused", IPv4: "127.0.0.1", IPv6: "none"},
},
},
},
}
derpMap2 := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
2: {
RegionID: 2,
RegionCode: "inc",
Nodes: []*tailcfg.DERPNode{
{Name: "2a", RegionID: 2, HostName: "inc.test.unused", IPv4: "127.0.0.1", IPv6: "none"},
},
},
},
}
var reSTUNCalls int
tstest.Replace(t, &reSTUNHookForTests, func(_ string) {
reSTUNCalls++
})
bus := eventbustest.NewBus(t)
ht := health.NewTracker(bus)
c := newConn(t.Logf)
ec := bus.Client("magicsock.Conn.Test")
c.eventClient = ec
c.homeDERPChangedPub = eventbus.Publish[HomeDERPChanged](ec)
c.eventBus = bus
c.health = ht
// With a zero private key and everHadKey=true, ReSTUN returns early without
// spawning updateEndpoints.
c.everHadKey = true
// Should not trigger a ReSTUN.
c.SetDERPMap(derpMap1, false)
if reSTUNCalls != 0 {
t.Errorf("SetDERPMap(dm, doReStun=false): got %d ReSTUN calls, want 0", reSTUNCalls)
}
// doReStun=true: should trigger a ReSTUN.
c.SetDERPMap(derpMap2, true)
if reSTUNCalls != 1 {
t.Errorf("SetDERPMap(dm, doReStun=true): got %d ReSTUN calls, want 1", reSTUNCalls)
}
}