mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-29 19:18:57 -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>
149 lines
4.7 KiB
Go
149 lines
4.7 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package ipnlocal
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"tailscale.com/feature/buildfeatures"
|
|
"tailscale.com/ipn/ipnlocal/netmapcache"
|
|
"tailscale.com/types/netmap"
|
|
)
|
|
|
|
// diskCache is the state netmap caching to disk.
|
|
type diskCache struct {
|
|
// all fields guarded by LocalBackend.mu
|
|
|
|
dir string // active profile cache directory
|
|
cache *netmapcache.Cache
|
|
}
|
|
|
|
// writeNetmapToDiskLockedWithoutPeers updates nm in the cache, excluding peers and profiles.
|
|
func (b *LocalBackend) writeNetmapToDiskLockedWithoutPeers(nm *netmap.NetworkMap) error {
|
|
if !buildfeatures.HasCacheNetMap || nm == nil || nm.Cached {
|
|
return nil
|
|
} else if err := b.ensureDiskCacheLocked(); err != nil {
|
|
return err
|
|
}
|
|
b.logf("updating netmap in disk cache")
|
|
return b.diskCache.cache.UpdateSelfOnly(b.currentNode().Context(), b.patchNetmapHomeDERPLocked(nm))
|
|
}
|
|
|
|
// writeNetmapToDiskLockedWithPeers writes nm into the cache, including peers and profiles.
|
|
func (b *LocalBackend) writeNetmapToDiskLockedWithPeers(nm *netmap.NetworkMap) error {
|
|
if !buildfeatures.HasCacheNetMap || nm == nil || nm.Cached {
|
|
return nil
|
|
} else if err := b.ensureDiskCacheLocked(); err != nil {
|
|
return err
|
|
}
|
|
b.logf("writing netmap to disk cache")
|
|
return b.diskCache.cache.Store(b.currentNode().Context(), b.patchNetmapHomeDERPLocked(nm))
|
|
}
|
|
|
|
func (b *LocalBackend) patchNetmapHomeDERPLocked(nm *netmap.NetworkMap) *netmap.NetworkMap {
|
|
// 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 &nmCopy
|
|
}
|
|
|
|
func (b *LocalBackend) ensureDiskCacheLocked() error {
|
|
dir, err := b.profileMkdirAllLocked(b.pm.CurrentProfile().ID(), "netmap-cache")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c := b.diskCache; c.cache == nil || c.dir != dir {
|
|
b.diskCache.cache = netmapcache.NewCache(netmapcache.FileStore(dir))
|
|
b.diskCache.dir = dir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *LocalBackend) loadDiskCacheLocked() (om *netmap.NetworkMap, ok bool) {
|
|
if !buildfeatures.HasCacheNetMap {
|
|
return nil, false
|
|
}
|
|
dir, err := b.profileMkdirAllLocked(b.pm.CurrentProfile().ID(), "netmap-cache")
|
|
if err != nil {
|
|
b.logf("profile data directory: %v", err)
|
|
return nil, false
|
|
}
|
|
if c := b.diskCache; c.cache == nil || c.dir != dir {
|
|
b.diskCache.cache = netmapcache.NewCache(netmapcache.FileStore(dir))
|
|
b.diskCache.dir = dir
|
|
}
|
|
nm, err := b.diskCache.cache.Load(b.currentNode().Context())
|
|
if err != nil {
|
|
b.logf("load netmap from cache: %v", err)
|
|
return nil, false
|
|
}
|
|
return nm, true
|
|
}
|
|
|
|
// discardDiskCacheLocked removes a cached network map for the current node, if
|
|
// one exists, and disables the cache.
|
|
func (b *LocalBackend) discardDiskCacheLocked() {
|
|
if !buildfeatures.HasCacheNetMap {
|
|
return
|
|
}
|
|
if b.diskCache.cache == nil {
|
|
return // nothing to do, we do not have a cache
|
|
}
|
|
// Reaching here, we have a cache directory that needs to be purged.
|
|
// Log errors but do not fail for them.
|
|
store := netmapcache.FileStore(b.diskCache.dir)
|
|
if err := b.clearStoreLocked(b.currentNode().Context(), store); err != nil {
|
|
b.logf("clearing netmap cache: %v", err)
|
|
}
|
|
b.diskCache = diskCache{} // drop in-memory state
|
|
}
|
|
|
|
// clearStoreLocked discards all the keys in the specified store.
|
|
func (b *LocalBackend) clearStoreLocked(ctx context.Context, store netmapcache.Store) error {
|
|
var errs []error
|
|
for key, err := range store.List(ctx, "") {
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("list cache contest: %w", err))
|
|
break
|
|
}
|
|
if err := store.Remove(ctx, key); err != nil {
|
|
errs = append(errs, fmt.Errorf("discard cache key %q: %w", key, err))
|
|
}
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
// ClearNetmapCache discards stored netmap caches (if any) for profiles for the
|
|
// current user of b. It also drops any cache from the active backend session,
|
|
// if there is one.
|
|
func (b *LocalBackend) ClearNetmapCache(ctx context.Context) error {
|
|
if !buildfeatures.HasCacheNetMap {
|
|
return nil // disabled
|
|
}
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
var errs []error
|
|
for _, p := range b.pm.Profiles() {
|
|
store := netmapcache.FileStore(b.profileDataPathLocked(p.ID(), "netmap-cache"))
|
|
err := b.clearStoreLocked(ctx, store)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("clear netmap cache for profile %q: %w", p.ID(), err))
|
|
}
|
|
}
|
|
|
|
b.diskCache = diskCache{} // drop in-memory state
|
|
return errors.Join(errs...)
|
|
}
|