diff --git a/management/server/account.go b/management/server/account.go index cd0c933f0..bb9c47b22 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -51,6 +51,8 @@ const ( peerSchedulerRetryInterval = 3 * time.Second emptyUserID = "empty user ID in claims" errorGettingDomainAccIDFmt = "error getting account ID by private domain: %v" + + envNewNetworkMapBuilder = "NB_EXPERIMENT_NETWORK_MAP" ) type userLoggedInOnce bool @@ -104,6 +106,10 @@ type DefaultAccountManager struct { updateAccountPeersBufferInterval atomic.Int64 disableDefaultPolicy bool + + holder *types.Holder + + expNewNetworkMap bool } func isUniqueConstraintError(err error) bool { @@ -191,6 +197,12 @@ func BuildManager( log.WithContext(ctx).Debugf("took %v to instantiate account manager", time.Since(start)) }() + newNetworkMapBuilder, err := strconv.ParseBool(os.Getenv(envNewNetworkMapBuilder)) + if err != nil { + log.WithContext(ctx).Warnf("failed to parse %s, using default value false: %v", envNewNetworkMapBuilder, err) + newNetworkMapBuilder = false + } + am := &DefaultAccountManager{ Store: store, geo: geo, @@ -211,6 +223,9 @@ func BuildManager( settingsManager: settingsManager, permissionsManager: permissionsManager, disableDefaultPolicy: disableDefaultPolicy, + holder: types.NewHolder(), + + expNewNetworkMap: newNetworkMapBuilder, } am.startWarmup(ctx) diff --git a/management/server/holder.go b/management/server/holder.go new file mode 100644 index 000000000..9f82c138c --- /dev/null +++ b/management/server/holder.go @@ -0,0 +1,16 @@ +package server + +import ( + "github.com/netbirdio/netbird/management/server/types" +) + +func (am *DefaultAccountManager) enrichAccountFromHolder(account *types.Account) { + a := am.holder.GetAccount(account.Id) + if a == nil { + am.holder.AddAccount(account) + return + } + account.NetworkMapCache = a.NetworkMapCache + account.NetworkMapCache.UpdateAccountPointer(account) + am.holder.AddAccount(account) +} diff --git a/management/server/networkmap.go b/management/server/networkmap.go new file mode 100644 index 000000000..b436c2d18 --- /dev/null +++ b/management/server/networkmap.go @@ -0,0 +1,37 @@ +package server + +import ( + "context" + + nbdns "github.com/netbirdio/netbird/dns" + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/telemetry" + "github.com/netbirdio/netbird/management/server/types" +) + +func (am *DefaultAccountManager) getPeerNetworkMapExp( + ctx context.Context, + account *types.Account, + peerId string, + validatedPeers map[string]struct{}, + customZone nbdns.CustomZone, + metrics *telemetry.AccountManagerMetrics, +) *types.NetworkMap { + am.enrichAccountFromHolder(account) + return account.GetPeerNetworkMapExp(ctx, peerId, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), metrics) +} + +func (am *DefaultAccountManager) onPeerAddedUpdNetworkMapCache(account *types.Account, peerId string, validatedPeers map[string]struct{}) { + am.enrichAccountFromHolder(account) + account.OnPeerAddedUpdNetworkMapCache(peerId, validatedPeers) +} + +func (am *DefaultAccountManager) onPeerDeletedUpdNetworkMapCache(account *types.Account, peerId string, validatedPeers map[string]struct{}) { + am.enrichAccountFromHolder(account) + account.OnPeerDeletedUpdNetworkMapCache(peerId, validatedPeers) +} + +func (am *DefaultAccountManager) updatePeerInNetworkMapCache(account *types.Account, peer *nbpeer.Peer) { + am.enrichAccountFromHolder(account) + account.UpdatePeerInNetworkMapCache(peer) +} diff --git a/management/server/peer.go b/management/server/peer.go index 3c40c6bb6..eb64560f0 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -389,6 +389,20 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer storeEvent() } + if updateAccountPeers && am.expNewNetworkMap { + account, err := am.Store.GetAccount(ctx, accountID) + if err != nil { + return err + } + + validatedPeers, err := am.integratedPeerValidator.GetValidatedPeers(ctx, account.Id, maps.Values(account.Groups), maps.Values(account.Peers), account.Settings.Extra) + if err != nil { + return err + } + am.onPeerDeletedUpdNetworkMapCache(account, peerID, validatedPeers) + + } + if updateAccountPeers && userID != activity.SystemInitiator { am.BufferUpdateAccountPeers(ctx, accountID) } @@ -425,7 +439,13 @@ func (am *DefaultAccountManager) GetNetworkMap(ctx context.Context, peerID strin return nil, err } - networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil) + var networkMap *types.NetworkMap + + if am.expNewNetworkMap { + networkMap = am.getPeerNetworkMapExp(ctx, account, peerID, validatedPeers, customZone, nil) + } else { + networkMap = account.GetPeerNetworkMap(ctx, peer.ID, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil) + } proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] if ok { @@ -695,6 +715,19 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s am.StoreEvent(ctx, opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta) + if am.expNewNetworkMap { + account, err := am.Store.GetAccount(ctx, accountID) + if err != nil { + return nil, nil, nil, err + } + + validatedPeers, err := am.integratedPeerValidator.GetValidatedPeers(ctx, account.Id, maps.Values(account.Groups), maps.Values(account.Peers), account.Settings.Extra) + if err != nil { + return nil, nil, nil, err + } + am.onPeerAddedUpdNetworkMapCache(account, newPeer.ID, validatedPeers) + } + if updateAccountPeers { am.BufferUpdateAccountPeers(ctx, accountID) } @@ -783,6 +816,11 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync types.PeerSy } if isStatusChanged || sync.UpdateAccountPeers || (updated && len(postureChecks) > 0) { + account, err := am.Store.GetAccountByPeerID(ctx, peer.ID) + if err != nil { + return nil, nil, nil, err + } + am.updatePeerInNetworkMapCache(account, peer) am.BufferUpdateAccountPeers(ctx, accountID) } @@ -1055,7 +1093,13 @@ func (am *DefaultAccountManager) getValidatedPeerWithMap(ctx context.Context, is return nil, nil, nil, err } - networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), am.metrics.AccountManagerMetrics()) + var networkMap *types.NetworkMap + + if am.expNewNetworkMap { + networkMap = am.getPeerNetworkMapExp(ctx, account, peer.ID, approvedPeersMap, customZone, am.metrics.AccountManagerMetrics()) + } else { + networkMap = account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), am.metrics.AccountManagerMetrics()) + } proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] if ok { @@ -1257,7 +1301,13 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account am.metrics.UpdateChannelMetrics().CountCalcPostureChecksDuration(time.Since(start)) start = time.Now() - remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, p.ID, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics()) + var remotePeerNetworkMap *types.NetworkMap + + if am.expNewNetworkMap { + remotePeerNetworkMap = am.getPeerNetworkMapExp(ctx, account, peer.ID, approvedPeersMap, customZone, am.metrics.AccountManagerMetrics()) + } else { + remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, p.ID, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics()) + } am.metrics.UpdateChannelMetrics().CountCalcPeerNetworkMapDuration(time.Since(start)) start = time.Now() @@ -1366,7 +1416,13 @@ func (am *DefaultAccountManager) UpdateAccountPeer(ctx context.Context, accountI return } - remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, peerId, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics()) + var remotePeerNetworkMap *types.NetworkMap + + if am.expNewNetworkMap { + remotePeerNetworkMap = am.getPeerNetworkMapExp(ctx, account, peer.ID, approvedPeersMap, customZone, am.metrics.AccountManagerMetrics()) + } else { + remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, peerId, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics()) + } proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] if ok { diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 4f6ae500e..2b9de26e3 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -166,6 +166,15 @@ func TestPeer_SessionExpired(t *testing.T) { } func TestAccountManager_GetNetworkMap(t *testing.T) { + testGetNetworkMapGeneral(t) +} + +func TestAccountManager_GetNetworkMap_Experimental(t *testing.T) { + os.Setenv(envNewNetworkMapBuilder, "true") + testGetNetworkMapGeneral(t) +} + +func testGetNetworkMapGeneral(t *testing.T) { manager, err := createManager(t) if err != nil { t.Fatal(err) @@ -1545,6 +1554,7 @@ func Test_RegisterPeerRollbackOnFailure(t *testing.T) { } func Test_LoginPeer(t *testing.T) { + os.Setenv(envNewNetworkMapBuilder, "true") if runtime.GOOS == "windows" { t.Skip("The SQLite store is not properly supported by Windows yet") } diff --git a/management/server/types/account.go b/management/server/types/account.go index 77f920b64..9d8c49d2d 100644 --- a/management/server/types/account.go +++ b/management/server/types/account.go @@ -86,6 +86,8 @@ type Account struct { NetworkRouters []*routerTypes.NetworkRouter `gorm:"foreignKey:AccountID;references:id"` NetworkResources []*resourceTypes.NetworkResource `gorm:"foreignKey:AccountID;references:id"` Onboarding AccountOnboarding `gorm:"foreignKey:AccountID;references:id;constraint:OnDelete:CASCADE"` + + NetworkMapCache *NetworkMapBuilder `gorm:"-"` } // Subclass used in gorm to only load network and not whole account diff --git a/management/server/types/holder.go b/management/server/types/holder.go new file mode 100644 index 000000000..04c019ce8 --- /dev/null +++ b/management/server/types/holder.go @@ -0,0 +1,26 @@ +package types + +import "sync" + +type Holder struct { + mu sync.RWMutex + accounts map[string]*Account +} + +func NewHolder() *Holder { + return &Holder{ + accounts: make(map[string]*Account), + } +} + +func (h *Holder) GetAccount(id string) *Account { + h.mu.RLock() + defer h.mu.RUnlock() + return h.accounts[id] +} + +func (h *Holder) AddAccount(account *Account) { + h.mu.Lock() + defer h.mu.Unlock() + h.accounts[account.Id] = account +} diff --git a/management/server/types/networkmap.go b/management/server/types/networkmap.go index 0627e6be8..e03a8c98e 100644 --- a/management/server/types/networkmap.go +++ b/management/server/types/networkmap.go @@ -841,3 +841,38 @@ func peerIsNameserver(peer *nbpeer.Peer, nsGroup *nbdns.NameServerGroup) bool { } return false } + +func (a *Account) initNetworkMapBuilder(validatedPeers map[string]struct{}) { + if a.NetworkMapCache != nil { + return + } + a.NetworkMapCache = NewNetworkMapBuilder(a, validatedPeers) +} + +func (a *Account) GetPeerNetworkMapExp( + ctx context.Context, + peerID string, + peersCustomZone nbdns.CustomZone, + validatedPeers map[string]struct{}, + resourcePolicies map[string][]*Policy, + routers map[string]map[string]*routerTypes.NetworkRouter, + metrics *telemetry.AccountManagerMetrics, +) *NetworkMap { + a.initNetworkMapBuilder(validatedPeers) + return a.NetworkMapCache.GetPeerNetworkMap(ctx, peerID, peersCustomZone, validatedPeers, resourcePolicies, routers, metrics) +} + +func (a *Account) OnPeerAddedUpdNetworkMapCache(peerId string, validatedPeers map[string]struct{}) error { + a.initNetworkMapBuilder(validatedPeers) + return a.NetworkMapCache.OnPeerAddedIncremental(peerId) +} + +func (a *Account) OnPeerDeletedUpdNetworkMapCache(peerId string, validatedPeers map[string]struct{}) error { + a.initNetworkMapBuilder(validatedPeers) + return a.NetworkMapCache.OnPeerDeleted(peerId) +} + +func (a *Account) UpdatePeerInNetworkMapCache(peer *nbpeer.Peer) { + a.initNetworkMapBuilder(nil) + a.NetworkMapCache.UpdatePeer(peer) +} diff --git a/management/server/types/networkmap_golden_test.go b/management/server/types/networkmap_golden_test.go index 5de6a1307..ffd219e19 100644 --- a/management/server/types/networkmap_golden_test.go +++ b/management/server/types/networkmap_golden_test.go @@ -105,7 +105,7 @@ func TestGetPeerNetworkMap_Golden_New(t *testing.T) { validatedPeersMap[peerID] = struct{}{} } - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) networkMap := builder.GetPeerNetworkMap(ctx, testingPeerID, dns.CustomZone{}, validatedPeersMap, nil, nil, nil) normalizeAndSortNetworkMap(networkMap) @@ -149,7 +149,7 @@ func BenchmarkGetPeerNetworkMap(b *testing.B) { b.ResetTimer() b.Run("new builder", func(b *testing.B) { for range b.N { - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) for _, peerID := range peerIDs { _ = builder.GetPeerNetworkMap(ctx, peerID, dns.CustomZone{}, validatedPeersMap, nil, nil, nil) } @@ -236,7 +236,7 @@ func TestGetPeerNetworkMap_Golden_New_WithOnPeerAdded(t *testing.T) { validatedPeersMap[peerID] = struct{}{} } - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) newPeerID := "peer-new-101" newPeerIP := net.IP{100, 64, 1, 1} @@ -299,7 +299,7 @@ func BenchmarkGetPeerNetworkMap_AfterPeerAdded(b *testing.B) { validatedPeersMap[peerID] = struct{}{} peerIDs = append(peerIDs, peerID) } - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) newPeerID := "peer-new-101" newPeer := &nbpeer.Peer{ ID: newPeerID, @@ -429,7 +429,7 @@ func TestGetPeerNetworkMap_Golden_New_WithOnPeerAddedRouter(t *testing.T) { validatedPeersMap[peerID] = struct{}{} } - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) newRouterID := "peer-new-router-102" newRouterIP := net.IP{100, 64, 1, 2} @@ -507,7 +507,7 @@ func BenchmarkGetPeerNetworkMap_AfterRouterPeerAdded(b *testing.B) { validatedPeersMap[peerID] = struct{}{} peerIDs = append(peerIDs, peerID) } - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) newRouterID := "peer-new-router-102" newRouterIP := net.IP{100, 64, 1, 2} newRouter := &nbpeer.Peer{ @@ -638,7 +638,7 @@ func TestGetPeerNetworkMap_Golden_New_WithOnPeerDeleted(t *testing.T) { validatedPeersMap[peerID] = struct{}{} } - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) deletedPeerID := "peer-25" // devs group peer @@ -764,7 +764,7 @@ func TestGetPeerNetworkMap_Golden_New_WithDeletedRouterPeer(t *testing.T) { validatedPeersMap[peerID] = struct{}{} } - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) deletedRouterID := "peer-75" // router peer @@ -841,7 +841,7 @@ func BenchmarkGetPeerNetworkMap_AfterPeerDeleted(b *testing.B) { }) delete(validatedPeersMap, deletedPeerID) - builder := types.NewImprovedNetworkMapBuilder(account, validatedPeersMap) + builder := types.NewNetworkMapBuilder(account, validatedPeersMap) b.ResetTimer() b.Run("old builder after delete", func(b *testing.B) { diff --git a/management/server/types/networkmapbuilder.go b/management/server/types/networkmapbuilder.go index ba847ac28..47e8ed2fc 100644 --- a/management/server/types/networkmapbuilder.go +++ b/management/server/types/networkmapbuilder.go @@ -6,6 +6,7 @@ import ( "slices" "strings" "sync" + "sync/atomic" "time" log "github.com/sirupsen/logrus" @@ -48,15 +49,14 @@ type PeerRoutesView struct { RouteFirewallRuleIDs []string } -type ImprovedNetworkMapBuilder struct { - account *Account +type NetworkMapBuilder struct { + account atomic.Pointer[Account] cache *NetworkMapCache validatedPeers map[string]struct{} } -func NewImprovedNetworkMapBuilder(account *Account, validatedPeers map[string]struct{}) *ImprovedNetworkMapBuilder { - builder := &ImprovedNetworkMapBuilder{ - account: account, +func NewNetworkMapBuilder(account *Account, validatedPeers map[string]struct{}) *NetworkMapBuilder { + builder := &NetworkMapBuilder{ cache: &NetworkMapCache{ globalRoutes: make(map[route.ID]*route.Route), globalRules: make(map[string]*FirewallRule), @@ -72,45 +72,43 @@ func NewImprovedNetworkMapBuilder(account *Account, validatedPeers map[string]st }, validatedPeers: make(map[string]struct{}), } + builder.account.Store(account) + maps.Copy(builder.validatedPeers, validatedPeers) - for k, v := range validatedPeers { - builder.validatedPeers[k] = v - } - - builder.initialBuild() + builder.initialBuild(account) return builder } -func (b *ImprovedNetworkMapBuilder) initialBuild() { +func (b *NetworkMapBuilder) initialBuild(account *Account) { b.cache.mu.Lock() defer b.cache.mu.Unlock() start := time.Now() - b.buildGlobalIndexes() + b.buildGlobalIndexes(account) - for peerID := range b.account.Peers { - b.buildPeerACLView(peerID) - b.buildPeerRoutesView(peerID) - b.buildPeerDNSView(peerID) + for peerID := range account.Peers { + b.buildPeerACLView(account, peerID) + b.buildPeerRoutesView(account, peerID) + b.buildPeerDNSView(account, peerID) } - log.Debugf("NetworkMapBuilder: Initial build completed in %v for account %s", time.Since(start), b.account.Id) + log.Debugf("NetworkMapBuilder: Initial build completed in %v for account %s", time.Since(start), account.Id) } -func (b *ImprovedNetworkMapBuilder) buildGlobalIndexes() { +func (b *NetworkMapBuilder) buildGlobalIndexes(account *Account) { clear(b.cache.globalPeers) clear(b.cache.groupToPeers) clear(b.cache.peerToGroups) clear(b.cache.policyToRules) clear(b.cache.groupToPolicies) - for id, peer := range b.account.Peers { + for id, peer := range account.Peers { b.cache.globalPeers[id] = peer } - for groupID, group := range b.account.Groups { + for groupID, group := range account.Groups { peersCopy := make([]string, len(group.Peers)) copy(peersCopy, group.Peers) b.cache.groupToPeers[groupID] = peersCopy @@ -120,7 +118,7 @@ func (b *ImprovedNetworkMapBuilder) buildGlobalIndexes() { } } - for _, policy := range b.account.Policies { + for _, policy := range account.Policies { if !policy.Enabled { continue } @@ -147,21 +145,21 @@ func (b *ImprovedNetworkMapBuilder) buildGlobalIndexes() { } } -func (b *ImprovedNetworkMapBuilder) buildPeerACLView(peerID string) { +func (b *NetworkMapBuilder) buildPeerACLView(account *Account, peerID string) { ctx := context.Background() - peer := b.account.GetPeer(peerID) + peer := account.GetPeer(peerID) if peer == nil { return } - allPotentialPeers, firewallRules := b.account.GetPeerConnectionResources(ctx, peer, b.validatedPeers) + allPotentialPeers, firewallRules := account.GetPeerConnectionResources(ctx, peer, b.validatedPeers) - resourceRouters := b.account.GetResourceRoutersMap() - resourcePolicies := b.account.GetResourcePoliciesMap() - isRouter, networkResourcesRoutes, sourcePeers := b.account.GetNetworkResourcesRoutesToSync(ctx, peerID, resourcePolicies, resourceRouters) + resourceRouters := account.GetResourceRoutersMap() + resourcePolicies := account.GetResourcePoliciesMap() + isRouter, networkResourcesRoutes, sourcePeers := account.GetNetworkResourcesRoutesToSync(ctx, peerID, resourcePolicies, resourceRouters) var emptyExpiredPeers []*nbpeer.Peer - finalAllPeers := b.account.addNetworksRoutingPeers( + finalAllPeers := account.addNetworksRoutingPeers( networkResourcesRoutes, peer, allPotentialPeers, @@ -188,9 +186,9 @@ func (b *ImprovedNetworkMapBuilder) buildPeerACLView(peerID string) { b.cache.peerACLs[peerID] = view } -func (b *ImprovedNetworkMapBuilder) buildPeerRoutesView(peerID string) { +func (b *NetworkMapBuilder) buildPeerRoutesView(account *Account, peerID string) { ctx := context.Background() - peer := b.account.GetPeer(peerID) + peer := account.GetPeer(peerID) if peer == nil { return } @@ -201,7 +199,7 @@ func (b *ImprovedNetworkMapBuilder) buildPeerRoutesView(peerID string) { RouteFirewallRuleIDs: make([]string, 0), } - enabledRoutes, disabledRoutes := b.account.getRoutingPeerRoutes(ctx, peerID) + enabledRoutes, disabledRoutes := account.getRoutingPeerRoutes(ctx, peerID) for _, rt := range enabledRoutes { if rt.PeerID != "" && rt.PeerID != peerID { if b.cache.globalPeers[rt.PeerID] == nil { @@ -231,9 +229,9 @@ func (b *ImprovedNetworkMapBuilder) buildPeerRoutesView(peerID string) { continue } - activeRoutes, _ := b.account.getRoutingPeerRoutes(ctx, aclPeerID) - groupFilteredRoutes := b.account.filterRoutesByGroups(activeRoutes, peerGroupsMap) - haFilteredRoutes := b.account.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership) + activeRoutes, _ := account.getRoutingPeerRoutes(ctx, aclPeerID) + groupFilteredRoutes := account.filterRoutesByGroups(activeRoutes, peerGroupsMap) + haFilteredRoutes := account.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership) for _, inheritedRoute := range haFilteredRoutes { b.cache.globalRoutes[inheritedRoute.ID] = inheritedRoute @@ -241,16 +239,16 @@ func (b *ImprovedNetworkMapBuilder) buildPeerRoutesView(peerID string) { } } - resourceRouters := b.account.GetResourceRoutersMap() - resourcePolicies := b.account.GetResourcePoliciesMap() - _, networkResourcesRoutes, _ := b.account.GetNetworkResourcesRoutesToSync(ctx, peerID, resourcePolicies, resourceRouters) + resourceRouters := account.GetResourceRoutersMap() + resourcePolicies := account.GetResourcePoliciesMap() + _, networkResourcesRoutes, _ := account.GetNetworkResourcesRoutesToSync(ctx, peerID, resourcePolicies, resourceRouters) for _, rt := range networkResourcesRoutes { view.NetworkResourceIDs = append(view.NetworkResourceIDs, rt.ID) b.cache.globalRoutes[rt.ID] = rt } - routeFirewallRules := b.account.GetPeerRoutesFirewallRules(ctx, peerID, b.validatedPeers) + routeFirewallRules := account.GetPeerRoutesFirewallRules(ctx, peerID, b.validatedPeers) for _, rule := range routeFirewallRules { ruleID := b.generateRouteFirewallRuleID(rule) view.RouteFirewallRuleIDs = append(view.RouteFirewallRuleIDs, ruleID) @@ -258,7 +256,7 @@ func (b *ImprovedNetworkMapBuilder) buildPeerRoutesView(peerID string) { } if len(networkResourcesRoutes) > 0 { - networkResourceFirewallRules := b.account.GetPeerNetworkResourceFirewallRules(ctx, peer, b.validatedPeers, networkResourcesRoutes, resourcePolicies) + networkResourceFirewallRules := account.GetPeerNetworkResourceFirewallRules(ctx, peer, b.validatedPeers, networkResourcesRoutes, resourcePolicies) for _, rule := range networkResourceFirewallRules { ruleID := b.generateRouteFirewallRuleID(rule) view.RouteFirewallRuleIDs = append(view.RouteFirewallRuleIDs, ruleID) @@ -269,20 +267,24 @@ func (b *ImprovedNetworkMapBuilder) buildPeerRoutesView(peerID string) { b.cache.peerRoutes[peerID] = view } -func (b *ImprovedNetworkMapBuilder) buildPeerDNSView(peerID string) { - dnsManagementStatus := b.account.getPeerDNSManagementStatus(peerID) +func (b *NetworkMapBuilder) buildPeerDNSView(account *Account, peerID string) { + dnsManagementStatus := account.getPeerDNSManagementStatus(peerID) dnsConfig := &nbdns.Config{ ServiceEnable: dnsManagementStatus, } if dnsManagementStatus { - dnsConfig.NameServerGroups = getPeerNSGroups(b.account, peerID) + dnsConfig.NameServerGroups = getPeerNSGroups(account, peerID) } b.cache.peerDNS[peerID] = dnsConfig } -func (b *ImprovedNetworkMapBuilder) GetPeerNetworkMap( +func (b *NetworkMapBuilder) UpdateAccountPointer(account *Account) { + b.account.Store(account) +} + +func (b *NetworkMapBuilder) GetPeerNetworkMap( ctx context.Context, peerID string, peersCustomZone nbdns.CustomZone, @@ -292,10 +294,11 @@ func (b *ImprovedNetworkMapBuilder) GetPeerNetworkMap( metrics *telemetry.AccountManagerMetrics, ) *NetworkMap { start := time.Now() + account := b.account.Load() - peer := b.account.GetPeer(peerID) + peer := account.GetPeer(peerID) if peer == nil { - return &NetworkMap{Network: b.account.Network.Copy()} + return &NetworkMap{Network: account.Network.Copy()} } if !maps.Equal(b.validatedPeers, validatedPeers) { @@ -311,10 +314,10 @@ func (b *ImprovedNetworkMapBuilder) GetPeerNetworkMap( if aclView == nil || routesView == nil || dnsConfig == nil { // log.Warnf("NetworkMapBuilder: Cache miss for peer %s, falling back to original method", peerID) - // return b.account.GetPeerNetworkMap(ctx, peerID, peersCustomZone, validatedPeers, resourcePolicies, routers, metrics) + // return account.GetPeerNetworkMap(ctx, peerID, peersCustomZone, validatedPeers, resourcePolicies, routers, metrics) } - nm := b.assembleNetworkMap(aclView, routesView, dnsConfig, peersCustomZone, validatedPeers) + nm := b.assembleNetworkMap(account, aclView, routesView, dnsConfig, peersCustomZone, validatedPeers) if metrics != nil { objectCount := int64(len(nm.Peers) + len(nm.OfflinePeers) + len(nm.Routes) + len(nm.FirewallRules) + len(nm.RoutesFirewallRules)) @@ -323,14 +326,15 @@ func (b *ImprovedNetworkMapBuilder) GetPeerNetworkMap( if objectCount > 5000 { log.WithContext(ctx).Tracef("account: %s has a total resource count of %d objects from cache", - b.account.Id, objectCount) + account.Id, objectCount) } } return nm } -func (b *ImprovedNetworkMapBuilder) assembleNetworkMap( +func (b *NetworkMapBuilder) assembleNetworkMap( + account *Account, aclView *PeerACLView, routesView *PeerRoutesView, dnsConfig *nbdns.Config, @@ -351,8 +355,8 @@ func (b *ImprovedNetworkMapBuilder) assembleNetworkMap( continue } - expired, _ := peer.LoginExpired(b.account.Settings.PeerLoginExpiration) - if b.account.Settings.PeerLoginExpirationEnabled && expired { + expired, _ := peer.LoginExpired(account.Settings.PeerLoginExpiration) + if account.Settings.PeerLoginExpirationEnabled && expired { expiredPeers = append(expiredPeers, peer) } else { peersToConnect = append(peersToConnect, peer) @@ -389,7 +393,7 @@ func (b *ImprovedNetworkMapBuilder) assembleNetworkMap( return &NetworkMap{ Peers: peersToConnect, - Network: b.account.Network.Copy(), + Network: account.Network.Copy(), Routes: routes, DNSConfig: finalDNSConfig, OfflinePeers: expiredPeers, @@ -398,19 +402,19 @@ func (b *ImprovedNetworkMapBuilder) assembleNetworkMap( } } -func (b *ImprovedNetworkMapBuilder) generateFirewallRuleID(rule *FirewallRule) string { +func (b *NetworkMapBuilder) generateFirewallRuleID(rule *FirewallRule) string { portRange := "" portRange = fmt.Sprintf("%d-%d", rule.PortRange.Start, rule.PortRange.End) return fmt.Sprintf("fw:%s:%s:%d:%s:%s:%s:%s", rule.PolicyID, rule.PeerIP, rule.Direction, rule.Protocol, rule.Action, rule.Port, portRange) } -func (b *ImprovedNetworkMapBuilder) generateRouteFirewallRuleID(rule *RouteFirewallRule) string { +func (b *NetworkMapBuilder) generateRouteFirewallRuleID(rule *RouteFirewallRule) string { return fmt.Sprintf("route-fw:%s:%s:%s:%s:%s:%d", rule.RouteID, rule.Destination, rule.Action, strings.Join(rule.SourceRanges, ","), rule.Protocol, rule.Port) } -// func (b *ImprovedNetworkMapBuilder) estimateMemoryUsage() int64 { +// func (b *NetworkMapBuilder) estimateMemoryUsage() int64 { // b.cache.mu.RLock() // defer b.cache.mu.RUnlock() @@ -449,7 +453,7 @@ func (b *ImprovedNetworkMapBuilder) generateRouteFirewallRuleID(rule *RouteFirew // return estimate // } -func (b *ImprovedNetworkMapBuilder) isPeerInGroups(groupIDs []string, peerGroups []string) bool { +func (b *NetworkMapBuilder) isPeerInGroups(groupIDs []string, peerGroups []string) bool { for _, groupID := range groupIDs { for _, peerGroupID := range peerGroups { if groupID == peerGroupID { @@ -460,8 +464,8 @@ func (b *ImprovedNetworkMapBuilder) isPeerInGroups(groupIDs []string, peerGroups return false } -func (b *ImprovedNetworkMapBuilder) isPeerRouter(peerID string) bool { - for _, r := range b.account.Routes { +func (b *NetworkMapBuilder) isPeerRouter(account *Account, peerID string) bool { + for _, r := range account.Routes { if !r.Enabled { continue } @@ -478,7 +482,7 @@ func (b *ImprovedNetworkMapBuilder) isPeerRouter(peerID string) bool { } } - routers := b.account.GetResourceRoutersMap() + routers := account.GetResourceRoutersMap() for _, networkRouters := range routers { if router, exists := networkRouters[peerID]; exists && router.Enabled { return true @@ -504,8 +508,9 @@ type ViewDelta struct { RemovedRuleIDs []string } -func (b *ImprovedNetworkMapBuilder) OnPeerAddedIncremental(peerID string) error { - peer := b.account.GetPeer(peerID) +func (b *NetworkMapBuilder) OnPeerAddedIncremental(peerID string) error { + account := b.account.Load() + peer := account.GetPeer(peerID) if peer == nil { return fmt.Errorf("peer %s not found in account", peerID) } @@ -517,21 +522,21 @@ func (b *ImprovedNetworkMapBuilder) OnPeerAddedIncremental(peerID string) error b.cache.globalPeers[peerID] = peer - peerGroups := b.updateGroupIndexesForNewPeer(peerID) + peerGroups := b.updateGroupIndexesForNewPeer(account, peerID) - b.buildPeerACLView(peerID) - b.buildPeerRoutesView(peerID) - b.buildPeerDNSView(peerID) + b.buildPeerACLView(account, peerID) + b.buildPeerRoutesView(account, peerID) + b.buildPeerDNSView(account, peerID) - b.incrementalUpdateAffectedPeers(peerID, peerGroups) + b.incrementalUpdateAffectedPeers(account, peerID, peerGroups) return nil } -func (b *ImprovedNetworkMapBuilder) updateGroupIndexesForNewPeer(peerID string) []string { +func (b *NetworkMapBuilder) updateGroupIndexesForNewPeer(account *Account, peerID string) []string { peerGroups := make([]string, 0) - for groupID, group := range b.account.Groups { + for groupID, group := range account.Groups { for _, pid := range group.Peers { if pid == peerID { if !slices.Contains(b.cache.groupToPeers[groupID], peerID) { @@ -547,13 +552,13 @@ func (b *ImprovedNetworkMapBuilder) updateGroupIndexesForNewPeer(peerID string) return peerGroups } -func (b *ImprovedNetworkMapBuilder) incrementalUpdateAffectedPeers(newPeerID string, peerGroups []string) { +func (b *NetworkMapBuilder) incrementalUpdateAffectedPeers(account *Account, newPeerID string, peerGroups []string) { ctx := context.Background() - updates := b.calculateIncrementalUpdates(newPeerID, peerGroups) + updates := b.calculateIncrementalUpdates(account, newPeerID, peerGroups) - if b.isPeerRouter(newPeerID) { - affectedByRoutes := b.findPeersAffectedByNewRouter(ctx, newPeerID, peerGroups) + if b.isPeerRouter(account, newPeerID) { + affectedByRoutes := b.findPeersAffectedByNewRouter(ctx, account, newPeerID, peerGroups) for affectedPeerID := range affectedByRoutes { if affectedPeerID == newPeerID { continue @@ -570,14 +575,14 @@ func (b *ImprovedNetworkMapBuilder) incrementalUpdateAffectedPeers(newPeerID str } for affectedPeerID, delta := range updates { - b.applyDeltaToPeer(affectedPeerID, delta) + b.applyDeltaToPeer(account, affectedPeerID, delta) } } -func (b *ImprovedNetworkMapBuilder) findPeersAffectedByNewRouter(ctx context.Context, newRouterID string, routerGroups []string) map[string]struct{} { +func (b *NetworkMapBuilder) findPeersAffectedByNewRouter(ctx context.Context, account *Account, newRouterID string, routerGroups []string) map[string]struct{} { affected := make(map[string]struct{}) - enabledRoutes, _ := b.account.getRoutingPeerRoutes(ctx, newRouterID) + enabledRoutes, _ := account.getRoutingPeerRoutes(ctx, newRouterID) for _, route := range enabledRoutes { for _, distGroupID := range route.Groups { @@ -601,7 +606,7 @@ func (b *ImprovedNetworkMapBuilder) findPeersAffectedByNewRouter(ctx context.Con } } - for _, route := range b.account.Routes { + for _, route := range account.Routes { if !route.Enabled { continue } @@ -628,7 +633,7 @@ func (b *ImprovedNetworkMapBuilder) findPeersAffectedByNewRouter(ctx context.Con return affected } -func (b *ImprovedNetworkMapBuilder) calculateIncrementalUpdates(newPeerID string, peerGroups []string) map[string]*PeerUpdateDelta { +func (b *NetworkMapBuilder) calculateIncrementalUpdates(account *Account, newPeerID string, peerGroups []string) map[string]*PeerUpdateDelta { updates := make(map[string]*PeerUpdateDelta) ctx := context.Background() @@ -637,7 +642,7 @@ func (b *ImprovedNetworkMapBuilder) calculateIncrementalUpdates(newPeerID string return updates } - for _, policy := range b.account.Policies { + for _, policy := range account.Policies { if !policy.Enabled { continue } @@ -671,12 +676,12 @@ func (b *ImprovedNetworkMapBuilder) calculateIncrementalUpdates(newPeerID string b.calculateRouteFirewallUpdates(newPeerID, newPeer, peerGroups, updates) - b.calculateNetworkResourceFirewallUpdates(ctx, newPeerID, newPeer, peerGroups, updates) + b.calculateNetworkResourceFirewallUpdates(ctx, account, newPeerID, newPeer, peerGroups, updates) return updates } -func (b *ImprovedNetworkMapBuilder) calculateRouteFirewallUpdates( +func (b *NetworkMapBuilder) calculateRouteFirewallUpdates( newPeerID string, newPeer *nbpeer.Peer, peerGroups []string, @@ -710,7 +715,7 @@ func (b *ImprovedNetworkMapBuilder) calculateRouteFirewallUpdates( } } -func (b *ImprovedNetworkMapBuilder) addRouteFirewallUpdate( +func (b *NetworkMapBuilder) addRouteFirewallUpdate( updates map[string]*PeerUpdateDelta, peerID string, routeID string, @@ -737,17 +742,18 @@ func (b *ImprovedNetworkMapBuilder) addRouteFirewallUpdate( }) } -func (b *ImprovedNetworkMapBuilder) calculateNetworkResourceFirewallUpdates( +func (b *NetworkMapBuilder) calculateNetworkResourceFirewallUpdates( ctx context.Context, + account *Account, newPeerID string, newPeer *nbpeer.Peer, peerGroups []string, updates map[string]*PeerUpdateDelta, ) { - resourcePolicies := b.account.GetResourcePoliciesMap() - routers := b.account.GetResourceRoutersMap() + resourcePolicies := account.GetResourcePoliciesMap() + routers := account.GetResourceRoutersMap() - for _, resource := range b.account.NetworkResources { + for _, resource := range account.NetworkResources { if !resource.Enabled { continue } @@ -763,7 +769,7 @@ func (b *ImprovedNetworkMapBuilder) calculateNetworkResourceFirewallUpdates( sourceGroups := policy.SourceGroups() for _, sourceGroup := range sourceGroups { if slices.Contains(peerGroups, sourceGroup) { - if b.account.validatePostureChecksOnPeer(ctx, policy.SourcePostureChecks, newPeerID) { + if account.validatePostureChecksOnPeer(ctx, policy.SourcePostureChecks, newPeerID) { peerHasAccess = true break } @@ -824,7 +830,7 @@ type RouteFirewallRuleUpdate struct { AddSourceIP string } -func (b *ImprovedNetworkMapBuilder) addUpdateForPeersInGroups( +func (b *NetworkMapBuilder) addUpdateForPeersInGroups( updates map[string]*PeerUpdateDelta, groupIDs []string, newPeerID string, @@ -885,7 +891,7 @@ func (b *ImprovedNetworkMapBuilder) addUpdateForPeersInGroups( } } -func (b *ImprovedNetworkMapBuilder) applyDeltaToPeer(peerID string, delta *PeerUpdateDelta) { +func (b *NetworkMapBuilder) applyDeltaToPeer(account *Account, peerID string, delta *PeerUpdateDelta) { if delta.AddConnectedPeer != "" || len(delta.AddFirewallRules) > 0 { if aclView := b.cache.peerACLs[peerID]; aclView != nil { if delta.AddConnectedPeer != "" && !slices.Contains(aclView.ConnectedPeerIDs, delta.AddConnectedPeer) { @@ -903,7 +909,7 @@ func (b *ImprovedNetworkMapBuilder) applyDeltaToPeer(peerID string, delta *PeerU } if delta.RebuildRoutesView { - b.buildPeerRoutesView(peerID) + b.buildPeerRoutesView(account, peerID) } else if len(delta.UpdateRouteFirewallRules) > 0 { if routesView := b.cache.peerRoutes[peerID]; routesView != nil { b.updateRouteFirewallRules(routesView, delta.UpdateRouteFirewallRules) @@ -911,10 +917,10 @@ func (b *ImprovedNetworkMapBuilder) applyDeltaToPeer(peerID string, delta *PeerU } if delta.UpdateDNS { - b.buildPeerDNSView(peerID) + b.buildPeerDNSView(account, peerID) } } -func (b *ImprovedNetworkMapBuilder) updateRouteFirewallRules( +func (b *NetworkMapBuilder) updateRouteFirewallRules( routesView *PeerRoutesView, updates []*RouteFirewallRuleUpdate, ) { @@ -951,10 +957,12 @@ func (b *ImprovedNetworkMapBuilder) updateRouteFirewallRules( } } -func (b *ImprovedNetworkMapBuilder) OnPeerDeleted(peerID string) error { +func (b *NetworkMapBuilder) OnPeerDeleted(peerID string) error { b.cache.mu.Lock() defer b.cache.mu.Unlock() + account := b.account.Load() + deletedPeer := b.cache.globalPeers[peerID] if deletedPeer == nil { return fmt.Errorf("peer %s not found in cache", peerID) @@ -969,15 +977,15 @@ func (b *ImprovedNetworkMapBuilder) OnPeerDeleted(peerID string) error { delete(b.validatedPeers, peerID) routesToDelete := []route.ID{} - for routeID, r := range b.account.Routes { + for routeID, r := range account.Routes { if r.Peer == deletedPeerKey || r.PeerID == peerID { if len(r.PeerGroups) > 0 { newPeerAssigned := false for _, groupID := range r.PeerGroups { - if group := b.account.GetGroup(groupID); group != nil { + if group := account.GetGroup(groupID); group != nil { for _, candidatePeerID := range group.Peers { if candidatePeerID != peerID { - if candidatePeer := b.account.GetPeer(candidatePeerID); candidatePeer != nil { + if candidatePeer := account.GetPeer(candidatePeerID); candidatePeer != nil { r.Peer = candidatePeer.Key r.PeerID = candidatePeerID newPeerAssigned = true @@ -1001,7 +1009,7 @@ func (b *ImprovedNetworkMapBuilder) OnPeerDeleted(peerID string) error { } for _, routeID := range routesToDelete { - delete(b.account.Routes, routeID) + delete(account.Routes, routeID) } delete(b.cache.peerACLs, peerID) @@ -1021,7 +1029,7 @@ func (b *ImprovedNetworkMapBuilder) OnPeerDeleted(peerID string) error { affectedPeers := make(map[string]struct{}) - for _, r := range b.account.Routes { + for _, r := range account.Routes { for _, groupID := range r.Groups { if peers := b.cache.groupToPeers[groupID]; peers != nil { for _, p := range peers { @@ -1043,7 +1051,7 @@ func (b *ImprovedNetworkMapBuilder) OnPeerDeleted(peerID string) error { if affectedPeerID == peerID { continue } - b.buildPeerRoutesView(affectedPeerID) + b.buildPeerRoutesView(account, affectedPeerID) } peerDeletionUpdates := b.findPeersAffectedByDeletedPeerACL(peerID, peerIP) @@ -1058,7 +1066,7 @@ func (b *ImprovedNetworkMapBuilder) OnPeerDeleted(peerID string) error { return nil } -func (b *ImprovedNetworkMapBuilder) findPeersAffectedByDeletedPeerACL( +func (b *NetworkMapBuilder) findPeersAffectedByDeletedPeerACL( deletedPeerID string, peerIP string, ) map[string]*PeerDeletionUpdate { @@ -1102,7 +1110,7 @@ type PeerDeletionUpdate struct { PeerIP string } -func (b *ImprovedNetworkMapBuilder) applyDeletionUpdates(peerID string, updates *PeerDeletionUpdate) { +func (b *NetworkMapBuilder) applyDeletionUpdates(peerID string, updates *PeerDeletionUpdate) { if aclView := b.cache.peerACLs[peerID]; aclView != nil { aclView.ConnectedPeerIDs = slices.DeleteFunc(aclView.ConnectedPeerIDs, func(id string) bool { return id == updates.RemovePeerID @@ -1128,7 +1136,7 @@ func (b *ImprovedNetworkMapBuilder) applyDeletionUpdates(peerID string, updates } } -func (b *ImprovedNetworkMapBuilder) removeIPFromRouteFirewallRules(routesView *PeerRoutesView, peerIP string) { +func (b *NetworkMapBuilder) removeIPFromRouteFirewallRules(routesView *PeerRoutesView, peerIP string) { sourceIPv4 := peerIP + "/32" sourceIPv6 := peerIP + "/128" @@ -1153,7 +1161,7 @@ func (b *ImprovedNetworkMapBuilder) removeIPFromRouteFirewallRules(routesView *P } } -func (b *ImprovedNetworkMapBuilder) cleanupUnusedRules() { +func (b *NetworkMapBuilder) cleanupUnusedRules() { usedFirewallRules := make(map[string]struct{}) usedRouteRules := make(map[string]struct{}) usedRoutes := make(map[route.ID]struct{}) @@ -1195,3 +1203,13 @@ func (b *ImprovedNetworkMapBuilder) cleanupUnusedRules() { } } } + +func (b *NetworkMapBuilder) UpdatePeer(peer *nbpeer.Peer) { + b.cache.mu.Lock() + defer b.cache.mu.Unlock() + peerStored, ok := b.cache.globalPeers[peer.ID] + if !ok { + return + } + *peerStored = *peer +} diff --git a/management/server/types/testdata/networkmap_golden_new_with_onpeeradded.json b/management/server/types/testdata/networkmap_golden_new_with_onpeeradded.json index c5157d7bd..5fb35cdfc 100644 --- a/management/server/types/testdata/networkmap_golden_new_with_onpeeradded.json +++ b/management/server/types/testdata/networkmap_golden_new_with_onpeeradded.json @@ -6064,7 +6064,7 @@ "SSHEnabled": false, "LoginExpirationEnabled": false, "InactivityExpirationEnabled": false, - "LastLogin": "2025-08-21T15:30:55.588569+02:00", + "LastLogin": "2025-09-03T00:23:03.190135+02:00", "CreatedAt": "0001-01-01T00:00:00Z", "Ephemeral": false, "Location": { diff --git a/management/server/types/testdata/networkmap_golden_with_new_peer.json b/management/server/types/testdata/networkmap_golden_with_new_peer.json index 302ac50b3..e41539a98 100644 --- a/management/server/types/testdata/networkmap_golden_with_new_peer.json +++ b/management/server/types/testdata/networkmap_golden_with_new_peer.json @@ -6064,7 +6064,7 @@ "SSHEnabled": false, "LoginExpirationEnabled": false, "InactivityExpirationEnabled": false, - "LastLogin": "2025-08-21T15:30:47.664393+02:00", + "LastLogin": "2025-09-03T00:22:59.223362+02:00", "CreatedAt": "0001-01-01T00:00:00Z", "Ephemeral": false, "Location": {