From 4aa486fde80eb5fcb61faa41d1aa1b743800da26 Mon Sep 17 00:00:00 2001 From: Dmitri Dolguikh Date: Fri, 10 Jul 2026 17:12:51 +0200 Subject: [PATCH] handle nil components consistently Signed-off-by: Dmitri Dolguikh --- .../network_map/controller/controller.go | 1 - .../grpc/components_envelope_response.go | 14 ++-------- management/server/types/account_components.go | 28 +++++++++---------- management/server/types/aliases.go | 3 ++ .../management/types/networkmap_components.go | 11 ++++++++ 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/management/internals/controllers/network_map/controller/controller.go b/management/internals/controllers/network_map/controller/controller.go index 833af320e..d25165e0c 100644 --- a/management/internals/controllers/network_map/controller/controller.go +++ b/management/internals/controllers/network_map/controller/controller.go @@ -601,7 +601,6 @@ func (c *Controller) GetValidatedPeerWithComponents(ctx context.Context, isRequi routers := account.GetResourceRoutersMap() groupIDToUserIDs := account.GetActiveGroupUsers() components := account.GetPeerNetworkMapComponents(ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, groupIDToUserIDs) - dnsFwdPort := computeForwarderPort(maps.Values(account.Peers), network_map.DnsForwarderPortMinVersion) return peer, components, proxyNetworkMaps[peer.ID], postureChecks, dnsFwdPort, nil diff --git a/management/internals/shared/grpc/components_envelope_response.go b/management/internals/shared/grpc/components_envelope_response.go index 7880b5135..95fb87271 100644 --- a/management/internals/shared/grpc/components_envelope_response.go +++ b/management/internals/shared/grpc/components_envelope_response.go @@ -25,6 +25,8 @@ import ( // computed by the client from the envelope's GroupIDToUserIDs / AllowedUserIDs // inside Calculate(), so the SshConfig.SshEnabled bit may flip true on the // client even though the server-side PeerConfig reports false. +// +// components parameter is expected to be !nil func ToComponentSyncResponse( ctx context.Context, config *nbconfig.Config, @@ -42,9 +44,8 @@ func ToComponentSyncResponse( peerGroups []string, dnsFwdPort int64, ) *proto.SyncResponse { - network := networkOrZero(components) enableSSH := computeSSHEnabledForPeer(components, peer) - peerConfig := toPeerConfig(peer, network, dnsName, settings, httpConfig, deviceFlowConfig, enableSSH) + peerConfig := toPeerConfig(peer, components.Network, dnsName, settings, httpConfig, deviceFlowConfig, enableSSH) includeIPv6 := peer.SupportsIPv6() && peer.IPv6.IsValid() useSourcePrefixes := peer.SupportsSourcePrefixes() @@ -85,15 +86,6 @@ func ToComponentSyncResponse( return resp } -// networkOrZero returns components.Network or a zero Network — toPeerConfig -// dereferences network.Net which would panic on nil. -func networkOrZero(c *types.NetworkMapComponents) *types.Network { - if c == nil || c.Network == nil { - return &types.Network{} - } - return c.Network -} - // toProxyPatch converts a proxy-injected *types.NetworkMap into the wire // patch the components envelope ships alongside. Returns nil when there are // no fragments to merge — proto3 omits a nil message field, so the receiver diff --git a/management/server/types/account_components.go b/management/server/types/account_components.go index 9dc082d87..e2fc9b94c 100644 --- a/management/server/types/account_components.go +++ b/management/server/types/account_components.go @@ -40,16 +40,6 @@ func (a *Account) GetPeerNetworkMapResult( components := a.GetPeerNetworkMapComponents( ctx, peerID, peersCustomZone, accountZones, validatedPeersMap, resourcePolicies, routers, groupIDToUserIDs, ) - // Mirror legacy graceful-degrade: GetPeerNetworkMapFromComponents - // returns &NetworkMap{Network: a.Network.Copy()} when components is - // nil. Match that floor so the receiving client always sees the - // account Network identifier, not a fully-empty envelope. - if components == nil { - components = &NetworkMapComponents{ - PeerID: peerID, - Network: a.Network.Copy(), - } - } return PeerNetworkMapResult{Components: components} } return PeerNetworkMapResult{ @@ -83,8 +73,8 @@ func (a *Account) GetPeerNetworkMapFromComponents( groupIDToUserIDs, ) - if components == nil { - return &NetworkMap{Network: a.Network.Copy()} + if components.IsEmpty() { + return &NetworkMap{Network: components.Network} } nm := CalculateNetworkMapFromComponents(ctx, components) @@ -117,11 +107,21 @@ func (a *Account) GetPeerNetworkMapComponents( peer := a.Peers[peerID] if peer == nil { - return nil + // Mirror legacy graceful-degrade: GetPeerNetworkMapFromComponents + // returns &NetworkMap{Network: a.Network.Copy()} when components is + // nil. Match that floor so the receiving client always sees the + // account Network identifier, not a fully-empty envelope. + return EmptyNetworkMapComponents(&NetworkMapComponents{ + PeerID: peerID, + Network: a.Network.Copy(), + }) } if _, ok := validatedPeersMap[peerID]; !ok { - return nil + return EmptyNetworkMapComponents(&NetworkMapComponents{ + PeerID: peerID, + Network: a.Network.Copy(), + }) } components := &NetworkMapComponents{ diff --git a/management/server/types/aliases.go b/management/server/types/aliases.go index 949bd7432..f5837a343 100644 --- a/management/server/types/aliases.go +++ b/management/server/types/aliases.go @@ -41,6 +41,9 @@ type ResourceType = sharedtypes.ResourceType type RouteFirewallRule = sharedtypes.RouteFirewallRule type NetworkMapComponents = sharedtypes.NetworkMapComponents + +var EmptyNetworkMapComponents = sharedtypes.EmptyNetworkMapComponents + type AccountSettingsInfo = sharedtypes.AccountSettingsInfo type GroupCompact = sharedtypes.GroupCompact diff --git a/shared/management/types/networkmap_components.go b/shared/management/types/networkmap_components.go index 97ca01816..c080ff055 100644 --- a/shared/management/types/networkmap_components.go +++ b/shared/management/types/networkmap_components.go @@ -53,6 +53,8 @@ type NetworkMapComponents struct { // Same role as NetworkXIDToSeq, used for PostureFailedPeers keys and // policy SourcePostureChecks references. PostureCheckXIDToPublicID map[string]string + // true when returning an empty-like map (returned instead of nil) + empty bool } type AccountSettingsInfo struct { @@ -62,6 +64,11 @@ type AccountSettingsInfo struct { PeerInactivityExpiration time.Duration } +func EmptyNetworkMapComponents(nm *NetworkMapComponents) *NetworkMapComponents { + nm.empty = true + return nm +} + func (c *NetworkMapComponents) GetPeerInfo(peerID string) *nbpeer.Peer { return c.Peers[peerID] } @@ -180,6 +187,10 @@ func (c *NetworkMapComponents) Calculate(ctx context.Context) *NetworkMap { } } +func (c *NetworkMapComponents) IsEmpty() bool { + return c.empty +} + func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) ([]*nbpeer.Peer, []*FirewallRule, map[string]map[string]struct{}, bool) { targetPeer := c.GetPeerInfo(targetPeerID) if targetPeer == nil {