mirror of
https://github.com/netbirdio/netbird.git
synced 2026-03-31 22:53:53 -04:00
Compare commits
14 Commits
fix/setup-
...
nmap/compa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca432ff681 | ||
|
|
7b5d7aeb2e | ||
|
|
3bdce8d0b6 | ||
|
|
d534ce9dfc | ||
|
|
bbc2b42807 | ||
|
|
db9cc52c96 | ||
|
|
3209b241d9 | ||
|
|
7566afd7d0 | ||
|
|
e93d4132d3 | ||
|
|
21e5e6ddff | ||
|
|
10fb18736b | ||
|
|
942abeca0c | ||
|
|
e184a43e8a | ||
|
|
f33f84299f |
@@ -36,6 +36,10 @@ import (
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
const (
|
||||
compactNetworkMapMinVersion = "v0.61.0" // TODO change to real version
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
repo Repository
|
||||
metrics *metrics
|
||||
@@ -483,6 +487,11 @@ func (c *Controller) getPeerNetworkMapExp(
|
||||
}
|
||||
}
|
||||
|
||||
peer := account.GetPeer(peerId)
|
||||
if peer != nil && supportsCompactNetworkMap(peer) {
|
||||
return account.GetPeerNetworkMapCompactExp(ctx, peerId, customZone, validatedPeers, metrics)
|
||||
}
|
||||
|
||||
return account.GetPeerNetworkMapExp(ctx, peerId, customZone, validatedPeers, metrics)
|
||||
}
|
||||
|
||||
@@ -622,6 +631,19 @@ func (c *Controller) StartWarmup(ctx context.Context) {
|
||||
|
||||
}
|
||||
|
||||
func supportsCompactNetworkMap(peer *nbpeer.Peer) bool {
|
||||
if peer.Meta.WtVersion == "development" || peer.Meta.WtVersion == "dev" {
|
||||
return true
|
||||
}
|
||||
|
||||
peerVersion := semver.Canonical("v" + peer.Meta.WtVersion)
|
||||
if peerVersion == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return semver.Compare(peerVersion, compactNetworkMapMinVersion) >= 0
|
||||
}
|
||||
|
||||
// computeForwarderPort checks if all peers in the account have updated to a specific version or newer.
|
||||
// If all peers have the required version, it returns the new well-known port (22054), otherwise returns 0.
|
||||
func computeForwarderPort(peers []*nbpeer.Peer, requiredVersion string) int64 {
|
||||
|
||||
@@ -361,6 +361,122 @@ func (a *Account) GetPeerNetworkMap(
|
||||
return nm
|
||||
}
|
||||
|
||||
// GetPeerNetworkMap returns the networkmap for the given peer ID.
|
||||
func (a *Account) GetPeerNetworkMapCompacted(
|
||||
ctx context.Context,
|
||||
peerID string,
|
||||
peersCustomZone nbdns.CustomZone,
|
||||
validatedPeersMap map[string]struct{},
|
||||
resourcePolicies map[string][]*Policy,
|
||||
routers map[string]map[string]*routerTypes.NetworkRouter,
|
||||
metrics *telemetry.AccountManagerMetrics,
|
||||
groupIDToUserIDs map[string][]string,
|
||||
) *NetworkMap {
|
||||
start := time.Now()
|
||||
peer := a.Peers[peerID]
|
||||
if peer == nil {
|
||||
return &NetworkMap{
|
||||
Network: a.Network.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := validatedPeersMap[peerID]; !ok {
|
||||
return &NetworkMap{
|
||||
Network: a.Network.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
aclPeers, firewallRules, authorizedUsers, enableSSH := a.GetPeerConnectionResources(ctx, peer, validatedPeersMap, groupIDToUserIDs)
|
||||
// exclude expired peers
|
||||
var peersToConnect []*nbpeer.Peer
|
||||
var expiredPeers []*nbpeer.Peer
|
||||
for _, p := range aclPeers {
|
||||
expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration)
|
||||
if a.Settings.PeerLoginExpirationEnabled && expired {
|
||||
expiredPeers = append(expiredPeers, p)
|
||||
continue
|
||||
}
|
||||
peersToConnect = append(peersToConnect, p)
|
||||
}
|
||||
|
||||
routesUpdate := a.GetRoutesToSync(ctx, peerID, peersToConnect)
|
||||
routesFirewallRules := a.GetPeerRoutesFirewallRules(ctx, peerID, validatedPeersMap)
|
||||
isRouter, networkResourcesRoutes, sourcePeers := a.GetNetworkResourcesRoutesToSync(ctx, peerID, resourcePolicies, routers)
|
||||
var networkResourcesFirewallRules []*RouteFirewallRule
|
||||
if isRouter {
|
||||
networkResourcesFirewallRules = a.GetPeerNetworkResourceFirewallRules(ctx, peer, validatedPeersMap, networkResourcesRoutes, resourcePolicies)
|
||||
}
|
||||
peersToConnectIncludingRouters := a.addNetworksRoutingPeers(networkResourcesRoutes, peer, peersToConnect, expiredPeers, isRouter, sourcePeers)
|
||||
|
||||
dnsManagementStatus := a.getPeerDNSManagementStatus(peerID)
|
||||
dnsUpdate := nbdns.Config{
|
||||
ServiceEnable: dnsManagementStatus,
|
||||
}
|
||||
|
||||
if dnsManagementStatus {
|
||||
var zones []nbdns.CustomZone
|
||||
if peersCustomZone.Domain != "" {
|
||||
records := filterZoneRecordsForPeers(peer, peersCustomZone, peersToConnectIncludingRouters, expiredPeers)
|
||||
zones = append(zones, nbdns.CustomZone{
|
||||
Domain: peersCustomZone.Domain,
|
||||
Records: records,
|
||||
})
|
||||
}
|
||||
dnsUpdate.CustomZones = zones
|
||||
dnsUpdate.NameServerGroups = getPeerNSGroups(a, peerID)
|
||||
}
|
||||
type crt struct {
|
||||
route *route.Route
|
||||
peerIds []string
|
||||
}
|
||||
var routes []*route.Route
|
||||
rtfilter := make(map[string]crt)
|
||||
otherRoutesIDs := slices.Concat(networkResourcesRoutes, routesUpdate)
|
||||
for _, route := range otherRoutesIDs {
|
||||
rid, pid := splitRouteAndPeer(route)
|
||||
if pid == peerID || len(pid) == 0 {
|
||||
routes = append(routes, route)
|
||||
continue
|
||||
}
|
||||
crt := rtfilter[rid]
|
||||
crt.peerIds = append(crt.peerIds, pid)
|
||||
crt.route = route.CopyClean()
|
||||
rtfilter[rid] = crt
|
||||
}
|
||||
|
||||
for rid, crt := range rtfilter {
|
||||
crt.route.ApplicablePeerIDs = crt.peerIds
|
||||
crt.route.ID = route.ID(rid)
|
||||
routes = append(routes, crt.route)
|
||||
}
|
||||
|
||||
nm := &NetworkMap{
|
||||
Peers: peersToConnectIncludingRouters,
|
||||
Network: a.Network.Copy(),
|
||||
Routes: routes,
|
||||
DNSConfig: dnsUpdate,
|
||||
OfflinePeers: expiredPeers,
|
||||
FirewallRules: firewallRules,
|
||||
RoutesFirewallRules: slices.Concat(networkResourcesFirewallRules, routesFirewallRules),
|
||||
AuthorizedUsers: authorizedUsers,
|
||||
EnableSSH: enableSSH,
|
||||
}
|
||||
|
||||
if metrics != nil {
|
||||
objectCount := int64(len(peersToConnectIncludingRouters) + len(expiredPeers) + len(routesUpdate) + len(networkResourcesRoutes) + len(firewallRules) + +len(networkResourcesFirewallRules) + len(routesFirewallRules))
|
||||
metrics.CountNetworkMapObjects(objectCount)
|
||||
metrics.CountGetPeerNetworkMapDuration(time.Since(start))
|
||||
|
||||
if objectCount > 5000 {
|
||||
log.WithContext(ctx).Tracef("account: %s has a total resource count of %d objects, "+
|
||||
"peers to connect: %d, expired peers: %d, routes: %d, firewall rules: %d, network resources routes: %d, network resources firewall rules: %d, routes firewall rules: %d",
|
||||
a.Id, objectCount, len(peersToConnectIncludingRouters), len(expiredPeers), len(routesUpdate), len(firewallRules), len(networkResourcesRoutes), len(networkResourcesFirewallRules), len(routesFirewallRules))
|
||||
}
|
||||
}
|
||||
|
||||
return nm
|
||||
}
|
||||
|
||||
func (a *Account) addNetworksRoutingPeers(
|
||||
networkResourcesRoutes []*route.Route,
|
||||
peer *nbpeer.Peer,
|
||||
|
||||
417
management/server/types/account_components.go
Normal file
417
management/server/types/account_components.go
Normal file
@@ -0,0 +1,417 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
func (a *Account) GetPeerNetworkMapComponents(
|
||||
ctx context.Context,
|
||||
peerID string,
|
||||
peersCustomZone nbdns.CustomZone,
|
||||
validatedPeersMap map[string]struct{},
|
||||
resourcePolicies map[string][]*Policy,
|
||||
routers map[string]map[string]*routerTypes.NetworkRouter,
|
||||
groupIDToUserIDs map[string][]string,
|
||||
) *NetworkMapComponents {
|
||||
|
||||
peer := a.Peers[peerID]
|
||||
if peer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := validatedPeersMap[peerID]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
components := &NetworkMapComponents{
|
||||
PeerID: peerID,
|
||||
Serial: a.Network.Serial,
|
||||
Network: a.Network.Copy(),
|
||||
// Peers: make(map[string]*nbpeer.Peer, len(a.Peers)/4),
|
||||
// Groups: make(map[string]*Group, len(a.Groups)/4),
|
||||
// Policies: make([]*Policy, 0, len(a.Policies)/4),
|
||||
// Routes: make([]*route.Route, 0, len(a.Routes)/4),
|
||||
NameServerGroups: make([]*nbdns.NameServerGroup, 0),
|
||||
CustomZoneDomain: peersCustomZone.Domain,
|
||||
AllDNSRecords: peersCustomZone.Records,
|
||||
ResourcePoliciesMap: make(map[string][]*Policy),
|
||||
RoutersMap: make(map[string]map[string]*routerTypes.NetworkRouter),
|
||||
NetworkResources: make([]*resourceTypes.NetworkResource, 0),
|
||||
GroupIDToUserIDs: groupIDToUserIDs,
|
||||
AllowedUserIDs: a.getAllowedUserIDs(),
|
||||
}
|
||||
|
||||
components.AccountSettings = &AccountSettingsInfo{
|
||||
PeerLoginExpirationEnabled: a.Settings.PeerLoginExpirationEnabled,
|
||||
PeerLoginExpiration: a.Settings.PeerLoginExpiration,
|
||||
PeerInactivityExpirationEnabled: a.Settings.PeerInactivityExpirationEnabled,
|
||||
PeerInactivityExpiration: a.Settings.PeerInactivityExpiration,
|
||||
}
|
||||
|
||||
components.DNSSettings = &a.DNSSettings
|
||||
|
||||
relevantPeers, relevantGroups, relevantPolicies, relevantRoutes := a.getPeersGroupsPoliciesRoutes(ctx, peerID, validatedPeersMap)
|
||||
|
||||
components.Peers = relevantPeers
|
||||
components.Groups = relevantGroups
|
||||
components.Policies = relevantPolicies
|
||||
components.Routes = relevantRoutes
|
||||
|
||||
for _, nsGroup := range a.NameServerGroups {
|
||||
if nsGroup.Enabled {
|
||||
for _, gID := range nsGroup.Groups {
|
||||
if _, found := relevantGroups[gID]; found {
|
||||
components.NameServerGroups = append(components.NameServerGroups, nsGroup.Copy())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relevantResourceIDs := make(map[string]struct{}, len(a.NetworkResources))
|
||||
relevantNetworkIDs := make(map[string]struct{}, len(a.NetworkResources))
|
||||
|
||||
for _, resource := range a.NetworkResources {
|
||||
if !resource.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
policies, exists := resourcePolicies[resource.ID]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
isRelevant := false
|
||||
isRouter := false
|
||||
|
||||
networkRoutingPeers, routerExists := routers[resource.NetworkID]
|
||||
if routerExists {
|
||||
if _, ok := networkRoutingPeers[peerID]; ok {
|
||||
isRelevant = true
|
||||
isRouter = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, policy := range policies {
|
||||
var peers []string
|
||||
if policy.Rules[0].SourceResource.Type == ResourceTypePeer && policy.Rules[0].SourceResource.ID != "" {
|
||||
peers = []string{policy.Rules[0].SourceResource.ID}
|
||||
} else {
|
||||
peers = a.getUniquePeerIDsFromGroupsIDs(ctx, policy.SourceGroups())
|
||||
}
|
||||
|
||||
if isRouter {
|
||||
for _, pID := range a.getPostureValidPeers(peers, policy.SourcePostureChecks) {
|
||||
if _, exists := components.Peers[pID]; !exists {
|
||||
components.Peers[pID] = a.GetPeer(pID)
|
||||
}
|
||||
}
|
||||
} else if !isRelevant {
|
||||
for _, p := range peers {
|
||||
if p == peerID && a.validatePostureChecksOnPeer(ctx, policy.SourcePostureChecks, peerID) {
|
||||
isRelevant = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isRelevant {
|
||||
relevantResourceIDs[resource.ID] = struct{}{}
|
||||
relevantNetworkIDs[resource.NetworkID] = struct{}{}
|
||||
components.NetworkResources = append(components.NetworkResources, resource)
|
||||
}
|
||||
}
|
||||
|
||||
for resID, policies := range resourcePolicies {
|
||||
if _, isRelevant := relevantResourceIDs[resID]; !isRelevant {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, p := range policies {
|
||||
for _, rule := range p.Rules {
|
||||
for _, srcGroupID := range rule.Sources {
|
||||
if g := a.Groups[srcGroupID]; g != nil {
|
||||
if _, exists := components.Groups[srcGroupID]; !exists {
|
||||
components.Groups[srcGroupID] = g
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, dstGroupID := range rule.Destinations {
|
||||
if g := a.Groups[dstGroupID]; g != nil {
|
||||
if _, exists := components.Groups[dstGroupID]; !exists {
|
||||
components.Groups[dstGroupID] = g
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
components.ResourcePoliciesMap[resID] = policies
|
||||
}
|
||||
|
||||
for networkID, networkRouters := range routers {
|
||||
if _, isRelevant := relevantNetworkIDs[networkID]; !isRelevant {
|
||||
continue
|
||||
}
|
||||
|
||||
components.RoutersMap[networkID] = networkRouters
|
||||
for peerIDKey := range networkRouters {
|
||||
if _, exists := components.Peers[peerIDKey]; !exists {
|
||||
if p := a.Peers[peerIDKey]; p != nil {
|
||||
components.Peers[peerIDKey] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for groupID, groupInfo := range components.Groups {
|
||||
needsFiltering := false
|
||||
for _, pid := range groupInfo.Peers {
|
||||
if _, exists := components.Peers[pid]; !exists {
|
||||
needsFiltering = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !needsFiltering {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredPeers := make([]string, 0, len(groupInfo.Peers))
|
||||
for _, pid := range groupInfo.Peers {
|
||||
if _, exists := components.Peers[pid]; exists {
|
||||
filteredPeers = append(filteredPeers, pid)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filteredPeers) == 0 {
|
||||
delete(components.Groups, groupID)
|
||||
} else {
|
||||
groupInfo.Peers = filteredPeers
|
||||
components.Groups[groupID] = groupInfo
|
||||
}
|
||||
}
|
||||
|
||||
return components
|
||||
}
|
||||
|
||||
func (a *Account) getPeersGroupsPoliciesRoutes(
|
||||
ctx context.Context,
|
||||
peerID string,
|
||||
validatedPeersMap map[string]struct{},
|
||||
) (map[string]*nbpeer.Peer, map[string]*Group, []*Policy, []*route.Route) {
|
||||
relevantPeerIDs := make(map[string]*nbpeer.Peer, len(a.Peers)/4)
|
||||
relevantGroupIDs := make(map[string]*Group, len(a.Groups)/4)
|
||||
relevantPolicies := make([]*Policy, 0, len(a.Policies))
|
||||
relevantRoutes := make([]*route.Route, 0, len(a.Routes))
|
||||
|
||||
relevantPeerIDs[peerID] = a.GetPeer(peerID)
|
||||
|
||||
for groupID, group := range a.Groups {
|
||||
for _, pid := range group.Peers {
|
||||
if pid == peerID {
|
||||
relevantGroupIDs[groupID] = a.GetGroup(groupID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, policy := range a.Policies {
|
||||
if !policy.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
policyRelevant := false
|
||||
for _, rule := range policy.Rules {
|
||||
if !rule.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
var sourcePeers, destinationPeers []string
|
||||
var peerInSources, peerInDestinations bool
|
||||
|
||||
if rule.SourceResource.Type == ResourceTypePeer && rule.SourceResource.ID != "" {
|
||||
sourcePeers = []string{rule.SourceResource.ID}
|
||||
if rule.SourceResource.ID == peerID {
|
||||
peerInSources = true
|
||||
}
|
||||
} else {
|
||||
sourcePeers, peerInSources = a.getPeersFromGroups(ctx, rule.Sources, peerID, policy.SourcePostureChecks, validatedPeersMap)
|
||||
}
|
||||
|
||||
if rule.DestinationResource.Type == ResourceTypePeer && rule.DestinationResource.ID != "" {
|
||||
destinationPeers = []string{rule.DestinationResource.ID}
|
||||
if rule.DestinationResource.ID == peerID {
|
||||
peerInDestinations = true
|
||||
}
|
||||
} else {
|
||||
destinationPeers, peerInDestinations = a.getPeersFromGroups(ctx, rule.Destinations, peerID, nil, validatedPeersMap)
|
||||
}
|
||||
|
||||
if peerInSources {
|
||||
policyRelevant = true
|
||||
for _, pid := range destinationPeers {
|
||||
relevantPeerIDs[pid] = a.GetPeer(pid)
|
||||
}
|
||||
for _, dstGroupID := range rule.Destinations {
|
||||
relevantGroupIDs[dstGroupID] = a.GetGroup(dstGroupID)
|
||||
}
|
||||
}
|
||||
|
||||
if peerInDestinations {
|
||||
policyRelevant = true
|
||||
for _, pid := range sourcePeers {
|
||||
relevantPeerIDs[pid] = a.GetPeer(pid)
|
||||
}
|
||||
for _, srcGroupID := range rule.Sources {
|
||||
relevantGroupIDs[srcGroupID] = a.GetGroup(srcGroupID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if policyRelevant {
|
||||
relevantPolicies = append(relevantPolicies, policy)
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range a.Routes {
|
||||
isRelevant := false
|
||||
|
||||
for _, groupID := range r.Groups {
|
||||
if _, found := relevantGroupIDs[groupID]; found {
|
||||
isRelevant = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if r.Peer == peerID || r.PeerID == peerID {
|
||||
isRelevant = true
|
||||
}
|
||||
|
||||
for _, groupID := range r.PeerGroups {
|
||||
if group := a.Groups[groupID]; group != nil {
|
||||
for _, pid := range group.Peers {
|
||||
if pid == peerID {
|
||||
isRelevant = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isRelevant {
|
||||
for _, groupID := range r.Groups {
|
||||
relevantGroupIDs[groupID] = a.GetGroup(groupID)
|
||||
}
|
||||
for _, groupID := range r.PeerGroups {
|
||||
relevantGroupIDs[groupID] = a.GetGroup(groupID)
|
||||
}
|
||||
for _, groupID := range r.AccessControlGroups {
|
||||
relevantGroupIDs[groupID] = a.GetGroup(groupID)
|
||||
}
|
||||
|
||||
if r.Peer != "" {
|
||||
relevantPeerIDs[r.Peer] = a.GetPeer(r.Peer)
|
||||
}
|
||||
if r.PeerID != "" {
|
||||
relevantPeerIDs[r.PeerID] = a.GetPeer(r.PeerID)
|
||||
}
|
||||
|
||||
relevantRoutes = append(relevantRoutes, r)
|
||||
}
|
||||
}
|
||||
|
||||
return relevantPeerIDs, relevantGroupIDs, relevantPolicies, relevantRoutes
|
||||
}
|
||||
|
||||
func (a *Account) getPeersFromGroups(ctx context.Context, groups []string, peerID string, sourcePostureChecksIDs []string, validatedPeersMap map[string]struct{}) ([]string, bool) {
|
||||
peerInGroups := false
|
||||
uniquePeerIDs := a.getUniquePeerIDsFromGroupsIDs(ctx, groups)
|
||||
filteredPeerIDs := make([]string, 0, len(uniquePeerIDs))
|
||||
|
||||
for _, p := range uniquePeerIDs {
|
||||
peer, ok := a.Peers[p]
|
||||
if !ok || peer == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
isValid := a.validatePostureChecksOnPeer(ctx, sourcePostureChecksIDs, peer.ID)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := validatedPeersMap[peer.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if peer.ID == peerID {
|
||||
peerInGroups = true
|
||||
continue
|
||||
}
|
||||
|
||||
filteredPeerIDs = append(filteredPeerIDs, peer.ID)
|
||||
}
|
||||
|
||||
return filteredPeerIDs, peerInGroups
|
||||
}
|
||||
|
||||
func (a *Account) isPolicyRelevantForPeer(ctx context.Context, policy *Policy, peerID string, relevantGroupIDs map[string]struct{}) bool {
|
||||
for _, rule := range policy.Rules {
|
||||
for _, srcGroupID := range rule.Sources {
|
||||
if _, found := relevantGroupIDs[srcGroupID]; found {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, dstGroupID := range rule.Destinations {
|
||||
if _, found := relevantGroupIDs[dstGroupID]; found {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if rule.SourceResource.Type == ResourceTypePeer && rule.SourceResource.ID == peerID {
|
||||
return true
|
||||
}
|
||||
|
||||
if rule.DestinationResource.Type == ResourceTypePeer && rule.DestinationResource.ID == peerID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *Account) isRouteRelevantForPeer(ctx context.Context, r *route.Route, peerID string, relevantGroupIDs map[string]struct{}) bool {
|
||||
if r.Peer == peerID || r.PeerID == peerID {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, groupID := range r.Groups {
|
||||
if _, found := relevantGroupIDs[groupID]; found {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, groupID := range r.PeerGroups {
|
||||
if group := a.Groups[groupID]; group != nil {
|
||||
for _, pid := range group.Peers {
|
||||
if pid == peerID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, groupID := range r.AccessControlGroups {
|
||||
if _, found := relevantGroupIDs[groupID]; found {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -40,6 +40,10 @@ type FirewallRule struct {
|
||||
|
||||
// PortRange represents the range of ports for a firewall rule
|
||||
PortRange RulePortRange
|
||||
|
||||
PeerIPs []string
|
||||
Ports []string
|
||||
PortRanges []RulePortRange
|
||||
}
|
||||
|
||||
// Equal checks if two firewall rules are equal.
|
||||
|
||||
@@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
@@ -51,6 +52,135 @@ func (nm *NetworkMap) Merge(other *NetworkMap) {
|
||||
nm.ForwardingRules = util.MergeUnique(nm.ForwardingRules, other.ForwardingRules)
|
||||
}
|
||||
|
||||
func (nm *NetworkMap) UncompactRoutes() {
|
||||
peers := make(map[string]*nbpeer.Peer, len(nm.Peers)+len(nm.OfflinePeers))
|
||||
for _, p := range nm.Peers {
|
||||
peers[p.ID] = p
|
||||
}
|
||||
uncompactedRoutes := make([]*route.Route, 0)
|
||||
for _, compactRoute := range nm.Routes {
|
||||
if len(compactRoute.ApplicablePeerIDs) == 0 {
|
||||
uncompactedRoutes = append(uncompactedRoutes, compactRoute.Copy())
|
||||
continue
|
||||
}
|
||||
|
||||
for _, peerID := range compactRoute.ApplicablePeerIDs {
|
||||
expandedRoute := compactRoute.Copy()
|
||||
expandedRoute.ID = route.ID(string(compactRoute.ID) + ":" + peerID)
|
||||
peer := peers[peerID]
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
expandedRoute.Peer = peer.Key
|
||||
expandedRoute.PeerID = peerID
|
||||
uncompactedRoutes = append(uncompactedRoutes, expandedRoute)
|
||||
}
|
||||
}
|
||||
|
||||
nm.Routes = uncompactedRoutes
|
||||
}
|
||||
|
||||
func (nm *NetworkMap) UncompactFirewallRules() {
|
||||
uncompactedRules := make([]*FirewallRule, 0, len(nm.FirewallRules)*2)
|
||||
|
||||
for _, compactRule := range nm.FirewallRules {
|
||||
if len(compactRule.PeerIPs) == 0 {
|
||||
uncompactedRules = append(uncompactedRules, compactRule)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, peerIP := range compactRule.PeerIPs {
|
||||
if len(compactRule.Ports) > 0 {
|
||||
for _, port := range compactRule.Ports {
|
||||
expandedRule := &FirewallRule{
|
||||
PolicyID: compactRule.PolicyID,
|
||||
PeerIP: peerIP,
|
||||
Direction: compactRule.Direction,
|
||||
Action: compactRule.Action,
|
||||
Protocol: compactRule.Protocol,
|
||||
Port: port,
|
||||
}
|
||||
uncompactedRules = append(uncompactedRules, expandedRule)
|
||||
}
|
||||
} else if len(compactRule.PortRanges) > 0 {
|
||||
for _, portRange := range compactRule.PortRanges {
|
||||
expandedRule := &FirewallRule{
|
||||
PolicyID: compactRule.PolicyID,
|
||||
PeerIP: peerIP,
|
||||
Direction: compactRule.Direction,
|
||||
Action: compactRule.Action,
|
||||
Protocol: compactRule.Protocol,
|
||||
PortRange: portRange,
|
||||
}
|
||||
uncompactedRules = append(uncompactedRules, expandedRule)
|
||||
}
|
||||
} else {
|
||||
expandedRule := &FirewallRule{
|
||||
PolicyID: compactRule.PolicyID,
|
||||
PeerIP: peerIP,
|
||||
Direction: compactRule.Direction,
|
||||
Action: compactRule.Action,
|
||||
Protocol: compactRule.Protocol,
|
||||
Port: compactRule.Port,
|
||||
PortRange: compactRule.PortRange,
|
||||
}
|
||||
uncompactedRules = append(uncompactedRules, expandedRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nm.FirewallRules = uncompactedRules
|
||||
}
|
||||
|
||||
func (nm *NetworkMap) ValidateApplicablePeerIDs(compactNm *NetworkMap, expectedPermsMap map[string]map[string]bool) error {
|
||||
if compactNm == nil {
|
||||
return fmt.Errorf("compact network map is nil")
|
||||
}
|
||||
|
||||
peerIDSet := make(map[string]struct{})
|
||||
for _, peer := range nm.Peers {
|
||||
peerIDSet[peer.ID] = struct{}{}
|
||||
}
|
||||
|
||||
for _, route := range compactNm.Routes {
|
||||
if len(route.ApplicablePeerIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, peerID := range route.ApplicablePeerIDs {
|
||||
if _, exists := peerIDSet[peerID]; !exists {
|
||||
return fmt.Errorf("route %s has applicable peer ID %s that doesn't exist in peer list", route.ID, peerID)
|
||||
}
|
||||
}
|
||||
|
||||
if expectedPermsMap != nil {
|
||||
expected, hasExpected := expectedPermsMap[string(route.ID)]
|
||||
if hasExpected {
|
||||
expectedPeerIDs := make(map[string]struct{})
|
||||
for peerID, shouldAccess := range expected {
|
||||
if shouldAccess {
|
||||
expectedPeerIDs[peerID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(route.ApplicablePeerIDs) != len(expectedPeerIDs) {
|
||||
return fmt.Errorf("route %s: expected %d applicable peers, got %d",
|
||||
route.ID, len(expectedPeerIDs), len(route.ApplicablePeerIDs))
|
||||
}
|
||||
|
||||
for _, peerID := range route.ApplicablePeerIDs {
|
||||
if _, expected := expectedPeerIDs[peerID]; !expected {
|
||||
return fmt.Errorf("route %s: peer %s should not have access but is in ApplicablePeerIDs",
|
||||
route.ID, peerID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeUniquePeersByID(peers1, peers2 []*nbpeer.Peer) []*nbpeer.Peer {
|
||||
result := make(map[string]*nbpeer.Peer)
|
||||
for _, peer := range peers1 {
|
||||
|
||||
@@ -32,6 +32,17 @@ func (a *Account) GetPeerNetworkMapExp(
|
||||
return a.NetworkMapCache.GetPeerNetworkMap(ctx, peerID, peersCustomZone, validatedPeers, metrics)
|
||||
}
|
||||
|
||||
func (a *Account) GetPeerNetworkMapCompactExp(
|
||||
ctx context.Context,
|
||||
peerID string,
|
||||
peersCustomZone nbdns.CustomZone,
|
||||
validatedPeers map[string]struct{},
|
||||
metrics *telemetry.AccountManagerMetrics,
|
||||
) *NetworkMap {
|
||||
a.initNetworkMapBuilder(validatedPeers)
|
||||
return a.NetworkMapCache.GetPeerNetworkMapCompact(ctx, peerID, peersCustomZone, validatedPeers, metrics)
|
||||
}
|
||||
|
||||
func (a *Account) OnPeerAddedUpdNetworkMapCache(peerId string) error {
|
||||
if a.NetworkMapCache == nil {
|
||||
return nil
|
||||
|
||||
788
management/server/types/networkmap_comparison_test.go
Normal file
788
management/server/types/networkmap_comparison_test.go
Normal file
@@ -0,0 +1,788 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/dns"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
||||
networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/posture"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
func TestNetworkMapComponents_CompareWithLegacy(t *testing.T) {
|
||||
account := createTestAccount()
|
||||
ctx := context.Background()
|
||||
|
||||
peerID := testingPeerID
|
||||
validatedPeersMap := make(map[string]struct{})
|
||||
for i := range numPeers {
|
||||
pid := fmt.Sprintf("peer-%d", i)
|
||||
if pid == offlinePeerID {
|
||||
continue
|
||||
}
|
||||
validatedPeersMap[pid] = struct{}{}
|
||||
}
|
||||
|
||||
peersCustomZone := nbdns.CustomZone{}
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
|
||||
legacyNetworkMap := account.GetPeerNetworkMap(
|
||||
ctx,
|
||||
peerID,
|
||||
peersCustomZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
nil,
|
||||
groupIDToUserIDs,
|
||||
)
|
||||
|
||||
components := account.GetPeerNetworkMapComponents(
|
||||
ctx,
|
||||
peerID,
|
||||
peersCustomZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
groupIDToUserIDs,
|
||||
)
|
||||
|
||||
if components == nil {
|
||||
t.Fatal("GetPeerNetworkMapComponents returned nil")
|
||||
}
|
||||
|
||||
newNetworkMap := CalculateNetworkMapFromComponents(ctx, components)
|
||||
|
||||
if newNetworkMap == nil {
|
||||
t.Fatal("CalculateNetworkMapFromComponents returned nil")
|
||||
}
|
||||
|
||||
compareNetworkMaps(t, legacyNetworkMap, newNetworkMap)
|
||||
}
|
||||
|
||||
func TestNetworkMapComponents_GoldenFileComparison(t *testing.T) {
|
||||
account := createTestAccount()
|
||||
ctx := context.Background()
|
||||
|
||||
peerID := testingPeerID
|
||||
validatedPeersMap := make(map[string]struct{})
|
||||
for i := range numPeers {
|
||||
pid := fmt.Sprintf("peer-%d", i)
|
||||
if pid == offlinePeerID {
|
||||
continue
|
||||
}
|
||||
validatedPeersMap[pid] = struct{}{}
|
||||
}
|
||||
|
||||
peersCustomZone := nbdns.CustomZone{}
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
|
||||
legacyNetworkMap := account.GetPeerNetworkMap(
|
||||
ctx,
|
||||
peerID,
|
||||
peersCustomZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
nil,
|
||||
groupIDToUserIDs,
|
||||
)
|
||||
|
||||
components := account.GetPeerNetworkMapComponents(
|
||||
ctx,
|
||||
peerID,
|
||||
peersCustomZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
groupIDToUserIDs,
|
||||
)
|
||||
|
||||
require.NotNil(t, components, "GetPeerNetworkMapComponents returned nil")
|
||||
|
||||
newNetworkMap := CalculateNetworkMapFromComponents(ctx, components)
|
||||
require.NotNil(t, newNetworkMap, "CalculateNetworkMapFromComponents returned nil")
|
||||
|
||||
normalizeAndSortNetworkMap(legacyNetworkMap)
|
||||
normalizeAndSortNetworkMap(newNetworkMap)
|
||||
|
||||
componentsJSON, err := json.MarshalIndent(components, "", " ")
|
||||
require.NoError(t, err, "error marshaling components to JSON")
|
||||
|
||||
legacyJSON, err := json.MarshalIndent(legacyNetworkMap, "", " ")
|
||||
require.NoError(t, err, "error marshaling legacy network map to JSON")
|
||||
|
||||
newJSON, err := json.MarshalIndent(newNetworkMap, "", " ")
|
||||
require.NoError(t, err, "error marshaling new network map to JSON")
|
||||
|
||||
goldenDir := filepath.Join("testdata", "comparison")
|
||||
err = os.MkdirAll(goldenDir, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
legacyGoldenPath := filepath.Join(goldenDir, "legacy_networkmap.json")
|
||||
err = os.WriteFile(legacyGoldenPath, legacyJSON, 0644)
|
||||
require.NoError(t, err, "error writing legacy golden file")
|
||||
|
||||
newGoldenPath := filepath.Join(goldenDir, "components_networkmap.json")
|
||||
err = os.WriteFile(newGoldenPath, newJSON, 0644)
|
||||
require.NoError(t, err, "error writing components golden file")
|
||||
|
||||
componentsPath := filepath.Join(goldenDir, "components.json")
|
||||
err = os.WriteFile(componentsPath, componentsJSON, 0644)
|
||||
require.NoError(t, err, "error writing components golden file")
|
||||
|
||||
require.JSONEq(t, string(legacyJSON), string(newJSON),
|
||||
"NetworkMaps from legacy and components approaches do not match.\n"+
|
||||
"Legacy JSON saved to: %s\n"+
|
||||
"Components JSON saved to: %s",
|
||||
legacyGoldenPath, newGoldenPath)
|
||||
|
||||
t.Logf("✅ NetworkMaps are identical")
|
||||
t.Logf(" Legacy NetworkMap: %s", legacyGoldenPath)
|
||||
t.Logf(" Components NetworkMap: %s", newGoldenPath)
|
||||
}
|
||||
|
||||
func normalizeAndSortNetworkMap(nm *NetworkMap) {
|
||||
if nm == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Slice(nm.Peers, func(i, j int) bool {
|
||||
return nm.Peers[i].ID < nm.Peers[j].ID
|
||||
})
|
||||
|
||||
sort.Slice(nm.OfflinePeers, func(i, j int) bool {
|
||||
return nm.OfflinePeers[i].ID < nm.OfflinePeers[j].ID
|
||||
})
|
||||
|
||||
sort.Slice(nm.Routes, func(i, j int) bool {
|
||||
return string(nm.Routes[i].ID) < string(nm.Routes[j].ID)
|
||||
})
|
||||
|
||||
sort.Slice(nm.FirewallRules, func(i, j int) bool {
|
||||
if nm.FirewallRules[i].PeerIP != nm.FirewallRules[j].PeerIP {
|
||||
return nm.FirewallRules[i].PeerIP < nm.FirewallRules[j].PeerIP
|
||||
}
|
||||
if nm.FirewallRules[i].Direction != nm.FirewallRules[j].Direction {
|
||||
return nm.FirewallRules[i].Direction < nm.FirewallRules[j].Direction
|
||||
}
|
||||
return nm.FirewallRules[i].Protocol < nm.FirewallRules[j].Protocol
|
||||
})
|
||||
|
||||
for i := range nm.RoutesFirewallRules {
|
||||
sort.Strings(nm.RoutesFirewallRules[i].SourceRanges)
|
||||
}
|
||||
|
||||
sort.Slice(nm.RoutesFirewallRules, func(i, j int) bool {
|
||||
if nm.RoutesFirewallRules[i].Destination != nm.RoutesFirewallRules[j].Destination {
|
||||
return nm.RoutesFirewallRules[i].Destination < nm.RoutesFirewallRules[j].Destination
|
||||
}
|
||||
|
||||
minLen := len(nm.RoutesFirewallRules[i].SourceRanges)
|
||||
if len(nm.RoutesFirewallRules[j].SourceRanges) < minLen {
|
||||
minLen = len(nm.RoutesFirewallRules[j].SourceRanges)
|
||||
}
|
||||
for k := 0; k < minLen; k++ {
|
||||
if nm.RoutesFirewallRules[i].SourceRanges[k] != nm.RoutesFirewallRules[j].SourceRanges[k] {
|
||||
return nm.RoutesFirewallRules[i].SourceRanges[k] < nm.RoutesFirewallRules[j].SourceRanges[k]
|
||||
}
|
||||
}
|
||||
if len(nm.RoutesFirewallRules[i].SourceRanges) != len(nm.RoutesFirewallRules[j].SourceRanges) {
|
||||
return len(nm.RoutesFirewallRules[i].SourceRanges) < len(nm.RoutesFirewallRules[j].SourceRanges)
|
||||
}
|
||||
|
||||
return string(nm.RoutesFirewallRules[i].RouteID) < string(nm.RoutesFirewallRules[j].RouteID)
|
||||
})
|
||||
|
||||
if nm.DNSConfig.CustomZones != nil {
|
||||
for i := range nm.DNSConfig.CustomZones {
|
||||
sort.Slice(nm.DNSConfig.CustomZones[i].Records, func(a, b int) bool {
|
||||
return nm.DNSConfig.CustomZones[i].Records[a].Name < nm.DNSConfig.CustomZones[i].Records[b].Name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compareNetworkMaps(t *testing.T, legacy, new *NetworkMap) {
|
||||
t.Helper()
|
||||
|
||||
if legacy.Network.Serial != new.Network.Serial {
|
||||
t.Errorf("Network Serial mismatch: legacy=%d, new=%d", legacy.Network.Serial, new.Network.Serial)
|
||||
}
|
||||
|
||||
if len(legacy.Peers) != len(new.Peers) {
|
||||
t.Errorf("Peers count mismatch: legacy=%d, new=%d", len(legacy.Peers), len(new.Peers))
|
||||
}
|
||||
|
||||
legacyPeerIDs := make(map[string]bool)
|
||||
for _, p := range legacy.Peers {
|
||||
legacyPeerIDs[p.ID] = true
|
||||
}
|
||||
|
||||
for _, p := range new.Peers {
|
||||
if !legacyPeerIDs[p.ID] {
|
||||
t.Errorf("New NetworkMap contains peer %s not in legacy", p.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(legacy.OfflinePeers) != len(new.OfflinePeers) {
|
||||
t.Errorf("OfflinePeers count mismatch: legacy=%d, new=%d", len(legacy.OfflinePeers), len(new.OfflinePeers))
|
||||
}
|
||||
|
||||
if len(legacy.FirewallRules) != len(new.FirewallRules) {
|
||||
t.Logf("FirewallRules count mismatch: legacy=%d, new=%d", len(legacy.FirewallRules), len(new.FirewallRules))
|
||||
}
|
||||
|
||||
if len(legacy.Routes) != len(new.Routes) {
|
||||
t.Logf("Routes count mismatch: legacy=%d, new=%d", len(legacy.Routes), len(new.Routes))
|
||||
}
|
||||
|
||||
if len(legacy.RoutesFirewallRules) != len(new.RoutesFirewallRules) {
|
||||
t.Logf("RoutesFirewallRules count mismatch: legacy=%d, new=%d", len(legacy.RoutesFirewallRules), len(new.RoutesFirewallRules))
|
||||
}
|
||||
|
||||
if legacy.DNSConfig.ServiceEnable != new.DNSConfig.ServiceEnable {
|
||||
t.Errorf("DNSConfig.ServiceEnable mismatch: legacy=%v, new=%v", legacy.DNSConfig.ServiceEnable, new.DNSConfig.ServiceEnable)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
numPeers = 100
|
||||
devGroupID = "group-dev"
|
||||
opsGroupID = "group-ops"
|
||||
allGroupID = "group-all"
|
||||
routeID = route.ID("route-main")
|
||||
routeHA1ID = route.ID("route-ha-1")
|
||||
routeHA2ID = route.ID("route-ha-2")
|
||||
policyIDDevOps = "policy-dev-ops"
|
||||
policyIDAll = "policy-all"
|
||||
policyIDPosture = "policy-posture"
|
||||
policyIDDrop = "policy-drop"
|
||||
postureCheckID = "posture-check-ver"
|
||||
networkResourceID = "res-database"
|
||||
networkID = "net-database"
|
||||
networkRouterID = "router-database"
|
||||
nameserverGroupID = "ns-group-main"
|
||||
testingPeerID = "peer-60"
|
||||
expiredPeerID = "peer-98"
|
||||
offlinePeerID = "peer-99"
|
||||
routingPeerID = "peer-95"
|
||||
testAccountID = "account-comparison-test"
|
||||
)
|
||||
|
||||
func createTestAccount() *Account {
|
||||
peers := make(map[string]*nbpeer.Peer)
|
||||
devGroupPeers, opsGroupPeers, allGroupPeers := []string{}, []string{}, []string{}
|
||||
|
||||
for i := range numPeers {
|
||||
peerID := fmt.Sprintf("peer-%d", i)
|
||||
ip := net.IP{100, 64, 0, byte(i + 1)}
|
||||
wtVersion := "0.25.0"
|
||||
if i%2 == 0 {
|
||||
wtVersion = "0.40.0"
|
||||
}
|
||||
|
||||
p := &nbpeer.Peer{
|
||||
ID: peerID, IP: ip, Key: fmt.Sprintf("key-%s", peerID), DNSLabel: fmt.Sprintf("peer%d", i+1),
|
||||
Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now()},
|
||||
UserID: "user-admin", Meta: nbpeer.PeerSystemMeta{WtVersion: wtVersion, GoOS: "linux"},
|
||||
}
|
||||
|
||||
if peerID == expiredPeerID {
|
||||
p.LoginExpirationEnabled = true
|
||||
pastTimestamp := time.Now().Add(-2 * time.Hour)
|
||||
p.LastLogin = &pastTimestamp
|
||||
}
|
||||
|
||||
peers[peerID] = p
|
||||
allGroupPeers = append(allGroupPeers, peerID)
|
||||
if i < numPeers/2 {
|
||||
devGroupPeers = append(devGroupPeers, peerID)
|
||||
} else {
|
||||
opsGroupPeers = append(opsGroupPeers, peerID)
|
||||
}
|
||||
}
|
||||
|
||||
groups := map[string]*Group{
|
||||
allGroupID: {ID: allGroupID, Name: "All", Peers: allGroupPeers},
|
||||
devGroupID: {ID: devGroupID, Name: "Developers", Peers: devGroupPeers},
|
||||
opsGroupID: {ID: opsGroupID, Name: "Operations", Peers: opsGroupPeers},
|
||||
}
|
||||
|
||||
policies := []*Policy{
|
||||
{
|
||||
ID: policyIDAll, Name: "Default-Allow", Enabled: true,
|
||||
Rules: []*PolicyRule{{
|
||||
ID: policyIDAll, Name: "Allow All", Enabled: true, Action: PolicyTrafficActionAccept,
|
||||
Protocol: PolicyRuleProtocolALL, Bidirectional: true,
|
||||
Sources: []string{allGroupID}, Destinations: []string{allGroupID},
|
||||
}},
|
||||
},
|
||||
{
|
||||
ID: policyIDDevOps, Name: "Dev to Ops Web Access", Enabled: true,
|
||||
Rules: []*PolicyRule{{
|
||||
ID: policyIDDevOps, Name: "Dev -> Ops (HTTP Range)", Enabled: true, Action: PolicyTrafficActionAccept,
|
||||
Protocol: PolicyRuleProtocolTCP, Bidirectional: false,
|
||||
PortRanges: []RulePortRange{{Start: 8080, End: 8090}},
|
||||
Sources: []string{devGroupID}, Destinations: []string{opsGroupID},
|
||||
}},
|
||||
},
|
||||
{
|
||||
ID: policyIDDrop, Name: "Drop DB traffic", Enabled: true,
|
||||
Rules: []*PolicyRule{{
|
||||
ID: policyIDDrop, Name: "Drop DB", Enabled: true, Action: PolicyTrafficActionDrop,
|
||||
Protocol: PolicyRuleProtocolTCP, Ports: []string{"5432"}, Bidirectional: true,
|
||||
Sources: []string{devGroupID}, Destinations: []string{opsGroupID},
|
||||
}},
|
||||
},
|
||||
{
|
||||
ID: policyIDPosture, Name: "Posture Check for DB Resource", Enabled: true,
|
||||
SourcePostureChecks: []string{postureCheckID},
|
||||
Rules: []*PolicyRule{{
|
||||
ID: policyIDPosture, Name: "Allow DB Access", Enabled: true, Action: PolicyTrafficActionAccept,
|
||||
Protocol: PolicyRuleProtocolALL, Bidirectional: true,
|
||||
Sources: []string{opsGroupID}, DestinationResource: Resource{ID: networkResourceID},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
routes := map[route.ID]*route.Route{
|
||||
routeID: {
|
||||
ID: routeID, Network: netip.MustParsePrefix("192.168.10.0/24"),
|
||||
Peer: peers["peer-75"].Key,
|
||||
PeerID: "peer-75",
|
||||
Description: "Route to internal resource", Enabled: true,
|
||||
PeerGroups: []string{devGroupID, opsGroupID},
|
||||
Groups: []string{devGroupID, opsGroupID},
|
||||
AccessControlGroups: []string{devGroupID},
|
||||
},
|
||||
routeHA1ID: {
|
||||
ID: routeHA1ID, Network: netip.MustParsePrefix("10.10.0.0/16"),
|
||||
Peer: peers["peer-80"].Key,
|
||||
PeerID: "peer-80",
|
||||
Description: "HA Route 1", Enabled: true, Metric: 1000,
|
||||
PeerGroups: []string{allGroupID},
|
||||
Groups: []string{allGroupID},
|
||||
AccessControlGroups: []string{allGroupID},
|
||||
},
|
||||
routeHA2ID: {
|
||||
ID: routeHA2ID, Network: netip.MustParsePrefix("10.10.0.0/16"),
|
||||
Peer: peers["peer-90"].Key,
|
||||
PeerID: "peer-90",
|
||||
Description: "HA Route 2", Enabled: true, Metric: 900,
|
||||
PeerGroups: []string{devGroupID, opsGroupID},
|
||||
Groups: []string{devGroupID, opsGroupID},
|
||||
AccessControlGroups: []string{allGroupID},
|
||||
},
|
||||
}
|
||||
|
||||
account := &Account{
|
||||
Id: testAccountID, Peers: peers, Groups: groups, Policies: policies, Routes: routes,
|
||||
Network: &Network{
|
||||
Identifier: "net-comparison-test", Net: net.IPNet{IP: net.IP{100, 64, 0, 0}, Mask: net.CIDRMask(16, 32)}, Serial: 1,
|
||||
},
|
||||
DNSSettings: DNSSettings{DisabledManagementGroups: []string{opsGroupID}},
|
||||
NameServerGroups: map[string]*nbdns.NameServerGroup{
|
||||
nameserverGroupID: {
|
||||
ID: nameserverGroupID, Name: "Main NS", Enabled: true, Groups: []string{devGroupID},
|
||||
NameServers: []nbdns.NameServer{{IP: netip.MustParseAddr("8.8.8.8"), NSType: nbdns.UDPNameServerType, Port: 53}},
|
||||
},
|
||||
},
|
||||
PostureChecks: []*posture.Checks{
|
||||
{ID: postureCheckID, Name: "Check version", Checks: posture.ChecksDefinition{
|
||||
NBVersionCheck: &posture.NBVersionCheck{MinVersion: "0.26.0"},
|
||||
}},
|
||||
},
|
||||
NetworkResources: []*resourceTypes.NetworkResource{
|
||||
{ID: networkResourceID, NetworkID: networkID, AccountID: testAccountID, Enabled: true, Address: "db.netbird.cloud"},
|
||||
},
|
||||
Networks: []*networkTypes.Network{{ID: networkID, Name: "DB Network", AccountID: testAccountID}},
|
||||
NetworkRouters: []*routerTypes.NetworkRouter{
|
||||
{ID: networkRouterID, NetworkID: networkID, Peer: routingPeerID, Enabled: true, AccountID: testAccountID},
|
||||
},
|
||||
Settings: &Settings{PeerLoginExpirationEnabled: true, PeerLoginExpiration: 1 * time.Hour},
|
||||
}
|
||||
|
||||
for _, p := range account.Policies {
|
||||
p.AccountID = account.Id
|
||||
}
|
||||
for _, r := range account.Routes {
|
||||
r.AccountID = account.Id
|
||||
}
|
||||
|
||||
return account
|
||||
}
|
||||
|
||||
func BenchmarkLegacyNetworkMap(b *testing.B) {
|
||||
account := createTestAccount()
|
||||
ctx := context.Background()
|
||||
peerID := testingPeerID
|
||||
validatedPeersMap := make(map[string]struct{})
|
||||
for i := range numPeers {
|
||||
pid := fmt.Sprintf("peer-%d", i)
|
||||
if pid != offlinePeerID {
|
||||
validatedPeersMap[pid] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
peersCustomZone := nbdns.CustomZone{}
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = account.GetPeerNetworkMap(
|
||||
ctx,
|
||||
peerID,
|
||||
peersCustomZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
nil,
|
||||
groupIDToUserIDs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkComponentsNetworkMap(b *testing.B) {
|
||||
account := createTestAccount()
|
||||
ctx := context.Background()
|
||||
peerID := testingPeerID
|
||||
validatedPeersMap := make(map[string]struct{})
|
||||
for i := range numPeers {
|
||||
pid := fmt.Sprintf("peer-%d", i)
|
||||
if pid != offlinePeerID {
|
||||
validatedPeersMap[pid] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
peersCustomZone := nbdns.CustomZone{}
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
components := account.GetPeerNetworkMapComponents(
|
||||
ctx,
|
||||
peerID,
|
||||
peersCustomZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
groupIDToUserIDs,
|
||||
)
|
||||
_ = CalculateNetworkMapFromComponents(ctx, components)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkComponentsCreation(b *testing.B) {
|
||||
account := createTestAccount()
|
||||
ctx := context.Background()
|
||||
peerID := testingPeerID
|
||||
validatedPeersMap := make(map[string]struct{})
|
||||
for i := range numPeers {
|
||||
pid := fmt.Sprintf("peer-%d", i)
|
||||
if pid != offlinePeerID {
|
||||
validatedPeersMap[pid] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
peersCustomZone := nbdns.CustomZone{}
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = account.GetPeerNetworkMapComponents(
|
||||
ctx,
|
||||
peerID,
|
||||
peersCustomZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
groupIDToUserIDs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCalculationFromComponents(b *testing.B) {
|
||||
account := createTestAccount()
|
||||
ctx := context.Background()
|
||||
peerID := testingPeerID
|
||||
validatedPeersMap := make(map[string]struct{})
|
||||
for i := range numPeers {
|
||||
pid := fmt.Sprintf("peer-%d", i)
|
||||
if pid != offlinePeerID {
|
||||
validatedPeersMap[pid] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
peersCustomZone := nbdns.CustomZone{}
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
|
||||
components := account.GetPeerNetworkMapComponents(
|
||||
ctx,
|
||||
peerID,
|
||||
peersCustomZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
groupIDToUserIDs,
|
||||
)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = CalculateNetworkMapFromComponents(ctx, components)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPeerNetworkMap_ProdAccount_CompareImplementations(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testAccount := loadProdAccountFromJSON(t)
|
||||
|
||||
testingPeerID := "cq3526bl0ubs73bbtpbg"
|
||||
require.Contains(t, testAccount.Peers, testingPeerID, "Testing peer should exist in account")
|
||||
|
||||
validatedPeersMap := make(map[string]struct{})
|
||||
for peerID := range testAccount.Peers {
|
||||
validatedPeersMap[peerID] = struct{}{}
|
||||
}
|
||||
|
||||
resourcePolicies := testAccount.GetResourcePoliciesMap()
|
||||
routers := testAccount.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := testAccount.GetActiveGroupUsers()
|
||||
|
||||
legacyNetworkMap := testAccount.GetPeerNetworkMap(ctx, testingPeerID, dns.CustomZone{}, validatedPeersMap, resourcePolicies, routers, nil, groupIDToUserIDs)
|
||||
require.NotNil(t, legacyNetworkMap, "GetPeerNetworkMap returned nil")
|
||||
|
||||
components := testAccount.GetPeerNetworkMapComponents(ctx, testingPeerID, dns.CustomZone{}, validatedPeersMap, resourcePolicies, routers, groupIDToUserIDs)
|
||||
require.NotNil(t, components, "GetPeerNetworkMapComponents returned nil")
|
||||
|
||||
newNetworkMap := CalculateNetworkMapFromComponents(ctx, components)
|
||||
require.NotNil(t, newNetworkMap, "CalculateNetworkMapFromComponents returned nil")
|
||||
|
||||
normalizeAndSortNetworkMap(legacyNetworkMap)
|
||||
normalizeAndSortNetworkMap(newNetworkMap)
|
||||
|
||||
componentsJSON, err := json.MarshalIndent(components, "", " ")
|
||||
require.NoError(t, err, "error marshaling components to JSON")
|
||||
|
||||
legacyJSON, err := json.MarshalIndent(legacyNetworkMap, "", " ")
|
||||
require.NoError(t, err, "error marshaling legacy network map to JSON")
|
||||
|
||||
newJSON, err := json.MarshalIndent(newNetworkMap, "", " ")
|
||||
require.NoError(t, err, "error marshaling new network map to JSON")
|
||||
|
||||
outputDir := filepath.Join("testdata", fmt.Sprintf("compare_peer_%s", testingPeerID))
|
||||
err = os.MkdirAll(outputDir, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
legacyFilePath := filepath.Join(outputDir, "legacy_networkmap.json")
|
||||
err = os.WriteFile(legacyFilePath, legacyJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
componentsPath := filepath.Join(outputDir, "components.json")
|
||||
err = os.WriteFile(componentsPath, componentsJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
newFilePath := filepath.Join(outputDir, "components_networkmap.json")
|
||||
err = os.WriteFile(newFilePath, newJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("Files saved to:\n Legacy NetworkMap: %s\n Components: %s\n Components NetworkMap: %s",
|
||||
legacyFilePath, componentsPath, newFilePath)
|
||||
|
||||
require.JSONEq(t, string(legacyJSON), string(newJSON),
|
||||
"NetworkMaps from legacy and components approaches do not match for peer %s.\n"+
|
||||
"Legacy JSON saved to: %s\n"+
|
||||
"Components JSON saved to: %s\n"+
|
||||
"Components NetworkMap saved to: %s",
|
||||
testingPeerID, legacyFilePath, componentsPath, newFilePath)
|
||||
|
||||
t.Logf("✅ NetworkMaps are identical for peer %s", testingPeerID)
|
||||
}
|
||||
|
||||
func loadProdAccountFromJSON(t testing.TB) *Account {
|
||||
t.Helper()
|
||||
|
||||
testDataPath := filepath.Join("testdata", "account_cnlf3j3l0ubs738o5d4g.json")
|
||||
data, err := os.ReadFile(testDataPath)
|
||||
require.NoError(t, err, "Failed to read prod account JSON file")
|
||||
|
||||
var account Account
|
||||
err = json.Unmarshal(data, &account)
|
||||
require.NoError(t, err, "Failed to unmarshal prod account")
|
||||
|
||||
if account.Groups == nil {
|
||||
account.Groups = make(map[string]*Group)
|
||||
}
|
||||
if account.Peers == nil {
|
||||
account.Peers = make(map[string]*nbpeer.Peer)
|
||||
}
|
||||
if account.Policies == nil {
|
||||
account.Policies = []*Policy{}
|
||||
}
|
||||
|
||||
return &account
|
||||
}
|
||||
|
||||
func BenchmarkGetPeerNetworkMapCompactCached(b *testing.B) {
|
||||
account := loadProdAccountFromJSON(b)
|
||||
|
||||
ctx := context.Background()
|
||||
validatedPeersMap := make(map[string]struct{}, len(account.Peers))
|
||||
for _, peer := range account.Peers {
|
||||
validatedPeersMap[peer.ID] = struct{}{}
|
||||
}
|
||||
dnsDomain := account.Settings.DNSDomain
|
||||
customZone := account.GetPeersCustomZone(ctx, dnsDomain)
|
||||
|
||||
builder := NewNetworkMapBuilder(account, validatedPeersMap)
|
||||
|
||||
testingPeerID := "d3knp53l0ubs738a3n6g"
|
||||
|
||||
regularNm := builder.GetPeerNetworkMap(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
compactNm := builder.GetPeerNetworkMapCompact(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
compactCachedNm := builder.GetPeerNetworkMapCompactCached(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
|
||||
regularJSON, err := json.Marshal(regularNm)
|
||||
require.NoError(b, err)
|
||||
|
||||
compactJSON, err := json.Marshal(compactNm)
|
||||
require.NoError(b, err)
|
||||
|
||||
compactCachedJSON, err := json.Marshal(compactCachedNm)
|
||||
require.NoError(b, err)
|
||||
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
agUsers := account.GetActiveGroupUsers()
|
||||
components := account.GetPeerNetworkMapComponents(ctx, testingPeerID, customZone, validatedPeersMap, resourcePolicies, routers, agUsers)
|
||||
componentsJSON, err := json.Marshal(components)
|
||||
require.NoError(b, err)
|
||||
|
||||
regularSize := len(regularJSON)
|
||||
compactSize := len(compactJSON)
|
||||
compactCachedSize := len(compactCachedJSON)
|
||||
componentsSize := len(componentsJSON)
|
||||
|
||||
compactSavingsPercent := 100 - int(float64(compactCachedSize)/float64(regularSize)*100)
|
||||
componentsSavingsPercent := 100 - int(float64(componentsSize)/float64(regularSize)*100)
|
||||
|
||||
b.ReportMetric(float64(regularSize), "regular_bytes")
|
||||
b.ReportMetric(float64(compactCachedSize), "compact_cached_bytes")
|
||||
b.ReportMetric(float64(componentsSize), "components_bytes")
|
||||
b.ReportMetric(float64(compactSavingsPercent), "compact_savings_%")
|
||||
b.ReportMetric(float64(componentsSavingsPercent), "components_savings_%")
|
||||
|
||||
b.Logf("========== Network Map Size Comparison ==========")
|
||||
b.Logf("Regular network map: %d bytes", regularSize)
|
||||
b.Logf("Compact network map: %d bytes (-%d%%)", compactSize, 100-int(float64(compactSize)/float64(regularSize)*100))
|
||||
b.Logf("Compact cached network map: %d bytes (-%d%%)", compactCachedSize, compactSavingsPercent)
|
||||
b.Logf("Components: %d bytes (-%d%%)", componentsSize, componentsSavingsPercent)
|
||||
b.Logf("")
|
||||
b.Logf("Bandwidth savings (Compact cached): %d bytes saved (%d%%)", regularSize-compactCachedSize, compactSavingsPercent)
|
||||
b.Logf("Bandwidth savings (Components): %d bytes saved (%d%%)", regularSize-componentsSize, componentsSavingsPercent)
|
||||
b.Logf("=================================================")
|
||||
|
||||
b.Run("Legacy", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = account.GetPeerNetworkMap(ctx, testingPeerID, customZone, validatedPeersMap, resourcePolicies, routers, nil, agUsers)
|
||||
}
|
||||
})
|
||||
b.Run("LegacyCompacted", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = account.GetPeerNetworkMapCompacted(ctx, testingPeerID, customZone, validatedPeersMap, resourcePolicies, routers, nil, agUsers)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("ComponentsNetworkMap", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
components := account.GetPeerNetworkMapComponents(
|
||||
ctx,
|
||||
testingPeerID,
|
||||
customZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
agUsers,
|
||||
)
|
||||
_ = CalculateNetworkMapFromComponents(ctx, components)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("ComponentsCreation", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = account.GetPeerNetworkMapComponents(
|
||||
ctx,
|
||||
testingPeerID,
|
||||
customZone,
|
||||
validatedPeersMap,
|
||||
resourcePolicies,
|
||||
routers,
|
||||
agUsers,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("CalculationFromComponents", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = CalculateNetworkMapFromComponents(ctx, components)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("CachedAsIs", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = builder.GetPeerNetworkMap(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("CachedAsIsAndCompacted", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = builder.GetPeerNetworkMapCompact(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("CachedCompacted", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = builder.GetPeerNetworkMapCompactCached(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
1007
management/server/types/networkmap_components.go
Normal file
1007
management/server/types/networkmap_components.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -636,6 +636,9 @@ func normalizeAndSortNetworkMap(networkMap *types.NetworkMap) {
|
||||
if r1.PeerIP != r2.PeerIP {
|
||||
return r1.PeerIP < r2.PeerIP
|
||||
}
|
||||
if r1.PolicyID != r2.PolicyID {
|
||||
return r1.PolicyID < r2.PolicyID
|
||||
}
|
||||
if r1.Protocol != r2.Protocol {
|
||||
return r1.Protocol < r2.Protocol
|
||||
}
|
||||
@@ -964,3 +967,193 @@ func TestGetPeerNetworkMap_Golden_New_WithOnPeerAddedRouter_Batched(t *testing.T
|
||||
|
||||
require.JSONEq(t, string(expectedJSON), string(jsonData), "network map from NEW builder with OnPeerAdded router does not match golden file")
|
||||
}
|
||||
|
||||
func createAccountFromFile() (*types.Account, error) {
|
||||
accraw := filepath.Join("testdata", "account_cnlf3j3l0ubs738o5d4g.json")
|
||||
data, err := os.ReadFile(accraw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var account types.Account
|
||||
err = json.Unmarshal(data, &account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func TestGetPeerNetworkMapCompact(t *testing.T) {
|
||||
account, err := createAccountFromFile()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
validatedPeersMap := make(map[string]struct{}, len(account.Peers))
|
||||
for _, peer := range account.Peers {
|
||||
validatedPeersMap[peer.ID] = struct{}{}
|
||||
}
|
||||
dnsDomain := account.Settings.DNSDomain
|
||||
customZone := account.GetPeersCustomZone(ctx, dnsDomain)
|
||||
|
||||
builder := types.NewNetworkMapBuilder(account, validatedPeersMap)
|
||||
|
||||
testingPeerID := "d3knp53l0ubs738a3n6g"
|
||||
|
||||
regularNm := builder.GetPeerNetworkMap(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
compactNm := builder.GetPeerNetworkMapCompact(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
|
||||
compactedJSON, err := json.MarshalIndent(compactNm, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
compactedBeforeUncompact := filepath.Join("testdata", "compact_before_uncompact.json")
|
||||
err = os.MkdirAll(filepath.Dir(compactedBeforeUncompact), 0755)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(compactedBeforeUncompact, compactedJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
compactNm.UncompactRoutes()
|
||||
compactNm.UncompactFirewallRules()
|
||||
|
||||
normalizeAndSortNetworkMap(regularNm)
|
||||
normalizeAndSortNetworkMap(compactNm)
|
||||
|
||||
regularJSON, err := json.MarshalIndent(regularNm, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
regularLn := len(regularJSON)
|
||||
compactLn := len(compactedJSON)
|
||||
|
||||
t.Logf("compacted less on %d percents", 100-int32((float32(compactLn)/float32(regularLn))*100))
|
||||
|
||||
regular := filepath.Join("testdata", "regular_nmap.json")
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(regular), 0755)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(regular, regularJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
uncompactedJSON, err := json.MarshalIndent(compactNm, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
uncompacted := filepath.Join("testdata", "compacted_nmap.json")
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(regular), 0755)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(uncompacted, uncompactedJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.JSONEq(t, string(regularJSON), string(uncompactedJSON), "regular and uncompacted network maps should be equal")
|
||||
}
|
||||
|
||||
func BenchmarkGetPeerNetworkMapCompact(b *testing.B) {
|
||||
account, err := createAccountFromFile()
|
||||
require.NoError(b, err)
|
||||
|
||||
ctx := context.Background()
|
||||
validatedPeersMap := make(map[string]struct{}, len(account.Peers))
|
||||
for _, peer := range account.Peers {
|
||||
validatedPeersMap[peer.ID] = struct{}{}
|
||||
}
|
||||
dnsDomain := account.Settings.DNSDomain
|
||||
customZone := account.GetPeersCustomZone(ctx, dnsDomain)
|
||||
|
||||
builder := types.NewNetworkMapBuilder(account, validatedPeersMap)
|
||||
|
||||
testingPeerID := "d3knp53l0ubs738a3n6g"
|
||||
|
||||
regularNm := builder.GetPeerNetworkMap(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
compactNm := builder.GetPeerNetworkMapCompact(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
|
||||
regularJSON, err := json.Marshal(regularNm)
|
||||
require.NoError(b, err)
|
||||
|
||||
compactJSON, err := json.Marshal(compactNm)
|
||||
require.NoError(b, err)
|
||||
|
||||
regularSize := len(regularJSON)
|
||||
compactSize := len(compactJSON)
|
||||
savingsPercent := 100 - int(float64(compactSize)/float64(regularSize)*100)
|
||||
|
||||
b.ReportMetric(float64(regularSize), "regular_bytes")
|
||||
b.ReportMetric(float64(compactSize), "compact_bytes")
|
||||
b.ReportMetric(float64(savingsPercent), "savings_%")
|
||||
|
||||
b.Logf("Regular network map: %d bytes", regularSize)
|
||||
b.Logf("Compact network map: %d bytes", compactSize)
|
||||
b.Logf("Data savings: %d%% (%d bytes saved)", savingsPercent, regularSize-compactSize)
|
||||
|
||||
b.Run("Regular", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = builder.GetPeerNetworkMap(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Compact", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = builder.GetPeerNetworkMapCompact(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPeerNetworkMapCompactCached(t *testing.T) {
|
||||
account, err := createAccountFromFile()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
validatedPeersMap := make(map[string]struct{}, len(account.Peers))
|
||||
for _, peer := range account.Peers {
|
||||
validatedPeersMap[peer.ID] = struct{}{}
|
||||
}
|
||||
dnsDomain := account.Settings.DNSDomain
|
||||
customZone := account.GetPeersCustomZone(ctx, dnsDomain)
|
||||
|
||||
builder := types.NewNetworkMapBuilder(account, validatedPeersMap)
|
||||
|
||||
testingPeerID := "d3knp53l0ubs738a3n6g"
|
||||
|
||||
regularNm := builder.GetPeerNetworkMap(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
compactCachedNm := builder.GetPeerNetworkMapCompactCached(ctx, testingPeerID, customZone, validatedPeersMap, nil)
|
||||
|
||||
compactedJSON, err := json.MarshalIndent(compactCachedNm, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
compactedBeforeUncompact := filepath.Join("testdata", "compact_cached_before_uncompact.json")
|
||||
err = os.MkdirAll(filepath.Dir(compactedBeforeUncompact), 0755)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(compactedBeforeUncompact, compactedJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
compactCachedNm.UncompactRoutes()
|
||||
compactCachedNm.UncompactFirewallRules()
|
||||
|
||||
normalizeAndSortNetworkMap(regularNm)
|
||||
normalizeAndSortNetworkMap(compactCachedNm)
|
||||
|
||||
regularJSON, err := json.MarshalIndent(regularNm, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
regularLn := len(regularJSON)
|
||||
compactLn := len(compactedJSON)
|
||||
|
||||
t.Logf("compacted less on %d percents", 100-int32((float32(compactLn)/float32(regularLn))*100))
|
||||
|
||||
regular := filepath.Join("testdata", "regular_nmap_cached.json")
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(regular), 0755)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(regular, regularJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
uncompactedJSON, err := json.MarshalIndent(compactCachedNm, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
uncompacted := filepath.Join("testdata", "compacted_cached_nmap.json")
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(regular), 0755)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(uncompacted, uncompactedJSON, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.JSONEq(t, string(regularJSON), string(uncompactedJSON), "regular and uncompacted network maps should be equal")
|
||||
}
|
||||
|
||||
@@ -45,10 +45,12 @@ type NetworkMapCache struct {
|
||||
groupToRoutes map[string][]*route.Route
|
||||
peerToRoutes map[string][]*route.Route
|
||||
|
||||
peerACLs map[string]*PeerACLView
|
||||
peerRoutes map[string]*PeerRoutesView
|
||||
peerDNS map[string]*nbdns.Config
|
||||
peerSSH map[string]*PeerSSHView
|
||||
peerACLs map[string]*PeerACLView
|
||||
peerRoutes map[string]*PeerRoutesView
|
||||
peerDNS map[string]*nbdns.Config
|
||||
peerFirewallRulesCompact map[string][]*FirewallRule
|
||||
peerRoutesCompact map[string][]*route.Route
|
||||
peerSSH map[string]*PeerSSHView
|
||||
|
||||
groupIDToUserIDs map[string][]string
|
||||
allowedUserIDs map[string]struct{}
|
||||
@@ -105,25 +107,27 @@ type addPeerBatch struct {
|
||||
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),
|
||||
globalRouteRules: make(map[string]*RouteFirewallRule),
|
||||
globalPeers: make(map[string]*nbpeer.Peer),
|
||||
groupToPeers: make(map[string][]string),
|
||||
peerToGroups: make(map[string][]string),
|
||||
policyToRules: make(map[string][]*PolicyRule),
|
||||
groupToPolicies: make(map[string][]*Policy),
|
||||
groupToRoutes: make(map[string][]*route.Route),
|
||||
peerToRoutes: make(map[string][]*route.Route),
|
||||
peerACLs: make(map[string]*PeerACLView),
|
||||
peerRoutes: make(map[string]*PeerRoutesView),
|
||||
peerDNS: make(map[string]*nbdns.Config),
|
||||
peerSSH: make(map[string]*PeerSSHView),
|
||||
groupIDToUserIDs: make(map[string][]string),
|
||||
allowedUserIDs: make(map[string]struct{}),
|
||||
globalResources: make(map[string]*resourceTypes.NetworkResource),
|
||||
acgToRoutes: make(map[string]map[route.ID]*RouteOwnerInfo),
|
||||
noACGRoutes: make(map[route.ID]*RouteOwnerInfo),
|
||||
globalRoutes: make(map[route.ID]*route.Route),
|
||||
globalRules: make(map[string]*FirewallRule),
|
||||
globalRouteRules: make(map[string]*RouteFirewallRule),
|
||||
globalPeers: make(map[string]*nbpeer.Peer),
|
||||
groupToPeers: make(map[string][]string),
|
||||
peerToGroups: make(map[string][]string),
|
||||
policyToRules: make(map[string][]*PolicyRule),
|
||||
groupToPolicies: make(map[string][]*Policy),
|
||||
groupToRoutes: make(map[string][]*route.Route),
|
||||
peerToRoutes: make(map[string][]*route.Route),
|
||||
peerACLs: make(map[string]*PeerACLView),
|
||||
peerRoutes: make(map[string]*PeerRoutesView),
|
||||
peerDNS: make(map[string]*nbdns.Config),
|
||||
peerSSH: make(map[string]*PeerSSHView),
|
||||
groupIDToUserIDs: make(map[string][]string),
|
||||
allowedUserIDs: make(map[string]struct{}),
|
||||
peerFirewallRulesCompact: make(map[string][]*FirewallRule),
|
||||
peerRoutesCompact: make(map[string][]*route.Route),
|
||||
globalResources: make(map[string]*resourceTypes.NetworkResource),
|
||||
acgToRoutes: make(map[string]map[route.ID]*RouteOwnerInfo),
|
||||
noACGRoutes: make(map[route.ID]*RouteOwnerInfo),
|
||||
},
|
||||
validatedPeers: make(map[string]struct{}),
|
||||
}
|
||||
@@ -291,6 +295,9 @@ func (b *NetworkMapBuilder) buildPeerACLView(account *Account, peerID string) {
|
||||
}
|
||||
|
||||
b.cache.peerACLs[peerID] = view
|
||||
|
||||
compactedRules := compactFirewallRules(firewallRules)
|
||||
b.cache.peerFirewallRulesCompact[peerID] = compactedRules
|
||||
b.cache.peerSSH[peerID] = &PeerSSHView{
|
||||
EnableSSH: sshEnabled,
|
||||
AuthorizedUsers: authorizedUsers,
|
||||
@@ -735,6 +742,10 @@ func (b *NetworkMapBuilder) buildPeerRoutesView(account *Account, peerID string)
|
||||
}
|
||||
}
|
||||
|
||||
otherRouteIDs := slices.Concat(view.NetworkResourceIDs, view.InheritedRouteIDs)
|
||||
compactedRoutes := b.compactRoutesForPeer(peerID, view.OwnRouteIDs, otherRouteIDs)
|
||||
b.cache.peerRoutesCompact[peerID] = compactedRoutes
|
||||
|
||||
b.cache.peerRoutes[peerID] = view
|
||||
}
|
||||
|
||||
@@ -1112,8 +1123,6 @@ func (b *NetworkMapBuilder) assembleNetworkMap(
|
||||
for _, ruleID := range aclView.FirewallRuleIDs {
|
||||
if rule := b.cache.globalRules[ruleID]; rule != nil {
|
||||
firewallRules = append(firewallRules, rule)
|
||||
} else {
|
||||
log.Debugf("NetworkMapBuilder: peer %s assembling network map has no fwrule %s in globalRules", peer.ID, ruleID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,6 +1162,395 @@ func (b *NetworkMapBuilder) assembleNetworkMap(
|
||||
return nm
|
||||
}
|
||||
|
||||
func (b *NetworkMapBuilder) GetPeerNetworkMapCompactCached(
|
||||
ctx context.Context, peerID string, peersCustomZone nbdns.CustomZone,
|
||||
validatedPeers map[string]struct{}, metrics *telemetry.AccountManagerMetrics,
|
||||
) *NetworkMap {
|
||||
start := time.Now()
|
||||
|
||||
b.cache.mu.RLock()
|
||||
defer b.cache.mu.RUnlock()
|
||||
|
||||
account := b.account
|
||||
|
||||
peer := account.GetPeer(peerID)
|
||||
if peer == nil {
|
||||
return &NetworkMap{Network: account.Network.Copy()}
|
||||
}
|
||||
|
||||
aclView := b.cache.peerACLs[peerID]
|
||||
routesView := b.cache.peerRoutes[peerID]
|
||||
dnsConfig := b.cache.peerDNS[peerID]
|
||||
sshView := b.cache.peerSSH[peerID]
|
||||
|
||||
if aclView == nil || routesView == nil || dnsConfig == nil {
|
||||
return &NetworkMap{Network: account.Network.Copy()}
|
||||
}
|
||||
|
||||
nm := b.assembleNetworkMapCompactCached(account, peer, aclView, routesView, dnsConfig, sshView, peersCustomZone, validatedPeers)
|
||||
|
||||
if metrics != nil {
|
||||
objectCount := int64(len(nm.Peers) + len(nm.OfflinePeers) + len(nm.Routes) + len(nm.FirewallRules) + len(nm.RoutesFirewallRules))
|
||||
metrics.CountNetworkMapObjects(objectCount)
|
||||
metrics.CountGetPeerNetworkMapDuration(time.Since(start))
|
||||
|
||||
if objectCount > 5000 {
|
||||
log.WithContext(ctx).Tracef("account: %s has a total resource count of %d objects from cache",
|
||||
account.Id, objectCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nm
|
||||
}
|
||||
|
||||
func (b *NetworkMapBuilder) assembleNetworkMapCompactCached(
|
||||
account *Account, peer *nbpeer.Peer, aclView *PeerACLView, routesView *PeerRoutesView,
|
||||
dnsConfig *nbdns.Config, sshView *PeerSSHView, customZone nbdns.CustomZone, validatedPeers map[string]struct{},
|
||||
) *NetworkMap {
|
||||
|
||||
var peersToConnect []*nbpeer.Peer
|
||||
var expiredPeers []*nbpeer.Peer
|
||||
|
||||
for _, peerID := range aclView.ConnectedPeerIDs {
|
||||
if _, ok := validatedPeers[peerID]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
peerItem := b.cache.globalPeers[peerID]
|
||||
if peerItem == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
expired, _ := peerItem.LoginExpired(account.Settings.PeerLoginExpiration)
|
||||
if account.Settings.PeerLoginExpirationEnabled && expired {
|
||||
expiredPeers = append(expiredPeers, peerItem)
|
||||
} else {
|
||||
peersToConnect = append(peersToConnect, peerItem)
|
||||
}
|
||||
}
|
||||
|
||||
routes := b.cache.peerRoutesCompact[peer.ID]
|
||||
|
||||
firewallRules := b.cache.peerFirewallRulesCompact[peer.ID]
|
||||
|
||||
var routesFirewallRules []*RouteFirewallRule
|
||||
for _, ruleID := range routesView.RouteFirewallRuleIDs {
|
||||
if rule := b.cache.globalRouteRules[ruleID]; rule != nil {
|
||||
routesFirewallRules = append(routesFirewallRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
finalDNSConfig := *dnsConfig
|
||||
if finalDNSConfig.ServiceEnable && customZone.Domain != "" {
|
||||
var zones []nbdns.CustomZone
|
||||
records := filterZoneRecordsForPeers(peer, customZone, peersToConnect, expiredPeers)
|
||||
zones = append(zones, nbdns.CustomZone{
|
||||
Domain: customZone.Domain,
|
||||
Records: records,
|
||||
})
|
||||
finalDNSConfig.CustomZones = zones
|
||||
}
|
||||
|
||||
nm := &NetworkMap{
|
||||
Peers: peersToConnect,
|
||||
Network: account.Network.Copy(),
|
||||
Routes: routes,
|
||||
DNSConfig: finalDNSConfig,
|
||||
OfflinePeers: expiredPeers,
|
||||
FirewallRules: firewallRules,
|
||||
RoutesFirewallRules: routesFirewallRules,
|
||||
}
|
||||
|
||||
if sshView != nil {
|
||||
nm.EnableSSH = sshView.EnableSSH
|
||||
nm.AuthorizedUsers = sshView.AuthorizedUsers
|
||||
}
|
||||
|
||||
return nm
|
||||
}
|
||||
|
||||
func (b *NetworkMapBuilder) GetPeerNetworkMapCompact(
|
||||
ctx context.Context, peerID string, peersCustomZone nbdns.CustomZone,
|
||||
validatedPeers map[string]struct{}, metrics *telemetry.AccountManagerMetrics,
|
||||
) *NetworkMap {
|
||||
start := time.Now()
|
||||
|
||||
b.cache.mu.RLock()
|
||||
defer b.cache.mu.RUnlock()
|
||||
|
||||
account := b.account
|
||||
|
||||
peer := account.GetPeer(peerID)
|
||||
if peer == nil {
|
||||
return &NetworkMap{Network: account.Network.Copy()}
|
||||
}
|
||||
|
||||
aclView := b.cache.peerACLs[peerID]
|
||||
routesView := b.cache.peerRoutes[peerID]
|
||||
dnsConfig := b.cache.peerDNS[peerID]
|
||||
sshView := b.cache.peerSSH[peerID]
|
||||
|
||||
if aclView == nil || routesView == nil || dnsConfig == nil {
|
||||
return &NetworkMap{Network: account.Network.Copy()}
|
||||
}
|
||||
|
||||
nm := b.assembleNetworkMapCompact(account, peer, aclView, routesView, dnsConfig, sshView, peersCustomZone, validatedPeers)
|
||||
|
||||
if metrics != nil {
|
||||
objectCount := int64(len(nm.Peers) + len(nm.OfflinePeers) + len(nm.Routes) + len(nm.FirewallRules) + len(nm.RoutesFirewallRules))
|
||||
metrics.CountNetworkMapObjects(objectCount)
|
||||
metrics.CountGetPeerNetworkMapDuration(time.Since(start))
|
||||
|
||||
if objectCount > 5000 {
|
||||
log.WithContext(ctx).Tracef("account: %s has a total resource count of %d objects from cache",
|
||||
account.Id, objectCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nm
|
||||
}
|
||||
|
||||
func (b *NetworkMapBuilder) assembleNetworkMapCompact(
|
||||
account *Account, peer *nbpeer.Peer, aclView *PeerACLView, routesView *PeerRoutesView,
|
||||
dnsConfig *nbdns.Config, sshView *PeerSSHView, customZone nbdns.CustomZone, validatedPeers map[string]struct{},
|
||||
) *NetworkMap {
|
||||
|
||||
var peersToConnect []*nbpeer.Peer
|
||||
var expiredPeers []*nbpeer.Peer
|
||||
|
||||
for _, peerID := range aclView.ConnectedPeerIDs {
|
||||
if _, ok := validatedPeers[peerID]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
peer := b.cache.globalPeers[peerID]
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
expired, _ := peer.LoginExpired(account.Settings.PeerLoginExpiration)
|
||||
if account.Settings.PeerLoginExpirationEnabled && expired {
|
||||
expiredPeers = append(expiredPeers, peer)
|
||||
} else {
|
||||
peersToConnect = append(peersToConnect, peer)
|
||||
}
|
||||
}
|
||||
|
||||
var routes []*route.Route
|
||||
for _, routeID := range routesView.OwnRouteIDs {
|
||||
if route := b.cache.globalRoutes[routeID]; route != nil {
|
||||
routes = append(routes, route)
|
||||
}
|
||||
}
|
||||
otherRouteIDs := slices.Concat(routesView.NetworkResourceIDs, routesView.InheritedRouteIDs)
|
||||
|
||||
type crt struct {
|
||||
route *route.Route
|
||||
peerIds []string
|
||||
}
|
||||
rtfilter := make(map[string]crt)
|
||||
peerId := peer.ID
|
||||
for _, routeID := range otherRouteIDs {
|
||||
if route := b.cache.globalRoutes[routeID]; route != nil {
|
||||
rid, pid := splitRouteAndPeer(route)
|
||||
if pid == peerId || len(pid) == 0 {
|
||||
routes = append(routes, route)
|
||||
continue
|
||||
}
|
||||
crt := rtfilter[rid]
|
||||
crt.peerIds = append(crt.peerIds, pid)
|
||||
crt.route = route.CopyClean()
|
||||
rtfilter[rid] = crt
|
||||
}
|
||||
}
|
||||
|
||||
for rid, crt := range rtfilter {
|
||||
crt.route.ApplicablePeerIDs = crt.peerIds
|
||||
crt.route.ID = route.ID(rid)
|
||||
routes = append(routes, crt.route)
|
||||
}
|
||||
|
||||
var expandedFirewallRules []*FirewallRule
|
||||
for _, ruleID := range aclView.FirewallRuleIDs {
|
||||
if rule := b.cache.globalRules[ruleID]; rule != nil {
|
||||
expandedFirewallRules = append(expandedFirewallRules, rule)
|
||||
}
|
||||
}
|
||||
firewallRules := compactFirewallRules(expandedFirewallRules)
|
||||
|
||||
var routesFirewallRules []*RouteFirewallRule
|
||||
for _, ruleID := range routesView.RouteFirewallRuleIDs {
|
||||
if rule := b.cache.globalRouteRules[ruleID]; rule != nil {
|
||||
routesFirewallRules = append(routesFirewallRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
finalDNSConfig := *dnsConfig
|
||||
if finalDNSConfig.ServiceEnable && customZone.Domain != "" {
|
||||
var zones []nbdns.CustomZone
|
||||
records := filterZoneRecordsForPeers(peer, customZone, peersToConnect, expiredPeers)
|
||||
zones = append(zones, nbdns.CustomZone{
|
||||
Domain: customZone.Domain,
|
||||
Records: records,
|
||||
})
|
||||
finalDNSConfig.CustomZones = zones
|
||||
}
|
||||
|
||||
nm := &NetworkMap{
|
||||
Peers: peersToConnect,
|
||||
Network: account.Network.Copy(),
|
||||
Routes: routes,
|
||||
DNSConfig: finalDNSConfig,
|
||||
OfflinePeers: expiredPeers,
|
||||
FirewallRules: firewallRules,
|
||||
RoutesFirewallRules: routesFirewallRules,
|
||||
}
|
||||
|
||||
if sshView != nil {
|
||||
nm.EnableSSH = sshView.EnableSSH
|
||||
nm.AuthorizedUsers = sshView.AuthorizedUsers
|
||||
}
|
||||
|
||||
return nm
|
||||
}
|
||||
|
||||
func (b *NetworkMapBuilder) compactRoutesForPeer(peerID string, ownRouteIDs []route.ID, otherRouteIDs []route.ID) []*route.Route {
|
||||
var routes []*route.Route
|
||||
|
||||
for _, routeID := range ownRouteIDs {
|
||||
if rt := b.cache.globalRoutes[routeID]; rt != nil {
|
||||
routes = append(routes, rt)
|
||||
}
|
||||
}
|
||||
|
||||
type crt struct {
|
||||
route *route.Route
|
||||
peerIds []string
|
||||
}
|
||||
rtfilter := make(map[string]crt)
|
||||
|
||||
for _, routeID := range otherRouteIDs {
|
||||
if rt := b.cache.globalRoutes[routeID]; rt != nil {
|
||||
rid, pid := splitRouteAndPeer(rt)
|
||||
if pid == peerID || len(pid) == 0 {
|
||||
routes = append(routes, rt)
|
||||
continue
|
||||
}
|
||||
crt := rtfilter[rid]
|
||||
crt.peerIds = append(crt.peerIds, pid)
|
||||
crt.route = rt.CopyClean()
|
||||
rtfilter[rid] = crt
|
||||
}
|
||||
}
|
||||
|
||||
for rid, crt := range rtfilter {
|
||||
crt.route.ApplicablePeerIDs = crt.peerIds
|
||||
crt.route.ID = route.ID(rid)
|
||||
routes = append(routes, crt.route)
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
func compactFirewallRules(expandedRules []*FirewallRule) []*FirewallRule {
|
||||
type peerKey struct {
|
||||
PolicyID string
|
||||
PeerIP string
|
||||
Direction int
|
||||
Action string
|
||||
Protocol string
|
||||
}
|
||||
|
||||
peerGroups := make(map[peerKey]struct {
|
||||
ports []string
|
||||
portRanges []RulePortRange
|
||||
})
|
||||
|
||||
for _, rule := range expandedRules {
|
||||
key := peerKey{
|
||||
PolicyID: rule.PolicyID,
|
||||
PeerIP: rule.PeerIP,
|
||||
Direction: rule.Direction,
|
||||
Action: rule.Action,
|
||||
Protocol: rule.Protocol,
|
||||
}
|
||||
|
||||
group := peerGroups[key]
|
||||
if rule.Port != "" {
|
||||
group.ports = append(group.ports, rule.Port)
|
||||
}
|
||||
if rule.PortRange.Start != 0 || rule.PortRange.End != 0 {
|
||||
group.portRanges = append(group.portRanges, rule.PortRange)
|
||||
}
|
||||
peerGroups[key] = group
|
||||
}
|
||||
|
||||
type ruleKey struct {
|
||||
PolicyID string
|
||||
Direction int
|
||||
Action string
|
||||
Protocol string
|
||||
PortsSig string
|
||||
RangesSig string
|
||||
}
|
||||
|
||||
ruleGroups := make(map[ruleKey]struct {
|
||||
peerIPs []string
|
||||
ports []string
|
||||
portRanges []RulePortRange
|
||||
})
|
||||
|
||||
for pKey, pGroup := range peerGroups {
|
||||
portsSig := strings.Join(pGroup.ports, ",")
|
||||
rangesSig := fmt.Sprintf("%v", pGroup.portRanges)
|
||||
|
||||
rKey := ruleKey{
|
||||
PolicyID: pKey.PolicyID,
|
||||
Direction: pKey.Direction,
|
||||
Action: pKey.Action,
|
||||
Protocol: pKey.Protocol,
|
||||
PortsSig: portsSig,
|
||||
RangesSig: rangesSig,
|
||||
}
|
||||
|
||||
group := ruleGroups[rKey]
|
||||
group.peerIPs = append(group.peerIPs, pKey.PeerIP)
|
||||
if len(group.ports) == 0 {
|
||||
group.ports = pGroup.ports
|
||||
}
|
||||
if len(group.portRanges) == 0 {
|
||||
group.portRanges = pGroup.portRanges
|
||||
}
|
||||
ruleGroups[rKey] = group
|
||||
}
|
||||
|
||||
compactRules := make([]*FirewallRule, 0, len(ruleGroups))
|
||||
|
||||
for rKey, group := range ruleGroups {
|
||||
compactRule := &FirewallRule{
|
||||
PolicyID: rKey.PolicyID,
|
||||
Direction: rKey.Direction,
|
||||
Action: rKey.Action,
|
||||
Protocol: rKey.Protocol,
|
||||
PeerIPs: group.peerIPs,
|
||||
Ports: group.ports,
|
||||
PortRanges: group.portRanges,
|
||||
}
|
||||
|
||||
compactRules = append(compactRules, compactRule)
|
||||
}
|
||||
|
||||
return compactRules
|
||||
}
|
||||
|
||||
func splitRouteAndPeer(r *route.Route) (string, string) {
|
||||
parts := strings.Split(string(r.ID), ":")
|
||||
if len(parts) < 2 {
|
||||
return string(r.ID), ""
|
||||
}
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
|
||||
func (b *NetworkMapBuilder) generateFirewallRuleID(rule *FirewallRule) string {
|
||||
var s strings.Builder
|
||||
s.WriteString(fw)
|
||||
|
||||
@@ -109,6 +109,9 @@ type Route struct {
|
||||
AccessControlGroups []string `gorm:"serializer:json"`
|
||||
// SkipAutoApply indicates if this exit node route (0.0.0.0/0) should skip auto-application for client routing
|
||||
SkipAutoApply bool
|
||||
// ApplicablePeerIDs is used in compact network maps to indicate which peers this route applies to
|
||||
// When populated, client should use these IDs to reference peers from the Peers array instead of using Peer/PeerID/Groups
|
||||
ApplicablePeerIDs []string `gorm:"-"`
|
||||
}
|
||||
|
||||
// EventMeta returns activity event meta related to the route
|
||||
@@ -144,6 +147,30 @@ func (r *Route) Copy() *Route {
|
||||
return route
|
||||
}
|
||||
|
||||
// CopyClean copies a route object without the peer-specific part of the ID
|
||||
// and peer data
|
||||
func (r *Route) CopyClean() *Route {
|
||||
cleanId := strings.Split(string(r.ID), ":")[0]
|
||||
route := &Route{
|
||||
ID: ID(cleanId),
|
||||
AccountID: r.AccountID,
|
||||
Description: r.Description,
|
||||
NetID: r.NetID,
|
||||
Network: r.Network,
|
||||
Domains: slices.Clone(r.Domains),
|
||||
KeepRoute: r.KeepRoute,
|
||||
NetworkType: r.NetworkType,
|
||||
PeerGroups: slices.Clone(r.PeerGroups),
|
||||
Metric: r.Metric,
|
||||
Masquerade: r.Masquerade,
|
||||
Enabled: r.Enabled,
|
||||
Groups: slices.Clone(r.Groups),
|
||||
AccessControlGroups: slices.Clone(r.AccessControlGroups),
|
||||
SkipAutoApply: r.SkipAutoApply,
|
||||
}
|
||||
return route
|
||||
}
|
||||
|
||||
// Equal compares one route with the other
|
||||
func (r *Route) Equal(other *Route) bool {
|
||||
if r == nil && other == nil {
|
||||
|
||||
Reference in New Issue
Block a user