mirror of
https://github.com/tailscale/tailscale.git
synced 2026-06-23 23:41:41 -04:00
Added in #20111, but it is too noisy under real load to be useful. Updates #12542 Change-Id: Ib99a8966ade0bfa4281fccc057249819cdcdfe83 Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
164 lines
5.3 KiB
Go
164 lines
5.3 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/tailcfg"
|
|
"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
|
|
}
|
|
|
|
// writePeerDeltaToDiskLocked applies the specified peer delta to the cache,
|
|
// leaving other existing cached fields (self, profiles, other peers not
|
|
// mentioned in the arguments) unchanged. It does nothing (without error) if
|
|
// both slices are empty.
|
|
func (b *LocalBackend) writePeerDeltaToDiskLocked(update []tailcfg.NodeView, remove []tailcfg.StableNodeID) error {
|
|
if !buildfeatures.HasCacheNetMap || (len(update) == 0 && len(remove) == 0) {
|
|
return nil
|
|
} else if err := b.ensureDiskCacheLocked(); err != nil {
|
|
return err
|
|
}
|
|
// Do not log this, we get deltas all the time and it's very noisy.
|
|
return b.diskCache.cache.UpdatePeers(b.currentNode().Context(), update, remove)
|
|
}
|
|
|
|
// 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...)
|
|
}
|