mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-30 02:29:08 +02:00
Replace IP allocation logic (#342)
The peer IP allocation logic was allocating sequential peer IP from the 100.64.0.0/10 address block. Each account is created with a random subnet from 100.64.0.0/10. The total amount of potential subnets is 64. The new logic allocates random peer IP from the account subnet. This gives us flexibility to add support for multi subnet accounts without overlapping IPs.
This commit is contained in:
@@ -1,16 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/c-robinson/iplib"
|
||||
"github.com/rs/xid"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
upperIPv4 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 255, 255, 255, 255}
|
||||
upperIPv6 = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
"time"
|
||||
)
|
||||
|
||||
type NetworkMap struct {
|
||||
@@ -30,10 +27,19 @@ type Network struct {
|
||||
}
|
||||
|
||||
// NewNetwork creates a new Network initializing it with a Serial=0
|
||||
// It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets)
|
||||
func NewNetwork() *Network {
|
||||
|
||||
n := iplib.NewNet4(net.ParseIP("100.64.0.0"), 10)
|
||||
sub, _ := n.Subnet(16)
|
||||
|
||||
s := rand.NewSource(time.Now().Unix())
|
||||
r := rand.New(s)
|
||||
intn := r.Intn(len(sub))
|
||||
|
||||
return &Network{
|
||||
Id: xid.New().String(),
|
||||
Net: net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 255, 0, 0}},
|
||||
Net: sub[intn].IPNet,
|
||||
Dns: "",
|
||||
Serial: 0}
|
||||
}
|
||||
@@ -63,51 +69,63 @@ func (n *Network) Copy() *Network {
|
||||
|
||||
// AllocatePeerIP pics an available IP from an net.IPNet.
|
||||
// This method considers already taken IPs and reuses IPs if there are gaps in takenIps
|
||||
// E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.5] then the result would be 100.30.0.2
|
||||
// E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.4] then the result would be 100.30.0.2 or 100.30.0.3
|
||||
func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) {
|
||||
takenIpMap := make(map[string]net.IP)
|
||||
takenIpMap[ipNet.IP.String()] = ipNet.IP
|
||||
takenIPMap := make(map[string]struct{})
|
||||
takenIPMap[ipNet.IP.String()] = struct{}{}
|
||||
for _, ip := range takenIps {
|
||||
takenIpMap[ip.String()] = ip
|
||||
}
|
||||
for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); ip = GetNextIP(ip) {
|
||||
if _, ok := takenIpMap[ip.String()]; !ok {
|
||||
return ip, nil
|
||||
}
|
||||
takenIPMap[ip.String()] = struct{}{}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed allocating new IP for the ipNet %s and takenIps %s", ipNet.String(), takenIps)
|
||||
ips, _, err := generateIPs(&ipNet, takenIPMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed allocating new IP for the ipNet %s and takenIps %s", ipNet.String(), takenIps)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("failed allocating new IP for the ipNet %s - network is out of IPs", ipNet.String())
|
||||
}
|
||||
|
||||
// pick a random IP
|
||||
s := rand.NewSource(time.Now().Unix())
|
||||
r := rand.New(s)
|
||||
intn := r.Intn(len(ips))
|
||||
|
||||
return ips[intn], nil
|
||||
}
|
||||
|
||||
// GetNextIP returns the next IP from the given IP address. If the given IP is
|
||||
// the last IP of a v4 or v6 range, the same IP is returned.
|
||||
// Credits to Cilium team.
|
||||
// Copyright 2017-2020 Authors of Cilium
|
||||
func GetNextIP(ip net.IP) net.IP {
|
||||
if ip.Equal(upperIPv4) || ip.Equal(upperIPv6) {
|
||||
return ip
|
||||
// generateIPs generates a list of all possible IPs of the given network excluding IPs specified in the exclusion list
|
||||
func generateIPs(ipNet *net.IPNet, exclusions map[string]struct{}) ([]net.IP, int, error) {
|
||||
|
||||
var ips []net.IP
|
||||
for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); incIP(ip) {
|
||||
if _, ok := exclusions[ip.String()]; !ok && ip[3] != 0 {
|
||||
ips = append(ips, copyIP(ip))
|
||||
}
|
||||
}
|
||||
|
||||
nextIP := make(net.IP, len(ip))
|
||||
switch len(ip) {
|
||||
case net.IPv4len:
|
||||
ipU32 := binary.BigEndian.Uint32(ip)
|
||||
ipU32++
|
||||
binary.BigEndian.PutUint32(nextIP, ipU32)
|
||||
return nextIP
|
||||
case net.IPv6len:
|
||||
ipU64 := binary.BigEndian.Uint64(ip[net.IPv6len/2:])
|
||||
ipU64++
|
||||
binary.BigEndian.PutUint64(nextIP[net.IPv6len/2:], ipU64)
|
||||
if ipU64 == 0 {
|
||||
ipU64 = binary.BigEndian.Uint64(ip[:net.IPv6len/2])
|
||||
ipU64++
|
||||
binary.BigEndian.PutUint64(nextIP[:net.IPv6len/2], ipU64)
|
||||
} else {
|
||||
copy(nextIP[:net.IPv6len/2], ip[:net.IPv6len/2])
|
||||
}
|
||||
return nextIP
|
||||
// remove network address and broadcast address
|
||||
lenIPs := len(ips)
|
||||
switch {
|
||||
case lenIPs < 2:
|
||||
return ips, lenIPs, nil
|
||||
|
||||
default:
|
||||
return ip
|
||||
return ips[1 : len(ips)-1], lenIPs - 2, nil
|
||||
}
|
||||
}
|
||||
|
||||
func copyIP(ip net.IP) net.IP {
|
||||
dup := make(net.IP, len(ip))
|
||||
copy(dup, ip)
|
||||
return dup
|
||||
}
|
||||
|
||||
func incIP(ip net.IP) {
|
||||
for j := len(ip) - 1; j >= 0; j-- {
|
||||
ip[j]++
|
||||
if ip[j] > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user