mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 07:16:38 +00:00
[management, client] Add API to change the network range (#4177)
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -324,6 +325,13 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
||||
return err
|
||||
}
|
||||
|
||||
if oldSettings.NetworkRange != newSettings.NetworkRange {
|
||||
if err = am.reallocateAccountPeerIPs(ctx, transaction, accountID, newSettings.NetworkRange); err != nil {
|
||||
return err
|
||||
}
|
||||
updateAccountPeers = true
|
||||
}
|
||||
|
||||
if oldSettings.RoutingPeerDNSResolutionEnabled != newSettings.RoutingPeerDNSResolutionEnabled ||
|
||||
oldSettings.LazyConnectionEnabled != newSettings.LazyConnectionEnabled ||
|
||||
oldSettings.DNSDomain != newSettings.DNSDomain {
|
||||
@@ -362,7 +370,18 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
||||
return nil, err
|
||||
}
|
||||
if oldSettings.DNSDomain != newSettings.DNSDomain {
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountDNSDomainUpdated, nil)
|
||||
eventMeta := map[string]any{
|
||||
"old_dns_domain": oldSettings.DNSDomain,
|
||||
"new_dns_domain": newSettings.DNSDomain,
|
||||
}
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountDNSDomainUpdated, eventMeta)
|
||||
}
|
||||
if oldSettings.NetworkRange != newSettings.NetworkRange {
|
||||
eventMeta := map[string]any{
|
||||
"old_network_range": oldSettings.NetworkRange.String(),
|
||||
"new_network_range": newSettings.NetworkRange.String(),
|
||||
}
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountNetworkRangeUpdated, eventMeta)
|
||||
}
|
||||
|
||||
if updateAccountPeers || extraSettingsChanged || groupChangesAffectPeers {
|
||||
@@ -2018,3 +2037,154 @@ func propagateUserGroupMemberships(ctx context.Context, transaction store.Store,
|
||||
|
||||
return len(updatedGroups) > 0, peersAffected, nil
|
||||
}
|
||||
|
||||
// reallocateAccountPeerIPs re-allocates all peer IPs when the network range changes
|
||||
func (am *DefaultAccountManager) reallocateAccountPeerIPs(ctx context.Context, transaction store.Store, accountID string, newNetworkRange netip.Prefix) error {
|
||||
if !newNetworkRange.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
newIPNet := net.IPNet{
|
||||
IP: newNetworkRange.Masked().Addr().AsSlice(),
|
||||
Mask: net.CIDRMask(newNetworkRange.Bits(), newNetworkRange.Addr().BitLen()),
|
||||
}
|
||||
|
||||
account, err := transaction.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Network.Net = newIPNet
|
||||
|
||||
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var takenIPs []net.IP
|
||||
|
||||
for _, peer := range peers {
|
||||
newIP, err := types.AllocatePeerIP(newIPNet, takenIPs)
|
||||
if err != nil {
|
||||
return status.Errorf(status.Internal, "allocate IP for peer %s: %v", peer.ID, err)
|
||||
}
|
||||
|
||||
log.WithContext(ctx).Infof("reallocating peer %s IP from %s to %s due to network range change",
|
||||
peer.ID, peer.IP.String(), newIP.String())
|
||||
|
||||
peer.IP = newIP
|
||||
takenIPs = append(takenIPs, newIP)
|
||||
}
|
||||
|
||||
if err = transaction.SaveAccount(ctx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, peer := range peers {
|
||||
if err = transaction.SavePeer(ctx, store.LockingStrengthUpdate, accountID, peer); err != nil {
|
||||
return status.Errorf(status.Internal, "save updated peer %s: %v", peer.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.WithContext(ctx).Infof("successfully re-allocated IPs for %d peers in account %s to network range %s",
|
||||
len(peers), accountID, newNetworkRange.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) validateIPForUpdate(account *types.Account, peers []*nbpeer.Peer, peerID string, newIP netip.Addr) error {
|
||||
if !account.Network.Net.Contains(newIP.AsSlice()) {
|
||||
return status.Errorf(status.InvalidArgument, "IP %s is not within the account network range %s", newIP.String(), account.Network.Net.String())
|
||||
}
|
||||
|
||||
for _, peer := range peers {
|
||||
if peer.ID != peerID && peer.IP.Equal(newIP.AsSlice()) {
|
||||
return status.Errorf(status.InvalidArgument, "IP %s is already assigned to peer %s", newIP.String(), peer.ID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) UpdatePeerIP(ctx context.Context, accountID, userID, peerID string, newIP netip.Addr) error {
|
||||
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Update)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validate user permissions: %w", err)
|
||||
}
|
||||
if !allowed {
|
||||
return status.NewPermissionDeniedError()
|
||||
}
|
||||
|
||||
updateNetworkMap, err := am.updatePeerIPInTransaction(ctx, accountID, userID, peerID, newIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update peer IP transaction: %w", err)
|
||||
}
|
||||
|
||||
if updateNetworkMap {
|
||||
am.BufferUpdateAccountPeers(ctx, accountID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) updatePeerIPInTransaction(ctx context.Context, accountID, userID, peerID string, newIP netip.Addr) (bool, error) {
|
||||
var updateNetworkMap bool
|
||||
err := am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||
account, err := transaction.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get account: %w", err)
|
||||
}
|
||||
|
||||
existingPeer, err := transaction.GetPeerByID(ctx, store.LockingStrengthShare, accountID, peerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get peer: %w", err)
|
||||
}
|
||||
|
||||
if existingPeer.IP.Equal(newIP.AsSlice()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get account peers: %w", err)
|
||||
}
|
||||
|
||||
if err := am.validateIPForUpdate(account, peers, peerID, newIP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := am.savePeerIPUpdate(ctx, transaction, accountID, userID, existingPeer, newIP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateNetworkMap = true
|
||||
return nil
|
||||
})
|
||||
return updateNetworkMap, err
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) savePeerIPUpdate(ctx context.Context, transaction store.Store, accountID, userID string, peer *nbpeer.Peer, newIP netip.Addr) error {
|
||||
log.WithContext(ctx).Infof("updating peer %s IP from %s to %s", peer.ID, peer.IP, newIP)
|
||||
|
||||
settings, err := transaction.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get account settings: %w", err)
|
||||
}
|
||||
dnsDomain := am.GetDNSDomain(settings)
|
||||
|
||||
eventMeta := peer.EventMeta(dnsDomain)
|
||||
oldIP := peer.IP.String()
|
||||
|
||||
peer.IP = newIP.AsSlice()
|
||||
err = transaction.SavePeer(ctx, store.LockingStrengthUpdate, accountID, peer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("save peer: %w", err)
|
||||
}
|
||||
|
||||
eventMeta["old_ip"] = oldIP
|
||||
eventMeta["ip"] = newIP.String()
|
||||
am.StoreEvent(ctx, userID, peer.ID, accountID, activity.PeerIPUpdated, eventMeta)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user