[management, client] Add IPv6 overlay support (#5631)

This commit is contained in:
Viktor Liu
2026-05-07 18:33:37 +09:00
committed by GitHub
parent f23aaa9ae7
commit 205ebcfda2
229 changed files with 10155 additions and 2816 deletions

View File

@@ -6,6 +6,7 @@ import (
b64 "encoding/base64"
"fmt"
"net"
"net/netip"
"slices"
"strings"
"time"
@@ -521,6 +522,27 @@ func (am *DefaultAccountManager) GetPeerNetwork(ctx context.Context, peerID stri
return account.Network.Copy(), err
}
// peerWillHaveIPv6 checks whether the peer's future group memberships
// (auto-groups + allGroupID) overlap with IPv6EnabledGroups.
func peerWillHaveIPv6(settings *types.Settings, groupsToAdd []string, allGroupID string) bool {
enabledSet := make(map[string]struct{}, len(settings.IPv6EnabledGroups))
for _, gid := range settings.IPv6EnabledGroups {
enabledSet[gid] = struct{}{}
}
if allGroupID != "" {
if _, ok := enabledSet[allGroupID]; ok {
return true
}
}
for _, gid := range groupsToAdd {
if _, ok := enabledSet[gid]; ok {
return true
}
}
return false
}
type peerAddAuthConfig struct {
AccountID string
SetupKeyID string
@@ -715,8 +737,11 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
maxAttempts := 10
for attempt := 1; attempt <= maxAttempts; attempt++ {
var freeIP net.IP
freeIP, err = types.AllocateRandomPeerIP(network.Net)
netPrefix, err := netip.ParsePrefix(network.Net.String())
if err != nil {
return nil, nil, nil, fmt.Errorf("parse network prefix: %w", err)
}
freeIP, err := types.AllocateRandomPeerIP(netPrefix)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to get free IP: %w", err)
}
@@ -736,6 +761,29 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
newPeer.DNSLabel = freeLabel
newPeer.IP = freeIP
if len(settings.IPv6EnabledGroups) > 0 && network.NetV6.IP != nil {
var allGroupID string
if !peer.ProxyMeta.Embedded {
allGroup, err := am.Store.GetGroupByName(ctx, store.LockingStrengthNone, accountID, "All")
if err != nil {
log.WithContext(ctx).Debugf("get All group for IPv6 allocation: %v", err)
} else {
allGroupID = allGroup.ID
}
}
if peerWillHaveIPv6(settings, peerAddConfig.GroupsToAdd, allGroupID) {
v6Prefix, err := netip.ParsePrefix(network.NetV6.String())
if err != nil {
return nil, nil, nil, fmt.Errorf("parse IPv6 prefix: %w", err)
}
freeIPv6, err := types.AllocateRandomPeerIPv6(v6Prefix)
if err != nil {
return nil, nil, nil, fmt.Errorf("allocate peer IPv6: %w", err)
}
newPeer.IPv6 = freeIPv6
}
}
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
err = transaction.AddPeerToAccount(ctx, newPeer)
if err != nil {
@@ -805,10 +853,6 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
return nil, nil, nil, fmt.Errorf("failed to add peer to database: %w", err)
}
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to add peer to database after %d attempts: %w", maxAttempts, err)
}
if newPeer == nil {
return nil, nil, nil, fmt.Errorf("new peer is nil")
}
@@ -834,21 +878,24 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
return p, nmap, pc, err
}
func getPeerIPDNSLabel(ip net.IP, peerHostName string) (string, error) {
ip = ip.To4()
func getPeerIPDNSLabel(ip netip.Addr, peerHostName string) (string, error) {
if !ip.Is4() {
return "", fmt.Errorf("DNS label generation requires an IPv4 address, got %s", ip)
}
b := ip.As4()
dnsName, err := nbdns.GetParsedDomainLabel(peerHostName)
if err != nil {
return "", fmt.Errorf("failed to parse peer host name %s: %w", peerHostName, err)
}
return fmt.Sprintf("%s-%d-%d", dnsName, ip[2], ip[3]), nil
return fmt.Sprintf("%s-%d-%d", dnsName, b[2], b[3]), nil
}
// SyncPeer checks whether peer is eligible for receiving NetworkMap (authenticated) and returns its NetworkMap if eligible
func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, int64, error) {
var peer *nbpeer.Peer
var updated, versionChanged bool
var updated, versionChanged, ipv6CapabilityChanged bool
var err error
var postureChecks []*posture.Checks
var peerGroupIDs []string
@@ -884,7 +931,9 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync types.PeerSy
return err
}
oldHasIPv6Cap := peer.HasCapability(nbpeer.PeerCapabilityIPv6Overlay)
updated, versionChanged = peer.UpdateMetaIfNew(sync.Meta)
ipv6CapabilityChanged = oldHasIPv6Cap != peer.HasCapability(nbpeer.PeerCapabilityIPv6Overlay)
if updated {
am.metrics.AccountManagerMetrics().CountPeerMetUpdate()
log.WithContext(ctx).Tracef("peer %s metadata updated", peer.ID)
@@ -908,7 +957,7 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync types.PeerSy
return nil, nil, nil, 0, err
}
if isStatusChanged || sync.UpdateAccountPeers || (updated && (len(postureChecks) > 0 || versionChanged)) {
if isStatusChanged || sync.UpdateAccountPeers || ipv6CapabilityChanged || (updated && (len(postureChecks) > 0 || versionChanged)) {
err = am.networkMapController.OnPeersUpdated(ctx, accountID, []string{peer.ID})
if err != nil {
return nil, nil, nil, 0, fmt.Errorf("notify network map controller of peer update: %w", err)
@@ -958,6 +1007,7 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.Peer
var peer *nbpeer.Peer
var updateRemotePeers bool
var isPeerUpdated bool
var ipv6CapabilityChanged bool
var postureChecks []*posture.Checks
var peerGroupIDs []string
@@ -997,7 +1047,9 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.Peer
return err
}
oldHasIPv6Cap := peer.HasCapability(nbpeer.PeerCapabilityIPv6Overlay)
isPeerUpdated, _ = peer.UpdateMetaIfNew(login.Meta)
ipv6CapabilityChanged = oldHasIPv6Cap != peer.HasCapability(nbpeer.PeerCapabilityIPv6Overlay)
if isPeerUpdated {
am.metrics.AccountManagerMetrics().CountPeerMetUpdate()
shouldStorePeer = true
@@ -1035,7 +1087,7 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.Peer
return nil, nil, nil, err
}
if updateRemotePeers || isStatusChanged || (isPeerUpdated && len(postureChecks) > 0) {
if updateRemotePeers || isStatusChanged || ipv6CapabilityChanged || (isPeerUpdated && len(postureChecks) > 0) {
err = am.networkMapController.OnPeersUpdated(ctx, accountID, []string{peer.ID})
if err != nil {
return nil, nil, nil, fmt.Errorf("notify network map controller of peer update: %w", err)