Merge pull request #44968 from corhere/libnet/ipam-cleanup
libnetwork/ipam: refactor all the things
This commit is contained in:
commit
e3215702ae
8 changed files with 572 additions and 560 deletions
|
@ -3,11 +3,12 @@ package ipam
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/libnetwork/bitseq"
|
||||
"github.com/docker/docker/libnetwork/bitmap"
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/ipbits"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -19,41 +20,40 @@ const (
|
|||
|
||||
// Allocator provides per address space ipv4/ipv6 book keeping
|
||||
type Allocator struct {
|
||||
// Predefined pools for default address spaces
|
||||
// Separate from the addrSpace because they should not be serialized
|
||||
predefined map[string][]*net.IPNet
|
||||
predefinedStartIndices map[string]int
|
||||
// The address spaces
|
||||
addrSpaces map[string]*addrSpace
|
||||
// Allocated addresses in each address space's subnet
|
||||
addresses map[SubnetKey]*bitseq.Handle
|
||||
sync.Mutex
|
||||
local, global *addrSpace
|
||||
}
|
||||
|
||||
// NewAllocator returns an instance of libnetwork ipam
|
||||
func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) {
|
||||
a := &Allocator{
|
||||
predefined: map[string][]*net.IPNet{
|
||||
localAddressSpace: lcAs,
|
||||
globalAddressSpace: glAs,
|
||||
},
|
||||
predefinedStartIndices: map[string]int{},
|
||||
addresses: map[SubnetKey]*bitseq.Handle{},
|
||||
var (
|
||||
a Allocator
|
||||
err error
|
||||
)
|
||||
a.local, err = newAddrSpace(lcAs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not construct local address space: %w", err)
|
||||
}
|
||||
|
||||
a.addrSpaces = map[string]*addrSpace{
|
||||
localAddressSpace: a.newAddressSpace(),
|
||||
globalAddressSpace: a.newAddressSpace(),
|
||||
a.global, err = newAddrSpace(glAs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not construct global address space: %w", err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (a *Allocator) newAddressSpace() *addrSpace {
|
||||
return &addrSpace{
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
alloc: a,
|
||||
func newAddrSpace(predefined []*net.IPNet) (*addrSpace, error) {
|
||||
pdf := make([]netip.Prefix, len(predefined))
|
||||
for i, n := range predefined {
|
||||
var ok bool
|
||||
pdf[i], ok = toPrefix(n)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("network at index %d (%v) is not in canonical form", i, n)
|
||||
}
|
||||
}
|
||||
return &addrSpace{
|
||||
subnets: map[netip.Prefix]*PoolData{},
|
||||
predefined: pdf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDefaultAddressSpaces returns the local and global default address spaces
|
||||
|
@ -69,42 +69,55 @@ func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) {
|
|||
func (a *Allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
|
||||
logrus.Debugf("RequestPool(%s, %s, %s, %v, %t)", addressSpace, pool, subPool, options, v6)
|
||||
|
||||
k, nw, ipr, err := a.parsePoolRequest(addressSpace, pool, subPool, v6)
|
||||
if err != nil {
|
||||
parseErr := func(err error) (string, *net.IPNet, map[string]string, error) {
|
||||
return "", nil, nil, types.InternalErrorf("failed to parse pool request for address space %q pool %q subpool %q: %v", addressSpace, pool, subPool, err)
|
||||
}
|
||||
|
||||
pdf := k == nil
|
||||
|
||||
retry:
|
||||
if pdf {
|
||||
if nw, err = a.getPredefinedPool(addressSpace, v6); err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
k = &SubnetKey{AddressSpace: addressSpace, Subnet: nw.String()}
|
||||
if addressSpace == "" {
|
||||
return parseErr(ipamapi.ErrInvalidAddressSpace)
|
||||
}
|
||||
|
||||
aSpace, err := a.getAddrSpace(addressSpace)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
k := PoolID{AddressSpace: addressSpace}
|
||||
|
||||
insert, err := aSpace.updatePoolDBOnAdd(*k, nw, ipr, pdf)
|
||||
if err != nil {
|
||||
if _, ok := err.(types.MaskableError); ok {
|
||||
logrus.Debugf("Retrying predefined pool search: %v", err)
|
||||
goto retry
|
||||
if pool == "" {
|
||||
if subPool != "" {
|
||||
return parseErr(ipamapi.ErrInvalidSubPool)
|
||||
}
|
||||
k.Subnet, err = aSpace.allocatePredefinedPool(v6)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
return k.String(), toIPNet(k.Subnet), nil, nil
|
||||
}
|
||||
|
||||
if k.Subnet, err = netip.ParsePrefix(pool); err != nil {
|
||||
return parseErr(ipamapi.ErrInvalidPool)
|
||||
}
|
||||
|
||||
if subPool != "" {
|
||||
var err error
|
||||
k.ChildSubnet, err = netip.ParsePrefix(subPool)
|
||||
if err != nil {
|
||||
return parseErr(ipamapi.ErrInvalidSubPool)
|
||||
}
|
||||
}
|
||||
|
||||
k.Subnet, k.ChildSubnet = k.Subnet.Masked(), k.ChildSubnet.Masked()
|
||||
err = aSpace.allocateSubnet(k.Subnet, k.ChildSubnet)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
return k.String(), nw, nil, insert()
|
||||
return k.String(), toIPNet(k.Subnet), nil, nil
|
||||
}
|
||||
|
||||
// ReleasePool releases the address pool identified by the passed id
|
||||
func (a *Allocator) ReleasePool(poolID string) error {
|
||||
logrus.Debugf("ReleasePool(%s)", poolID)
|
||||
k := SubnetKey{}
|
||||
k := PoolID{}
|
||||
if err := k.FromString(poolID); err != nil {
|
||||
return types.BadRequestErrorf("invalid pool id: %s", poolID)
|
||||
}
|
||||
|
@ -114,174 +127,106 @@ func (a *Allocator) ReleasePool(poolID string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return aSpace.updatePoolDBOnRemoval(k)
|
||||
return aSpace.releaseSubnet(k.Subnet, k.ChildSubnet)
|
||||
}
|
||||
|
||||
// Given the address space, returns the local or global PoolConfig based on whether the
|
||||
// address space is local or global. AddressSpace locality is registered with IPAM out of band.
|
||||
func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
aSpace, ok := a.addrSpaces[as]
|
||||
if !ok {
|
||||
return nil, types.BadRequestErrorf("cannot find address space %s", as)
|
||||
switch as {
|
||||
case localAddressSpace:
|
||||
return a.local, nil
|
||||
case globalAddressSpace:
|
||||
return a.global, nil
|
||||
}
|
||||
return aSpace, nil
|
||||
return nil, types.BadRequestErrorf("cannot find address space %s", as)
|
||||
}
|
||||
|
||||
// parsePoolRequest parses and validates a request to create a new pool under addressSpace and returns
|
||||
// a SubnetKey, network and range describing the request.
|
||||
func (a *Allocator) parsePoolRequest(addressSpace, pool, subPool string, v6 bool) (*SubnetKey, *net.IPNet, *AddressRange, error) {
|
||||
var (
|
||||
nw *net.IPNet
|
||||
ipr *AddressRange
|
||||
err error
|
||||
)
|
||||
|
||||
if addressSpace == "" {
|
||||
return nil, nil, nil, ipamapi.ErrInvalidAddressSpace
|
||||
}
|
||||
|
||||
if pool == "" && subPool != "" {
|
||||
return nil, nil, nil, ipamapi.ErrInvalidSubPool
|
||||
}
|
||||
|
||||
if pool == "" {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
if _, nw, err = net.ParseCIDR(pool); err != nil {
|
||||
return nil, nil, nil, ipamapi.ErrInvalidPool
|
||||
}
|
||||
|
||||
if subPool != "" {
|
||||
if ipr, err = getAddressRange(subPool, nw); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &SubnetKey{AddressSpace: addressSpace, Subnet: nw.String(), ChildSubnet: subPool}, nw, ipr, nil
|
||||
}
|
||||
|
||||
func (a *Allocator) insertBitMask(key SubnetKey, pool *net.IPNet) error {
|
||||
//logrus.Debugf("Inserting bitmask (%s, %s)", key.String(), pool.String())
|
||||
|
||||
ipVer := getAddressVersion(pool.IP)
|
||||
ones, bits := pool.Mask.Size()
|
||||
func newPoolData(pool netip.Prefix) *PoolData {
|
||||
ones, bits := pool.Bits(), pool.Addr().BitLen()
|
||||
numAddresses := uint64(1 << uint(bits-ones))
|
||||
|
||||
// Allow /64 subnet
|
||||
if ipVer == v6 && numAddresses == 0 {
|
||||
if pool.Addr().Is6() && numAddresses == 0 {
|
||||
numAddresses--
|
||||
}
|
||||
|
||||
// Generate the new address masks.
|
||||
h, err := bitseq.NewHandle("", nil, "", numAddresses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := bitmap.New(numAddresses)
|
||||
|
||||
// Pre-reserve the network address on IPv4 networks large
|
||||
// enough to have one (i.e., anything bigger than a /31.
|
||||
if !(ipVer == v4 && numAddresses <= 2) {
|
||||
if !(pool.Addr().Is4() && numAddresses <= 2) {
|
||||
h.Set(0)
|
||||
}
|
||||
|
||||
// Pre-reserve the broadcast address on IPv4 networks large
|
||||
// enough to have one (i.e., anything bigger than a /31).
|
||||
if ipVer == v4 && numAddresses > 2 {
|
||||
if pool.Addr().Is4() && numAddresses > 2 {
|
||||
h.Set(numAddresses - 1)
|
||||
}
|
||||
|
||||
a.Lock()
|
||||
a.addresses[key] = h
|
||||
a.Unlock()
|
||||
return nil
|
||||
return &PoolData{addrs: h, children: map[netip.Prefix]struct{}{}}
|
||||
}
|
||||
|
||||
func (a *Allocator) retrieveBitmask(k SubnetKey, n *net.IPNet) (*bitseq.Handle, error) {
|
||||
a.Lock()
|
||||
bm, ok := a.addresses[k]
|
||||
a.Unlock()
|
||||
if !ok {
|
||||
logrus.Debugf("Retrieving bitmask (%s, %s)", k, n)
|
||||
if err := a.insertBitMask(k, n); err != nil {
|
||||
return nil, types.InternalErrorf("could not find bitmask for %s", k.String())
|
||||
}
|
||||
a.Lock()
|
||||
bm = a.addresses[k]
|
||||
a.Unlock()
|
||||
}
|
||||
return bm, nil
|
||||
}
|
||||
|
||||
func (a *Allocator) getPredefineds(as string) []*net.IPNet {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
p := a.predefined[as]
|
||||
i := a.predefinedStartIndices[as]
|
||||
// getPredefineds returns the predefined subnets for the address space.
|
||||
//
|
||||
// It should not be called concurrently with any other method on the addrSpace.
|
||||
func (aSpace *addrSpace) getPredefineds() []netip.Prefix {
|
||||
i := aSpace.predefinedStartIndex
|
||||
// defensive in case the list changed since last update
|
||||
if i >= len(p) {
|
||||
if i >= len(aSpace.predefined) {
|
||||
i = 0
|
||||
}
|
||||
return append(p[i:], p[:i]...)
|
||||
return append(aSpace.predefined[i:], aSpace.predefined[:i]...)
|
||||
}
|
||||
|
||||
func (a *Allocator) updateStartIndex(as string, amt int) {
|
||||
a.Lock()
|
||||
i := a.predefinedStartIndices[as] + amt
|
||||
if i < 0 || i >= len(a.predefined[as]) {
|
||||
// updatePredefinedStartIndex rotates the predefined subnet list by amt.
|
||||
//
|
||||
// It should not be called concurrently with any other method on the addrSpace.
|
||||
func (aSpace *addrSpace) updatePredefinedStartIndex(amt int) {
|
||||
i := aSpace.predefinedStartIndex + amt
|
||||
if i < 0 || i >= len(aSpace.predefined) {
|
||||
i = 0
|
||||
}
|
||||
a.predefinedStartIndices[as] = i
|
||||
a.Unlock()
|
||||
aSpace.predefinedStartIndex = i
|
||||
}
|
||||
|
||||
func (a *Allocator) getPredefinedPool(as string, ipV6 bool) (*net.IPNet, error) {
|
||||
var v ipVersion
|
||||
v = v4
|
||||
if ipV6 {
|
||||
v = v6
|
||||
}
|
||||
|
||||
if as != localAddressSpace && as != globalAddressSpace {
|
||||
return nil, types.NotImplementedErrorf("no default pool available for non-default address spaces")
|
||||
}
|
||||
|
||||
aSpace, err := a.getAddrSpace(as)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
predefined := a.getPredefineds(as)
|
||||
|
||||
func (aSpace *addrSpace) allocatePredefinedPool(ipV6 bool) (netip.Prefix, error) {
|
||||
aSpace.Lock()
|
||||
for i, nw := range predefined {
|
||||
if v != getAddressVersion(nw.IP) {
|
||||
defer aSpace.Unlock()
|
||||
|
||||
for i, nw := range aSpace.getPredefineds() {
|
||||
if ipV6 != nw.Addr().Is6() {
|
||||
continue
|
||||
}
|
||||
// Checks whether pool has already been allocated
|
||||
if _, ok := aSpace.subnets[SubnetKey{AddressSpace: as, Subnet: nw.String()}]; ok {
|
||||
if _, ok := aSpace.subnets[nw]; ok {
|
||||
continue
|
||||
}
|
||||
// Shouldn't be necessary, but check prevents IP collisions should
|
||||
// predefined pools overlap for any reason.
|
||||
if !aSpace.contains(as, nw) {
|
||||
aSpace.Unlock()
|
||||
a.updateStartIndex(as, i+1)
|
||||
if !aSpace.contains(nw) {
|
||||
aSpace.updatePredefinedStartIndex(i + 1)
|
||||
err := aSpace.allocateSubnetL(nw, netip.Prefix{})
|
||||
if err != nil {
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
return nw, nil
|
||||
}
|
||||
}
|
||||
aSpace.Unlock()
|
||||
|
||||
return nil, types.NotFoundErrorf("could not find an available, non-overlapping IPv%d address pool among the defaults to assign to the network", v)
|
||||
v := 4
|
||||
if ipV6 {
|
||||
v = 6
|
||||
}
|
||||
return netip.Prefix{}, types.NotFoundErrorf("could not find an available, non-overlapping IPv%d address pool among the defaults to assign to the network", v)
|
||||
}
|
||||
|
||||
// RequestAddress returns an address from the specified pool ID
|
||||
func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
|
||||
logrus.Debugf("RequestAddress(%s, %v, %v)", poolID, prefAddress, opts)
|
||||
k := SubnetKey{}
|
||||
k := PoolID{}
|
||||
if err := k.FromString(poolID); err != nil {
|
||||
return nil, nil, types.BadRequestErrorf("invalid pool id: %s", poolID)
|
||||
}
|
||||
|
@ -290,51 +235,58 @@ func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[s
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
aSpace.Lock()
|
||||
p, ok := aSpace.subnets[k]
|
||||
if !ok {
|
||||
aSpace.Unlock()
|
||||
return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%s", poolID)
|
||||
}
|
||||
|
||||
if prefAddress != nil && !p.Pool.Contains(prefAddress) {
|
||||
aSpace.Unlock()
|
||||
return nil, nil, ipamapi.ErrIPOutOfRange
|
||||
}
|
||||
|
||||
c := p
|
||||
for c.Range != nil {
|
||||
k = c.ParentKey
|
||||
c = aSpace.subnets[k]
|
||||
}
|
||||
aSpace.Unlock()
|
||||
|
||||
bm, err := a.retrieveBitmask(k, c.Pool)
|
||||
if err != nil {
|
||||
return nil, nil, types.InternalErrorf("could not find bitmask for %s on address %v request from pool %s: %v",
|
||||
k.String(), prefAddress, poolID, err)
|
||||
}
|
||||
// In order to request for a serial ip address allocation, callers can pass in the option to request
|
||||
// IP allocation serially or first available IP in the subnet
|
||||
var serial bool
|
||||
if opts != nil {
|
||||
if val, ok := opts[ipamapi.AllocSerialPrefix]; ok {
|
||||
serial = (val == "true")
|
||||
var pref netip.Addr
|
||||
if prefAddress != nil {
|
||||
var ok bool
|
||||
pref, ok = netip.AddrFromSlice(prefAddress)
|
||||
if !ok {
|
||||
return nil, nil, types.BadRequestErrorf("invalid preferred address: %v", prefAddress)
|
||||
}
|
||||
}
|
||||
ip, err := a.getAddress(p.Pool, bm, prefAddress, p.Range, serial)
|
||||
p, err := aSpace.requestAddress(k.Subnet, k.ChildSubnet, pref.Unmap(), opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &net.IPNet{
|
||||
IP: p.AsSlice(),
|
||||
Mask: net.CIDRMask(k.Subnet.Bits(), k.Subnet.Addr().BitLen()),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
return &net.IPNet{IP: ip, Mask: p.Pool.Mask}, nil, nil
|
||||
func (aSpace *addrSpace) requestAddress(nw, sub netip.Prefix, prefAddress netip.Addr, opts map[string]string) (netip.Addr, error) {
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
p, ok := aSpace.subnets[nw]
|
||||
if !ok {
|
||||
return netip.Addr{}, types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub)
|
||||
}
|
||||
|
||||
if prefAddress != (netip.Addr{}) && !nw.Contains(prefAddress) {
|
||||
return netip.Addr{}, ipamapi.ErrIPOutOfRange
|
||||
}
|
||||
|
||||
if sub != (netip.Prefix{}) {
|
||||
if _, ok := p.children[sub]; !ok {
|
||||
return netip.Addr{}, types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub)
|
||||
}
|
||||
}
|
||||
|
||||
// In order to request for a serial ip address allocation, callers can pass in the option to request
|
||||
// IP allocation serially or first available IP in the subnet
|
||||
serial := opts[ipamapi.AllocSerialPrefix] == "true"
|
||||
ip, err := getAddress(nw, p.addrs, prefAddress, sub, serial)
|
||||
if err != nil {
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
// ReleaseAddress releases the address from the specified pool ID
|
||||
func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
|
||||
logrus.Debugf("ReleaseAddress(%s, %v)", poolID, address)
|
||||
k := SubnetKey{}
|
||||
k := PoolID{}
|
||||
if err := k.FromString(poolID); err != nil {
|
||||
return types.BadRequestErrorf("invalid pool id: %s", poolID)
|
||||
}
|
||||
|
@ -344,119 +296,103 @@ func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
|
|||
return err
|
||||
}
|
||||
|
||||
aSpace.Lock()
|
||||
p, ok := aSpace.subnets[k]
|
||||
addr, ok := netip.AddrFromSlice(address)
|
||||
if !ok {
|
||||
aSpace.Unlock()
|
||||
return types.NotFoundErrorf("cannot find address pool for poolID:%s", poolID)
|
||||
return types.BadRequestErrorf("invalid address: %v", address)
|
||||
}
|
||||
|
||||
if address == nil {
|
||||
aSpace.Unlock()
|
||||
return types.BadRequestErrorf("invalid address: nil")
|
||||
return aSpace.releaseAddress(k.Subnet, k.ChildSubnet, addr.Unmap())
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) releaseAddress(nw, sub netip.Prefix, address netip.Addr) error {
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
p, ok := aSpace.subnets[nw]
|
||||
if !ok {
|
||||
return types.NotFoundErrorf("cannot find address pool for %v/%v", nw, sub)
|
||||
}
|
||||
if sub != (netip.Prefix{}) {
|
||||
if _, ok := p.children[sub]; !ok {
|
||||
return types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub)
|
||||
}
|
||||
}
|
||||
|
||||
if !p.Pool.Contains(address) {
|
||||
aSpace.Unlock()
|
||||
if !address.IsValid() {
|
||||
return types.BadRequestErrorf("invalid address")
|
||||
}
|
||||
|
||||
if !nw.Contains(address) {
|
||||
return ipamapi.ErrIPOutOfRange
|
||||
}
|
||||
|
||||
c := p
|
||||
for c.Range != nil {
|
||||
k = c.ParentKey
|
||||
c = aSpace.subnets[k]
|
||||
}
|
||||
aSpace.Unlock()
|
||||
defer logrus.Debugf("Released address Address:%v Sequence:%s", address, p.addrs)
|
||||
|
||||
mask := p.Pool.Mask
|
||||
|
||||
h, err := types.GetHostPartIP(address, mask)
|
||||
if err != nil {
|
||||
return types.InternalErrorf("failed to release address %s: %v", address.String(), err)
|
||||
}
|
||||
|
||||
bm, err := a.retrieveBitmask(k, c.Pool)
|
||||
if err != nil {
|
||||
return types.InternalErrorf("could not find bitmask for %s on address %v release from pool %s: %v",
|
||||
k.String(), address, poolID, err)
|
||||
}
|
||||
defer logrus.Debugf("Released address PoolID:%s, Address:%v Sequence:%s", poolID, address, bm)
|
||||
|
||||
return bm.Unset(ipToUint64(h))
|
||||
return p.addrs.Unset(hostID(address, uint(nw.Bits())))
|
||||
}
|
||||
|
||||
func (a *Allocator) getAddress(nw *net.IPNet, bitmask *bitseq.Handle, prefAddress net.IP, ipr *AddressRange, serial bool) (net.IP, error) {
|
||||
func getAddress(base netip.Prefix, bitmask *bitmap.Bitmap, prefAddress netip.Addr, ipr netip.Prefix, serial bool) (netip.Addr, error) {
|
||||
var (
|
||||
ordinal uint64
|
||||
err error
|
||||
base *net.IPNet
|
||||
)
|
||||
|
||||
logrus.Debugf("Request address PoolID:%v %s Serial:%v PrefAddress:%v ", nw, bitmask, serial, prefAddress)
|
||||
base = types.GetIPNetCopy(nw)
|
||||
logrus.Debugf("Request address PoolID:%v %s Serial:%v PrefAddress:%v ", base, bitmask, serial, prefAddress)
|
||||
|
||||
if bitmask.Unselected() == 0 {
|
||||
return nil, ipamapi.ErrNoAvailableIPs
|
||||
return netip.Addr{}, ipamapi.ErrNoAvailableIPs
|
||||
}
|
||||
if ipr == nil && prefAddress == nil {
|
||||
if ipr == (netip.Prefix{}) && prefAddress == (netip.Addr{}) {
|
||||
ordinal, err = bitmask.SetAny(serial)
|
||||
} else if prefAddress != nil {
|
||||
hostPart, e := types.GetHostPartIP(prefAddress, base.Mask)
|
||||
if e != nil {
|
||||
return nil, types.InternalErrorf("failed to allocate requested address %s: %v", prefAddress.String(), e)
|
||||
}
|
||||
ordinal = ipToUint64(types.GetMinimalIP(hostPart))
|
||||
} else if prefAddress != (netip.Addr{}) {
|
||||
ordinal = hostID(prefAddress, uint(base.Bits()))
|
||||
err = bitmask.Set(ordinal)
|
||||
} else {
|
||||
ordinal, err = bitmask.SetAnyInRange(ipr.Start, ipr.End, serial)
|
||||
start, end := subnetRange(base, ipr)
|
||||
ordinal, err = bitmask.SetAnyInRange(start, end, serial)
|
||||
}
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
// Convert IP ordinal for this subnet into IP address
|
||||
return generateAddress(ordinal, base), nil
|
||||
case bitseq.ErrBitAllocated:
|
||||
return nil, ipamapi.ErrIPAlreadyAllocated
|
||||
case bitseq.ErrNoBitAvailable:
|
||||
return nil, ipamapi.ErrNoAvailableIPs
|
||||
return ipbits.Add(base.Addr(), ordinal, 0), nil
|
||||
case bitmap.ErrBitAllocated:
|
||||
return netip.Addr{}, ipamapi.ErrIPAlreadyAllocated
|
||||
case bitmap.ErrNoBitAvailable:
|
||||
return netip.Addr{}, ipamapi.ErrNoAvailableIPs
|
||||
default:
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// DumpDatabase dumps the internal info
|
||||
func (a *Allocator) DumpDatabase() string {
|
||||
a.Lock()
|
||||
aspaces := make(map[string]*addrSpace, len(a.addrSpaces))
|
||||
orderedAS := make([]string, 0, len(a.addrSpaces))
|
||||
for as, aSpace := range a.addrSpaces {
|
||||
orderedAS = append(orderedAS, as)
|
||||
aspaces[as] = aSpace
|
||||
aspaces := map[string]*addrSpace{
|
||||
localAddressSpace: a.local,
|
||||
globalAddressSpace: a.global,
|
||||
}
|
||||
a.Unlock()
|
||||
|
||||
sort.Strings(orderedAS)
|
||||
var b strings.Builder
|
||||
for _, as := range []string{localAddressSpace, globalAddressSpace} {
|
||||
fmt.Fprintf(&b, "\n### %s\n", as)
|
||||
b.WriteString(aspaces[as].DumpDatabase())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
var s string
|
||||
for _, as := range orderedAS {
|
||||
aSpace := aspaces[as]
|
||||
s = fmt.Sprintf("\n\n%s Config", as)
|
||||
aSpace.Lock()
|
||||
for k, config := range aSpace.subnets {
|
||||
s += fmt.Sprintf("\n%v: %v", k, config)
|
||||
if config.Range == nil {
|
||||
a.retrieveBitmask(k, config.Pool)
|
||||
}
|
||||
func (aSpace *addrSpace) DumpDatabase() string {
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
var b strings.Builder
|
||||
for k, config := range aSpace.subnets {
|
||||
fmt.Fprintf(&b, "%v: %v\n", k, config)
|
||||
fmt.Fprintf(&b, " Bitmap: %v\n", config.addrs)
|
||||
for k := range config.children {
|
||||
fmt.Fprintf(&b, " - Subpool: %v\n", k)
|
||||
}
|
||||
aSpace.Unlock()
|
||||
}
|
||||
|
||||
s = fmt.Sprintf("%s\n\nBitmasks", s)
|
||||
for k, bm := range a.addresses {
|
||||
s += fmt.Sprintf("\n%s: %s", k, bm)
|
||||
}
|
||||
|
||||
return s
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// IsBuiltIn returns true for builtin drivers
|
||||
|
|
|
@ -6,13 +6,14 @@ import (
|
|||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/libnetwork/bitseq"
|
||||
"github.com/docker/docker/libnetwork/bitmap"
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/ipamutils"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
|
@ -21,40 +22,14 @@ import (
|
|||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestInt2IP2IntConversion(t *testing.T) {
|
||||
for i := uint64(0); i < 256*256*256; i++ {
|
||||
var array [4]byte // new array at each cycle
|
||||
addIntToIP(array[:], i)
|
||||
j := ipToUint64(array[:])
|
||||
if j != i {
|
||||
t.Fatalf("Failed to convert ordinal %d to IP % x and back to ordinal. Got %d", i, array, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAddressVersion(t *testing.T) {
|
||||
if v4 != getAddressVersion(net.ParseIP("172.28.30.112")) {
|
||||
t.Fatal("Failed to detect IPv4 version")
|
||||
}
|
||||
if v4 != getAddressVersion(net.ParseIP("0.0.0.1")) {
|
||||
t.Fatal("Failed to detect IPv4 version")
|
||||
}
|
||||
if v6 != getAddressVersion(net.ParseIP("ff01::1")) {
|
||||
t.Fatal("Failed to detect IPv6 version")
|
||||
}
|
||||
if v6 != getAddressVersion(net.ParseIP("2001:db8::76:51")) {
|
||||
t.Fatal("Failed to detect IPv6 version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyString(t *testing.T) {
|
||||
k := &SubnetKey{AddressSpace: "default", Subnet: "172.27.0.0/16"}
|
||||
k := &PoolID{AddressSpace: "default", SubnetKey: SubnetKey{Subnet: netip.MustParsePrefix("172.27.0.0/16")}}
|
||||
expected := "default/172.27.0.0/16"
|
||||
if expected != k.String() {
|
||||
t.Fatalf("Unexpected key string: %s", k.String())
|
||||
}
|
||||
|
||||
k2 := &SubnetKey{}
|
||||
k2 := &PoolID{}
|
||||
err := k2.FromString(expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -64,7 +39,7 @@ func TestKeyString(t *testing.T) {
|
|||
}
|
||||
|
||||
expected = fmt.Sprintf("%s/%s", expected, "172.27.3.0/24")
|
||||
k.ChildSubnet = "172.27.3.0/24"
|
||||
k.ChildSubnet = netip.MustParsePrefix("172.27.3.0/24")
|
||||
if expected != k.String() {
|
||||
t.Fatalf("Unexpected key string: %s", k.String())
|
||||
}
|
||||
|
@ -83,14 +58,13 @@ func TestAddSubnets(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.addrSpaces["abc"] = a.addrSpaces[localAddressSpace]
|
||||
|
||||
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure in adding subnet")
|
||||
}
|
||||
|
||||
pid1, _, _, err := a.RequestPool("abc", "10.0.0.0/8", "", nil, false)
|
||||
pid1, _, _, err := a.RequestPool(globalAddressSpace, "10.0.0.0/8", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected failure in adding overlapping subnets to different address spaces: %v", err)
|
||||
}
|
||||
|
@ -99,21 +73,21 @@ func TestAddSubnets(t *testing.T) {
|
|||
t.Fatal("returned same pool id for same subnets in different namespaces")
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "", nil, false)
|
||||
_, _, _, err = a.RequestPool(globalAddressSpace, "10.0.0.0/8", "", nil, false)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected failure requesting existing subnet")
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool("abc", "10.128.0.0/9", "", nil, false)
|
||||
_, _, _, err = a.RequestPool(globalAddressSpace, "10.128.0.0/9", "", nil, false)
|
||||
if err == nil {
|
||||
t.Fatal("Expected failure on adding overlapping base subnet")
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "10.128.0.0/9", nil, false)
|
||||
_, _, _, err = a.RequestPool(globalAddressSpace, "10.0.0.0/8", "10.128.0.0/9", nil, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected failure on adding sub pool: %v", err)
|
||||
}
|
||||
_, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "10.128.0.0/9", nil, false)
|
||||
_, _, _, err = a.RequestPool(globalAddressSpace, "10.0.0.0/8", "10.128.0.0/9", nil, false)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected failure on adding overlapping sub pool")
|
||||
}
|
||||
|
@ -159,7 +133,7 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
assert.NilError(t, err)
|
||||
|
||||
var k0, k1 SubnetKey
|
||||
var k0, k1 PoolID
|
||||
_, err = a.getAddrSpace(localAddressSpace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -167,7 +141,7 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
|
||||
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure in adding pool")
|
||||
t.Fatalf("Unexpected failure in adding pool: %v", err)
|
||||
}
|
||||
if err := k0.FromString(pid0); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -178,15 +152,13 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets := aSpace.subnets
|
||||
|
||||
if subnets[k0].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
|
||||
}
|
||||
|
||||
pid1, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure in adding sub pool")
|
||||
t.Fatalf("Unexpected failure in adding sub pool: %v", err)
|
||||
}
|
||||
if err := k1.FromString(pid1); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -201,14 +173,13 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if subnets[k1].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k1, subnets[k1].RefCount)
|
||||
if got := aSpace.subnets[k1.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k1, got)
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
|
||||
if err == nil {
|
||||
t.Fatal("Expected failure in adding sub pool")
|
||||
t.Fatalf("Expected failure in adding sub pool: %v", err)
|
||||
}
|
||||
|
||||
aSpace, err = a.getAddrSpace(localAddressSpace)
|
||||
|
@ -216,10 +187,8 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
|
||||
if subnets[k0].RefCount != 2 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
|
||||
}
|
||||
|
||||
if err := a.ReleasePool(pid1); err != nil {
|
||||
|
@ -231,20 +200,23 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if subnets[k0].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
|
||||
}
|
||||
if err := a.ReleasePool(pid0); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, ok := aSpace.subnets[k0.Subnet]; ok {
|
||||
t.Error("Pool should have been deleted when released")
|
||||
}
|
||||
|
||||
pid00, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure in adding pool")
|
||||
t.Errorf("Unexpected failure in adding pool: %v", err)
|
||||
}
|
||||
if pid00 != pid0 {
|
||||
t.Fatal("main pool should still exist")
|
||||
t.Errorf("main pool should still exist. Got poolID %q, want %q", pid00, pid0)
|
||||
}
|
||||
|
||||
aSpace, err = a.getAddrSpace(localAddressSpace)
|
||||
|
@ -252,13 +224,12 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if subnets[k0].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
|
||||
}
|
||||
|
||||
if err := a.ReleasePool(pid00); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
aSpace, err = a.getAddrSpace(localAddressSpace)
|
||||
|
@ -266,14 +237,13 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if bp, ok := subnets[k0]; ok {
|
||||
t.Fatalf("Base pool %s is still present: %v", k0, bp)
|
||||
if bp, ok := aSpace.subnets[k0.Subnet]; ok {
|
||||
t.Errorf("Base pool %s is still present: %v", k0, bp)
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure in adding pool")
|
||||
t.Errorf("Unexpected failure in adding pool: %v", err)
|
||||
}
|
||||
|
||||
aSpace, err = a.getAddrSpace(localAddressSpace)
|
||||
|
@ -281,9 +251,8 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if subnets[k0].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,19 +260,16 @@ func TestPredefinedPool(t *testing.T) {
|
|||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
assert.NilError(t, err)
|
||||
|
||||
if _, err := a.getPredefinedPool("blue", false); err == nil {
|
||||
t.Fatal("Expected failure for non default addr space")
|
||||
}
|
||||
|
||||
pid, nw, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nw2, err := a.getPredefinedPool(localAddressSpace, false)
|
||||
pid2, nw2, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if types.CompareIPNet(nw, nw2) {
|
||||
t.Fatalf("Unexpected default network returned: %s = %s", nw2, nw)
|
||||
}
|
||||
|
@ -311,17 +277,16 @@ func TestPredefinedPool(t *testing.T) {
|
|||
if err := a.ReleasePool(pid); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := a.ReleasePool(pid2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveSubnet(t *testing.T) {
|
||||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
assert.NilError(t, err)
|
||||
|
||||
a.addrSpaces["splane"] = &addrSpace{
|
||||
alloc: a.addrSpaces[localAddressSpace].alloc,
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
}
|
||||
|
||||
input := []struct {
|
||||
addrSpace string
|
||||
subnet string
|
||||
|
@ -331,10 +296,10 @@ func TestRemoveSubnet(t *testing.T) {
|
|||
{localAddressSpace, "172.17.0.0/16", false},
|
||||
{localAddressSpace, "10.0.0.0/8", false},
|
||||
{localAddressSpace, "2001:db8:1:2:3:4:ffff::/112", false},
|
||||
{"splane", "172.17.0.0/16", false},
|
||||
{"splane", "10.0.0.0/8", false},
|
||||
{"splane", "2001:db8:1:2:3:4:5::/112", true},
|
||||
{"splane", "2001:db8:1:2:3:4:ffff::/112", true},
|
||||
{globalAddressSpace, "172.17.0.0/16", false},
|
||||
{globalAddressSpace, "10.0.0.0/8", false},
|
||||
{globalAddressSpace, "2001:db8:1:2:3:4:5::/112", true},
|
||||
{globalAddressSpace, "2001:db8:1:2:3:4:ffff::/112", true},
|
||||
}
|
||||
|
||||
poolIDs := make([]string, len(input))
|
||||
|
@ -356,12 +321,7 @@ func TestGetSameAddress(t *testing.T) {
|
|||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
assert.NilError(t, err)
|
||||
|
||||
a.addrSpaces["giallo"] = &addrSpace{
|
||||
alloc: a.addrSpaces[localAddressSpace].alloc,
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
}
|
||||
|
||||
pid, _, _, err := a.RequestPool("giallo", "192.168.100.0/24", "", nil, false)
|
||||
pid, _, _, err := a.RequestPool(localAddressSpace, "192.168.100.0/24", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -435,12 +395,7 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
assert.NilError(t, err)
|
||||
|
||||
a.addrSpaces["rosso"] = &addrSpace{
|
||||
alloc: a.addrSpaces[localAddressSpace].alloc,
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
}
|
||||
|
||||
poolID, _, _, err := a.RequestPool("rosso", "172.28.0.0/16", "172.28.30.0/24", nil, false)
|
||||
poolID, _, _, err := a.RequestPool(localAddressSpace, "172.28.0.0/16", "172.28.30.0/24", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -470,11 +425,11 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool("rosso", "10.0.0.0/8", "10.0.0.0/16", nil, false)
|
||||
_, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
poolID, _, _, err = a.RequestPool("rosso", "10.0.0.0/16", "10.0.0.0/24", nil, false)
|
||||
poolID, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/16", "10.0.0.0/24", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -507,7 +462,7 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
dueExp, _ := types.ParseCIDR("10.2.2.2/16")
|
||||
treExp, _ := types.ParseCIDR("10.2.2.1/16")
|
||||
|
||||
if poolID, _, _, err = a.RequestPool("rosso", "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
|
||||
if poolID, _, _, err = a.RequestPool(localAddressSpace, "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tre, _, err := a.RequestAddress(poolID, treExp.IP, nil)
|
||||
|
@ -515,7 +470,7 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
if !types.CompareIPNet(tre, treExp) {
|
||||
t.Fatalf("Unexpected address: %v", tre)
|
||||
t.Fatalf("Unexpected address: want %v, got %v", treExp, tre)
|
||||
}
|
||||
|
||||
uno, _, err := a.RequestAddress(poolID, nil, nil)
|
||||
|
@ -563,12 +518,7 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
assert.NilError(t, err)
|
||||
|
||||
a.addrSpaces["rosso"] = &addrSpace{
|
||||
alloc: a.addrSpaces[localAddressSpace].alloc,
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
}
|
||||
|
||||
poolID, _, _, err := a.RequestPool("rosso", "172.28.0.0/16", "172.28.30.0/24", nil, false)
|
||||
poolID, _, _, err := a.RequestPool(localAddressSpace, "172.28.0.0/16", "172.28.30.0/24", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -598,11 +548,11 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool("rosso", "10.0.0.0/8", "10.0.0.0/16", nil, false)
|
||||
_, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
poolID, _, _, err = a.RequestPool("rosso", "10.0.0.0/16", "10.0.0.0/24", nil, false)
|
||||
poolID, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/16", "10.0.0.0/24", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -636,7 +586,7 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
treExp, _ := types.ParseCIDR("10.2.2.1/16")
|
||||
quaExp, _ := types.ParseCIDR("10.2.2.3/16")
|
||||
fivExp, _ := types.ParseCIDR("10.2.2.4/16")
|
||||
if poolID, _, _, err = a.RequestPool("rosso", "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
|
||||
if poolID, _, _, err = a.RequestPool(localAddressSpace, "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tre, _, err := a.RequestAddress(poolID, treExp.IP, opts)
|
||||
|
@ -644,7 +594,7 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
if !types.CompareIPNet(tre, treExp) {
|
||||
t.Fatalf("Unexpected address: %v", tre)
|
||||
t.Fatalf("Unexpected address: want %v, got %v", treExp, tre)
|
||||
}
|
||||
|
||||
uno, _, err := a.RequestAddress(poolID, nil, opts)
|
||||
|
@ -702,17 +652,11 @@ func TestRequestSyntaxCheck(t *testing.T) {
|
|||
var (
|
||||
pool = "192.168.0.0/16"
|
||||
subPool = "192.168.0.0/24"
|
||||
as = "green"
|
||||
)
|
||||
|
||||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
assert.NilError(t, err)
|
||||
|
||||
a.addrSpaces[as] = &addrSpace{
|
||||
alloc: a.addrSpaces[localAddressSpace].alloc,
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool("", pool, "", nil, false)
|
||||
if err == nil {
|
||||
t.Fatal("Failed to detect wrong request: empty address space")
|
||||
|
@ -723,12 +667,12 @@ func TestRequestSyntaxCheck(t *testing.T) {
|
|||
t.Fatal("Failed to detect wrong request: empty address space")
|
||||
}
|
||||
|
||||
_, _, _, err = a.RequestPool(as, "", subPool, nil, false)
|
||||
_, _, _, err = a.RequestPool(localAddressSpace, "", subPool, nil, false)
|
||||
if err == nil {
|
||||
t.Fatal("Failed to detect wrong request: subPool specified and no pool")
|
||||
}
|
||||
|
||||
pid, _, _, err := a.RequestPool(as, pool, subPool, nil, false)
|
||||
pid, _, _, err := a.RequestPool(localAddressSpace, pool, subPool, nil, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected failure: %v", err)
|
||||
}
|
||||
|
@ -985,7 +929,7 @@ func TestRelease(t *testing.T) {
|
|||
for i, inp := range toRelease {
|
||||
ip0 := net.ParseIP(inp.address)
|
||||
a.ReleaseAddress(pid, ip0)
|
||||
bm := a.addresses[SubnetKey{localAddressSpace, subnet, ""}]
|
||||
bm := a.local.subnets[netip.MustParsePrefix(subnet)].addrs
|
||||
if bm.Unselected() != 1 {
|
||||
t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected())
|
||||
}
|
||||
|
@ -1005,23 +949,19 @@ func assertGetAddress(t *testing.T, subnet string) {
|
|||
var (
|
||||
err error
|
||||
printTime = false
|
||||
a = &Allocator{}
|
||||
)
|
||||
|
||||
_, sub, _ := net.ParseCIDR(subnet)
|
||||
ones, bits := sub.Mask.Size()
|
||||
sub := netip.MustParsePrefix(subnet)
|
||||
ones, bits := sub.Bits(), sub.Addr().BitLen()
|
||||
zeroes := bits - ones
|
||||
numAddresses := 1 << uint(zeroes)
|
||||
|
||||
bm, err := bitseq.NewHandle("ipam_test", nil, "default/"+subnet, uint64(numAddresses))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bm := bitmap.New(uint64(numAddresses))
|
||||
|
||||
start := time.Now()
|
||||
run := 0
|
||||
for err != ipamapi.ErrNoAvailableIPs {
|
||||
_, err = a.getAddress(sub, bm, nil, nil, false)
|
||||
_, err = getAddress(sub, bm, netip.Addr{}, netip.Prefix{}, false)
|
||||
run++
|
||||
}
|
||||
if printTime {
|
||||
|
@ -1057,6 +997,9 @@ func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP str
|
|||
start := time.Now()
|
||||
for ; i < numReq; i++ {
|
||||
nw, _, err = a.RequestAddress(pid, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if printTime {
|
||||
fmt.Printf("\nTaken %v, to allocate %d addresses on %s\n", time.Since(start), numReq, subnet)
|
||||
|
@ -1234,17 +1177,13 @@ func TestRequestReleaseAddressDuplicate(t *testing.T) {
|
|||
}
|
||||
ips := []IP{}
|
||||
allocatedIPs := []*net.IPNet{}
|
||||
a.addrSpaces["rosso"] = &addrSpace{
|
||||
alloc: a.addrSpaces[localAddressSpace].alloc,
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
}
|
||||
|
||||
opts := map[string]string{
|
||||
ipamapi.AllocSerialPrefix: "true",
|
||||
}
|
||||
var l sync.Mutex
|
||||
|
||||
poolID, _, _, err := a.RequestPool("rosso", "198.168.0.0/23", "", nil, false)
|
||||
poolID, _, _, err := a.RequestPool(localAddressSpace, "198.168.0.0/23", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -41,16 +41,11 @@ func newTestContext(t *testing.T, mask int, options map[string]string) *testCont
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.addrSpaces["giallo"] = &addrSpace{
|
||||
alloc: a.addrSpaces[localAddressSpace].alloc,
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
}
|
||||
|
||||
network := fmt.Sprintf("192.168.100.0/%d", mask)
|
||||
// total ips 2^(32-mask) - 2 (network and broadcast)
|
||||
totalIps := 1<<uint(32-mask) - 2
|
||||
|
||||
pid, _, _, err := a.RequestPool("giallo", network, "", nil, false)
|
||||
pid, _, _, err := a.RequestPool(localAddressSpace, network, "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -2,59 +2,58 @@ package ipam
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/libnetwork/bitmap"
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
)
|
||||
|
||||
// SubnetKey is the pointer to the configured pools in each address space
|
||||
type SubnetKey struct {
|
||||
// PoolID is the pointer to the configured pools in each address space
|
||||
type PoolID struct {
|
||||
AddressSpace string
|
||||
Subnet string
|
||||
ChildSubnet string
|
||||
SubnetKey
|
||||
}
|
||||
|
||||
// PoolData contains the configured pool data
|
||||
type PoolData struct {
|
||||
ParentKey SubnetKey
|
||||
Pool *net.IPNet
|
||||
Range *AddressRange `json:",omitempty"`
|
||||
RefCount int
|
||||
addrs *bitmap.Bitmap
|
||||
children map[netip.Prefix]struct{}
|
||||
|
||||
// Whether to implicitly release the pool once it no longer has any children.
|
||||
autoRelease bool
|
||||
}
|
||||
|
||||
// SubnetKey is the composite key to an address pool within an address space.
|
||||
type SubnetKey struct {
|
||||
Subnet, ChildSubnet netip.Prefix
|
||||
}
|
||||
|
||||
// addrSpace contains the pool configurations for the address space
|
||||
type addrSpace struct {
|
||||
subnets map[SubnetKey]*PoolData
|
||||
alloc *Allocator
|
||||
// Master subnet pools, indexed by the value's stringified PoolData.Pool field.
|
||||
subnets map[netip.Prefix]*PoolData
|
||||
|
||||
// Predefined pool for the address space
|
||||
predefined []netip.Prefix
|
||||
predefinedStartIndex int
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// AddressRange specifies first and last ip ordinal which
|
||||
// identifies a range in a pool of addresses
|
||||
type AddressRange struct {
|
||||
Sub *net.IPNet
|
||||
Start, End uint64
|
||||
}
|
||||
|
||||
// String returns the string form of the AddressRange object
|
||||
func (r *AddressRange) String() string {
|
||||
return fmt.Sprintf("Sub: %s, range [%d, %d]", r.Sub, r.Start, r.End)
|
||||
}
|
||||
|
||||
// String returns the string form of the SubnetKey object
|
||||
func (s *SubnetKey) String() string {
|
||||
func (s *PoolID) String() string {
|
||||
k := fmt.Sprintf("%s/%s", s.AddressSpace, s.Subnet)
|
||||
if s.ChildSubnet != "" {
|
||||
if s.ChildSubnet != (netip.Prefix{}) {
|
||||
k = fmt.Sprintf("%s/%s", k, s.ChildSubnet)
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
// FromString populates the SubnetKey object reading it from string
|
||||
func (s *SubnetKey) FromString(str string) error {
|
||||
func (s *PoolID) FromString(str string) error {
|
||||
if str == "" || !strings.Contains(str, "/") {
|
||||
return types.BadRequestErrorf("invalid string form for subnetkey: %s", str)
|
||||
}
|
||||
|
@ -63,109 +62,112 @@ func (s *SubnetKey) FromString(str string) error {
|
|||
if len(p) != 3 && len(p) != 5 {
|
||||
return types.BadRequestErrorf("invalid string form for subnetkey: %s", str)
|
||||
}
|
||||
s.AddressSpace = p[0]
|
||||
s.Subnet = fmt.Sprintf("%s/%s", p[1], p[2])
|
||||
sub, err := netip.ParsePrefix(p[1] + "/" + p[2])
|
||||
if err != nil {
|
||||
return types.BadRequestErrorf("%v", err)
|
||||
}
|
||||
var child netip.Prefix
|
||||
if len(p) == 5 {
|
||||
s.ChildSubnet = fmt.Sprintf("%s/%s", p[3], p[4])
|
||||
child, err = netip.ParsePrefix(p[3] + "/" + p[4])
|
||||
if err != nil {
|
||||
return types.BadRequestErrorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
*s = PoolID{
|
||||
AddressSpace: p[0],
|
||||
SubnetKey: SubnetKey{
|
||||
Subnet: sub,
|
||||
ChildSubnet: child,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the string form of the PoolData object
|
||||
func (p *PoolData) String() string {
|
||||
return fmt.Sprintf("ParentKey: %s, Pool: %s, Range: %s, RefCount: %d",
|
||||
p.ParentKey.String(), p.Pool.String(), p.Range, p.RefCount)
|
||||
return fmt.Sprintf("PoolData[Children: %d]", len(p.children))
|
||||
}
|
||||
|
||||
// updatePoolDBOnAdd returns a closure which will add the subnet k to the address space when executed.
|
||||
func (aSpace *addrSpace) updatePoolDBOnAdd(k SubnetKey, nw *net.IPNet, ipr *AddressRange, pdf bool) (func() error, error) {
|
||||
// allocateSubnet adds the subnet k to the address space.
|
||||
func (aSpace *addrSpace) allocateSubnet(nw, sub netip.Prefix) error {
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
// Check if already allocated
|
||||
if _, ok := aSpace.subnets[k]; ok {
|
||||
if pdf {
|
||||
return nil, types.InternalMaskableErrorf("predefined pool %s is already reserved", nw)
|
||||
if pool, ok := aSpace.subnets[nw]; ok {
|
||||
var childExists bool
|
||||
if sub != (netip.Prefix{}) {
|
||||
_, childExists = pool.children[sub]
|
||||
}
|
||||
if sub == (netip.Prefix{}) || childExists {
|
||||
// This means the same pool is already allocated. allocateSubnet is called when there
|
||||
// is request for a pool/subpool. It should ensure there is no overlap with existing pools
|
||||
return ipamapi.ErrPoolOverlap
|
||||
}
|
||||
// This means the same pool is already allocated. updatePoolDBOnAdd is called when there
|
||||
// is request for a pool/subpool. It should ensure there is no overlap with existing pools
|
||||
return nil, ipamapi.ErrPoolOverlap
|
||||
}
|
||||
|
||||
return aSpace.allocateSubnetL(nw, sub)
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) allocateSubnetL(nw, sub netip.Prefix) error {
|
||||
// If master pool, check for overlap
|
||||
if ipr == nil {
|
||||
if aSpace.contains(k.AddressSpace, nw) {
|
||||
return nil, ipamapi.ErrPoolOverlap
|
||||
if sub == (netip.Prefix{}) {
|
||||
if aSpace.contains(nw) {
|
||||
return ipamapi.ErrPoolOverlap
|
||||
}
|
||||
// This is a new master pool, add it along with corresponding bitmask
|
||||
aSpace.subnets[k] = &PoolData{Pool: nw, RefCount: 1}
|
||||
return func() error { return aSpace.alloc.insertBitMask(k, nw) }, nil
|
||||
aSpace.subnets[nw] = newPoolData(nw)
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is a new non-master pool (subPool)
|
||||
p := &PoolData{
|
||||
ParentKey: SubnetKey{AddressSpace: k.AddressSpace, Subnet: k.Subnet},
|
||||
Pool: nw,
|
||||
Range: ipr,
|
||||
RefCount: 1,
|
||||
if nw.Addr().BitLen() != sub.Addr().BitLen() {
|
||||
return fmt.Errorf("pool and subpool are of incompatible address families")
|
||||
}
|
||||
aSpace.subnets[k] = p
|
||||
|
||||
// Look for parent pool
|
||||
pp, ok := aSpace.subnets[p.ParentKey]
|
||||
if ok {
|
||||
aSpace.incRefCount(pp, 1)
|
||||
return func() error { return nil }, nil
|
||||
pp, ok := aSpace.subnets[nw]
|
||||
if !ok {
|
||||
// Parent pool does not exist, add it along with corresponding bitmask
|
||||
pp = newPoolData(nw)
|
||||
pp.autoRelease = true
|
||||
aSpace.subnets[nw] = pp
|
||||
}
|
||||
|
||||
// Parent pool does not exist, add it along with corresponding bitmask
|
||||
aSpace.subnets[p.ParentKey] = &PoolData{Pool: nw, RefCount: 1}
|
||||
return func() error { return aSpace.alloc.insertBitMask(p.ParentKey, nw) }, nil
|
||||
pp.children[sub] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) updatePoolDBOnRemoval(k SubnetKey) error {
|
||||
func (aSpace *addrSpace) releaseSubnet(nw, sub netip.Prefix) error {
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
p, ok := aSpace.subnets[k]
|
||||
p, ok := aSpace.subnets[nw]
|
||||
if !ok {
|
||||
return ipamapi.ErrBadPool
|
||||
}
|
||||
|
||||
aSpace.incRefCount(p, -1)
|
||||
|
||||
c := p
|
||||
for ok {
|
||||
if c.RefCount == 0 {
|
||||
delete(aSpace.subnets, k)
|
||||
if c.Range == nil {
|
||||
return nil
|
||||
}
|
||||
if sub != (netip.Prefix{}) {
|
||||
if _, ok := p.children[sub]; !ok {
|
||||
return ipamapi.ErrBadPool
|
||||
}
|
||||
k = c.ParentKey
|
||||
c, ok = aSpace.subnets[k]
|
||||
delete(p.children, sub)
|
||||
} else {
|
||||
p.autoRelease = true
|
||||
}
|
||||
|
||||
if len(p.children) == 0 && p.autoRelease {
|
||||
delete(aSpace.subnets, nw)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) incRefCount(p *PoolData, delta int) {
|
||||
c := p
|
||||
ok := true
|
||||
for ok {
|
||||
c.RefCount += delta
|
||||
c, ok = aSpace.subnets[c.ParentKey]
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether the passed subnet is a superset or subset of any of the subset in this config db
|
||||
func (aSpace *addrSpace) contains(space string, nw *net.IPNet) bool {
|
||||
for k, v := range aSpace.subnets {
|
||||
if space == k.AddressSpace && k.ChildSubnet == "" {
|
||||
if nw.Contains(v.Pool.IP) || v.Pool.Contains(nw.IP) {
|
||||
return true
|
||||
}
|
||||
// contains checks whether nw is a superset or subset of any of the existing subnets in this address space.
|
||||
func (aSpace *addrSpace) contains(nw netip.Prefix) bool {
|
||||
for pool := range aSpace.subnets {
|
||||
if nw.Contains(pool.Addr()) || pool.Contains(nw.Addr()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -1,81 +1,48 @@
|
|||
package ipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
"github.com/docker/docker/libnetwork/ipbits"
|
||||
)
|
||||
|
||||
type ipVersion int
|
||||
|
||||
const (
|
||||
v4 = 4
|
||||
v6 = 6
|
||||
)
|
||||
|
||||
func getAddressRange(pool string, masterNw *net.IPNet) (*AddressRange, error) {
|
||||
ip, nw, err := net.ParseCIDR(pool)
|
||||
if err != nil {
|
||||
return nil, ipamapi.ErrInvalidSubPool
|
||||
func toIPNet(p netip.Prefix) *net.IPNet {
|
||||
if !p.IsValid() {
|
||||
return nil
|
||||
}
|
||||
lIP, e := types.GetHostPartIP(nw.IP, masterNw.Mask)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's lowest ip address: %v", e)
|
||||
}
|
||||
bIP, e := types.GetBroadcastIP(nw.IP, nw.Mask)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's broadcast ip address: %v", e)
|
||||
}
|
||||
hIP, e := types.GetHostPartIP(bIP, masterNw.Mask)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's highest ip address: %v", e)
|
||||
}
|
||||
nw.IP = ip
|
||||
return &AddressRange{nw, ipToUint64(types.GetMinimalIP(lIP)), ipToUint64(types.GetMinimalIP(hIP))}, nil
|
||||
}
|
||||
|
||||
// It generates the ip address in the passed subnet specified by
|
||||
// the passed host address ordinal
|
||||
func generateAddress(ordinal uint64, network *net.IPNet) net.IP {
|
||||
var address [16]byte
|
||||
|
||||
// Get network portion of IP
|
||||
if getAddressVersion(network.IP) == v4 {
|
||||
copy(address[:], network.IP.To4())
|
||||
} else {
|
||||
copy(address[:], network.IP)
|
||||
}
|
||||
|
||||
end := len(network.Mask)
|
||||
addIntToIP(address[:end], ordinal)
|
||||
|
||||
return net.IP(address[:end])
|
||||
}
|
||||
|
||||
func getAddressVersion(ip net.IP) ipVersion {
|
||||
if ip.To4() == nil {
|
||||
return v6
|
||||
}
|
||||
return v4
|
||||
}
|
||||
|
||||
// Adds the ordinal IP to the current array
|
||||
// 192.168.0.0 + 53 => 192.168.0.53
|
||||
func addIntToIP(array []byte, ordinal uint64) {
|
||||
for i := len(array) - 1; i >= 0; i-- {
|
||||
array[i] |= (byte)(ordinal & 0xff)
|
||||
ordinal >>= 8
|
||||
return &net.IPNet{
|
||||
IP: p.Addr().AsSlice(),
|
||||
Mask: net.CIDRMask(p.Bits(), p.Addr().BitLen()),
|
||||
}
|
||||
}
|
||||
|
||||
// Convert an ordinal to the respective IP address
|
||||
func ipToUint64(ip []byte) (value uint64) {
|
||||
cip := types.GetMinimalIP(ip)
|
||||
for i := 0; i < len(cip); i++ {
|
||||
j := len(cip) - 1 - i
|
||||
value += uint64(cip[i]) << uint(j*8)
|
||||
func toPrefix(n *net.IPNet) (netip.Prefix, bool) {
|
||||
if ll := len(n.Mask); ll != net.IPv4len && ll != net.IPv6len {
|
||||
return netip.Prefix{}, false
|
||||
}
|
||||
return value
|
||||
|
||||
addr, ok := netip.AddrFromSlice(n.IP)
|
||||
if !ok {
|
||||
return netip.Prefix{}, false
|
||||
}
|
||||
|
||||
ones, bits := n.Mask.Size()
|
||||
if ones == 0 && bits == 0 {
|
||||
return netip.Prefix{}, false
|
||||
}
|
||||
|
||||
return netip.PrefixFrom(addr.Unmap(), ones), true
|
||||
}
|
||||
|
||||
func hostID(addr netip.Addr, bits uint) uint64 {
|
||||
return ipbits.Field(addr, bits, uint(addr.BitLen()))
|
||||
}
|
||||
|
||||
// subnetRange returns the amount to add to network.Addr() in order to yield the
|
||||
// first and last addresses in subnet, respectively.
|
||||
func subnetRange(network, subnet netip.Prefix) (start, end uint64) {
|
||||
start = hostID(subnet.Addr(), uint(network.Bits()))
|
||||
end = start + (1 << uint64(subnet.Addr().BitLen()-subnet.Bits())) - 1
|
||||
return start, end
|
||||
}
|
||||
|
|
41
libnetwork/ipbits/ipbits.go
Normal file
41
libnetwork/ipbits/ipbits.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Package ipbits contains utilities for manipulating [netip.Addr] values as
|
||||
// numbers or bitfields.
|
||||
package ipbits
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// Add returns ip + (x << shift).
|
||||
func Add(ip netip.Addr, x uint64, shift uint) netip.Addr {
|
||||
if ip.Is4() {
|
||||
a := ip.As4()
|
||||
addr := binary.BigEndian.Uint32(a[:])
|
||||
addr += uint32(x) << shift
|
||||
binary.BigEndian.PutUint32(a[:], addr)
|
||||
return netip.AddrFrom4(a)
|
||||
} else {
|
||||
a := ip.As16()
|
||||
addr := uint128From16(a)
|
||||
addr = addr.add(uint128From(x).lsh(shift))
|
||||
addr.fill16(&a)
|
||||
return netip.AddrFrom16(a)
|
||||
}
|
||||
}
|
||||
|
||||
// Field returns the value of the bitfield [u, v] in ip as an integer,
|
||||
// where bit 0 is the most-significant bit of ip.
|
||||
//
|
||||
// The result is undefined if u > v, if v-u > 64, or if u or v is larger than
|
||||
// ip.BitLen().
|
||||
func Field(ip netip.Addr, u, v uint) uint64 {
|
||||
if ip.Is4() {
|
||||
mask := ^uint32(0) >> u
|
||||
a := ip.As4()
|
||||
return uint64((binary.BigEndian.Uint32(a[:]) & mask) >> (32 - v))
|
||||
} else {
|
||||
mask := uint128From(0).not().rsh(u)
|
||||
return uint128From16(ip.As16()).and(mask).rsh(128 - v).uint64()
|
||||
}
|
||||
}
|
70
libnetwork/ipbits/ipbits_test.go
Normal file
70
libnetwork/ipbits/ipbits_test.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package ipbits
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
tests := []struct {
|
||||
in netip.Addr
|
||||
x uint64
|
||||
shift uint
|
||||
want netip.Addr
|
||||
}{
|
||||
{netip.MustParseAddr("10.0.0.1"), 0, 0, netip.MustParseAddr("10.0.0.1")},
|
||||
{netip.MustParseAddr("10.0.0.1"), 41, 0, netip.MustParseAddr("10.0.0.42")},
|
||||
{netip.MustParseAddr("10.0.0.1"), 42, 16, netip.MustParseAddr("10.42.0.1")},
|
||||
{netip.MustParseAddr("10.0.0.1"), 1, 7, netip.MustParseAddr("10.0.0.129")},
|
||||
{netip.MustParseAddr("10.0.0.1"), 1, 24, netip.MustParseAddr("11.0.0.1")},
|
||||
{netip.MustParseAddr("2001::1"), 0, 0, netip.MustParseAddr("2001::1")},
|
||||
{netip.MustParseAddr("2001::1"), 0x41, 0, netip.MustParseAddr("2001::42")},
|
||||
{netip.MustParseAddr("2001::1"), 1, 7, netip.MustParseAddr("2001::81")},
|
||||
{netip.MustParseAddr("2001::1"), 0xcafe, 96, netip.MustParseAddr("2001:cafe::1")},
|
||||
{netip.MustParseAddr("2001::1"), 1, 112, netip.MustParseAddr("2002::1")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got := Add(tt.in, tt.x, tt.shift); tt.want != got {
|
||||
t.Errorf("%v + (%v << %v) = %v; want %v", tt.in, tt.x, tt.shift, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAdd(b *testing.B) {
|
||||
do := func(b *testing.B, addr netip.Addr) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Add(addr, uint64(i), 0)
|
||||
}
|
||||
}
|
||||
|
||||
b.Run("IPv4", func(b *testing.B) { do(b, netip.IPv4Unspecified()) })
|
||||
b.Run("IPv6", func(b *testing.B) { do(b, netip.IPv6Unspecified()) })
|
||||
}
|
||||
|
||||
func TestField(t *testing.T) {
|
||||
tests := []struct {
|
||||
in netip.Addr
|
||||
u, v uint
|
||||
want uint64
|
||||
}{
|
||||
{netip.MustParseAddr("1.2.3.4"), 0, 8, 1},
|
||||
{netip.MustParseAddr("1.2.3.4"), 8, 16, 2},
|
||||
{netip.MustParseAddr("1.2.3.4"), 16, 24, 3},
|
||||
{netip.MustParseAddr("1.2.3.4"), 24, 32, 4},
|
||||
{netip.MustParseAddr("1.2.3.4"), 0, 32, 0x01020304},
|
||||
{netip.MustParseAddr("1.2.3.4"), 0, 28, 0x102030},
|
||||
{netip.MustParseAddr("1234:5678:9abc:def0::7654:3210"), 0, 8, 0x12},
|
||||
{netip.MustParseAddr("1234:5678:9abc:def0::7654:3210"), 8, 16, 0x34},
|
||||
{netip.MustParseAddr("1234:5678:9abc:def0::7654:3210"), 16, 24, 0x56},
|
||||
{netip.MustParseAddr("1234:5678:9abc:def0::7654:3210"), 64, 128, 0x76543210},
|
||||
{netip.MustParseAddr("1234:5678:9abc:def0:beef::7654:3210"), 48, 80, 0xdef0beef},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got := Field(tt.in, tt.u, tt.v); got != tt.want {
|
||||
t.Errorf("Field(%v, %v, %v) = %v (0x%[4]x); want %v (0x%[5]x)", tt.in, tt.u, tt.v, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
62
libnetwork/ipbits/uint128.go
Normal file
62
libnetwork/ipbits/uint128.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package ipbits
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
type uint128 struct{ hi, lo uint64 }
|
||||
|
||||
func uint128From16(b [16]byte) uint128 {
|
||||
return uint128{
|
||||
hi: binary.BigEndian.Uint64(b[:8]),
|
||||
lo: binary.BigEndian.Uint64(b[8:]),
|
||||
}
|
||||
}
|
||||
|
||||
func uint128From(x uint64) uint128 {
|
||||
return uint128{lo: x}
|
||||
}
|
||||
|
||||
func (x uint128) add(y uint128) uint128 {
|
||||
lo, carry := bits.Add64(x.lo, y.lo, 0)
|
||||
hi, _ := bits.Add64(x.hi, y.hi, carry)
|
||||
return uint128{hi: hi, lo: lo}
|
||||
}
|
||||
|
||||
func (x uint128) lsh(n uint) uint128 {
|
||||
if n > 64 {
|
||||
return uint128{hi: x.lo << (n - 64)}
|
||||
}
|
||||
return uint128{
|
||||
hi: x.hi<<n | x.lo>>(64-n),
|
||||
lo: x.lo << n,
|
||||
}
|
||||
}
|
||||
|
||||
func (x uint128) rsh(n uint) uint128 {
|
||||
if n > 64 {
|
||||
return uint128{lo: x.hi >> (n - 64)}
|
||||
}
|
||||
return uint128{
|
||||
hi: x.hi >> n,
|
||||
lo: x.lo>>n | x.hi<<(64-n),
|
||||
}
|
||||
}
|
||||
|
||||
func (x uint128) and(y uint128) uint128 {
|
||||
return uint128{hi: x.hi & y.hi, lo: x.lo & y.lo}
|
||||
}
|
||||
|
||||
func (x uint128) not() uint128 {
|
||||
return uint128{hi: ^x.hi, lo: ^x.lo}
|
||||
}
|
||||
|
||||
func (x uint128) fill16(a *[16]byte) {
|
||||
binary.BigEndian.PutUint64(a[:8], x.hi)
|
||||
binary.BigEndian.PutUint64(a[8:], x.lo)
|
||||
}
|
||||
|
||||
func (x uint128) uint64() uint64 {
|
||||
return x.lo
|
||||
}
|
Loading…
Add table
Reference in a new issue