add support for sync message version negotiation

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
This commit is contained in:
Dmitri Dolguikh
2026-07-14 12:12:49 +02:00
parent d5178416af
commit 5d793985d4
9 changed files with 240 additions and 98 deletions
+13 -1
View File
@@ -86,6 +86,8 @@ type Server struct {
reverseProxyManager rpservice.Manager
reverseProxyMu sync.RWMutex
supportedSyncMessageVersions []SyncMessageVersion
}
// NewServer creates a new Management server
@@ -130,6 +132,8 @@ func NewServer(
}
}
syncMessageVersions := SyncMessageVersionsFromString(config.SupportedSyncMessageVersions)
return &Server{
jobManager: jobManager,
accountManager: accountManager,
@@ -149,6 +153,8 @@ func NewServer(
syncLim: syncLim,
syncLimEnabled: syncLimEnabled,
supportedSyncMessageVersions: syncMessageVersions,
}, nil
}
@@ -245,6 +251,7 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
realIP := getRealIP(ctx)
sRealIP := realIP.String()
peerMeta := extractPeerMeta(ctx, syncReq.GetMeta())
userID, err := s.accountManager.GetUserIDByPeerKey(ctx, peerKey.String())
if err != nil {
s.syncSem.Add(-1)
@@ -1019,7 +1026,12 @@ func (s *Server) sendInitialSync(ctx context.Context, peerKey wgtypes.Key, peer
dnsName := s.networkMapController.GetDNSDomain(settings)
var plainResp *proto.SyncResponse
if s.networkMapController.PeerNeedsComponents(peer) {
commonSyncMessageVersions := CommonSyncMessageVersions(
SyncMessageVersionsFromString(s.config.SupportedSyncMessageVersions),
SyncMessageVersionsFromProtoEnums(peer.Meta.Capabilities))
if commonSyncMessageVersions[0] == 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
@@ -0,0 +1,104 @@
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 UnrecognizedSyncMessageVersionError = 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, UnrecognizedSyncMessageVersionError)
}
}
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, len(toconvert))
for _, s := range toconvert {
toret = append(toret, allversions[s])
}
return toret
}
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
}