[management] Add IPv6 overlay addressing and capability gating (#5698)

This commit is contained in:
Viktor Liu
2026-04-08 22:40:51 +08:00
committed by GitHub
parent 86f1b53bd4
commit a1e7db2713
51 changed files with 2622 additions and 394 deletions

View File

@@ -11,6 +11,12 @@ import (
"github.com/netbirdio/netbird/shared/management/http/api"
)
// Peer capability constants mirror the proto enum values.
const (
PeerCapabilitySourcePrefixes int32 = 1
PeerCapabilityIPv6Overlay int32 = 2
)
// Peer represents a machine connected to the network.
// The Peer is a WireGuard peer identified by a public key
type Peer struct {
@@ -21,7 +27,9 @@ type Peer struct {
// WireGuard public key
Key string // uniqueness index (check migrations)
// IP address of the Peer
IP net.IP `gorm:"serializer:json"` // uniqueness index per accountID (check migrations)
IP netip.Addr `gorm:"serializer:json"` // uniqueness index per accountID (check migrations)
// IPv6 overlay address of the Peer, zero value if IPv6 is not enabled for the account.
IPv6 netip.Addr `gorm:"serializer:json"`
// Meta is a Peer system meta data
Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"`
// ProxyMeta is metadata related to proxy peers
@@ -115,6 +123,7 @@ type Flags struct {
DisableFirewall bool
BlockLANAccess bool
BlockInbound bool
DisableIPv6 bool
LazyConnectionEnabled bool
}
@@ -138,6 +147,7 @@ type PeerSystemMeta struct { //nolint:revive
Environment Environment `gorm:"serializer:json"`
Flags Flags `gorm:"serializer:json"`
Files []File `gorm:"serializer:json"`
Capabilities []int32 `gorm:"serializer:json"`
}
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
@@ -182,7 +192,8 @@ func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
p.SystemManufacturer == other.SystemManufacturer &&
p.Environment.Cloud == other.Environment.Cloud &&
p.Environment.Platform == other.Environment.Platform &&
p.Flags.isEqual(other.Flags)
p.Flags.isEqual(other.Flags) &&
capabilitiesEqual(p.Capabilities, other.Capabilities)
}
func (p PeerSystemMeta) isEmpty() bool {
@@ -210,6 +221,37 @@ func (p *Peer) AddedWithSSOLogin() bool {
return p.UserID != ""
}
// HasCapability reports whether the peer has the given capability.
func (p *Peer) HasCapability(capability int32) bool {
return slices.Contains(p.Meta.Capabilities, capability)
}
// SupportsIPv6 reports whether the peer supports IPv6 overlay.
func (p *Peer) SupportsIPv6() bool {
return !p.Meta.Flags.DisableIPv6 && p.HasCapability(PeerCapabilityIPv6Overlay)
}
// SupportsSourcePrefixes reports whether the peer reads SourcePrefixes.
func (p *Peer) SupportsSourcePrefixes() bool {
return p.HasCapability(PeerCapabilitySourcePrefixes)
}
func capabilitiesEqual(a, b []int32) bool {
if len(a) != len(b) {
return false
}
set := make(map[int32]struct{}, len(a))
for _, c := range a {
set[c] = struct{}{}
}
for _, c := range b {
if _, ok := set[c]; !ok {
return false
}
}
return true
}
// Copy copies Peer object
func (p *Peer) Copy() *Peer {
peerStatus := p.Status
@@ -221,6 +263,7 @@ func (p *Peer) Copy() *Peer {
AccountID: p.AccountID,
Key: p.Key,
IP: p.IP,
IPv6: p.IPv6,
Meta: p.Meta,
Name: p.Name,
DNSLabel: p.DNSLabel,
@@ -323,9 +366,13 @@ func (p *Peer) FQDN(dnsDomain string) string {
// EventMeta returns activity event meta related to the peer
func (p *Peer) EventMeta(dnsDomain string) map[string]any {
return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP, "created_at": p.CreatedAt,
meta := map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP, "created_at": p.CreatedAt,
"location_city_name": p.Location.CityName, "location_country_code": p.Location.CountryCode,
"location_geo_name_id": p.Location.GeoNameID, "location_connection_ip": p.Location.ConnectionIP}
if p.IPv6.IsValid() {
meta["ipv6"] = p.IPv6.String()
}
return meta
}
// Copy PeerStatus
@@ -369,5 +416,6 @@ func (f Flags) isEqual(other Flags) bool {
f.DisableFirewall == other.DisableFirewall &&
f.BlockLANAccess == other.BlockLANAccess &&
f.BlockInbound == other.BlockInbound &&
f.LazyConnectionEnabled == other.LazyConnectionEnabled
f.LazyConnectionEnabled == other.LazyConnectionEnabled &&
f.DisableIPv6 == other.DisableIPv6
}

View File

@@ -5,6 +5,7 @@ import (
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -141,3 +142,25 @@ func TestFlags_IsEqual(t *testing.T) {
})
}
}
func TestPeerCapabilities(t *testing.T) {
tests := []struct {
name string
capabilities []int32
ipv6 bool
srcPrefixes bool
}{
{"no capabilities", nil, false, false},
{"only source prefixes", []int32{PeerCapabilitySourcePrefixes}, false, true},
{"only ipv6", []int32{PeerCapabilityIPv6Overlay}, true, false},
{"both", []int32{PeerCapabilitySourcePrefixes, PeerCapabilityIPv6Overlay}, true, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Peer{Meta: PeerSystemMeta{Capabilities: tt.capabilities}}
assert.Equal(t, tt.ipv6, p.SupportsIPv6())
assert.Equal(t, tt.srcPrefixes, p.SupportsSourcePrefixes())
})
}
}