Merge remote-tracking branch 'origin/revert/component-types' into revert/component-types

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
This commit is contained in:
Dmitri Dolguikh
2026-08-04 13:04:51 +02:00
16 changed files with 300 additions and 143 deletions

View File

@@ -65,7 +65,7 @@ type Controller struct {
perAccountServerSupportedSyncMessageVersions map[string]sharedgrpc.SyncMessageVersion
nmdataStore networkmapdb.NetworkMapDBStore
nmdataStore *networkmapdb.NetworkMapDBStoreImpl
}
type bufferUpdate struct {
@@ -83,7 +83,7 @@ type bufferAffectedUpdate struct {
var _ network_map.Controller = (*Controller)(nil)
func NewController(ctx context.Context, store store.Store, metrics telemetry.AppMetrics, peersUpdateManager network_map.PeersUpdateManager, requestBuffer account.RequestBuffer, integratedPeerValidator integrated_validator.IntegratedValidator, settingsManager settings.Manager, dnsDomain string, proxyController port_forwarding.Controller, ephemeralPeersManager ephemeral.Manager, config *config.Config, nmdataStore networkmapdb.NetworkMapDBStore) *Controller {
func NewController(ctx context.Context, store store.Store, metrics telemetry.AppMetrics, peersUpdateManager network_map.PeersUpdateManager, requestBuffer account.RequestBuffer, integratedPeerValidator integrated_validator.IntegratedValidator, settingsManager settings.Manager, dnsDomain string, proxyController port_forwarding.Controller, ephemeralPeersManager ephemeral.Manager, config *config.Config, nmdataStore *networkmapdb.NetworkMapDBStoreImpl) *Controller {
nMetrics, err := newMetrics(metrics.UpdateChannelMetrics())
if err != nil {
log.Fatal(fmt.Errorf("error creating metrics: %w", err))

View File

@@ -8,9 +8,13 @@ import (
"reflect"
"strings"
"github.com/rs/xid"
"golang.org/x/exp/maps"
"github.com/netbirdio/management-integrations/integrations"
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
"github.com/netbirdio/netbird/shared/management/networkmap"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/rs/xid"
)
const (
@@ -37,7 +41,36 @@ type NetworkMapDBStore interface {
}
type NetworkMapDBStoreImpl struct {
store NetworkMapDBStore
store NetworkMapDBStore
integratedPeerValidator integrated_validator.IntegratedValidator
extraSettingsManager integrations.Manager
}
func NewNetworkMapDBStoreImpl(store NetworkMapDBStore, integratedPeerValidator integrated_validator.IntegratedValidator, extraSettingsManager integrations.Manager) *NetworkMapDBStoreImpl {
return &NetworkMapDBStoreImpl{
store: store,
integratedPeerValidator: integratedPeerValidator,
extraSettingsManager: extraSettingsManager,
}
}
func (s *NetworkMapDBStoreImpl) GetNetworkMapData(ctx context.Context, accountId string) (*networkmap.NetworkMapData, error) {
nmdata, err := s.store.GetNetworkMapData(ctx, accountId)
if err != nil {
return nil, err
}
extraSettings, err := s.extraSettingsManager.GetExtraSettings(ctx, accountId)
if err != nil {
return nil, err
}
nmdata.ValidatedPeers, err = s.integratedPeerValidator.GetValidatedPeers(ctx, accountId, maps.Values(nmdata.Groups), maps.Values(nmdata.Peers), extraSettings)
if err != nil {
return nil, err
}
return nmdata, nil
}
func FromSqlTypesToSharedTypes(src reflect.Value, dst reflect.Value) error {

View File

@@ -49,6 +49,10 @@ func GetAppliedZoneCandidatesViaPgxConnection(ctx context.Context, conn *pgx.Con
toret := make([]networkmap.AppliedZoneCandidate, 0, len(zones))
currentZoneId := ""
for _, z := range zones {
if !z.RecordType.Valid {
continue
}
zone := nmdata.CustomZone{}
err := networkmapdb.FromSqlTypesToSharedTypes(
reflect.ValueOf(&z), reflect.ValueOf(&zone))
@@ -61,33 +65,28 @@ func GetAppliedZoneCandidatesViaPgxConnection(ctx context.Context, conn *pgx.Con
return nil, err
}
if z.Id != currentZoneId {
zone.Records = []nmdata.SimpleRecord{}
toret = append(toret, appliedZoneCandidateFromZone(zone, distributionGroups))
currentZoneId = z.Id
}
rtype, rdata, err := recordTypeAndRdata(z.RecordType.String, z.RecordRData.String)
if err != nil {
if errors.Is(err, DnsUnsupportedRecordTypeError) {
continue
}
return nil, err
}
record := nmdata.SimpleRecord{
lastZone := &toret[len(toret)-1]
lastZone.Zone.Records = append(lastZone.Zone.Records, nmdata.SimpleRecord{
Name: z.RecordName.String,
Class: z.RecordClass.String,
TTL: int(z.RecordTTL.Int64),
RData: rdata,
Type: rtype,
}
zone.Records = []nmdata.SimpleRecord{record}
if len(toret) == 0 {
toret = append(toret, appliedZoneCandidateFromZone(zone, distributionGroups))
currentZoneId = z.Id
continue
}
if z.Id == currentZoneId {
lastZone := &toret[len(toret)-1]
lastZone.Zone.Records = append(lastZone.Zone.Records, record)
continue
}
toret = append(toret, appliedZoneCandidateFromZone(zone, distributionGroups))
currentZoneId = z.Id
})
}
return toret, nil
}

View File

@@ -81,11 +81,10 @@ func (pg *PgStore) GetNetworkMapData(ctx context.Context, accountId string) (*ne
if err != nil {
return rollbackAndReturnError(ctx, tx, err)
}
extraSettings, err := pg.settingsManager.GetExtraSettings(ctx, accountId)
proxyTargetedDomainResourceIDs, err := GetProxyTargetedDomainResourceIDsViaPgxConnection(ctx, tx.Conn(), accountId)
if err != nil {
return rollbackAndReturnError(ctx, tx, err)
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get proxy targeted domain resources: %w", err))
}
validatedPeers, err := pg.integratedPeerValidator.GetValidatedPeers(ctx, accountId, toSliceOfPtrs(groups), toSliceOfPtrs(peers), extraSettings)
resourcePolicies := make(map[string][]*nmdata.Policy)
for _, resource := range networkResources {
@@ -99,7 +98,7 @@ func (pg *PgStore) GetNetworkMapData(ctx context.Context, accountId string) (*ne
}
if _, ok := policyToDestinationResourceIdx[policy.ID][resource.ID]; ok {
resourcePolicies[resource.ID] = append(resourcePolicies[resource.ID], &policy) // TODO (dmitri) maybe use public id?
break
continue
}
if groupIds, ok := policyToDestinationGroupIdx[policy.ID]; ok {
for networkResourceGroup := range networkResourceGroups {
@@ -118,25 +117,25 @@ func (pg *PgStore) GetNetworkMapData(ctx context.Context, accountId string) (*ne
}
toret := networkmap.NetworkMapData{
AccountSettings: &acctSettings,
DNSSettings: &dnsSettings,
Network: &network,
Peers: toMap(peers, func(p nmdata.Peer) string { return p.ID }),
ValidatedPeers: validatedPeers,
Groups: toMap(groups, func(g nmdata.Group) string { return g.PublicID }),
Policies: toSliceOfPtrs(policies),
ResourcePolicies: resourcePolicies,
Routes: toSliceOfPtrs(routes),
Routers: routers,
NameServerGroups: toSliceOfPtrs(nsGroups),
NetworkResources: toSliceOfPtrs(networkResources),
PostureChecks: toMap(postureChecks, func(pc nmdata.PostureChecks) string { return pc.ID }),
AllowedUserIDs: allowedUserIds,
GroupIDToUserIDs: groupsToUserIds,
NetworkXIDToPublicID: networkXIDToPublicID, // TODO (dmitri) maybe we can switch to public ids everywhere?
AppliedZoneCandidates: dnsZones,
PrivateServiceCandidates: buildPrivateServiceCandidates(services, domains, proxyPeers),
PostureCheckXIDToPublicID: postureCheckXIDToPublicID,
AccountSettings: &acctSettings,
DNSSettings: &dnsSettings,
Network: &network,
Peers: toMap(peers, func(p nmdata.Peer) string { return p.ID }),
Groups: toMap(groups, func(g nmdata.Group) string { return g.ID }),
Policies: toSliceOfPtrs(policies),
ResourcePolicies: resourcePolicies,
Routes: toSliceOfPtrs(routes),
Routers: routers,
NameServerGroups: toSliceOfPtrs(nsGroups),
NetworkResources: toSliceOfPtrs(networkResources),
PostureChecks: toMap(postureChecks, func(pc nmdata.PostureChecks) string { return pc.ID }),
AllowedUserIDs: allowedUserIds,
GroupIDToUserIDs: groupsToUserIds,
NetworkXIDToPublicID: networkXIDToPublicID, // TODO (dmitri) maybe we can switch to public ids everywhere?
AppliedZoneCandidates: dnsZones,
PrivateServiceCandidates: buildPrivateServiceCandidates(services, domains, proxyPeers),
PostureCheckXIDToPublicID: postureCheckXIDToPublicID,
ProxyTargetedDomainResourceIDs: proxyTargetedDomainResourceIDs,
}
return &toret, nil
@@ -158,7 +157,7 @@ func toMap[T any](all []T, id func(t T) string) map[string]*T {
}
func toSliceOfPtrs[T any](all []T) []*T {
toret := make([]*T, len(all))
toret := make([]*T, 0, len(all))
for _, t := range all {
toret = append(toret, &t)
}
@@ -198,6 +197,9 @@ func buildPrivateServiceCandidates(svcs []service, domains []domain, proxyPeersB
}
for _, svc := range svcs {
if !svc.Enabled.Bool || !svc.Private.Bool {
continue
}
if len(svc.AccessGroups) == 0 {
continue
}

View File

@@ -65,6 +65,7 @@ func GetNetworkRoutersViaPgxConnection(ctx context.Context, con *pgx.Conn, accou
}
if router.Peer.String != "" {
toret[networkId][router.Peer.String] = &nmdatarouter
continue
}
for _, peerId := range router.PeersViaGroups {
toret[networkId][peerId] = &nmdatarouter

View File

@@ -7,8 +7,6 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
"github.com/netbirdio/netbird/management/server/settings"
)
const (
@@ -21,9 +19,7 @@ const (
var _ networkmapdb.NetworkMapDBStore = &PgStore{}
type PgStore struct {
Pool *pgxpool.Pool
integratedPeerValidator integrated_validator.IntegratedValidator
settingsManager settings.Manager
Pool *pgxpool.Pool
}
func NewPostgresqlStore(ctx context.Context, dsn string) (*PgStore, error) {

View File

@@ -86,11 +86,13 @@ func GetPoliciesViaPgxConnection(ctx context.Context, con *pgx.Conn, accountId s
return toret, nil, nil, err
}
for _, dst := range pr().Destinations {
if _, ok := policyToDestinationGroupIdx[p.ID]; !ok {
policyToDestinationGroupIdx[p.ID] = make(map[string]any)
if p.RuleEnabled.Valid && p.RuleEnabled.Bool {
for _, dst := range pr().Destinations {
if _, ok := policyToDestinationGroupIdx[p.ID]; !ok {
policyToDestinationGroupIdx[p.ID] = make(map[string]any)
}
policyToDestinationGroupIdx[p.ID][dst] = struct{}{}
}
policyToDestinationGroupIdx[p.ID][dst] = struct{}{}
}
}
if len(p.SourceResource) > 0 {
@@ -105,10 +107,12 @@ func GetPoliciesViaPgxConnection(ctx context.Context, con *pgx.Conn, accountId s
return toret, nil, nil, err
}
if _, ok := policyToDestinationResourceIdx[p.ID]; !ok {
policyToDestinationResourceIdx[p.ID] = make(map[string]any)
if p.RuleEnabled.Valid && p.RuleEnabled.Bool {
if _, ok := policyToDestinationResourceIdx[p.ID]; !ok {
policyToDestinationResourceIdx[p.ID] = make(map[string]any)
}
policyToDestinationResourceIdx[p.ID][pr().DestinationResource.ID] = struct{}{}
}
policyToDestinationResourceIdx[p.ID][pr().DestinationResource.ID] = struct{}{}
}
if len(p.Ports) > 0 {
err := json.Unmarshal([]byte(p.Ports), &pr().Ports)

View File

@@ -13,6 +13,14 @@ const (
from services
where account_id=$1
`
GetProxyTargetedDomainResourcesQuery = `
select t.target_id
from targets as t
join services as s on s.id = t.service_id
where s.account_id=$1 and s.enabled and not coalesce(s.terminated, false)
and t.enabled and t.target_type='domain' and t.target_id is not null
`
)
func (pg *PgStore) GetPrivateServices(ctx context.Context, accountId string) ([]service, error) {
@@ -32,6 +40,24 @@ func GetPrivateServicesViaPgxConnection(ctx context.Context, conn *pgx.Conn, acc
return pgx.CollectRows(rows, pgx.RowToStructByName[service])
}
func GetProxyTargetedDomainResourceIDsViaPgxConnection(ctx context.Context, conn *pgx.Conn, accountId string) (map[string]struct{}, error) {
rows, err := conn.Query(ctx, GetProxyTargetedDomainResourcesQuery, accountId)
if err != nil {
return nil, err
}
ids, err := pgx.CollectRows(rows, pgx.RowTo[string])
if err != nil {
return nil, err
}
toret := make(map[string]struct{}, len(ids))
for _, id := range ids {
toret[id] = struct{}{}
}
return toret, nil
}
type service struct {
Enabled sql.NullBool
Private sql.NullBool

View File

@@ -8,10 +8,15 @@ import (
const (
GetAllowedUserIdsQuery = `
select id, array (select json_array_elements_text(auto_groups::json)) as auto_groups
select id, array (select json_array_elements_text(auto_groups::json)) as auto_groups
from users
where account_id=$1 and not blocked and not is_service_user
`
GetAllGroupIdQuery = `
select id from groups
where account_id=$1 and name='All'
`
)
func (pg *PgStore) GetAllowedUsers(ctx context.Context, accountId string) (map[string]struct{}, map[string][]string, error) {
@@ -33,6 +38,19 @@ func GetAllowedUsersViaPgxConnection(ctx context.Context, con *pgx.Conn, account
return nil, nil, err
}
rows, err = con.Query(ctx, GetAllGroupIdQuery, accountId)
if err != nil {
return nil, nil, err
}
allGroupIds, err := pgx.CollectRows(rows, pgx.RowTo[string])
if err != nil {
return nil, nil, err
}
allGroupId := ""
if len(allGroupIds) > 0 {
allGroupId = allGroupIds[0]
}
userIdIdx := make(map[string]struct{})
groupIdToUserIds := make(map[string][]string)
for _, user := range users {
@@ -40,6 +58,9 @@ func GetAllowedUsersViaPgxConnection(ctx context.Context, con *pgx.Conn, account
for _, groupId := range user.AutoGroups {
groupIdToUserIds[groupId] = append(groupIdToUserIds[groupId], user.ID)
}
if allGroupId != "" {
groupIdToUserIds[allGroupId] = append(groupIdToUserIds[allGroupId], user.ID)
}
}
return userIdIdx, groupIdToUserIds, nil

View File

@@ -102,8 +102,8 @@ func (s *BaseServer) Store() store.Store {
})
}
func (s *BaseServer) NetworkMapStore() networkmapdb.NetworkMapDBStore {
return Create(s, func() networkmapdb.NetworkMapDBStore {
func (s *BaseServer) NetworkMapStore() *networkmapdb.NetworkMapDBStoreImpl {
return Create(s, func() *networkmapdb.NetworkMapDBStoreImpl {
dsn := os.Getenv("NETBIRD_NMAP_STORE_DSN") // Todo: this needs to be hoocked up properly
if dsn == "" {
return nil
@@ -114,7 +114,7 @@ func (s *BaseServer) NetworkMapStore() networkmapdb.NetworkMapDBStore {
log.Fatalf("failed to create network map store: %v", err)
}
return store
return networkmapdb.NewNetworkMapDBStoreImpl(store, s.IntegratedValidator(), s.SettingsManager())
})
}

View File

@@ -978,34 +978,6 @@ func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.P
return peers, fwRules, authorizedUsers, sshEnabled
}
// forcesRoutingPeerDNSResolution reports whether the given peer must run
// routing-peer DNS resolution regardless of the account-global
// RoutingPeerDNSResolutionEnabled setting. It returns true when the peer is a
// router for a domain network resource that is targeted by an enabled
// reverse-proxy service, so the peer's DNS forwarder starts and can resolve
// the target for the embedded proxy peers. Embedded proxy peers themselves are
// handled at PeerConfig build time.
func (a *Account) forcesRoutingPeerDNSResolution(peerID string, routers map[string]map[string]*routerTypes.NetworkRouter) bool {
targeted := a.proxyTargetedDomainResourceIDs()
if len(targeted) == 0 {
return false
}
for _, resource := range a.NetworkResources {
if resource == nil || !resource.Enabled || resource.Type != resourceTypes.Domain {
continue
}
if _, ok := targeted[resource.ID]; !ok {
continue
}
if _, isRouter := routers[resource.NetworkID][peerID]; isRouter {
return true
}
}
return false
}
// proxyTargetedDomainResourceIDs returns the set of domain network resource IDs
// targeted by an enabled, non-terminated reverse-proxy service.
func (a *Account) proxyTargetedDomainResourceIDs() map[string]struct{} {

View File

@@ -104,9 +104,5 @@ func (a *Account) GetPeerNetworkMapComponents(
groupIDToUserIDs map[string][]string,
) *NetworkMapComponents {
nmd := a.toNetworkMapData(accountZones, validatedPeersMap, resourcePolicies, routers, groupIDToUserIDs)
components := nmd.GetPeerNetworkMapComponents(peerID, TwinCustomZone(peersCustomZone))
if components != nil {
components.ForceRoutingPeerDNSResolution = a.forcesRoutingPeerDNSResolution(peerID, routers)
}
return components
return nmd.GetPeerNetworkMapComponents(peerID, TwinCustomZone(peersCustomZone))
}

View File

@@ -108,6 +108,7 @@ func (a *Account) toNetworkMapData(
nmd.Routers[networkID] = twinInner
}
nmd.ProxyTargetedDomainResourceIDs = a.proxyTargetedDomainResourceIDs()
nmd.AppliedZoneCandidates = buildAppliedZoneCandidates(accountZones)
nmd.PrivateServiceCandidates = a.buildPrivateServiceCandidates()

View File

@@ -3,10 +3,14 @@
// Main-vs-branch equivalence check. For every peer of every account in a real
// Postgres copy it computes the client-facing proto.NetworkMap twice:
//
// - legacy path: main's Account → NetworkMapComponents → Calculate → proto
// - legacy path: main's Account → NetworkMapComponents → Calculate → proto
// (the frozen copy in this package)
// - new path: this branch's Account → NetworkMapData → components →
// Calculate → ToSyncResponse → proto
// - store path: the pgsql nmdata store's NetworkMapData → components →
// Calculate → ToSyncResponse → proto (no Account involved)
// - account path: Account → toNetworkMapData twins → components → Calculate
// → ToSyncResponse → proto (the in-memory builder, no store queries)
//
// Both new paths are checked against the legacy proto.
//
// proto.NetworkMap is generated code identical in both trees, which is what
// makes it the one usable comparison surface — the intermediate Go types differ
@@ -47,10 +51,12 @@ import (
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
networkmap_pgsql "github.com/netbirdio/netbird/management/internals/network_map_db/pgsql"
mgmtgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
"github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/management/server/types/legacynmap"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/shared/management/proto"
)
@@ -62,7 +68,6 @@ const (
type equivStats struct {
accounts int
peersChecked int
skippedNilNM int
}
func TestNetworkMapProtoEquivalence(t *testing.T) {
@@ -81,6 +86,10 @@ func TestNetworkMapProtoEquivalence(t *testing.T) {
require.NoError(t, err, "connect to postgres")
t.Cleanup(func() { testStore.Close(ctx) })
nmStore, err := networkmap_pgsql.NewPostgresqlStore(ctx, dsn)
require.NoError(t, err, "connect nmdata store")
t.Cleanup(func() { nmStore.Pool.Close() })
accountIDs := equivAccountIDs(t, dsn)
require.NotEmpty(t, accountIDs, "no accounts selected")
@@ -94,7 +103,7 @@ func TestNetworkMapProtoEquivalence(t *testing.T) {
continue
}
checkAccount(ctx, t, account, maxPeers, stats)
checkAccount(ctx, t, nmStore, account, maxPeers, stats)
account = nil
debug.FreeOSMemory()
@@ -106,19 +115,22 @@ func TestNetworkMapProtoEquivalence(t *testing.T) {
}
}
t.Logf("equivalence: accounts=%d peers_checked=%d skipped_nil_nm=%d — no divergence",
stats.accounts, stats.peersChecked, stats.skippedNilNM)
t.Logf("equivalence: accounts=%d peers_checked=%d — no divergence",
stats.accounts, stats.peersChecked)
}
// checkAccount compares both paths for every peer of one account. Nothing is
// retained across peers, so memory stays flat within an account.
func checkAccount(ctx context.Context, t *testing.T, account *types.Account, maxPeers int, stats *equivStats) {
func checkAccount(ctx context.Context, t *testing.T, nmStore *networkmap_pgsql.PgStore, account *types.Account, maxPeers int, stats *equivStats) {
t.Helper()
if len(account.Peers) == 0 {
return
}
nmData, err := nmStore.GetNetworkMapData(ctx, account.Id)
require.NoError(t, err, "account %s: nmdata store load", account.Id)
validated := make(map[string]struct{}, len(account.Peers))
peerIDs := make([]string, 0, len(account.Peers))
for peerID := range account.Peers {
@@ -130,6 +142,15 @@ func checkAccount(ctx context.Context, t *testing.T, account *types.Account, max
peerIDs = peerIDs[:maxPeers]
}
// Production fills ValidatedPeers via the integrated-validator wrapper; here
// every peer counts as validated, matching the legacy side's map.
nmData.ValidatedPeers = validated
// The legacy side receives no account zones (main sourced them from the
// external zones manager), so the DB-sourced applied-zone candidates must be
// dropped to keep the comparison surface identical. PrivateServiceCandidates
// stay: all paths derive them from account/DB data.
nmData.AppliedZoneCandidates = nil
resourcePolicies := account.GetResourcePoliciesMap()
routers := account.GetResourceRoutersMap()
groupUsers := account.GetActiveGroupUsers()
@@ -144,18 +165,32 @@ func checkAccount(ctx context.Context, t *testing.T, account *types.Account, max
if peer == nil {
continue
}
dataPeer := nmData.Peers[peerID]
if dataPeer == nil {
t.Fatalf("after %d peers: account=%s peer=%s present in account store, missing in nmdata store", stats.peersChecked, account.Id, peerID)
}
// NEW PATH — this branch, through the production conversion.
newNM := account.GetPeerNetworkMapFromComponents(
// STORE PATH — nmdata store through the production computation, mirroring
// the controller's networkMapFromData.
components := nmData.GetPeerNetworkMapComponents(peerID, nmdata.CustomZone{})
storeNM := &types.NetworkMap{Network: components.Network}
if !components.IsEmpty() {
storeNM = types.CalculateNetworkMapFromComponents(ctx, components)
}
// A separate cache per side: sharing one would let the first path
// populate entries the second then reuses, which can mask a real diff.
storeProto := mgmtgrpc.ToSyncResponse(
ctx, nil, nil, nil, dataPeer, nil, nil, storeNM, equivDNSName, nil,
&cache.DNSConfigCache{}, nmData.AccountSettings, settings.Extra, nil, 0,
).NetworkMap
// ACCOUNT PATH — Account → toNetworkMapData twins → components.
acctNM := account.GetPeerNetworkMapFromComponents(
ctx, peerID, nbdns.CustomZone{}, nil, validated, resourcePolicies, routers, nil, groupUsers,
)
if newNM == nil {
stats.skippedNilNM++
continue
}
newProto := mgmtgrpc.ToSyncResponse(
ctx, nil, nil, nil, peer, nil, nil, newNM, equivDNSName, nil,
&cache.DNSConfigCache{}, settings, settings.Extra, nil, 0,
acctProto := mgmtgrpc.ToSyncResponse(
ctx, nil, nil, nil, types.TwinPeer(peer), nil, nil, acctNM, equivDNSName, nil,
&cache.DNSConfigCache{}, types.TwinAccountSettings(settings), settings.Extra, nil, 0,
).NetworkMap
// LEGACY PATH — main's frozen copy.
@@ -165,18 +200,20 @@ func checkAccount(ctx context.Context, t *testing.T, account *types.Account, max
if legacyNM == nil {
t.Fatalf("after %d peers: account=%s peer=%s legacy NetworkMap nil, new non-nil", stats.peersChecked, account.Id, peerID)
}
// A separate cache per side: sharing one would let the first path
// populate entries the second then reuses, which can mask a real diff.
legacyProto := legacynmap.ToProtoNetworkMap(
ctx, peer, legacyNM, equivDNSName, settings, nil, &cache.DNSConfigCache{}, 0,
)
canonicalize(legacyProto)
canonicalize(newProto)
canonicalize(storeProto)
canonicalize(acctProto)
stats.peersChecked++
if !goproto.Equal(legacyProto, newProto) {
t.Fatalf("after %d peers: %s", stats.peersChecked, describeDivergence(legacyProto, newProto, account.Id, peerID))
if !goproto.Equal(legacyProto, storeProto) {
t.Fatalf("after %d peers: store path: %s", stats.peersChecked, describeDivergence(legacyProto, storeProto, account.Id, peerID))
}
if !goproto.Equal(legacyProto, acctProto) {
t.Fatalf("after %d peers: account path: %s", stats.peersChecked, describeDivergence(legacyProto, acctProto, account.Id, peerID))
}
}
}
@@ -508,17 +545,18 @@ func describeDivergence(legacy, updated *proto.NetworkMap, accountID, peerID str
lens := []struct {
field string
a, b int
diff func() string
}{
{"RemotePeers", len(legacy.RemotePeers), len(updated.RemotePeers)},
{"OfflinePeers", len(legacy.OfflinePeers), len(updated.OfflinePeers)},
{"Routes", len(legacy.Routes), len(updated.Routes)},
{"FirewallRules", len(legacy.FirewallRules), len(updated.FirewallRules)},
{"RoutesFirewallRules", len(legacy.RoutesFirewallRules), len(updated.RoutesFirewallRules)},
{"ForwardingRules", len(legacy.ForwardingRules), len(updated.ForwardingRules)},
{"RemotePeers", len(legacy.RemotePeers), len(updated.RemotePeers), func() string { return diffLists(legacy.RemotePeers, updated.RemotePeers) }},
{"OfflinePeers", len(legacy.OfflinePeers), len(updated.OfflinePeers), func() string { return diffLists(legacy.OfflinePeers, updated.OfflinePeers) }},
{"Routes", len(legacy.Routes), len(updated.Routes), func() string { return diffLists(legacy.Routes, updated.Routes) }},
{"FirewallRules", len(legacy.FirewallRules), len(updated.FirewallRules), func() string { return diffLists(legacy.FirewallRules, updated.FirewallRules) }},
{"RoutesFirewallRules", len(legacy.RoutesFirewallRules), len(updated.RoutesFirewallRules), func() string { return diffLists(legacy.RoutesFirewallRules, updated.RoutesFirewallRules) }},
{"ForwardingRules", len(legacy.ForwardingRules), len(updated.ForwardingRules), func() string { return diffLists(legacy.ForwardingRules, updated.ForwardingRules) }},
}
for _, l := range lens {
if l.a != l.b {
return prefix + " field=" + l.field + " legacy_len=" + strconv.Itoa(l.a) + " new_len=" + strconv.Itoa(l.b)
return prefix + " field=" + l.field + " legacy_len=" + strconv.Itoa(l.a) + " new_len=" + strconv.Itoa(l.b) + l.diff()
}
}
@@ -557,6 +595,39 @@ func describeDivergence(legacy, updated *proto.NetworkMap, accountID, peerID str
return prefix + " (repeated fields equal element-wise — scalar/oneof mismatch)"
}
// diffLists reports the multiset difference of two repeated proto fields, so a
// length mismatch shows which elements each side is missing.
func diffLists[M goproto.Message](legacy, updated []M) string {
counts := make(map[string]int)
for _, m := range legacy {
counts[prototext.MarshalOptions{}.Format(m)]++
}
for _, m := range updated {
counts[prototext.MarshalOptions{}.Format(m)]--
}
var onlyLegacy, onlyNew []string
for k, c := range counts {
for ; c > 0; c-- {
onlyLegacy = append(onlyLegacy, k)
}
for ; c < 0; c++ {
onlyNew = append(onlyNew, k)
}
}
slices.Sort(onlyLegacy)
slices.Sort(onlyNew)
var b strings.Builder
for _, k := range onlyLegacy {
b.WriteString("\n only_legacy: " + k)
}
for _, k := range onlyNew {
b.WriteString("\n only_new: " + k)
}
return b.String()
}
func protoStr(m goproto.Message) string {
if m == nil {
return "<nil>"

View File

@@ -17,37 +17,42 @@ type sshRequirements struct {
// exactly, operating on nmdata twins throughout — no Account reference and no
// twin↔real conversion, since the produced components hold twins.
func (nmd *NetworkMapData) GetPeerNetworkMapComponents(peerID string, peersCustomZone nmdata.CustomZone) *types.NetworkMapComponents {
forceRoutingPeerDNS := nmd.forcesRoutingPeerDNSResolution(peerID)
peer := nmd.Peers[peerID]
if peer == nil {
return types.EmptyNetworkMapComponents(&types.NetworkMapComponents{
PeerID: peerID,
Network: nmd.Network,
Peers: map[string]*nmdata.Peer{peerID: peer},
PeerID: peerID,
Network: nmd.Network,
Peers: map[string]*nmdata.Peer{peerID: peer},
ForceRoutingPeerDNSResolution: forceRoutingPeerDNS,
})
}
if _, ok := nmd.ValidatedPeers[peerID]; !ok {
return types.EmptyNetworkMapComponents(&types.NetworkMapComponents{
PeerID: peerID,
Network: nmd.Network,
Peers: map[string]*nmdata.Peer{peerID: peer},
PeerID: peerID,
Network: nmd.Network,
Peers: map[string]*nmdata.Peer{peerID: peer},
ForceRoutingPeerDNSResolution: forceRoutingPeerDNS,
})
}
components := &types.NetworkMapComponents{
PeerID: peerID,
Network: nmd.Network,
AccountSettings: nmd.AccountSettings,
DNSSettings: nmd.DNSSettings,
CustomZoneDomain: peersCustomZone.Domain,
NameServerGroups: make([]*nmdata.NameServerGroup, 0),
ResourcePoliciesMap: make(map[string][]*nmdata.Policy),
RoutersMap: make(map[string]map[string]*nmdata.NetworkRouter),
NetworkResources: make([]*nmdata.NetworkResource, 0),
PostureFailedPeers: make(map[string]map[string]struct{}, len(nmd.PostureChecks)),
RouterPeers: make(map[string]*nmdata.Peer),
NetworkXIDToPublicID: nmd.NetworkXIDToPublicID,
PostureCheckXIDToPublicID: nmd.PostureCheckXIDToPublicID,
PeerID: peerID,
Network: nmd.Network,
AccountSettings: nmd.AccountSettings,
DNSSettings: nmd.DNSSettings,
CustomZoneDomain: peersCustomZone.Domain,
NameServerGroups: make([]*nmdata.NameServerGroup, 0),
ResourcePoliciesMap: make(map[string][]*nmdata.Policy),
RoutersMap: make(map[string]map[string]*nmdata.NetworkRouter),
NetworkResources: make([]*nmdata.NetworkResource, 0),
PostureFailedPeers: make(map[string]map[string]struct{}, len(nmd.PostureChecks)),
RouterPeers: make(map[string]*nmdata.Peer),
NetworkXIDToPublicID: nmd.NetworkXIDToPublicID,
PostureCheckXIDToPublicID: nmd.PostureCheckXIDToPublicID,
ForceRoutingPeerDNSResolution: forceRoutingPeerDNS,
}
relevantPeers, relevantGroups, relevantPolicies, relevantRoutes, sshReqs := nmd.getPeersGroupsPoliciesRoutes(peerID, peer.SSHEnabled, &components.PostureFailedPeers)
@@ -473,6 +478,31 @@ func (nmd *NetworkMapData) getPostureValidPeersSaveFailed(inputPeers []string, p
return dest
}
// forcesRoutingPeerDNSResolution reports whether the given peer must run
// routing-peer DNS resolution regardless of the account-global
// RoutingPeerDNSResolutionEnabled setting: true when the peer routes a domain
// network resource targeted by an enabled reverse-proxy service, so the peer's
// DNS forwarder starts and can resolve the target for the embedded proxy peers.
func (nmd *NetworkMapData) forcesRoutingPeerDNSResolution(peerID string) bool {
if len(nmd.ProxyTargetedDomainResourceIDs) == 0 {
return false
}
for _, resource := range nmd.NetworkResources {
if resource == nil || !resource.Enabled || resource.Type != string(types.ResourceTypeDomain) {
continue
}
if _, ok := nmd.ProxyTargetedDomainResourceIDs[resource.ID]; !ok {
continue
}
if _, isRouter := nmd.Routers[resource.NetworkID][peerID]; isRouter {
return true
}
}
return false
}
func (nmd *NetworkMapData) GetPeerGroups(peerID string) map[string]struct{} {
groups := make(map[string]struct{})
for groupID, group := range nmd.Groups {

View File

@@ -32,6 +32,11 @@ type NetworkMapData struct {
GroupIDToUserIDs map[string][]string
DNSDomain string
// ProxyTargetedDomainResourceIDs is the account-level half of
// forcesRoutingPeerDNSResolution: domain network resources targeted by an
// enabled reverse-proxy service.
ProxyTargetedDomainResourceIDs map[string]struct{}
AppliedZoneCandidates []AppliedZoneCandidate
PrivateServiceCandidates []PrivateServiceCandidate
}