redid versioning

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
This commit is contained in:
Dmitri Dolguikh
2026-07-16 17:45:57 +02:00
parent cfb534af8f
commit 20243fcfee
23 changed files with 1570 additions and 1657 deletions
+4 -4
View File
@@ -24,8 +24,8 @@ import (
"github.com/netbirdio/netbird/formatter/hook"
"github.com/netbirdio/netbird/management/internals/server"
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
"github.com/netbirdio/netbird/management/internals/shared/grpc"
nbdomain "github.com/netbirdio/netbird/shared/management/domain"
"github.com/netbirdio/netbird/shared/management/grpc"
"github.com/netbirdio/netbird/util"
"github.com/netbirdio/netbird/util/crypt"
)
@@ -154,13 +154,13 @@ func LoadMgmtConfig(ctx context.Context, mgmtConfigPath string) (*nbconfig.Confi
ApplyCommandLineOverrides(loadedConfig)
err := grpc.ValidateSyncMessageVersions(loadedConfig.SupportedSyncMessageVersions)
err := grpc.ValidateSyncMessageVersion(loadedConfig.HighestSupportedSyncMessageVersion)
if err != nil {
return nil, err
}
for account, versions := range loadedConfig.PerAccountSupportedSyncMessageVersions {
err := grpc.ValidateSyncMessageVersions(versions)
for account, version := range loadedConfig.PerAccountHighestSupportedSyncMessageVersion {
err := grpc.ValidateSyncMessageVersion(&version)
if err != nil {
return nil, fmt.Errorf("unrecognized sync message version for account %s, %w", account, err)
}
+27 -12
View File
@@ -5,6 +5,7 @@ import (
"os"
"testing"
"github.com/netbirdio/netbird/shared/management/grpc"
"github.com/stretchr/testify/assert"
)
@@ -23,34 +24,48 @@ const (
"AuthIssuer": "https://something.eu.auth0.com/",
"OIDCConfigEndpoint": "https://something.eu.auth0.com/.well-known/openid-configuration"
},
"SupportedSyncMessageVersions": ["Base", "ComponentNetworkMap"],
"PerAccountSupportedSyncMessageVersions": {
"1": ["Base"],
"2": ["ComponentNetworkMap"],
"3": []
"HighestSupportedSyncMessageVersion": 1,
"PerAccountHighestSupportedSyncMessageVersion": {
"1": 0,
"2": 1
}
}`
)
func Test_loadMgmtConfig(t *testing.T) {
tmpFile, err := createConfig()
func Test_LoadMgmtConfig(t *testing.T) {
tmpFile, err := createConfig(exampleConfig)
assert.NoError(t, err)
cfg, err := LoadMgmtConfig(context.Background(), tmpFile)
assert.NoError(t, err)
assert.NotEmpty(t, cfg.Relay)
assert.NotEmpty(t, cfg.Relay.Addresses)
assert.Equal(t, []string{"Base", "ComponentNetworkMap"}, cfg.SupportedSyncMessageVersions)
assert.Equal(t, map[string][]string{
"1": {"Base"}, "2": {"ComponentNetworkMap"}, "3": {}}, cfg.PerAccountSupportedSyncMessageVersions)
assert.Equal(t, int(grpc.ComponentNetworkMap), *cfg.HighestSupportedSyncMessageVersion)
assert.Equal(t, map[string]int{"1": int(grpc.Base), "2": int(grpc.ComponentNetworkMap)}, cfg.PerAccountHighestSupportedSyncMessageVersion)
}
func createConfig() (string, error) {
func Test_LoadMgmtConfig_Empty(t *testing.T) {
tmpFile, err := createConfig(`{
"HttpConfig": {
"AuthAudience": "https://stageapp/",
"AuthIssuer": "https://something.eu.auth0.com/",
"OIDCConfigEndpoint": "https://something.eu.auth0.com/.well-known/openid-configuration"
}
}`)
assert.NoError(t, err)
cfg, err := LoadMgmtConfig(context.Background(), tmpFile)
assert.NoError(t, err)
assert.Nil(t, cfg.HighestSupportedSyncMessageVersion)
assert.Nil(t, cfg.PerAccountHighestSupportedSyncMessageVersion)
}
func createConfig(config string) (string, error) {
tmpfile, err := os.CreateTemp("", "config.json")
if err != nil {
return "", err
}
_, err = tmpfile.Write([]byte(exampleConfig))
_, err = tmpfile.Write([]byte(config))
if err != nil {
return "", err
}
@@ -21,6 +21,7 @@ import (
"github.com/netbirdio/netbird/management/internals/server/config"
"github.com/netbirdio/netbird/management/internals/shared/grpc"
"github.com/netbirdio/netbird/management/server/account"
sharedgrpc "github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
@@ -57,9 +58,9 @@ type Controller struct {
integratedPeerValidator integrated_validator.IntegratedValidator
serverSupportedSyncMessageVersions []grpc.SyncMessageVersion
serverSupportedSyncMessageVersion sharedgrpc.SyncMessageVersion
perAccountServerSupportedSyncMessageVersions map[string][]grpc.SyncMessageVersion
perAccountServerSupportedSyncMessageVersions map[string]sharedgrpc.SyncMessageVersion
}
type bufferUpdate struct {
@@ -96,8 +97,8 @@ func NewController(ctx context.Context, store store.Store, metrics telemetry.App
proxyController: proxyController,
EphemeralPeersManager: ephemeralPeersManager,
serverSupportedSyncMessageVersions: grpc.SyncMessageVersionsFromString(config.SupportedSyncMessageVersions),
perAccountServerSupportedSyncMessageVersions: grpc.SyncMessageVersionsFromMap(config.PerAccountSupportedSyncMessageVersions),
serverSupportedSyncMessageVersion: sharedgrpc.SyncMessageVersionFromConfig(config.HighestSupportedSyncMessageVersion),
perAccountServerSupportedSyncMessageVersions: sharedgrpc.SyncMessageVersionsFromMap(config.PerAccountHighestSupportedSyncMessageVersion),
}
}
@@ -232,13 +233,13 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
proxyNetworkMap := proxyNetworkMaps[p.ID]
var update *proto.SyncResponse
commonSyncMessageVersions := grpc.CommonSyncMessageVersions(
commonSyncMessageVersion := sharedgrpc.HighestCommonSyncMessageVersions(
c.perAccountOrGlobalSupportedSyncMessageVersions(accountID),
grpc.SyncMessageVersionsFromProtoEnums(peer.Meta.Capabilities))
sharedgrpc.SyncMessageVersionFromConfig(&peer.Meta.SyncMessageVersion))
log.WithContext(ctx).WithField("sync_message_version", commonSyncMessageVersions[0]).Debug("common highest sync message version")
log.WithContext(ctx).WithField("sync_message_version", commonSyncMessageVersion).Debug("common highest sync message version")
if commonSyncMessageVersions[0] == grpc.ComponentNetworkMap {
if commonSyncMessageVersion == sharedgrpc.ComponentNetworkMap {
components := account.GetPeerNetworkMapComponents(
ctx, p.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, groupIDToUserIDs)
@@ -287,11 +288,11 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
return nil
}
func (c *Controller) perAccountOrGlobalSupportedSyncMessageVersions(accountId string) []grpc.SyncMessageVersion {
func (c *Controller) perAccountOrGlobalSupportedSyncMessageVersions(accountId string) sharedgrpc.SyncMessageVersion {
if perAccount, ok := c.perAccountServerSupportedSyncMessageVersions[accountId]; ok {
return perAccount
}
return c.serverSupportedSyncMessageVersions
return c.serverSupportedSyncMessageVersion
}
// UpdatePeers updates all peers that belong to an account.
@@ -399,13 +400,13 @@ func (c *Controller) sendUpdateForAffectedPeers(ctx context.Context, accountID s
proxyNetworkMap := proxyNetworkMaps[p.ID]
var update *proto.SyncResponse
commonSyncMessageVersions := grpc.CommonSyncMessageVersions(
commonSyncMessageVersion := sharedgrpc.HighestCommonSyncMessageVersions(
c.perAccountOrGlobalSupportedSyncMessageVersions(accountID),
grpc.SyncMessageVersionsFromProtoEnums(peer.Meta.Capabilities))
sharedgrpc.SyncMessageVersionFromConfig(&peer.Meta.SyncMessageVersion))
log.WithContext(ctx).WithField("sync_message_version", commonSyncMessageVersions[0]).Debug("common highest sync message version")
log.WithContext(ctx).WithField("sync_message_version", commonSyncMessageVersion).Debug("common highest sync message version")
if commonSyncMessageVersions[0] == grpc.ComponentNetworkMap {
if commonSyncMessageVersion == sharedgrpc.ComponentNetworkMap {
components := account.GetPeerNetworkMapComponents(
ctx, p.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, groupIDToUserIDs)
@@ -535,13 +536,13 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe
var update *proto.SyncResponse
commonSyncMessageVersions := grpc.CommonSyncMessageVersions(
commonSyncMessageVersion := sharedgrpc.HighestCommonSyncMessageVersions(
c.perAccountOrGlobalSupportedSyncMessageVersions(accountId),
grpc.SyncMessageVersionsFromProtoEnums(peer.Meta.Capabilities))
sharedgrpc.SyncMessageVersionFromConfig(&peer.Meta.SyncMessageVersion))
log.WithContext(ctx).WithField("sync_message_version", commonSyncMessageVersions[0]).Debug("common highest sync message version")
log.WithContext(ctx).WithField("sync_message_version", commonSyncMessageVersion).Debug("common highest sync message version")
if commonSyncMessageVersions[0] == grpc.ComponentNetworkMap {
if commonSyncMessageVersion == sharedgrpc.ComponentNetworkMap {
components := account.GetPeerNetworkMapComponents(
ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, groupIDToUserIDs)
+2 -2
View File
@@ -62,9 +62,9 @@ type Config struct {
// When set, Dex will be embedded in the management server and serve requests at /oauth2/
EmbeddedIdP *idp.EmbeddedIdPConfig
SupportedSyncMessageVersions []string
HighestSupportedSyncMessageVersion *int
PerAccountSupportedSyncMessageVersions map[string][]string
PerAccountHighestSupportedSyncMessageVersion map[string]int
}
// GetAuthAudiences returns the audience from the http config and device authorization flow config
@@ -10,6 +10,7 @@ import (
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/types"
sharedgrpc "github.com/netbirdio/netbird/shared/management/grpc"
"github.com/netbirdio/netbird/shared/management/networkmap"
"github.com/netbirdio/netbird/shared/management/proto"
)
@@ -72,7 +73,7 @@ func ToComponentSyncResponse(
PeerConfig: peerConfig,
NetworkMapEnvelope: envelope,
Checks: toProtocolChecks(ctx, checks),
Version: proto.SyncResponseVersion_VersionComponentNetworkMap,
Version: int32(sharedgrpc.ComponentNetworkMap),
}
nbConfig := toNetbirdConfig(config, turnCredentials, relayCredentials, extraSettings, settings)
+13 -11
View File
@@ -45,6 +45,7 @@ import (
"github.com/netbirdio/netbird/management/server/settings"
"github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/management/server/types"
sharedgrpc "github.com/netbirdio/netbird/shared/management/grpc"
"github.com/netbirdio/netbird/shared/management/proto"
internalStatus "github.com/netbirdio/netbird/shared/management/status"
)
@@ -684,8 +685,9 @@ func extractPeerMeta(ctx context.Context, meta *proto.PeerSystemMeta) nbpeer.Pee
LazyConnectionEnabled: meta.GetFlags().GetLazyConnectionEnabled(),
DisableIPv6: meta.GetFlags().GetDisableIPv6(),
},
Files: files,
Capabilities: capabilitiesToInt32(meta.GetCapabilities()),
Files: files,
Capabilities: capabilitiesToInt32(meta.GetCapabilities()),
SyncMessageVersion: int(meta.GetSyncMessageVersion()),
}
}
@@ -1021,13 +1023,13 @@ func (s *Server) sendInitialSync(ctx context.Context, peerKey wgtypes.Key, peer
var plainResp *proto.SyncResponse
commonSyncMessageVersions := CommonSyncMessageVersions(
SyncMessageVersionsFromString(s.perAccountOrGlobalSyncMessageVersions(peer.AccountID)),
SyncMessageVersionsFromProtoEnums(peer.Meta.Capabilities))
commonSyncMessageVersion := sharedgrpc.HighestCommonSyncMessageVersions(
s.perAccountOrGlobalSyncMessageVersions(peer.AccountID),
sharedgrpc.SyncMessageVersionFromConfig(&peer.Meta.SyncMessageVersion))
log.WithContext(ctx).WithField("sync_message_version", commonSyncMessageVersions[0]).Debug("common highest sync message version")
log.WithContext(ctx).WithField("sync_message_version", commonSyncMessageVersion).Debug("common highest sync message version")
if commonSyncMessageVersions[0] == ComponentNetworkMap {
if commonSyncMessageVersion == sharedgrpc.ComponentNetworkMap {
// Capable peer: discard the legacy NetworkMap that SyncAndMarkPeer
// computed and recompute the raw components instead. This wastes one
// Calculate() call per initial-sync — the component-based wire
@@ -1073,11 +1075,11 @@ func (s *Server) sendInitialSync(ctx context.Context, peerKey wgtypes.Key, peer
return nil
}
func (s *Server) perAccountOrGlobalSyncMessageVersions(accountId string) []string {
if versions, ok := s.config.PerAccountSupportedSyncMessageVersions[accountId]; ok {
return versions
func (s *Server) perAccountOrGlobalSyncMessageVersions(accountId string) sharedgrpc.SyncMessageVersion {
if version, ok := s.config.PerAccountHighestSupportedSyncMessageVersion[accountId]; ok {
return sharedgrpc.SyncMessageVersionFromConfig(&version)
}
return s.config.SupportedSyncMessageVersions
return sharedgrpc.SyncMessageVersionFromConfig(s.config.HighestSupportedSyncMessageVersion)
}
// GetDeviceAuthorizationFlow returns a device authorization flow information
@@ -1,127 +0,0 @@
package grpc
import (
"cmp"
"errors"
"fmt"
"slices"
"github.com/netbirdio/netbird/shared/management/proto"
)
type SyncMessageVersion uint16
const (
Base SyncMessageVersion = iota
ComponentNetworkMap
)
var (
AllSyncMessageVersions = []SyncMessageVersion{Base, ComponentNetworkMap}
ProtoToMessageSyncVersion = map[proto.PeerCapability]SyncMessageVersion{
proto.PeerCapability_PeerCapabilityComponentNetworkMap: ComponentNetworkMap,
}
MessageSyncVersionToProto = map[SyncMessageVersion]proto.PeerCapability{
ComponentNetworkMap: proto.PeerCapability_PeerCapabilityComponentNetworkMap,
}
)
var ErrorUnrecognizedSyncMessageVersion = errors.New("unrecognized SyncMessageVersion")
func (sm SyncMessageVersion) String() string {
return [...]string{"Base", "ComponentNetworkMap"}[sm]
}
func AllSupportedSyncMessageVersions() []SyncMessageVersion {
return AllSyncMessageVersions
}
func ValidateSyncMessageVersions(tovalidate []string) error {
// empty list == we support all available versions
if len(tovalidate) == 0 {
return nil
}
allversions := make(map[string]SyncMessageVersion, len(AllSyncMessageVersions))
for _, v := range AllSyncMessageVersions {
allversions[v.String()] = v
}
for _, s := range tovalidate {
if _, ok := allversions[s]; !ok {
return fmt.Errorf("%s: %w", s, ErrorUnrecognizedSyncMessageVersion)
}
}
return nil
}
// convert human-readable versions to enums
// please note no validation on input strings is done, misses are silently discarded
// the assumption is ValidateSyncMessageVersions() has been called before using SyncMessageVersionsFromString()
func SyncMessageVersionsFromString(toconvert []string) []SyncMessageVersion {
// empty list == we support all available versions
if len(toconvert) == 0 {
return AllSyncMessageVersions
}
allversions := make(map[string]SyncMessageVersion, len(AllSyncMessageVersions))
for _, v := range AllSyncMessageVersions {
allversions[v.String()] = v
}
toret := make([]SyncMessageVersion, 0)
for _, s := range toconvert {
toret = append(toret, allversions[s])
}
return toret
}
// convert per-account human-readable versions to enums
// please note no validation on versions strings is done, misses are silently discarded
// the assumption is ValidateSyncMessageVersions() has been called before using SyncMessageVersionsFromMap()
func SyncMessageVersionsFromMap(toconvert map[string][]string) map[string][]SyncMessageVersion {
// no per-account overrides
if len(toconvert) == 0 {
return nil
}
allversions := make(map[string]SyncMessageVersion, len(AllSyncMessageVersions))
for _, v := range AllSyncMessageVersions {
allversions[v.String()] = v
}
toret := make(map[string][]SyncMessageVersion, len(toconvert))
for account, versions := range toconvert {
toret[account] = SyncMessageVersionsFromString(versions)
}
return toret
}
// these come from the client; peer capabilities are expected to contain all enabled sync message versions.
// an empty list is interpreted as all but the base version are disabled.
func SyncMessageVersionsFromProtoEnums(peerCapabilities []int32) []SyncMessageVersion {
toret := make([]SyncMessageVersion, 0)
for _, pc := range peerCapabilities {
if _, ok := ProtoToMessageSyncVersion[proto.PeerCapability(pc)]; ok {
toret = append(toret, ProtoToMessageSyncVersion[proto.PeerCapability(pc)])
}
}
return toret
}
// return highest common sync message version, or Default (which is always available)
func CommonSyncMessageVersions(a []SyncMessageVersion, b []SyncMessageVersion) []SyncMessageVersion {
toret := []SyncMessageVersion{Base}
aversions := make(map[SyncMessageVersion]struct{})
for _, va := range a {
aversions[va] = struct{}{}
}
for _, vb := range b {
if _, ok := aversions[vb]; ok && vb != Base { // we already added 'Base' version
toret = append(toret, vb)
}
}
slices.SortFunc(toret, func(a SyncMessageVersion, b SyncMessageVersion) int {
return -1 * cmp.Compare(a, b)
})
return toret
}
@@ -1,45 +0,0 @@
package grpc
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidation(t *testing.T) {
assert.NoError(t, ValidateSyncMessageVersions([]string{"Base", "ComponentNetworkMap"}))
assert.NoError(t, ValidateSyncMessageVersions([]string{}))
assert.ErrorIs(t, ValidateSyncMessageVersions([]string{"Boom"}), ErrorUnrecognizedSyncMessageVersion)
assert.ErrorIs(t, ValidateSyncMessageVersions([]string{"Base", "Boom"}), ErrorUnrecognizedSyncMessageVersion)
}
func TestConversionStringToEnum(t *testing.T) {
assert.Equal(t, []SyncMessageVersion{0, 1}, SyncMessageVersionsFromString([]string{}))
assert.Equal(t, []SyncMessageVersion{0}, SyncMessageVersionsFromString([]string{"Base"}))
assert.Equal(t, []SyncMessageVersion{1}, SyncMessageVersionsFromString([]string{"ComponentNetworkMap"}))
}
func TestPerAccountConversionStringToEnum(t *testing.T) {
assert.Equal(t, map[string][]SyncMessageVersion{"1": {0, 1}}, SyncMessageVersionsFromMap(map[string][]string{"1": {}}))
assert.Equal(t, map[string][]SyncMessageVersion{"2": {0}}, SyncMessageVersionsFromMap(map[string][]string{"2": {"Base"}}))
assert.Equal(t, map[string][]SyncMessageVersion{"3": {1}, "4": {0, 1}},
SyncMessageVersionsFromMap(map[string][]string{
"3": {"ComponentNetworkMap"},
"4": {"Base", "ComponentNetworkMap"},
}))
}
func TestConversionFromProtoEnums(t *testing.T) {
assert.Equal(t, []SyncMessageVersion{}, SyncMessageVersionsFromProtoEnums([]int32{}))
assert.Equal(t, []SyncMessageVersion{}, SyncMessageVersionsFromProtoEnums([]int32{0}))
assert.Equal(t, []SyncMessageVersion{1}, SyncMessageVersionsFromProtoEnums([]int32{3}))
}
func TestCommonVersions(t *testing.T) {
assert.Equal(t, []SyncMessageVersion{0},
CommonSyncMessageVersions([]SyncMessageVersion{0, 1}, []SyncMessageVersion{}))
assert.Equal(t, []SyncMessageVersion{0},
CommonSyncMessageVersions([]SyncMessageVersion{0}, []SyncMessageVersion{0, 1, 2}))
assert.Equal(t, []SyncMessageVersion{5, 1, 0},
CommonSyncMessageVersions([]SyncMessageVersion{0, 1, 4, 5}, []SyncMessageVersion{1, 0, 5, 2}))
}
+4
View File
@@ -173,6 +173,7 @@ type PeerSystemMeta struct { //nolint:revive
Flags Flags `gorm:"serializer:json"`
Files []File `gorm:"serializer:json"`
Capabilities []int32 `gorm:"serializer:json"`
SyncMessageVersion int
}
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
@@ -415,6 +416,9 @@ func diffMeta(oldMeta, newMeta PeerSystemMeta, oldLocation, newLocation Location
if !sameMultiset(oldMeta.Files, newMeta.Files) {
add("files", fmt.Sprintf("%v", oldMeta.Files), fmt.Sprintf("%v", newMeta.Files))
}
if oldMeta.SyncMessageVersion != newMeta.SyncMessageVersion {
add("sync_meta_version", fmt.Sprintf("%d", oldMeta.SyncMessageVersion), fmt.Sprintf("%d", newMeta.SyncMessageVersion))
}
if !oldLocation.equal(newLocation) {
add("connection_ip", oldLocation.ConnectionIP, newLocation.ConnectionIP)