diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index d5283d97a..283248060 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -2103,7 +2103,7 @@ func (b *LocalBackend) setControlClientStatusLocked(c controlclient.Client, st c } } - b.e.SetNetworkMap(st.NetMap) + b.e.SetSelfNode(st.NetMap.SelfNode) var cachedHome int if c == nil && st.NetMap.Cached && st.NetMap.SelfNode.Valid() { @@ -2514,21 +2514,25 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo } ms.UpdateNetmapDelta(muts) - // Temporary for 1.100.x: force a full authReconfig + SetNetworkMap - // on any peer add or remove. netmapDeltaNeedsAuthReconfig only - // considered NodeMutationUpsert of already-known NodeIDs whose + // Force a full authReconfig + SetSelfNode on any peer add or + // remove. netmapDeltaNeedsAuthReconfig only considered + // NodeMutationUpsert of already-known NodeIDs whose // peerRouteConfigChanged, so brand-new peers and removes left - // e.lastCfgFull / the engine BART / wgdev's PeerLookupFunc closure - // / e.netMap all stale, and Engine.PeerForIP, lookupPeerByIP, and - // outbound wgdev encryption all missed those peers. authReconfig - // fixes the wireguard side; SetNetworkMap fixes the e.netMap that - // PeerForIP reads. The proper per-peer fix lands in the next dev - // cycle. See tailscale/corp#43394. + // e.lastCfgFull and wgdev's PeerLookupFunc closure stale, and + // outbound wgdev encryption missed those peers. authReconfig + // fixes the wireguard side; SetSelfNode refreshes the engine's + // cached self node. PeerForIP / lookupPeerByIP staleness was + // addressed separately in d4f2917c1b, which routes those lookups + // through nodeBackend's live data, and as part of the broader + // netmap.NetworkMap removal effort the engine no longer caches + // the netmap at all (see tailscale/corp#43394). As of 2026-06-24 + // the only remaining staleness this guards against is + // e.lastCfgFull and the wgdev peer set. needsAuthReconfig = needsAuthReconfig || peersUpsertedOrRemoved if needsAuthReconfig { if peersUpsertedOrRemoved { if nm := cn.netMapWithPeers(); nm != nil { - b.e.SetNetworkMap(nm) + b.e.SetSelfNode(nm.SelfNode) } } b.authReconfigLocked() @@ -3953,7 +3957,11 @@ func (b *LocalBackend) DebugForceNetmapUpdate() { defer b.mu.Unlock() // TODO(nickkhyl): this all should be done in [LocalBackend.setNetMapLocked]. nm := b.currentNode().NetMap() - b.e.SetNetworkMap(nm) + var self tailcfg.NodeView + if nm != nil { + self = nm.SelfNode + } + b.e.SetSelfNode(self) if nm != nil { b.MagicConn().SetDERPMap(nm.DERPMap) } @@ -8375,8 +8383,8 @@ func (b *LocalBackend) resetForProfileChangeLocked() error { defer newNode.ready() b.setNetMapLocked(nil) // Reset netmap. b.updateFilterLocked(ipn.PrefsView{}) - // Reset the NetworkMap in the engine - b.e.SetNetworkMap(new(netmap.NetworkMap)) + // Reset the self node in the engine. + b.e.SetSelfNode(tailcfg.NodeView{}) if prevCC := b.resetControlClientLocked(); prevCC != nil { // Shutdown outside of b.mu to avoid deadlocks. b.goTracker.Go(prevCC.Shutdown) diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index 6ca96e4bd..0e6adfa1f 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -1972,7 +1972,7 @@ func (e *mockEngine) PeerByKey(key.NodePublic) (_ wgint.Peer, ok bool) { return wgint.Peer{}, false } -func (e *mockEngine) SetNetworkMap(*netmap.NetworkMap) {} +func (e *mockEngine) SetSelfNode(tailcfg.NodeView) {} func (e *mockEngine) UpdateStatus(*ipnstate.StatusBuilder) {} diff --git a/wgengine/bench/wg.go b/wgengine/bench/wg.go index 7b35a089a..4264ae448 100644 --- a/wgengine/bench/wg.go +++ b/wgengine/bench/wg.go @@ -19,20 +19,12 @@ "tailscale.com/tsd" "tailscale.com/types/key" "tailscale.com/types/logger" - "tailscale.com/types/netmap" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" "tailscale.com/wgengine/wgcfg" ) -func epFromTyped(eps []tailcfg.Endpoint) (ret []netip.AddrPort) { - for _, ep := range eps { - ret = append(ret, ep.Addr) - } - return -} - func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip.Prefix) { l1 := logger.WithPrefix(logf, "e1: ") k1 := key.NewNode() @@ -103,17 +95,7 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip. } logf("e1 status: %v", *st) - n := &tailcfg.Node{ - ID: tailcfg.NodeID(0), - Name: "n1", - Addresses: []netip.Prefix{a1}, - AllowedIPs: []netip.Prefix{a1}, - Endpoints: epFromTyped(st.LocalAddrs), - } - e2.SetNetworkMap(&netmap.NetworkMap{ - NodeKey: k2.Public(), - Peers: []tailcfg.NodeView{n.View()}, - }) + e2.SetSelfNode(tailcfg.NodeView{}) p := wgcfg.Peer{ PublicKey: c1.PrivateKey.Public(), @@ -134,17 +116,7 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip. } logf("e2 status: %v", *st) - n := &tailcfg.Node{ - ID: tailcfg.NodeID(0), - Name: "n2", - Addresses: []netip.Prefix{a2}, - AllowedIPs: []netip.Prefix{a2}, - Endpoints: epFromTyped(st.LocalAddrs), - } - e1.SetNetworkMap(&netmap.NetworkMap{ - NodeKey: k1.Public(), - Peers: []tailcfg.NodeView{n.View()}, - }) + e1.SetSelfNode(tailcfg.NodeView{}) p := wgcfg.Peer{ PublicKey: c2.PrivateKey.Public(), diff --git a/wgengine/userspace.go b/wgengine/userspace.go index aa9ce053c..e471f2554 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -45,7 +45,6 @@ "tailscale.com/types/ipproto" "tailscale.com/types/key" "tailscale.com/types/logger" - "tailscale.com/types/netmap" "tailscale.com/types/views" "tailscale.com/util/checkchange" "tailscale.com/util/clientmetric" @@ -142,9 +141,9 @@ type userspaceEngine struct { lastAppliedDisableTUNUDPGRO bool lastAppliedDisableTUNTCPGRO bool - mu sync.Mutex // guards following; see lock order comment below - netMap *netmap.NetworkMap // or nil - closing bool // Close was called (even if we're still closing) + mu sync.Mutex // guards following; see lock order comment below + selfNode tailcfg.NodeView // or invalid if none + closing bool // Close was called (even if we're still closing) statusCallback StatusCallback endpoints []tailcfg.Endpoint pendOpen map[flowtrackTuple]*pendingOpenFlow // see pendopen.go @@ -837,7 +836,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, } e.mu.Lock() - nm := e.netMap + self := e.selfNode e.mu.Unlock() listenPort := e.confListenPort @@ -848,10 +847,10 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, peerMTUEnable := e.magicConn.ShouldPMTUD() isSubnetRouter := false - if buildfeatures.HasBird && e.birdClient != nil && nm != nil && nm.SelfNode.Valid() { - isSubnetRouter = hasOverlap(nm.SelfNode.PrimaryRoutes(), nm.SelfNode.Hostinfo().RoutableIPs()) + if buildfeatures.HasBird && e.birdClient != nil && self.Valid() { + isSubnetRouter = hasOverlap(self.PrimaryRoutes(), self.Hostinfo().RoutableIPs()) e.logf("[v1] Reconfig: hasOverlap(%v, %v) = %v; isSubnetRouter=%v lastIsSubnetRouter=%v", - nm.SelfNode.PrimaryRoutes(), nm.SelfNode.Hostinfo().RoutableIPs(), + self.PrimaryRoutes(), self.Hostinfo().RoutableIPs(), isSubnetRouter, isSubnetRouter, e.lastIsSubnetRouter) } isSubnetRouterChanged := buildfeatures.HasAdvertiseRoutes && isSubnetRouter != e.lastIsSubnetRouter @@ -1331,9 +1330,9 @@ func (e *userspaceEngine) linkChange(delta *netmon.ChangeDelta) { } } -func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) { +func (e *userspaceEngine) SetSelfNode(self tailcfg.NodeView) { e.mu.Lock() - e.netMap = nm + e.selfNode = self tunGROKnobsChanged := false var curUDP, curTCP bool if buildfeatures.HasGRO && runtime.GOOS == "linux" && e.controlKnobs != nil { @@ -1409,19 +1408,19 @@ func (e *userspaceEngine) mySelfIPMatchingFamily(dst netip.Addr) (src netip.Addr var zero netip.Addr e.mu.Lock() defer e.mu.Unlock() - if e.netMap == nil { - return zero, errors.New("no netmap") + if !e.selfNode.Valid() { + return zero, errors.New("no self node") } - addrs := e.netMap.GetAddresses() + addrs := e.selfNode.Addresses() if addrs.Len() == 0 { - return zero, errors.New("no self address in netmap") + return zero, errors.New("no self address") } for _, p := range addrs.All() { if p.IsSingleIP() && p.Addr().BitLen() == dst.BitLen() { return p.Addr(), nil } } - return zero, errors.New("no self address in netmap matching address family") + return zero, errors.New("no self address matching address family") } func (e *userspaceEngine) sendICMPEchoRequest(destIP netip.Addr, peer tailcfg.NodeView, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) { diff --git a/wgengine/userspace_test.go b/wgengine/userspace_test.go index 4cadbcb69..809190bfa 100644 --- a/wgengine/userspace_test.go +++ b/wgengine/userspace_test.go @@ -116,7 +116,7 @@ func TestUserspaceEngineReconfig(t *testing.T) { }, } - e.SetNetworkMap(nm) + e.SetSelfNode(nm.SelfNode) err = e.Reconfig(cfg, routerCfg, &dns.Config{}) if err != nil { t.Fatal(err) @@ -168,7 +168,7 @@ func TestUserspaceEngineTSMPLearned(t *testing.T) { if err != nil { t.Fatal(err) } - e.SetNetworkMap(nm) + e.SetSelfNode(nm.SelfNode) newDisco := key.NewDisco() cfg := &wgcfg.Config{ @@ -244,7 +244,7 @@ func TestUserspaceEngineTSMPLearnedMismatch(t *testing.T) { if err != nil { t.Fatal(err) } - e.SetNetworkMap(nm) + e.SetSelfNode(nm.SelfNode) newDisco := key.NewDisco() cfg := &wgcfg.Config{ @@ -485,7 +485,7 @@ func TestTSMPKeyAdvertisement(t *testing.T) { }, } - ue.SetNetworkMap(nm) + ue.SetSelfNode(nm.SelfNode) err = ue.Reconfig(cfg, routerCfg, &dns.Config{}) if err != nil { t.Fatal(err) diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index 8ca37a65b..6b996fe81 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -175,12 +175,9 @@ type Engine interface { // You don't have to call this. Done() <-chan struct{} - // SetNetworkMap informs the engine of the latest network map - // from the server. The network map's DERPMap field should be - // ignored as as it might be disabled; get it from SetDERPMap - // instead. - // The network map should only be read from. - SetNetworkMap(*netmap.NetworkMap) + // SetSelfNode informs the engine of the current self node. + // The zero (invalid) NodeView indicates no self node. + SetSelfNode(tailcfg.NodeView) // UpdateStatus populates the network state using the provided // status builder.