WIP: rebase for 2026-05-18 #7

Closed
codinget wants to merge 234 commits from rebase/2026-05-18 into webnet
2 changed files with 240 additions and 41 deletions
Showing only changes of commit 27f1d4c15d - Show all commits
+30 -8
View File
@@ -298,7 +298,12 @@ func (ms *mapSession) handleNonKeepAliveMapResponse(ctx context.Context, resp *t
ms.patchifyPeersChanged(resp) ms.patchifyPeersChanged(resp)
ms.removeUnwantedDiscoUpdates(resp) ms.removeUnwantedDiscoUpdates(resp, viaTSMP)
// TSMP learned key was rejected, no need to do any more work in the engine.
if viaTSMP && len(resp.PeersChangedPatch) == 0 {
return nil
}
ms.removeUnwantedDiscoUpdatesFromFullNetmapUpdate(resp) ms.removeUnwantedDiscoUpdatesFromFullNetmapUpdate(resp)
ms.updateStateFromResponse(resp) ms.updateStateFromResponse(resp)
@@ -407,7 +412,7 @@ type updateStats struct {
// removeUnwantedDiscoUpdates goes over the patchified updates and reject items // removeUnwantedDiscoUpdates goes over the patchified updates and reject items
// where the node is offline and has last been seen before the recorded last seen. // where the node is offline and has last been seen before the recorded last seen.
func (ms *mapSession) removeUnwantedDiscoUpdates(resp *tailcfg.MapResponse) { func (ms *mapSession) removeUnwantedDiscoUpdates(resp *tailcfg.MapResponse, viaTSMP bool) {
ms.peersMu.RLock() ms.peersMu.RLock()
defer ms.peersMu.RUnlock() defer ms.peersMu.RUnlock()
@@ -422,17 +427,33 @@ func (ms *mapSession) removeUnwantedDiscoUpdates(resp *tailcfg.MapResponse) {
continue continue
} }
existingNode, ok := ms.peers[change.NodeID]
// Accept if: // Accept if:
// - Node is online. // - Cannot find the peer, don't have enough data.
if *change.Online { if !ok {
acceptedDiscoUpdates = append(acceptedDiscoUpdates, change) acceptedDiscoUpdates = append(acceptedDiscoUpdates, change)
continue continue
} }
existingNode, ok := ms.peers[change.NodeID] // Reject if:
// - key was learned via tsmp AND,
// - existing node is online AND,
// - key did not change.
// Here to avoid a deeper reconfig in the case where we get a TSMP key
// exchange while that node is already in a connected state (from the view
// of the control plane). This is meant to keep the node stable, avoiding a
// reconfiguration of the node deeper down in the engine.
// With this, we are avoiding updating the LastSeen and Online fields from
// TSMP updates when that is not relevant, overall making the connection
// state change less, and updating the engine less.
if viaTSMP && existingNode.Online().Get() &&
*change.DiscoKey == existingNode.DiscoKey() {
continue
}
// Accept if: // Accept if:
// - Cannot find the peer, don't have enough data // - Node is online.
if !ok { if *change.Online {
acceptedDiscoUpdates = append(acceptedDiscoUpdates, change) acceptedDiscoUpdates = append(acceptedDiscoUpdates, change)
continue continue
} }
@@ -497,8 +518,9 @@ func (ms *mapSession) removeUnwantedDiscoUpdatesFromFullNetmapUpdate(resp *tailc
continue continue
} }
// Overwrite the key in the full netmap update. // Overwrite the key and last seen in the full netmap update.
peer.DiscoKey = existingNode.DiscoKey() peer.DiscoKey = existingNode.DiscoKey()
*peer.LastSeen = existingNode.LastSeen().Get()
} }
} }
+210 -33
View File
@@ -14,6 +14,7 @@ import (
"strings" "strings"
"sync/atomic" "sync/atomic"
"testing" "testing"
"testing/synctest"
"time" "time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@@ -630,81 +631,149 @@ func TestUpdateDiscoForNode(t *testing.T) {
name string name string
initialOnline bool initialOnline bool
initialLastSeen time.Time initialLastSeen time.Time
updateDiscoKey bool
updateOnline bool updateOnline bool
updateLastSeen time.Time updateLastSeen time.Time
wantUpdate bool wantUpdate bool
wantKeyChanged bool
}{ }{
{ {
name: "newer_key_not_online", name: "newer_key_not_online",
initialOnline: true, initialOnline: true,
initialLastSeen: time.Unix(1, 0), initialLastSeen: time.Unix(1, 0),
updateDiscoKey: true,
updateOnline: false, updateOnline: false,
updateLastSeen: time.Now(), updateLastSeen: time.Now(),
wantUpdate: true, wantUpdate: true,
wantKeyChanged: true,
}, },
{ {
name: "newer_key_online", name: "newer_key_online",
initialOnline: true, initialOnline: true,
initialLastSeen: time.Unix(1, 0), initialLastSeen: time.Unix(1, 0),
updateDiscoKey: true,
updateOnline: true, updateOnline: true,
updateLastSeen: time.Now(), updateLastSeen: time.Now(),
wantUpdate: true, wantUpdate: true,
wantKeyChanged: true,
}, },
{ {
name: "older_key_not_online", name: "older_key_not_online",
initialOnline: false, initialOnline: false,
initialLastSeen: time.Now(), initialLastSeen: time.Now(),
updateDiscoKey: true,
updateOnline: false, updateOnline: false,
updateLastSeen: time.Unix(1, 0), updateLastSeen: time.Unix(1, 0),
wantUpdate: false, wantUpdate: false,
wantKeyChanged: false,
}, },
{ {
name: "older_key_online", name: "older_key_online",
initialOnline: false, initialOnline: false,
initialLastSeen: time.Now(), initialLastSeen: time.Now(),
updateDiscoKey: true,
updateOnline: true, updateOnline: true,
updateLastSeen: time.Unix(1, 0), updateLastSeen: time.Unix(1, 0),
wantUpdate: true, wantUpdate: true,
wantKeyChanged: true,
},
{
name: "same_newer_key_not_online",
initialOnline: true,
initialLastSeen: time.Unix(1, 0),
updateDiscoKey: false,
updateOnline: false,
updateLastSeen: time.Now(),
wantUpdate: false,
wantKeyChanged: false,
},
{
name: "same_newer_key_online",
initialOnline: true,
initialLastSeen: time.Unix(1, 0),
updateDiscoKey: false,
updateOnline: true,
updateLastSeen: time.Now(),
wantUpdate: false,
wantKeyChanged: false,
},
{
name: "same_older_key_not_online",
initialOnline: false,
initialLastSeen: time.Now(),
updateDiscoKey: false,
updateOnline: false,
updateLastSeen: time.Unix(1, 0),
wantUpdate: false,
wantKeyChanged: false,
},
{
name: "same_older_key_online",
initialOnline: false,
initialLastSeen: time.Now(),
updateDiscoKey: false,
updateOnline: true,
updateLastSeen: time.Unix(1, 0),
wantUpdate: true,
wantKeyChanged: false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
nu := &rememberLastNetmapUpdater{ synctest.Test(t, func(*testing.T) {
done: make(chan any, 1), nu := &rememberLastNetmapUpdater{
} done: make(chan any, 1),
ms := newTestMapSession(t, nu) }
ms := newTestMapSession(t, nu)
defer ms.Close()
oldKey := key.NewDisco() oldKey := key.NewDisco()
// Insert existing node // Insert existing node
node := tailcfg.Node{ node := tailcfg.Node{
ID: 1, ID: 1,
Key: key.NewNode().Public(), Key: key.NewNode().Public(),
DiscoKey: oldKey.Public(), DiscoKey: oldKey.Public(),
Online: &tt.initialOnline, Online: &tt.initialOnline,
LastSeen: &tt.initialLastSeen, LastSeen: &tt.initialLastSeen,
} }
if nm := ms.netmapForResponse(&tailcfg.MapResponse{ if nm := ms.netmapForResponse(&tailcfg.MapResponse{
Peers: []*tailcfg.Node{&node}, Peers: []*tailcfg.Node{&node},
}); len(nm.Peers) != 1 { }); len(nm.Peers) != 1 {
t.Fatalf("node not inserted") t.Fatalf("node not inserted")
} }
newKey := key.NewDisco() newKey := oldKey.Public()
ms.updateDiscoForNode(node.ID, node.Key, newKey.Public(), tt.updateLastSeen, tt.updateOnline) if tt.updateDiscoKey {
<-nu.done newKey = key.NewDisco().Public()
}
ms.updateDiscoForNode(node.ID, node.Key, newKey, tt.updateLastSeen, tt.updateOnline)
peer, ok := ms.peers[node.ID] // We have an early escape that would not trigger the netmap updater.
if !ok { synctest.Wait()
t.Fatal("node not found") select {
} case <-nu.done:
if !tt.wantUpdate {
t.Errorf("did not expect update, got: %v", nu.last)
}
default:
if tt.wantUpdate {
t.Errorf("expected update, did not get any")
}
}
updated := peer.DiscoKey().Compare(newKey.Public()) == 0 peer, ok := ms.peers[node.ID]
if updated != tt.wantUpdate { if !ok {
t.Fatalf("Disco key update: %t, wanted update: %t", updated, tt.wantUpdate) t.Fatal("node not found")
} }
keyChanged := peer.DiscoKey().Compare(oldKey.Public()) != 0
if keyChanged != tt.wantKeyChanged {
t.Errorf("Disco key update: %t, wanted update: %t", keyChanged, tt.wantKeyChanged)
}
})
}) })
} }
} }
@@ -831,6 +900,14 @@ func TestUpdateDiscoForNodeCallbackWithFullNetmap(t *testing.T) {
updateLastSeen: now, updateLastSeen: now,
expectNewDisco: true, expectNewDisco: true,
}, },
{
name: "local-lastseen-preserved-after-first-reconnect",
initialOnline: false,
initialLastSeen: now,
updateOnline: false,
updateLastSeen: now,
expectNewDisco: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -1115,17 +1192,20 @@ func TestPeerChangeDiff(t *testing.T) {
a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}}, b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}},
want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}}, want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}},
}, { },
{
name: "patch-capmap-remove-key", name: "patch-capmap-remove-key",
a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{}}, b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{}},
want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}}, want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}},
}, { },
{
name: "patch-capmap-remove-as-nil", name: "patch-capmap-remove-as-nil",
a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
b: &tailcfg.Node{ID: 1}, b: &tailcfg.Node{ID: 1},
want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}}, want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}},
}, { },
{
name: "patch-capmap-add-key-to-empty-map", name: "patch-capmap-add-key-to-empty-map",
a: &tailcfg.Node{ID: 1}, a: &tailcfg.Node{ID: 1},
b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
@@ -1366,7 +1446,6 @@ func TestUpgradeNode(t *testing.T) {
} }
}) })
} }
} }
func BenchmarkMapSessionDelta(b *testing.B) { func BenchmarkMapSessionDelta(b *testing.B) {
@@ -1838,3 +1917,101 @@ func TestPeerIDAndKeyByTailscaleIP(t *testing.T) {
} }
}) })
} }
func TestRemoveUnwantedDiscoUpdates(t *testing.T) {
tests := []struct {
name string
viaTSMP bool
existingOnline bool
sameKey bool
newerLastSeen bool
wantAccepted bool
}{
{
name: "tsmp_online_peer_same_key",
viaTSMP: true,
existingOnline: true,
sameKey: true,
newerLastSeen: true,
wantAccepted: false,
},
{
name: "not_tsmp_online_peer_same_key",
viaTSMP: false,
existingOnline: true,
sameKey: true,
newerLastSeen: true,
wantAccepted: true,
},
{
name: "tsmp_offline_peer_same_key",
viaTSMP: true,
existingOnline: false,
sameKey: true,
newerLastSeen: true,
wantAccepted: true,
},
{
name: "tsmp_online_peer_diff_key",
viaTSMP: true,
existingOnline: true,
sameKey: false,
newerLastSeen: true,
wantAccepted: true,
},
{
name: "tsmp_online_peer_same_key_old_lastseen",
viaTSMP: true,
existingOnline: true,
sameKey: true,
newerLastSeen: false,
wantAccepted: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ms := newTestMapSession(t, &rememberLastNetmapUpdater{done: make(chan any, 1)})
existingKey := key.NewDisco().Public()
existingOnline := tt.existingOnline
initialLastSeen := time.Unix(1, 0)
ms.updateStateFromResponse(&tailcfg.MapResponse{
Peers: []*tailcfg.Node{{
ID: 1,
Key: key.NewNode().Public(),
DiscoKey: existingKey,
Online: &existingOnline,
LastSeen: &initialLastSeen,
}},
})
changeKey := existingKey
if !tt.sameKey {
changeKey = key.NewDisco().Public()
}
changeOnline := false // must be false to reach the new guard
updateLastSeen := time.Unix(2, 0)
if !tt.newerLastSeen {
updateLastSeen = time.Unix(0, 0)
}
resp := &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
DiscoKey: &changeKey,
Online: &changeOnline,
LastSeen: &updateLastSeen,
}},
}
ms.removeUnwantedDiscoUpdates(resp, tt.viaTSMP)
got := len(resp.PeersChangedPatch) > 0
if got != tt.wantAccepted {
t.Errorf("accepted=%v, want %v", got, tt.wantAccepted)
}
})
}
}