diff --git a/management/internals/controllers/network_map/controller/controller.go b/management/internals/controllers/network_map/controller/controller.go index dac3f6990..bee550f86 100644 --- a/management/internals/controllers/network_map/controller/controller.go +++ b/management/internals/controllers/network_map/controller/controller.go @@ -321,12 +321,12 @@ func (c *Controller) sendUpdateAccountPeersFromData(ctx context.Context, account // sendUpdateForAffectedPeersFromData is the account-free variant of // sendUpdateForAffectedPeers. func (c *Controller) sendUpdateForAffectedPeersFromData(ctx context.Context, accountID string, peerIDs []string, nmData *networkmap.NetworkMapData) error { - affected := make(map[string]struct{}, len(peerIDs)) - for _, id := range peerIDs { - affected[id] = struct{}{} + if len(peerIDs) == 0 { + log.WithContext(ctx).Tracef("sendUpdateForAffectedPeersFromData: no affected peers") + return nil } - peersToUpdate := c.connectedPeersFromData(nmData, affected) + peersToUpdate := c.connectedPeersFromData(nmData, peerIDs) if len(peersToUpdate) == 0 { log.WithContext(ctx).Tracef("sendUpdateForAffectedPeersFromData: no peers to update (affected peers not found in data or no channels)") return nil @@ -337,15 +337,27 @@ func (c *Controller) sendUpdateForAffectedPeersFromData(ctx context.Context, acc return c.sendUpdatesFromData(ctx, accountID, nmData, peersToUpdate, nil) } -func (c *Controller) connectedPeersFromData(nmData *networkmap.NetworkMapData, affected map[string]struct{}) []*nmdata.Peer { - var result []*nmdata.Peer - for _, peer := range nmData.Peers { - if affected != nil { - if _, ok := affected[peer.ID]; !ok { - continue +// connectedPeersFromData returns the peers with an open update channel. An +// empty affected list means all peers; a non-empty list restricts the result +// to those peer IDs. +func (c *Controller) connectedPeersFromData(nmData *networkmap.NetworkMapData, affected []string) []*nmdata.Peer { + if len(affected) == 0 { + result := make([]*nmdata.Peer, 0, len(nmData.Peers)) + for _, peer := range nmData.Peers { + if c.peersUpdateManager.HasChannel(peer.ID) { + result = append(result, peer) } } - if c.peersUpdateManager.HasChannel(peer.ID) { + return result + } + + result := make([]*nmdata.Peer, 0, len(affected)) + for _, peerID := range affected { + peer := nmData.Peers[peerID] + if peer == nil { + continue + } + if c.peersUpdateManager.HasChannel(peerID) { result = append(result, peer) } } diff --git a/shared/management/networkmap/networkmapcompute.go b/shared/management/networkmap/networkmapcompute.go index f07ff0ba1..dedfaaedd 100644 --- a/shared/management/networkmap/networkmapcompute.go +++ b/shared/management/networkmap/networkmapcompute.go @@ -199,12 +199,9 @@ func (nmd *NetworkMapData) getPeersGroupsPoliciesRoutes( relevantPeerIDs[peerID] = nmd.Peers[peerID] - peerGroupSet := make(map[string]struct{}, 8) - for groupID, group := range nmd.Groups { - if slices.Contains(group.Peers, peerID) { - relevantGroupIDs[groupID] = group - peerGroupSet[groupID] = struct{}{} - } + peerGroupSet := nmd.GetPeerGroups(peerID) + for groupID := range peerGroupSet { + relevantGroupIDs[groupID] = nmd.Groups[groupID] } routeAccessControlGroups := make(map[string]struct{}) @@ -520,14 +517,32 @@ func (nmd *NetworkMapData) forcesRoutingPeerDNSResolution(peerID string) bool { return false } +// GetPeerGroups returns the set of group IDs the peer belongs to. The +// underlying peer→groups index is built once per NetworkMapData and the +// returned set is shared — callers must not mutate it. func (nmd *NetworkMapData) GetPeerGroups(peerID string) map[string]struct{} { - groups := make(map[string]struct{}) - for groupID, group := range nmd.Groups { - if slices.Contains(group.Peers, peerID) { - groups[groupID] = struct{}{} + nmd.peerGroupsOnce.Do(func() { + idx := make(map[string]map[string]struct{}, len(nmd.Peers)) + for groupID, group := range nmd.Groups { + if group == nil { + continue + } + for _, pid := range group.Peers { + set, ok := idx[pid] + if !ok { + set = make(map[string]struct{}) + idx[pid] = set + } + set[groupID] = struct{}{} + } } + nmd.peerGroupsIdx = idx + }) + + if set, ok := nmd.peerGroupsIdx[peerID]; ok { + return set } - return groups + return map[string]struct{}{} } func (nmd *NetworkMapData) getUniquePeerIDsFromGroupsIDs(groups []string) []string { diff --git a/shared/management/networkmap/networkmapdata.go b/shared/management/networkmap/networkmapdata.go index 71bb0ba96..e874752d2 100644 --- a/shared/management/networkmap/networkmapdata.go +++ b/shared/management/networkmap/networkmapdata.go @@ -1,6 +1,8 @@ package networkmap import ( + "sync" + "github.com/netbirdio/netbird/shared/management/networkmap/nmdata" ) @@ -39,6 +41,9 @@ type NetworkMapData struct { //nolint:revive // established name across the code AppliedZoneCandidates []AppliedZoneCandidate PrivateServiceCandidates []PrivateServiceCandidate + + peerGroupsOnce sync.Once + peerGroupsIdx map[string]map[string]struct{} } // AppliedZoneCandidate is an account-level custom DNS zone reduced to the