mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-30 03:25:06 -04:00
Some netmap updates are guaranteed to affect only the "static" parts of the netmap, and so should not require us to walk through all the peers and user profiles when updating the cache. To support this, the new UpdateSelfOnly method updates only the Self node and other tailnet settings that are not dependent on the peers and profiles. Use this when updating the cache on DERP home changes. Updates #12542 Change-Id: Ifed522b29d579fb76e010b4ff738cc4e0a72d27f Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
230 lines
6.4 KiB
Go
230 lines
6.4 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package ipnlocal
|
|
|
|
import (
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/tstest"
|
|
"tailscale.com/types/netmap"
|
|
"tailscale.com/util/eventbus"
|
|
"tailscale.com/wgengine/magicsock"
|
|
)
|
|
|
|
// newCacheTestNetmap returns a minimal valid netmap suitable for testing disk
|
|
// cache operations.
|
|
func newCacheTestNetmap() *netmap.NetworkMap {
|
|
return &netmap.NetworkMap{
|
|
SelfNode: (&tailcfg.Node{
|
|
Name: "test-node.ts.net",
|
|
User: tailcfg.UserID(1),
|
|
Addresses: []netip.Prefix{
|
|
netip.MustParsePrefix("100.64.0.1/32"),
|
|
},
|
|
}).View(),
|
|
UserProfiles: map[tailcfg.UserID]tailcfg.UserProfileView{
|
|
tailcfg.UserID(1): (&tailcfg.UserProfile{
|
|
LoginName: "user@example.com",
|
|
DisplayName: "Test User",
|
|
}).View(),
|
|
},
|
|
DERPMap: &tailcfg.DERPMap{
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
1: {},
|
|
2: {},
|
|
3: {},
|
|
4: {},
|
|
5: {},
|
|
6: {},
|
|
7: {},
|
|
8: {},
|
|
9: {},
|
|
10: {},
|
|
11: {},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestWriteAndLoadHomeDERP(t *testing.T) {
|
|
b := newTestBackend(t)
|
|
|
|
nm := newCacheTestNetmap()
|
|
b.currentNode().SetNetMap(nm)
|
|
|
|
const wantDERP = 7
|
|
b.currentNode().homeDERP.Store(wantDERP)
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if err := b.writeNetmapToDiskLockedWithPeers(nm); err != nil {
|
|
t.Fatalf("writeNetmapToDiskLocked: %v", err)
|
|
}
|
|
|
|
loaded, ok := b.loadDiskCacheLocked()
|
|
if !ok {
|
|
t.Fatal("loadDiskCacheLocked returned ok=false")
|
|
}
|
|
if !loaded.SelfNode.Valid() {
|
|
t.Fatal("loaded netmap SelfNode is invalid")
|
|
}
|
|
if got := loaded.SelfNode.HomeDERP(); got != wantDERP {
|
|
t.Errorf("loaded SelfNode.HomeDERP() = %d, want %d", got, wantDERP)
|
|
}
|
|
}
|
|
|
|
func TestOnHomeDERPUpdate(t *testing.T) {
|
|
t.Run("normal_derp_change", func(t *testing.T) {
|
|
b := newTestBackend(t)
|
|
done := make(chan struct{})
|
|
tstest.Replace(t, &testOnlyHomeDERPUpdate, func() { close(done) })
|
|
|
|
nm := newCacheTestNetmap()
|
|
b.currentNode().SetNetMap(nm)
|
|
|
|
// Publish a HomeDERPChanged event via the backend's event bus.
|
|
bus := b.Sys().Bus.Get()
|
|
ec := bus.Client("test.TestOnHomeDERPUpdate")
|
|
pub := eventbus.Publish[magicsock.HomeDERPChanged](ec)
|
|
|
|
const wantDERP = 11
|
|
pub.Publish(magicsock.HomeDERPChanged{Old: 0, New: wantDERP})
|
|
<-done
|
|
|
|
if got := b.currentNode().homeDERP.Load(); got != wantDERP {
|
|
t.Errorf("b.homeDERP = %d, want %d", got, wantDERP)
|
|
}
|
|
|
|
// Verify the value was persisted to the disk cache.
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
loaded, ok := b.loadDiskCacheLocked()
|
|
if !ok {
|
|
t.Fatal("loadDiskCacheLocked returned ok=false after homeDERP update")
|
|
}
|
|
if got := loaded.SelfNode.HomeDERP(); got != wantDERP {
|
|
t.Errorf("cached SelfNode.HomeDERP() = %d, want %d", got, wantDERP)
|
|
}
|
|
})
|
|
t.Run("old_does_not_match", func(t *testing.T) {
|
|
b := newTestBackend(t)
|
|
done := make(chan struct{})
|
|
tstest.Replace(t, &testOnlyHomeDERPUpdate, func() { close(done) })
|
|
|
|
const setDERP = 11
|
|
const wantDERP = 4
|
|
|
|
nm := newCacheTestNetmap()
|
|
selfNode := nm.SelfNode.AsStruct()
|
|
selfNode.HomeDERP = wantDERP
|
|
nm.SelfNode = selfNode.View()
|
|
b.currentNode().SetNetMap(nm)
|
|
b.currentNode().homeDERP.Store(wantDERP)
|
|
|
|
// Write an initial cache entry so we can verify it is not overwritten.
|
|
b.mu.Lock()
|
|
if err := b.writeNetmapToDiskLockedWithPeers(nm); err != nil {
|
|
b.mu.Unlock()
|
|
t.Fatalf("setup writeNetmapToDiskLocked: %v", err)
|
|
}
|
|
b.mu.Unlock()
|
|
|
|
// Publish a HomeDERPChanged event via the backend's event bus.
|
|
bus := b.Sys().Bus.Get()
|
|
ec := bus.Client("test.TestOnHomeDERPUpdate")
|
|
pub := eventbus.Publish[magicsock.HomeDERPChanged](ec)
|
|
pub.Publish(magicsock.HomeDERPChanged{Old: wantDERP + 1, New: setDERP})
|
|
<-done
|
|
|
|
if got := b.currentNode().homeDERP.Load(); got != wantDERP {
|
|
t.Errorf("b.homeDERP = %d, wanted no change %d", got, wantDERP)
|
|
}
|
|
|
|
// Verify the cache still exists and still holds the original value.
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
loaded, ok := b.loadDiskCacheLocked()
|
|
if !ok {
|
|
t.Fatal("loadDiskCacheLocked returned ok=false; expected cache to still exist")
|
|
}
|
|
if got := loaded.SelfNode.HomeDERP(); got != wantDERP {
|
|
t.Errorf("cached SelfNode.HomeDERP() = %d after rejected event, want original %d", got, wantDERP)
|
|
}
|
|
})
|
|
t.Run("new_does_not_exist_in_map", func(t *testing.T) {
|
|
b := newTestBackend(t)
|
|
done := make(chan struct{})
|
|
tstest.Replace(t, &testOnlyHomeDERPUpdate, func() { close(done) })
|
|
|
|
const setDERP = 111
|
|
const wantDERP = 4
|
|
|
|
nm := newCacheTestNetmap()
|
|
selfNode := nm.SelfNode.AsStruct()
|
|
selfNode.HomeDERP = wantDERP
|
|
nm.SelfNode = selfNode.View()
|
|
b.currentNode().SetNetMap(nm)
|
|
b.currentNode().homeDERP.Store(wantDERP)
|
|
|
|
// Write an initial cache entry so we can verify it is not overwritten.
|
|
b.mu.Lock()
|
|
if err := b.writeNetmapToDiskLockedWithPeers(nm); err != nil {
|
|
b.mu.Unlock()
|
|
t.Fatalf("setup writeNetmapToDiskLocked: %v", err)
|
|
}
|
|
b.mu.Unlock()
|
|
|
|
// Publish a HomeDERPChanged event via the backend's event bus.
|
|
// Old matches the stored homeDERP so only the "new region not in map"
|
|
// guard is exercised.
|
|
bus := b.Sys().Bus.Get()
|
|
ec := bus.Client("test.TestOnHomeDERPUpdate")
|
|
pub := eventbus.Publish[magicsock.HomeDERPChanged](ec)
|
|
pub.Publish(magicsock.HomeDERPChanged{Old: wantDERP, New: setDERP})
|
|
<-done
|
|
|
|
if got := b.currentNode().homeDERP.Load(); got != wantDERP {
|
|
t.Errorf("b.homeDERP = %d, wanted no change %d", got, wantDERP)
|
|
}
|
|
|
|
// Verify the cache still exists and still holds the original value.
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
loaded, ok := b.loadDiskCacheLocked()
|
|
if !ok {
|
|
t.Fatal("loadDiskCacheLocked returned ok=false; expected cache to still exist")
|
|
}
|
|
if got := loaded.SelfNode.HomeDERP(); got != wantDERP {
|
|
t.Errorf("cached SelfNode.HomeDERP() = %d after rejected event, want original %d", got, wantDERP)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWriteNetmapDoesNotMutateOriginal(t *testing.T) {
|
|
b := newTestBackend(t)
|
|
|
|
nm := newCacheTestNetmap()
|
|
b.currentNode().SetNetMap(nm)
|
|
|
|
originalDERP := nm.SelfNode.HomeDERP() // expected to be 0 initially
|
|
|
|
const storeDERP = 5
|
|
b.currentNode().homeDERP.Store(storeDERP)
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if err := b.writeNetmapToDiskLockedWithPeers(nm); err != nil {
|
|
t.Fatalf("writeNetmapToDiskLocked: %v", err)
|
|
}
|
|
|
|
// The original netmap must not have been mutated.
|
|
if got := nm.SelfNode.HomeDERP(); got != originalDERP {
|
|
t.Errorf("original nm.SelfNode.HomeDERP() = %d after write, want %d (original was mutated)", got, originalDERP)
|
|
}
|
|
}
|