Compare commits

...

10 Commits

Author SHA1 Message Date
Dmitri Dolguikh
aa52972b9c cleanup handling of peers and routing peers
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 16:38:11 +02:00
Dmitri Dolguikh
27af495347 removed indexes from PostureFailedPeers
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 16:18:45 +02:00
Dmitri Dolguikh
bc7073b4c6 dopped indexes from group-id to user-id map used to control ssh access
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 16:08:13 +02:00
Dmitri Dolguikh
1a2c370f3d drop indexes from policies and resource policies
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 16:03:24 +02:00
Dmitri Dolguikh
afb4044ed7 removed indexes from routers map
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 14:39:53 +02:00
Dmitri Dolguikh
5cab8e4fa1 removed indexes from encodeNetworkResources()
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 14:18:14 +02:00
Dmitri Dolguikh
84fb3050d3 removed indexes from NameServerGroup
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 14:09:59 +02:00
Dmitri Dolguikh
5c0641a3da reved seq id check from encodeGroups()
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 14:01:17 +02:00
Dmitri Dolguikh
1ee1f5d35a removed indexes from encodeDNSSettings()
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 13:57:35 +02:00
Dmitri Dolguikh
2f1d185884 removed indexes for peers, routing peers, groups
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-07-07 11:39:09 +02:00
7 changed files with 744 additions and 940 deletions

View File

