diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index d754f35b4..8a006057a 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -4710,19 +4710,18 @@ func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netip.Addr) (peer tai var zero tailcfg.NodeView ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - // PeerByTailscaleIP needs an up-to-date Peers slice. - nm := b.NetMapWithPeers() - if nm == nil { - return zero, "", errors.New("no netmap") + cn := b.currentNode() + var ok bool + if nid, addrOK := cn.NodeByAddr(ip); addrOK { + peer, ok = cn.PeerByID(nid) } - peer, ok := nm.PeerByTailscaleIP(ip) if !ok { return zero, "", fmt.Errorf("no peer found with Tailscale IP %v", ip) } if peer.Expired() { return zero, "", errors.New("peer's node key has expired") } - base := peerAPIBase(nm, peer) + base := peerAPIBase(cn.NetMap(), peer) if base == "" { return zero, "", fmt.Errorf("no PeerAPI base found for peer %v (%v)", peer.ID(), ip) } @@ -7968,22 +7967,12 @@ func (b *LocalBackend) DebugPeerRelayServers() set.Set[netip.Addr] { } // DebugPeerDiscoKeys returns the disco public keys this node has learned for -// each of its peers from the most recent network map. Intended for tests +// each of its current peers. Intended for tests // (the production [ipnstate.PeerStatus] purposefully does not surface disco // keys; surfacing them via the [ipnstate.Status] API would also pollute // every PeerStatus consumer with a non-comparable struct field). func (b *LocalBackend) DebugPeerDiscoKeys() map[key.NodePublic]key.DiscoPublic { - nm := b.currentNode().NetMap() - if nm == nil { - return nil - } - m := make(map[key.NodePublic]key.DiscoPublic, len(nm.Peers)) - for _, p := range nm.Peers { - if dk := p.DiscoKey(); !dk.IsZero() { - m[p.Key()] = dk - } - } - return m + return b.currentNode().peerDiscoKeys() } // ControlKnobs returns the node's control knobs. diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go index ffc820be8..0d46af9af 100644 --- a/ipn/ipnlocal/node_backend.go +++ b/ipn/ipnlocal/node_backend.go @@ -345,6 +345,30 @@ func (nb *nodeBackend) Peers() []tailcfg.NodeView { return slicesx.MapValues(nb.peers) } +// PeerByID returns the current state of the peer (not self) node with +// the given ID, or ok=false if it is not a current peer. +func (nb *nodeBackend) PeerByID(id tailcfg.NodeID) (_ tailcfg.NodeView, ok bool) { + nb.mu.Lock() + defer nb.mu.Unlock() + n, ok := nb.peers[id] + return n, ok +} + +// peerDiscoKeys returns the disco public keys of all current peers, +// keyed by their node public keys. Peers without a disco key are +// omitted. +func (nb *nodeBackend) peerDiscoKeys() map[key.NodePublic]key.DiscoPublic { + nb.mu.Lock() + defer nb.mu.Unlock() + m := make(map[key.NodePublic]key.DiscoPublic, len(nb.peers)) + for _, p := range nb.peers { + if dk := p.DiscoKey(); !dk.IsZero() { + m[p.Key()] = dk + } + } + return m +} + func (nb *nodeBackend) PeersForTest() []tailcfg.NodeView { nb.mu.Lock() defer nb.mu.Unlock() @@ -363,28 +387,22 @@ func (nb *nodeBackend) CollectServices() bool { // AppendMatchingPeers returns base with all peers that match pred appended. // -// It acquires b.mu to read the netmap but releases it before calling pred. +// It acquires nb.mu to snapshot the peers but releases it before +// calling pred. func (nb *nodeBackend) AppendMatchingPeers(base []tailcfg.NodeView, pred func(tailcfg.NodeView) bool) []tailcfg.NodeView { - var peers []tailcfg.NodeView - nb.mu.Lock() - if nb.netMap != nil { - // All fields on b.netMap are immutable, so this is - // safe to copy and use outside the lock. - peers = nb.netMap.Peers - } + peers := slicesx.MapValues(nb.peers) nb.mu.Unlock() + // Sort by node ID for deterministic results; the map iteration + // above is randomly ordered. + slices.SortFunc(peers, func(a, b tailcfg.NodeView) int { + return cmp.Compare(a.ID(), b.ID()) + }) + ret := base for _, peer := range peers { - // The peers in b.netMap don't contain updates made via - // UpdateNetmapDelta. So only use PeerView in b.netMap for its NodeID, - // and then look up the latest copy in b.peers which is updated in - // response to UpdateNetmapDelta edits. - nb.mu.Lock() - peer, ok := nb.peers[peer.ID()] - nb.mu.Unlock() - if ok && pred(peer) { + if pred(peer) { ret = append(ret, peer) } }