WIP: rebase fork onto upstream/main (v1.103.0) #15

Closed
codinget wants to merge 670 commits from webnet into save/webnet-2026-07-29
3 changed files with 138 additions and 38 deletions
Showing only changes of commit 7771ce4e58 - Show all commits
+26 -32
View File
@@ -327,12 +327,6 @@ type Conn struct {
// for a period of time before withdrawing them.
endpointTracker endpointTracker
// peerSet is the set of peers that are currently configured in
// WireGuard. These are not used to filter inbound or outbound
// traffic at all, but only to track what state can be cleaned up
// in other maps below that are keyed by peer public key.
peerSet set.Set[key.NodePublic]
// peerMap tracks the networkmap Node entity for each peer
// by node key, node ID, and discovery key.
peerMap peerMap
@@ -2801,31 +2795,6 @@ func (c *Conn) SetPrivateKey(privateKey key.NodePrivate) error {
return nil
}
// UpdatePeers is called when the set of WireGuard peers changes. It
// then removes any state for old peers.
//
// The caller passes ownership of newPeers map to UpdatePeers.
func (c *Conn) UpdatePeers(newPeers set.Set[key.NodePublic]) {
c.mu.Lock()
defer c.mu.Unlock()
oldPeers := c.peerSet
c.peerSet = newPeers
// Clean up any key.NodePublic-keyed maps for peers that no longer
// exist.
for peer := range oldPeers {
if !newPeers.Contains(peer) {
delete(c.derpRoute, peer)
delete(c.peerLastDerp, peer)
}
}
if len(oldPeers) == 0 && len(newPeers) > 0 {
go c.ReSTUN("non-zero-peers")
}
}
// debugRingBufferSize returns a maximum size for our set of endpoint ring
// buffers by assuming that a single large update is ~500 bytes, and that we
// want to not use more than 1MiB of memory on phones / 4MiB on other devices.
@@ -3168,7 +3137,21 @@ func (c *Conn) updateNodes(self tailcfg.NodeView, peers []tailcfg.NodeView) (pee
// Duplicate NodeIDs in the input shouldn't happen, but log if so.
c.logf("[unexpected] magicsock.updateNodes: %d peers input but %d unique IDs", len(peers), len(newPeers))
}
// Clean up node-key-keyed state for peers that are gone or have
// rotated their node key.
for id, prev := range c.peersByID {
if n, ok := newPeers[id]; !ok || n.Key() != prev.Key() {
delete(c.derpRoute, prev.Key())
delete(c.peerLastDerp, prev.Key())
}
}
hadPeers := len(c.peersByID) > 0
c.peersByID = newPeers
// A key-less conn can't STUN; the ReSTUN("set-private-key") on
// first key covers peers that arrived before the key.
if !hadPeers && len(newPeers) > 0 && !c.privateKey.IsZero() {
go c.ReSTUN("non-zero-peers")
}
// If the upsert pass left stale endpoints in peerMap (peers removed
// relative to before), clean them up.
@@ -3322,7 +3305,16 @@ func (c *Conn) UpsertPeer(n tailcfg.NodeView) {
return
}
flags := c.debugFlagsLocked()
if prev, ok := c.peersByID[n.ID()]; ok && prev.Key() != n.Key() {
delete(c.derpRoute, prev.Key())
delete(c.peerLastDerp, prev.Key())
}
hadPeers := len(c.peersByID) > 0
mak.Set(&c.peersByID, n.ID(), n)
// See the matching trigger in updateNodes for why the key check.
if !hadPeers && !c.privateKey.IsZero() {
go c.ReSTUN("non-zero-peers")
}
c.upsertPeerLocked(n, flags, debugRingBufferSize(len(c.peersByID)))
var relayUpsert candidatePeerRelay
@@ -3360,6 +3352,8 @@ func (c *Conn) RemovePeer(nid tailcfg.NodeID) {
return
}
delete(c.peersByID, nid)
delete(c.derpRoute, prev.Key())
delete(c.peerLastDerp, prev.Key())
if ep, ok := c.peerMap.endpointForNodeID(nid); ok {
c.peerMap.deleteEndpoint(ep)
}
@@ -3629,7 +3623,7 @@ func (c *Conn) shouldDoPeriodicReSTUNLocked() bool {
if c.networkDown() || c.homeless {
return false
}
if len(c.peerSet) == 0 || c.privateKey.IsZero() {
if len(c.peersByID) == 0 || c.privateKey.IsZero() {
// If no peers, not worth doing.
// Also don't if there's no key (not running).
return false
+112 -5
View File
@@ -21,6 +21,7 @@ import (
"os"
"reflect"
"runtime"
"slices"
"strconv"
"strings"
"sync"
@@ -69,6 +70,7 @@ import (
"tailscale.com/util/clientmetric"
"tailscale.com/util/eventbus"
"tailscale.com/util/eventbus/eventbustest"
"tailscale.com/util/mak"
"tailscale.com/util/must"
"tailscale.com/util/racebuild"
"tailscale.com/util/set"
@@ -365,11 +367,6 @@ func meshStacks(logf logger.Logf, mutateNetmap func(idx int, nm *netmap.NetworkM
for i, m := range ms {
nm := buildNetmapLocked(i)
m.conn.SetNetworkMap(nm.SelfNode, nm.Peers)
peerSet := make(set.Set[key.NodePublic], len(nm.Peers))
for _, peer := range nm.Peers {
peerSet.Add(peer.Key())
}
m.conn.UpdatePeers(peerSet)
wg, err := nmcfg.WGCfg(ms[i].privateKey, nm, logf, 0, "")
if err != nil {
// We're too far from the *testing.T to be graceful,
@@ -2328,6 +2325,116 @@ func TestSetNetworkMapWithNoPeers(t *testing.T) {
}
}
// Tests that the full-set peer update path (SetNetworkMap) and the
// incremental per-peer paths (UpsertPeer, RemovePeer) maintain the
// node-key-keyed DERP state (derpRoute and peerLastDerp) identically
// when peers are removed or rotate their node keys. LocalBackend uses
// either path depending on whether it received a full netmap or a
// delta, so a change to the cleanup in one path must be made to both.
func TestPeerDERPStateCleanup(t *testing.T) {
peerA := &tailcfg.Node{ID: 1, Key: randNodeKey(), DiscoKey: randDiscoKey(), Endpoints: eps("192.168.1.2:1")}
peerB := &tailcfg.Node{ID: 2, Key: randNodeKey(), DiscoKey: randDiscoKey(), Endpoints: eps("192.168.1.2:2")}
peerARotated := peerA.Clone()
peerARotated.Key = randNodeKey()
steps := []struct {
name string
peers []*tailcfg.Node // the desired peer set after the step
}{
{"initial", []*tailcfg.Node{peerA, peerB}},
{"rotate-A-key", []*tailcfg.Node{peerARotated, peerB}},
{"remove-B", []*tailcfg.Node{peerARotated}},
{"remove-all", nil},
}
// seedDERPState populates the node-key-keyed DERP maps for each
// current peer, as receiving DERP traffic from them would.
seedDERPState := func(c *Conn) {
c.mu.Lock()
defer c.mu.Unlock()
for _, n := range c.peersByID {
mak.Set(&c.derpRoute, n.Key(), derpRoute{regionID: 1})
mak.Set(&c.peerLastDerp, n.Key(), 1)
}
}
// derpKeys returns a canonical dump of the node-key-keyed DERP
// state that peer removals and key rotations must clean up.
derpKeys := func(c *Conn) []string {
c.mu.Lock()
defer c.mu.Unlock()
var got []string
for k := range c.derpRoute {
got = append(got, "derpRoute "+k.String())
}
for k := range c.peerLastDerp {
got = append(got, "peerLastDerp "+k.String())
}
slices.Sort(got)
return got
}
// stateString returns a canonical dump of all the per-peer state
// that the two update paths must maintain identically.
stateString := func(c *Conn) string {
c.mu.Lock()
lines := []string{fmt.Sprintf("peerMap %d", c.peerMap.nodeCount())}
for id, n := range c.peersByID {
lines = append(lines, fmt.Sprintf("peer %d %s", id, n.Key()))
}
c.mu.Unlock()
lines = append(lines, derpKeys(c)...)
slices.Sort(lines)
return strings.Join(lines, "\n")
}
connFull := newTestConn(t) // updated via SetNetworkMap
defer connFull.Close()
connInc := newTestConn(t) // updated via UpsertPeer/RemovePeer
defer connInc.Close()
prevKeyByID := map[tailcfg.NodeID]key.NodePublic{}
for _, step := range steps {
connFull.SetNetworkMap(tailcfg.NodeView{}, nodeViews(step.peers))
curIDs := set.Set[tailcfg.NodeID]{}
for _, n := range step.peers {
curIDs.Add(n.ID)
connInc.UpsertPeer(n.View())
}
for id := range prevKeyByID {
if !curIDs.Contains(id) {
connInc.RemovePeer(id)
}
}
// The DERP state seeded before this step must survive only for
// peers still present with an unchanged node key.
var want []string
for _, n := range step.peers {
if k, ok := prevKeyByID[n.ID]; ok && k == n.Key {
want = append(want, "derpRoute "+n.Key.String(), "peerLastDerp "+n.Key.String())
}
}
slices.Sort(want)
if got := derpKeys(connFull); !slices.Equal(got, want) {
t.Errorf("step %q: SetNetworkMap path DERP state = %q; want %q", step.name, got, want)
}
// Both update paths must have produced identical state.
if full, inc := stateString(connFull), stateString(connInc); full != inc {
t.Errorf("step %q: state mismatch between update paths\nSetNetworkMap path:\n%s\nUpsertPeer/RemovePeer path:\n%s", step.name, full, inc)
}
clear(prevKeyByID)
for _, n := range step.peers {
prevKeyByID[n.ID] = n.Key
}
seedDERPState(connFull)
seedDERPState(connInc)
}
}
// newWireguard starts up a new wireguard-go device attached to a test tun, and
// returns the device, tun and endpoint port. To add peers call device.IpcSet
// with UAPI instructions.
-1
View File
@@ -970,7 +970,6 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
e.lastCfgFull = *cfg.Clone()
e.magicConn.UpdatePeers(peerSet)
e.magicConn.SetPreferredPort(listenPort)
e.magicConn.UpdatePMTUD()