control/controlclient, types/netmap: start plumbing delta netmap updates

Currently only the top four most popular changes: endpoints, DERP
home, online, and LastSeen.

Updates #1909

Change-Id: I03152da176b2b95232b56acabfb55dcdfaa16b79
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2023-09-01 19:28:00 -07:00
committed by Brad Fitzpatrick
parent c0ade132e6
commit 3af051ea27
14 changed files with 715 additions and 13 deletions

View File

@@ -203,9 +203,8 @@ type LocalBackend struct {
capFileSharing bool // whether netMap contains the file sharing capability
capTailnetLock bool // whether netMap contains the tailnet lock capability
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo
// netMap is not mutated in-place once set.
netMap *netmap.NetworkMap
hostinfo *tailcfg.Hostinfo
netMap *netmap.NetworkMap // not mutated in place once set (except for Peers slice)
nmExpiryTimer tstime.TimerController // for updating netMap on node expiry; can be nil
nodeByAddr map[netip.Addr]tailcfg.NodeView
activeLogin string // last logged LoginName from netMap
@@ -1121,6 +1120,56 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
b.authReconfig()
}
var _ controlclient.NetmapDeltaUpdater = (*LocalBackend)(nil)
// UpdateNetmapDelta implements controlclient.NetmapDeltaUpdater.
func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bool) {
mc, err := b.magicConn()
if err != nil {
panic(err) // shouldn't happen
}
if !mc.UpdateNetmapDelta(muts) {
return false
}
b.mu.Lock()
defer b.mu.Unlock()
return b.updateNetmapDeltaLocked(muts)
}
func (b *LocalBackend) updateNetmapDeltaLocked(muts []netmap.NodeMutation) (handled bool) {
if b.netMap == nil {
return false
}
peers := b.netMap.Peers
for _, m := range muts {
// LocalBackend only cares about some types of mutations.
// (magicsock cares about different ones.)
switch m.(type) {
case netmap.NodeMutationOnline, netmap.NodeMutationLastSeen:
default:
continue
}
nodeID := m.NodeIDBeingMutated()
idx := b.netMap.PeerIndexByNodeID(nodeID)
if idx == -1 {
continue
}
mut := peers[idx].AsStruct()
switch m := m.(type) {
case netmap.NodeMutationOnline:
mut.Online = ptr.To(m.Online)
case netmap.NodeMutationLastSeen:
mut.LastSeen = ptr.To(m.LastSeen)
}
peers[idx] = mut.View()
}
return true
}
// setExitNodeID updates prefs to reference an exit node by ID, rather
// than by IP. It returns whether prefs was mutated.
func setExitNodeID(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool) {

View File

@@ -26,6 +26,7 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/types/netmap"
"tailscale.com/types/ptr"
"tailscale.com/util/set"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
@@ -879,3 +880,75 @@ func TestWatchNotificationsCallbacks(t *testing.T) {
t.Fatalf("unexpected number of watchers in new LocalBackend, want: 0 got: %v", len(b.notifyWatchers))
}
}
// tests LocalBackend.updateNetmapDeltaLocked
func TestUpdateNetmapDelta(t *testing.T) {
var b LocalBackend
if b.updateNetmapDeltaLocked(nil) {
t.Errorf("updateNetmapDeltaLocked() = true, want false with nil netmap")
}
b.netMap = &netmap.NetworkMap{}
for i := 0; i < 5; i++ {
b.netMap.Peers = append(b.netMap.Peers, (&tailcfg.Node{ID: (tailcfg.NodeID(i) + 1)}).View())
}
someTime := time.Unix(123, 0)
muts, ok := netmap.MutationsFromMapResponse(&tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{
{
NodeID: 1,
DERPRegion: 1,
},
{
NodeID: 2,
Online: ptr.To(true),
},
{
NodeID: 3,
Online: ptr.To(false),
},
{
NodeID: 4,
LastSeen: ptr.To(someTime),
},
},
}, someTime)
if !ok {
t.Fatal("netmap.MutationsFromMapResponse failed")
}
if !b.updateNetmapDeltaLocked(muts) {
t.Fatalf("updateNetmapDeltaLocked() = false, want true with new netmap")
}
wants := []*tailcfg.Node{
{
ID: 1,
DERP: "", // unmodified by the delta
},
{
ID: 2,
Online: ptr.To(true),
},
{
ID: 3,
Online: ptr.To(false),
},
{
ID: 4,
LastSeen: ptr.To(someTime),
},
}
for _, want := range wants {
idx := b.netMap.PeerIndexByNodeID(want.ID)
if idx == -1 {
t.Errorf("ID %v not found in netmap", want.ID)
continue
}
got := b.netMap.Peers[idx].AsStruct()
if !reflect.DeepEqual(got, want) {
t.Errorf("netmap.Peer %v wrong.\n got: %v\nwant: %v", want.ID, logger.AsJSON(got), logger.AsJSON(want))
}
}
}