summaryrefslogtreecommitdiffhomepage
path: root/ipn/ipnlocal
diff options
context:
space:
mode:
Diffstat (limited to 'ipn/ipnlocal')
-rw-r--r--ipn/ipnlocal/diskcache.go14
-rw-r--r--ipn/ipnlocal/diskcache_test.go120
-rw-r--r--ipn/ipnlocal/local.go30
-rw-r--r--ipn/ipnlocal/node_backend.go7
4 files changed, 166 insertions, 5 deletions
diff --git a/ipn/ipnlocal/diskcache.go b/ipn/ipnlocal/diskcache.go
index 03ced7967..3235869e6 100644
--- a/ipn/ipnlocal/diskcache.go
+++ b/ipn/ipnlocal/diskcache.go
@@ -35,7 +35,19 @@ func (b *LocalBackend) writeNetmapToDiskLocked(nm *netmap.NetworkMap) error {
b.diskCache.cache = netmapcache.NewCache(netmapcache.FileStore(dir))
b.diskCache.dir = dir
}
- return b.diskCache.cache.Store(b.currentNode().Context(), nm)
+
+ // Set the homeDERP on the self node before saving. The self node homeDERP is
+ // generally not used since the homeDERP for self is stored in magicsock, but
+ // to be able to load it during loading the cache, we use the existing field
+ // to save it.
+
+ // Make a shallow copy and mutate a copy of the selfNode.
+ nmCopy := *nm
+ selfNode := nm.SelfNode.AsStruct()
+ selfNode.HomeDERP = int(b.currentNode().homeDERP.Load())
+ nmCopy.SelfNode = selfNode.View()
+
+ return b.diskCache.cache.Store(b.currentNode().Context(), &nmCopy)
}
func (b *LocalBackend) loadDiskCacheLocked() (om *netmap.NetworkMap, ok bool) {
diff --git a/ipn/ipnlocal/diskcache_test.go b/ipn/ipnlocal/diskcache_test.go
new file mode 100644
index 000000000..0b064b8ab
--- /dev/null
+++ b/ipn/ipnlocal/diskcache_test.go
@@ -0,0 +1,120 @@
+// 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(),
+ },
+ }
+}
+
+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.writeNetmapToDiskLocked(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) {
+ b := newTestBackend(t)
+ done := make(chan struct{})
+ tstest.Replace(t, &testOnlyHomeDERPUpdate, func() { close(done) })
+
+ nm := newCacheTestNetmap()
+ b.currentNode().SetNetMap(nm)
+
+ // Publish a NewHomeDERP event via the backend's event bus.
+ bus := b.Sys().Bus.Get()
+ ec := bus.Client("test.TestOnHomeDERPUpdate")
+ pub := eventbus.Publish[magicsock.NewHomeDERP](ec)
+
+ const wantDERP = 11
+ pub.Publish(magicsock.NewHomeDERP{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)
+ }
+}
+
+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.writeNetmapToDiskLocked(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)
+ }
+}
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index 5d8b210f0..8c60abd4f 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -627,6 +627,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
}
eventbus.SubscribeFunc(ec, b.onAppConnectorRouteUpdate)
eventbus.SubscribeFunc(ec, b.onAppConnectorStoreRoutes)
+ eventbus.SubscribeFunc(ec, b.onHomeDERPUpdate)
mConn.SetNetInfoCallback(b.setNetInfo) // TODO(tailscale/tailscale#17887): move to eventbus
return b, nil
@@ -658,6 +659,20 @@ func (b *LocalBackend) onAppConnectorStoreRoutes(ri appctype.RouteInfo) {
}
}
+// testOnlyHomeDERPUpdate if non-nil is called after setting home DERP and
+// writing netmap to disk.
+var testOnlyHomeDERPUpdate func()
+
+func (b *LocalBackend) onHomeDERPUpdate(du magicsock.NewHomeDERP) {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+ b.currentNode().homeDERP.Store(int64(du.New))
+ b.writeNetmapToDiskLocked(b.NetMap())
+ if testOnlyHomeDERPUpdate != nil {
+ testOnlyHomeDERPUpdate()
+ }
+}
+
func (b *LocalBackend) Clock() tstime.Clock { return b.clock }
func (b *LocalBackend) Sys() *tsd.System { return b.sys }
@@ -1821,7 +1836,15 @@ func (b *LocalBackend) setControlClientStatusLocked(c controlclient.Client, st c
}
b.e.SetNetworkMap(st.NetMap)
- b.MagicConn().SetDERPMap(st.NetMap.DERPMap)
+ b.MagicConn().SetDERPMap(st.NetMap.DERPMap, false)
+ if c == nil && st.NetMap.Cached && st.NetMap.SelfNode.Valid() {
+ // Loading from a cached netmap (c == nil means no live control
+ // client). Pre-seed the home DERP from the cached self node so
+ // that the guard in maybeSetNearestDERP prevents changing the
+ // DERP home before we reconnect to the control plane.
+ b.health.SetOutOfPollNetMap()
+ b.MagicConn().ForceSetNearestDERP(st.NetMap.SelfNode.HomeDERP())
+ }
b.MagicConn().SetOnlyTCP443(st.NetMap.HasCap(tailcfg.NodeAttrOnlyTCP443))
// Update our cached DERP map
@@ -3380,7 +3403,7 @@ func (b *LocalBackend) DebugForceNetmapUpdate() {
nm := b.currentNode().NetMap()
b.e.SetNetworkMap(nm)
if nm != nil {
- b.MagicConn().SetDERPMap(nm.DERPMap)
+ b.MagicConn().SetDERPMap(nm.DERPMap, true)
}
b.setNetMapLocked(nm)
}
@@ -4837,7 +4860,7 @@ func (b *LocalBackend) setPrefsLocked(newp *ipn.Prefs) ipn.PrefsView {
}
if netMap != nil {
- b.MagicConn().SetDERPMap(netMap.DERPMap)
+ b.MagicConn().SetDERPMap(netMap.DERPMap, true)
}
if !oldp.WantRunning() && newp.WantRunning && cc != nil {
@@ -5199,7 +5222,6 @@ func (b *LocalBackend) authReconfig() {
//
// b.mu must be held.
func (b *LocalBackend) authReconfigLocked() {
-
if b.shutdownCalled {
b.logf("[v1] authReconfig: skipping because in shutdown")
return
diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go
index 6c5db0e0d..48b5dfa3b 100644
--- a/ipn/ipnlocal/node_backend.go
+++ b/ipn/ipnlocal/node_backend.go
@@ -79,6 +79,13 @@ type nodeBackend struct {
eventClient *eventbus.Client
derpMapViewPub *eventbus.Publisher[tailcfg.DERPMapView]
+ // homeDERP lives here temporarily. as long as mapSession is short lived, we
+ // don't have a location delivering netmaps to local backend that knows our
+ // homeDERP hence why it is cached here for now.
+ // TODO(cmol): move this field into a refactorred mapSession that is not
+ // short lived.
+ homeDERP atomic.Int64
+
// TODO(nickkhyl): maybe use sync.RWMutex?
mu syncs.Mutex // protects the following fields