@@ -53,9 +53,6 @@ type NameServerGroup struct {
ID string `gorm:"primaryKey"`
// AccountID is a reference to Account that this object belongs
AccountID string `gorm:"index"`
// AccountSeqID is a per-account monotonically increasing identifier used as the
// compact wire id when sending NetworkMap components to capable peers.
AccountSeqID uint32 `json:"-" gorm:"index:idx_nameserver_groups_account_seq_id;not null;default:0"`
// Name group name
Name string
// Description group description

View File

@@ -2,6 +2,8 @@ package grpc
import (
"encoding/base64"
"maps"
"slices"
"strconv"
nbdns "github.com/netbirdio/netbird/dns"
@@ -81,14 +83,12 @@ func EncodeNetworkMapEnvelope(in ComponentsEnvelopeInput) *proto.NetworkMapEnvel
// peers that exist only in c.RouterPeers would silently lose their
// peer_index reference.
enc := newComponentEncoder(c)
enc.indexAllPeers()
routerIdxs := enc.indexRouterPeers(c.RouterPeers)
// Phase 2: gather every policy that any consumer references (peer-pair
// policies + resource-only policies) so encodeResourcePoliciesMap can
// translate every *Policy pointer to a wire index.
allPolicies := unionPolicies(c.Policies, c.ResourcePoliciesMap)
policies, policyToIdxs := enc.encodePolicies(allPolicies)
policies, policyToRuleIds := enc.encodePolicies(allPolicies)
// Phase 3: emit. Order of struct field expressions no longer matters:
// every encoder either reads from the dedup tables or works on
@@ -105,8 +105,8 @@ func EncodeNetworkMapEnvelope(in ComponentsEnvelopeInput) *proto.NetworkMapEnvel
DnsDomain: in.DNSDomain,
CustomZoneDomain: c.CustomZoneDomain,
AgentVersions: enc.agentVersions,
Peers: enc.peers,
RouterPeerIndexes: routerIdxs,
Peers: encodePeers(c.Peers),
RouterPeers: encodePeers(c.RouterPeers),
Policies: policies,
Groups: enc.encodeGroups(),
Routes: enc.encodeRoutes(c.Routes),
@@ -115,7 +115,7 @@ func EncodeNetworkMapEnvelope(in ComponentsEnvelopeInput) *proto.NetworkMapEnvel
AccountZones: encodeCustomZones(c.AccountZones),
NetworkResources: enc.encodeNetworkResources(c.NetworkResources),
RoutersMap: enc.encodeRoutersMap(c.RoutersMap),
ResourcePoliciesMap: enc.encodeResourcePoliciesMap(c.ResourcePoliciesMap, policyToIdxs),
ResourcePoliciesMap: enc.encodeResourcePoliciesMap(c.ResourcePoliciesMap, policyToRuleIds),
GroupIdToUserIds: enc.encodeGroupIDToUserIDs(c.GroupIDToUserIDs),
AllowedUserIds: stringSetToSlice(c.AllowedUserIDs),
PostureFailedPeers: enc.encodePostureFailedPeers(c.PostureFailedPeers),
@@ -137,84 +137,31 @@ func networkSerial(n *types.Network) uint64 {
}
type componentEncoder struct {
components *types.NetworkMapComponents
peerOrder map[string]uint32
peers []*proto.PeerCompact
agentVersionOrder map[string]uint32
agentVersions []string
dedupPeers map[string]struct{}
components *types.NetworkMapComponents
agentVersions []string
}
func newComponentEncoder(c *types.NetworkMapComponents) *componentEncoder {
return &componentEncoder{
components: c,
peerOrder: make(map[string]uint32, len(c.Peers)),
peers: make([]*proto.PeerCompact, 0, len(c.Peers)),
agentVersionOrder: make(map[string]uint32),
components: c,
}
}
func (e *componentEncoder) indexAllPeers() {
for _, p := range e.components.Peers {
if p == nil {
continue
}
e.appendPeer(p)
}
}
func (e *componentEncoder) appendPeer(p *nbpeer.Peer) uint32 {
if idx, ok := e.peerOrder[p.ID]; ok {
return idx
}
idx := uint32(len(e.peers))
e.peerOrder[p.ID] = idx
e.peers = append(e.peers, toPeerCompact(p, e.agentVersionIndex(p.Meta.WtVersion)))
return idx
}
func (e *componentEncoder) agentVersionIndex(v string) uint32 {
if idx, ok := e.agentVersionOrder[v]; ok {
return idx
}
// Lazy-initialise the table with "" at index 0 so the empty string
// stays interchangeable with proto3's default uint32=0 — peers without
// a WtVersion don't force the table to materialise.
if v == "" {
idx := uint32(len(e.agentVersions))
if idx == 0 {
e.agentVersions = append(e.agentVersions, "")
}
e.agentVersionOrder[""] = idx
return idx
}
if len(e.agentVersions) == 0 {
e.agentVersions = append(e.agentVersions, "")
e.agentVersionOrder[""] = 0
}
idx := uint32(len(e.agentVersions))
e.agentVersionOrder[v] = idx
e.agentVersions = append(e.agentVersions, v)
return idx
}
// indexRouterPeers ensures every router peer is in the peer dedup table
// (c.RouterPeers may contain peers not in c.Peers when validation rules drop
// them) and returns their wire indexes for the RouterPeerIndexes field. Must
// run before any encoder that resolves peer ids via e.peerOrder.
func (e *componentEncoder) indexRouterPeers(routers map[string]*nbpeer.Peer) []uint32 {
if len(routers) == 0 {
func encodePeers(mp map[string]*nbpeer.Peer) []*proto.PeerCompact {
if mp == nil {
return nil
}
out := make([]uint32, 0, len(routers))
for _, p := range routers {
if p == nil {
continue
dedupPeers := make(map[string]struct{})
peers := make([]*proto.PeerCompact, 0, len(mp))
for id, p := range mp {
if _, ok := dedupPeers[id]; !ok {
dedupPeers[id] = struct{}{}
peers = append(e.peers, toPeerCompact(p))
}
out = append(out, e.appendPeer(p))
}
return out
return peers
}
func (e *componentEncoder) encodeGroups() []*proto.GroupCompact {
@@ -224,67 +171,64 @@ func (e *componentEncoder) encodeGroups() []*proto.GroupCompact {
out := make([]*proto.GroupCompact, 0, len(e.components.Groups))
for _, g := range e.components.Groups {
if !g.HasSeqID() {
continue
}
peerIdxs := make([]uint32, 0, len(g.Peers))
peers := make([]string, 0, len(g.Peers))
for _, peerID := range g.Peers {
if idx, ok := e.peerOrder[peerID]; ok {
peerIdxs = append(peerIdxs, idx)
if _, ok := e.dedupPeers[peerID]; ok { // TODO (dmitri) is that necessary?
peers = append(peers, peerID)
}
}
out = append(out, &proto.GroupCompact{
Id: g.AccountSeqID,
Name: g.Name,
PeerIndexes: peerIdxs,
Id: g.ID,
Name: g.Name,
PeerIds: peers,
})
}
return out
}
// encodePolicies flattens Policy{Rules} → []PolicyCompact. Returns the wire
// list and a map from policy pointer to the indexes of its emitted rules in
// that list — used by encodeResourcePoliciesMap to translate
// ResourcePoliciesMap[resourceID][]*Policy into wire-side indexes.
func (e *componentEncoder) encodePolicies(policies []*types.Policy) ([]*proto.PolicyCompact, map[*types.Policy][]uint32) {
// list and a map from policy pointer to the ids of its emitted rules in
// that list — used by encodeResourcePoliciesMap
func (e *componentEncoder) encodePolicies(policies []*types.Policy) ([]*proto.PolicyCompact, map[*types.Policy][]string) {
if len(policies) == 0 {
return nil, nil
}
out := make([]*proto.PolicyCompact, 0, len(policies))
idxByPolicy := make(map[*types.Policy][]uint32, len(policies))
routeIdsByPolicy := make(map[*types.Policy][]string, len(policies))
for _, pol := range policies {
if !pol.HasSeqID() || !pol.Enabled {
if !pol.Enabled {
continue
}
for _, r := range pol.Rules {
if r == nil || !r.Enabled {
continue
}
idxByPolicy[pol] = append(idxByPolicy[pol], uint32(len(out)))
routeIdsByPolicy[pol] = append(routeIdsByPolicy[pol], r.ID)
out = append(out, e.encodePolicyRule(pol, r))
}
}
return out, idxByPolicy
return out, routeIdsByPolicy
}
// encodePolicyRule maps a single PolicyRule under pol to a PolicyCompact entry.
func (e *componentEncoder) encodePolicyRule(pol *types.Policy, r *types.PolicyRule) *proto.PolicyCompact {
return &proto.PolicyCompact{
Id: pol.AccountSeqID,
Action: networkmap.GetProtoAction(string(r.Action)),
Protocol: networkmap.GetProtoProtocol(string(r.Protocol)),
Bidirectional: r.Bidirectional,
Ports: portsToUint32(r.Ports),
PortRanges: portRangesToProto(r.PortRanges),
SourceGroupIds: e.groupSeqIDs(r.Sources),
DestinationGroupIds: e.groupSeqIDs(r.Destinations),
AuthorizedUser: r.AuthorizedUser,
AuthorizedGroups: e.encodeAuthorizedGroups(r.AuthorizedGroups),
SourceResource: e.resourceToProto(r.SourceResource),
DestinationResource: e.resourceToProto(r.DestinationResource),
SourcePostureCheckSeqIds: e.postureCheckSeqs(pol.SourcePostureChecks),
Id: pol.ID,
RuleId: r.ID,
Action: networkmap.GetProtoAction(string(r.Action)),
Protocol: networkmap.GetProtoProtocol(string(r.Protocol)),
Bidirectional: r.Bidirectional,
Ports: portsToUint32(r.Ports),
PortRanges: portRangesToProto(r.PortRanges),
SourceGroupIds: r.Sources,
DestinationGroupIds: r.Destinations,
AuthorizedUser: r.AuthorizedUser,
AuthorizedGroups: e.encodeAuthorizedGroups(r.AuthorizedGroups),
SourceResource: e.resourceToProto(r.SourceResource),
DestinationResource: e.resourceToProto(r.DestinationResource),
SourcePostureCheckIds: pol.SourcePostureChecks,
}
}
@@ -343,20 +287,15 @@ func unionPolicies(policies []*types.Policy, resourcePolicies map[string][]*type
}
// encodeAuthorizedGroups translates rule.AuthorizedGroups (map keyed by
// group xid → local-user names) to the wire form (map keyed by group
// account_seq_id → UserNameList). Groups without a seq id are dropped —
// matches how source/destination group references handle the same case.
func (e *componentEncoder) encodeAuthorizedGroups(m map[string][]string) map[uint32]*proto.UserNameList {
// group xid → local-user names) to the wire form (map keyed by
// group_id → UserNameList).
func (e *componentEncoder) encodeAuthorizedGroups(m map[string][]string) map[string]*proto.UserNameList {
if len(m) == 0 {
return nil
}
out := make(map[uint32]*proto.UserNameList, len(m))
out := make(map[string]*proto.UserNameList, len(m))
for groupID, names := range m {
seq, ok := e.groupSeq(groupID)
if !ok {
continue
}
out[seq] = &proto.UserNameList{Names: append([]string(nil), names...)}
out[groupID] = &proto.UserNameList{Names: append([]string(nil), names...)}
}
return out
}
@@ -393,7 +332,7 @@ func (e *componentEncoder) resourceToProto(r types.Resource) *proto.ResourceComp
// lookup. Unresolvable xids are silently dropped — matches how group/peer
// references handle the same case.
func (e *componentEncoder) postureCheckSeqs(xids []string) []uint32 {
if len(xids) == 0 || len(e.components.PostureCheckXIDToSeq) == 0 {
if len(xids) == 0 {
return nil
}
out := make([]uint32, 0, len(xids))
@@ -424,12 +363,7 @@ func (e *componentEncoder) encodeDNSSettings(s *types.DNSSettings) *proto.DNSSet
return nil
}
out := &proto.DNSSettingsCompact{
DisabledManagementGroupIds: make([]uint32, 0, len(s.DisabledManagementGroups)),
}
for _, gid := range s.DisabledManagementGroups {
if seq, ok := e.groupSeq(gid); ok {
out.DisabledManagementGroupIds = append(out.DisabledManagementGroupIds, seq)
}
DisabledManagementGroupIds: slices.Clone(s.DisabledManagementGroups), // todo (dmitri) I think it would be ok to skip CLone()
}
return out
}
@@ -444,8 +378,7 @@ func (e *componentEncoder) encodeRoutes(routes []*nbroute.Route) []*proto.RouteR
continue
}
rr := &proto.RouteRaw{
Id: r.AccountSeqID,
NetId: string(r.NetID),
Id: string(r.ID),
Description: r.Description,
KeepRoute: r.KeepRoute,
NetworkType: int32(r.NetworkType),
@@ -454,17 +387,16 @@ func (e *componentEncoder) encodeRoutes(routes []*nbroute.Route) []*proto.RouteR
Enabled: r.Enabled,
SkipAutoApply: r.SkipAutoApply,
Domains: r.Domains.ToPunycodeList(),
GroupIds: e.groupIDsToSeq(r.Groups),
AccessControlGroupIds: e.groupIDsToSeq(r.AccessControlGroups),
PeerGroupIds: e.groupIDsToSeq(r.PeerGroups),
GroupIds: r.Groups,
AccessControlGroupIds: r.AccessControlGroups,
PeerGroupIds: r.PeerGroups,
}
if r.Network.IsValid() {
rr.NetworkCidr = r.Network.String()
}
if r.Peer != "" {
if idx, ok := e.peerOrder[r.Peer]; ok {
rr.PeerIndexSet = true
rr.PeerIndex = idx
if _, ok := e.dedupPeers[r.Peer]; ok { // todo (dmitri) is this necessary?
rr.PeerId = r.Peer
}
}
out = append(out, rr)
@@ -495,11 +427,11 @@ func (e *componentEncoder) encodeNameServerGroups(nsgs []*nbdns.NameServerGroup)
continue
}
entry := &proto.NameServerGroupRaw{
Id: nsg.AccountSeqID,
Id: nsg.ID,
Name: nsg.Name,
Description: nsg.Description,
Nameservers: encodeNameServers(nsg.NameServers),
GroupIds: e.groupIDsToSeq(nsg.Groups),
GroupIds: nsg.Groups,
Primary: nsg.Primary,
Domains: nsg.Domains,
Enabled: nsg.Enabled,
@@ -568,7 +500,8 @@ func (e *componentEncoder) encodeNetworkResources(resources []*resourceTypes.Net
continue
}
entry := &proto.NetworkResourceRaw{
Id: r.AccountSeqID,
Id: r.ID,
NetworkId: r.NetworkID,
Name: r.Name,
Description: r.Description,
Type: string(r.Type),
@@ -576,9 +509,6 @@ func (e *componentEncoder) encodeNetworkResources(resources []*resourceTypes.Net
DomainValue: r.Domain,
Enabled: r.Enabled,
}
if seq, ok := e.networkSeq(r.NetworkID); ok {
entry.NetworkSeq = seq
}
if r.Prefix.IsValid() {
entry.PrefixCidr = r.Prefix.String()
}
@@ -587,84 +517,63 @@ func (e *componentEncoder) encodeNetworkResources(resources []*resourceTypes.Net
return out
}
func (e *componentEncoder) encodeRoutersMap(routersMap map[string]map[string]*routerTypes.NetworkRouter) map[uint32]*proto.NetworkRouterList {
func (e *componentEncoder) encodeRoutersMap(routersMap map[string]map[string]*routerTypes.NetworkRouter) map[string]*proto.NetworkRouterList {
if len(routersMap) == 0 {
return nil
}
out := make(map[uint32]*proto.NetworkRouterList, len(routersMap))
out := make(map[string]*proto.NetworkRouterList, len(routersMap))
for networkXID, routers := range routersMap {
if len(routers) == 0 {
continue
}
netSeq, ok := e.networkSeq(networkXID)
if !ok {
continue
}
entries := make([]*proto.NetworkRouterEntry, 0, len(routers))
for peerID, r := range routers {
if r == nil {
continue
}
entry := &proto.NetworkRouterEntry{
Id: r.AccountSeqID,
PeerGroupIds: e.groupIDsToSeq(r.PeerGroups),
Id: r.ID,
PeerGroupIds: r.PeerGroups,
PeerId: peerID,
Masquerade: r.Masquerade,
Metric: int32(r.Metric),
Enabled: r.Enabled,
}
if idx, ok := e.peerOrder[peerID]; ok {
entry.PeerIndexSet = true
entry.PeerIndex = idx
}
entries = append(entries, entry)
}
out[netSeq] = &proto.NetworkRouterList{Entries: entries}
out[networkXID] = &proto.NetworkRouterList{Entries: entries}
}
return out
}
func (e *componentEncoder) encodeResourcePoliciesMap(rpm map[string][]*types.Policy, policyToIdxs map[*types.Policy][]uint32) map[uint32]*proto.PolicyIndexes {
func (e *componentEncoder) encodeResourcePoliciesMap(rpm map[string][]*types.Policy, policyToRuleIds map[*types.Policy][]string) map[string]*proto.PolicyIndexes {
if len(rpm) == 0 {
return nil
}
// resourceXIDToSeq is local to one encode — built from components.NetworkResources
// (small slice). Network resources without seq id are dropped, matching how
// other components-without-seq are silently filtered.
resourceXIDToSeq := make(map[string]uint32, len(e.components.NetworkResources))
for _, r := range e.components.NetworkResources {
if r != nil && r.AccountSeqID != 0 {
resourceXIDToSeq[r.ID] = r.AccountSeqID
}
}
out := make(map[uint32]*proto.PolicyIndexes, len(rpm))
out := make(map[string]*proto.PolicyIndexes, len(rpm))
for resourceXID, policies := range rpm {
seq, ok := resourceXIDToSeq[resourceXID]
if !ok {
continue
}
idxs := make([]uint32, 0, len(policies)*2)
idxs := make([]string, 0, len(policies)*2)
for _, pol := range policies {
idxs = append(idxs, policyToIdxs[pol]...)
idxs = append(idxs, policyToRuleIds[pol]...)
}
if len(idxs) == 0 {
continue
}
out[seq] = &proto.PolicyIndexes{Indexes: idxs}
out[resourceXID] = &proto.PolicyIndexes{Indexes: idxs}
}
return out
}
func (e *componentEncoder) encodeGroupIDToUserIDs(m map[string][]string) map[uint32]*proto.UserIDList {
func (e *componentEncoder) encodeGroupIDToUserIDs(m map[string][]string) map[string]*proto.UserIDList {
if len(m) == 0 {
return nil
}
out := make(map[uint32]*proto.UserIDList, len(m))
out := make(map[string]*proto.UserIDList, len(m))
for groupID, userIDs := range m {
seq, ok := e.groupSeq(groupID)
if !ok || len(userIDs) == 0 {
if len(userIDs) == 0 {
continue
}
out[seq] = &proto.UserIDList{UserIds: userIDs}
out[groupID] = &proto.UserIDList{UserIds: userIDs}
}
return out
}
@@ -684,22 +593,13 @@ func (e *componentEncoder) encodePostureFailedPeers(m map[string]map[string]stru
if len(m) == 0 {
return nil
}
out := make(map[uint32]*proto.PeerIndexSet, len(m))
out := make(map[string]*proto.PeerSet, len(m))
for checkXID, failedPeerIDs := range m {
seq, ok := e.components.PostureCheckXIDToSeq[checkXID]
if !ok || seq == 0 {
ids := slices.Collect(maps.Keys(failedPeerIDs))
if len(ids) == 0 {
continue
}
idxs := make([]uint32, 0, len(failedPeerIDs))
for peerID := range failedPeerIDs {
if idx, ok := e.peerOrder[peerID]; ok {
idxs = append(idxs, idx)
}
}
if len(idxs) == 0 {
continue
}
out[seq] = &proto.PeerIndexSet{PeerIndexes: idxs}
out[checkXID] = &proto.PeerSet{PeerIds: ids}
}
return out
}
@@ -736,12 +636,13 @@ func toAccountNetwork(n *types.Network) *proto.AccountNetwork {
return out
}
func toPeerCompact(p *nbpeer.Peer, agentVersionIdx uint32) *proto.PeerCompact {
func toPeerCompact(p *nbpeer.Peer) *proto.PeerCompact {
pc := &proto.PeerCompact{
Id: p.ID,
WgPubKey: decodeWgKey(p.Key),
SshPubKey: []byte(p.SSHKey),
DnsLabel: p.DNSLabel,
AgentVersionIdx: agentVersionIdx,
AgentVersion: p.Meta.WtVersion,
AddedWithSsoLogin: p.UserID != "",
LoginExpirationEnabled: p.LoginExpirationEnabled,
SshEnabled: p.SSHEnabled,

View File

@@ -32,9 +32,6 @@ type NetworkResource struct {
ID string `gorm:"primaryKey"`
NetworkID string `gorm:"index"`
AccountID string `gorm:"index"`
// AccountSeqID is a per-account monotonically increasing identifier used as the
// compact wire id when sending NetworkMap components to capable peers.
AccountSeqID uint32 `json:"-" gorm:"index:idx_network_resources_account_seq_id;not null;default:0"`
Name string
Description string
Type NetworkResourceType

View File

@@ -95,9 +95,6 @@ type Route struct {
ID ID `gorm:"primaryKey"`
// AccountID is a reference to Account that this object belongs
AccountID string `gorm:"index"`
// AccountSeqID is a per-account monotonically increasing identifier used as the
// compact wire id when sending NetworkMap components to capable peers.
AccountSeqID uint32 `json:"-" gorm:"index:idx_routes_account_seq_id;not null;default:0"`
// Network and Domains are mutually exclusive
Network netip.Prefix `gorm:"serializer:json"`
Domains domain.List `gorm:"serializer:json"`
@@ -131,7 +128,6 @@ func (r *Route) Copy() *Route {
route := &Route{
ID: r.ID,
AccountID: r.AccountID,
AccountSeqID: r.AccountSeqID,
Description: r.Description,
NetID: r.NetID,
Network: r.Network,

File diff suppressed because it is too large Load Diff

View File

@@ -826,12 +826,12 @@ message NetworkMapComponentsFull {
// Empty string at index 0 if any peer has no version.
repeated string agent_versions = 8;
// All peers (deduplicated). The client splits peers into online / offline
// peers (deduplicated). The client splits peers into online / offline
// locally using account_settings.peer_login_expiration on receive.
repeated PeerCompact peers = 9;
// Indexes into peers for the subset that may act as routers.
repeated uint32 router_peer_indexes = 10;
// router peers (deduplicated)
repeated PeerCompact router_peers = 10;
// Policies that affect the receiving peer.
repeated PolicyCompact policies = 11;
@@ -858,32 +858,22 @@ message NetworkMapComponentsFull {
// Routers per network. Outer key: network account_seq_id. Each entry is
// the set of routers backing that network for this peer's view.
//
// INCOMPATIBLE WIRE CHANGE: the map key changed from string (network xid)
// to uint32 (account_seq_id). Field 18 was reused without a `reserved`
// entry because capability=3 has never been released — every cap=3
// producer and consumer carries the same regenerated descriptor. Do NOT
// reuse this pattern for any further wire change once cap=3 ships.
map<uint32, NetworkRouterList> routers_map = 18;
map<string, NetworkRouterList> routers_map = 18;
// For each NetworkResource account_seq_id, the indexes into policies[]
// For each NetworkResource id, the indexes into policies[]
// that apply to it.
//
// INCOMPATIBLE WIRE CHANGE: see routers_map note above.
map<uint32, PolicyIndexes> resource_policies_map = 19;
map<string, PolicyIndexes> resource_policies_map = 19;
// Group-id (account_seq_id) → user ids authorized for SSH on members.
map<uint32, UserIDList> group_id_to_user_ids = 20;
// Group-id → user ids authorized for SSH on members.
map<string, UserIDList> group_id_to_user_ids = 20;
// Account-level allowed user ids (used by Calculate() when assembling SSH
// authorized users for the receiving peer).
repeated string allowed_user_ids = 21;
// Per posture-check account_seq_id, the set of peer indexes that failed
// Per posture-check id to the set of peer ids that failed
// the check. Server-side evaluation result; clients do not re-evaluate.
//
// INCOMPATIBLE WIRE CHANGE: see routers_map note above.
map<uint32, PeerIndexSet> posture_failed_peers = 22;
map<string, PeerSet> posture_failed_peers = 22;
// Account-level DNS forwarder port (mirrors the legacy
// proto.DNSConfig.ForwarderPort). Computed by the controller from peer
@@ -960,50 +950,51 @@ message NetworkMapComponentsDelta {
// last_login_unix_nano). Fields the client does not consume (Status,
// CreatedAt, etc.) are not shipped.
message PeerCompact {
string id = 1;
// Raw 32-byte WireGuard public key (no base64 wrapping).
bytes wg_pub_key = 1;
bytes wg_pub_key = 2;
// Raw 4-byte IPv4 overlay address. Always a /32 host route, so no prefix
// byte is needed.
bytes ip = 2;
bytes ip = 3;
// Raw 16-byte IPv6 overlay address; always a /128 host route. Empty when
// the peer has no IPv6 overlay address.
bytes ipv6 = 3;
bytes ipv6 = 4;
// Raw SSH public key bytes (or empty).
bytes ssh_pub_key = 4;
bytes ssh_pub_key = 5;
// DNS label without the account's domain suffix. Full FQDN is
// dns_label + "." + NetworkMapComponentsFull.dns_domain.
string dns_label = 5;
string dns_label = 6;
// Index into NetworkMapComponentsFull.agent_versions.
uint32 agent_version_idx = 6;
// agent version
string agent_version = 7;
// True iff the peer was added via SSO login (i.e., types.Peer.UserID is
// non-empty). Combined with login_expiration_enabled and
// last_login_unix_nano this lets the client reproduce
// (*Peer).LoginExpired() locally.
bool added_with_sso_login = 7;
bool added_with_sso_login = 8;
// True when the peer's login can expire — mirrors
// types.Peer.LoginExpirationEnabled.
bool login_expiration_enabled = 8;
bool login_expiration_enabled = 9;
// Unix-nanosecond timestamp of the peer's last login. 0 when the peer has
// never logged in (server stores nil; client treats 0 as "epoch", which
// makes a fresh peer immediately expired iff login_expiration_enabled is
// true — the same semantics as types.Peer.GetLastLogin).
int64 last_login_unix_nano = 9;
int64 last_login_unix_nano = 10;
// True when the peer has an SSH server enabled locally. Used by the
// legacy SSH path in Calculate() (`policyRuleImpliesLegacySSH`): a rule
// with protocol ALL/TCP-with-SSH-ports activates SSH for the receiving
// peer when this bit is set, even without an explicit NetbirdSSH rule.
bool ssh_enabled = 10;
reserved 11; // was: id (string xid)
bool ssh_enabled = 11;
// Mirror of types.Peer.SupportsIPv6() — !Meta.Flags.DisableIPv6 &&
// HasCapability(PeerCapabilityIPv6Overlay). Used by the local peer's
@@ -1030,26 +1021,22 @@ message PeerCompact {
// on the client (ingress when the peer is in destination_group_ids, egress
// when in source_group_ids; both when bidirectional).
message PolicyCompact {
// Per-account integer id (matches policies.account_seq_id). Used as a
// stable reference for ResourcePoliciesMap.indexes and future delta
// updates.
uint32 id = 1;
string id = 1;
string rule_id = 2;
RuleAction action = 2;
RuleProtocol protocol = 3;
bool bidirectional = 4;
RuleAction action = 3;
RuleProtocol protocol = 4;
bool bidirectional = 5;
// Single ports referenced by the rule.
repeated uint32 ports = 5;
repeated uint32 ports = 6;
// Port ranges (start..end) referenced by the rule.
repeated PortInfo.Range port_ranges = 6;
repeated PortInfo.Range port_ranges = 7;
// Group ids (account_seq_id) of source / destination groups.
repeated uint32 source_group_ids = 7;
repeated uint32 destination_group_ids = 8;
reserved 9; // was: xid (string)
repeated string source_group_ids = 8;
repeated string destination_group_ids = 9;
// SSH authorization fields. PolicyRule.AuthorizedGroups maps the rule's
// applicable group ids (account_seq_id) to a list of local-user names —
@@ -1059,7 +1046,7 @@ message PolicyCompact {
//
// Both fields are only consumed by Calculate() when the rule's protocol
// is NetbirdSSH (or the legacy implicit-SSH heuristic).
map<uint32, UserNameList> authorized_groups = 10;
map<string, UserNameList> authorized_groups = 10;
string authorized_user = 11;
// Resource-typed rule sources/destinations. When a rule targets a specific
@@ -1072,13 +1059,11 @@ message PolicyCompact {
ResourceCompact source_resource = 12;
ResourceCompact destination_resource = 13;
// Posture-check seq ids gating this policy's source peers. Calculate()
// Posture-check ids gating this policy's source peers. Calculate()
// reads them when filtering rule peers (peers that fail any listed check
// are dropped from sourcePeers). Match keys in
// NetworkMapComponentsFull.posture_failed_peers.
repeated uint32 source_posture_check_seq_ids = 15;
reserved 14; // was: source_posture_check_ids (repeated string xid)
repeated string source_posture_check_ids = 14;
}
// ResourceCompact mirrors types.Resource. Used by PolicyCompact to carry
@@ -1102,21 +1087,19 @@ message UserNameList {
// GroupCompact is the wire-shape of a group: per-account integer id, optional
// name, and indexes into NetworkMapComponentsFull.peers identifying members.
message GroupCompact {
// Per-account integer id (matches groups.account_seq_id). Used by
// PolicyCompact.source_group_ids / destination_group_ids.
uint32 id = 1;
// Group id
string id = 1;
// Group name; only sent when non-empty (clients use it for diagnostics).
string name = 2;
// Indexes into NetworkMapComponentsFull.peers.
repeated uint32 peer_indexes = 3;
repeated string peer_ids = 3;
}
// DNSSettingsCompact mirrors types.DNSSettings.
message DNSSettingsCompact {
// Group ids (account_seq_id) whose DNS management is disabled.
repeated uint32 disabled_management_group_ids = 1;
// Group ids whose DNS management is disabled.
repeated string disabled_management_group_ids = 1;
}
// RouteRaw mirrors *route.Route (the domain type), trimmed to fields that
@@ -1125,51 +1108,37 @@ message DNSSettingsCompact {
// NetworkMapComponentsFull.peers.
message RouteRaw {
// Per-account integer id (matches routes.account_seq_id).
uint32 id = 1;
string net_id = 2;
string description = 3;
string id = 1;
string description = 2;
// Either network_cidr (e.g. "10.0.0.0/16") or domains is set, not both.
string network_cidr = 4;
repeated string domains = 5;
bool keep_route = 6;
string network_cidr = 3;
repeated string domains = 4;
bool keep_route = 5;
// Routing peer reference: peer_index_set tells whether peer_index is valid
// (proto3 uint32 cannot disambiguate "0" from "unset"). Mutually exclusive
// with peer_group_ids.
//
// peer_index decodes back to types.Peer.ID (the peer's xid string), NOT
// to its WireGuard public key. This matches the server-side data flow:
// c.Routes carry route.Peer = peer.ID, and getRoutingPeerRoutes mutates
// it to peer.Key only after the route has been admitted to the network
// map. Decoders MUST set Route.Peer = peer.ID; the legacy Calculate()
// path will substitute the WG key downstream.
bool peer_index_set = 7;
uint32 peer_index = 8;
repeated uint32 peer_group_ids = 9;
string peer_id = 7;
repeated string peer_group_ids = 8;
int32 network_type = 10;
bool masquerade = 11;
int32 metric = 12;
bool enabled = 13;
repeated uint32 group_ids = 14;
repeated uint32 access_control_group_ids = 15;
bool skip_auto_apply = 16;
reserved 17; // was: xid (string)
int32 network_type = 9;
bool masquerade = 10;
int32 metric = 11;
bool enabled = 12;
repeated string group_ids = 13;
repeated string access_control_group_ids = 14;
bool skip_auto_apply = 15;
}
// NameServerGroupRaw mirrors *nbdns.NameServerGroup. Distinct from the
// legacy NameServerGroup (which is the wire-trimmed shape consumed by
// proto.DNSConfig and lacks the Name/Description/Groups/Enabled fields).
message NameServerGroupRaw {
uint32 id = 1; // nameserver_groups.account_seq_id
string id = 1;
string name = 2;
string description = 3;
// Reuses the legacy NameServer wire shape (IP as string).
repeated NameServer nameservers = 4;
// Group ids (account_seq_id) the NSG distributes nameservers to.
repeated uint32 group_ids = 5;
// Group ids the NSG distributes nameservers to.
repeated string group_ids = 5;
bool primary = 6;
repeated string domains = 7;
bool enabled = 8;
@@ -1177,15 +1146,9 @@ message NameServerGroupRaw {
}
// NetworkResourceRaw mirrors *resourceTypes.NetworkResource.
//
// INCOMPATIBLE WIRE CHANGE: field 2 changed from `string network_id` (xid)
// to `uint32 network_seq` without a `reserved` entry. Safe only because
// capability=3 has never been released — every cap=3 producer and consumer
// carries the same regenerated descriptor. Do NOT reuse this pattern once
// cap=3 ships.
message NetworkResourceRaw {
uint32 id = 1; // network_resources.account_seq_id
uint32 network_seq = 2; // networks.account_seq_id (replaces xid)
string id = 1;
string network_id = 2;
string name = 3;
string description = 4;
// Resource type: "host" / "subnet" / "domain".
@@ -1194,7 +1157,6 @@ message NetworkResourceRaw {
string domain_value = 7; // resource.Domain
string prefix_cidr = 8;
bool enabled = 9;
reserved 10; // was: xid (string)
}
// NetworkRouterList carries the routers backing one network.
@@ -1203,13 +1165,11 @@ message NetworkRouterList {
repeated NetworkRouterEntry entries = 1;
}
// NetworkRouterEntry mirrors a single *routerTypes.NetworkRouter; the routing
// peer is referenced by index into NetworkMapComponentsFull.peers.
// NetworkRouterEntry mirrors a single *routerTypes.NetworkRouter
message NetworkRouterEntry {
uint32 id = 1; // network_routers.account_seq_id
uint32 peer_index = 2;
bool peer_index_set = 3;
repeated uint32 peer_group_ids = 4;
string id = 1;
string peer_id = 2;
repeated string peer_group_ids = 4;
bool masquerade = 5;
int32 metric = 6;
bool enabled = 7;
@@ -1217,7 +1177,7 @@ message NetworkRouterEntry {
// PolicyIndexes is a list of indexes into NetworkMapComponentsFull.policies.
message PolicyIndexes {
repeated uint32 indexes = 1;
repeated string indexes = 1;
}
// UserIDList is a list of user ids — used as the value type in
@@ -1226,8 +1186,8 @@ message UserIDList {
repeated string user_ids = 1;
}
// PeerIndexSet is a set of peer indexes — used as the value type in
// PeerSet is a set of peer ids — used as the value type in
// NetworkMapComponentsFull.posture_failed_peers.
message PeerIndexSet {
repeated uint32 peer_indexes = 1;
message PeerSet {
repeated string peer_ids = 1;
}

View File

@@ -26,15 +26,15 @@ type NetworkMapComponents struct {
DNSSettings *DNSSettings
CustomZoneDomain string
Peers map[string]*nbpeer.Peer
Groups map[string]*Group
Peers map[string]*nbpeer.Peer // peerId to Peer
Groups map[string]*Group // groupId to Group
Policies []*Policy
Routes []*route.Route
NameServerGroups []*nbdns.NameServerGroup
AllDNSRecords []nbdns.SimpleRecord
AccountZones []nbdns.CustomZone
ResourcePoliciesMap map[string][]*Policy
RoutersMap map[string]map[string]*routerTypes.NetworkRouter
ResourcePoliciesMap map[string][]*Policy // resourceId to Policy
RoutersMap map[string]map[string]*routerTypes.NetworkRouter // networkId to peerId to router
NetworkResources []*resourceTypes.NetworkResource
GroupIDToUserIDs map[string][]string