Files
moby/libnetwork/ipams/defaultipam/structures.go
Rob Murray bc93bba099 Use addrset.AddrSet instead of bitmap.Bitmap in IPAM
The default IPAM allocator is unable to represent subnets larger than
64-bits (subnets with a smaller prefix), because it uses a Bitmap
that's limited to 64-bits.

When it's used to represent a 64-bit subnet, the top address can't
be allocated (because bitmap.Bitmap is initialised with the number
of bits it needs to represent in a uint64, so it's one short).

The rest of the daemon doesn't know about these limitations, so
strange things happen when a large IPv6 subnet is used.

No errors are reported, addresses/subnets are just set up incorrectly.
The IPAM code calculates offsets into the bitmap itself, details it
shouldn't need to understand and, because it's working on offsets
into a range it doesn't always notice when it's asked to set a bit
outside the range.

It's unusual to need a big subnet but, for example, it may be useful
for modelling an ISP network, or an ISP's gateway may be in a "/56"
subnet that's outside a 64-bit range used by hosts.

So, use addrset.AddrSet instead of bitmap.Bitmap.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-01-20 16:48:46 +00:00

128 lines
2.9 KiB
Go

package defaultipam
import (
"fmt"
"net/netip"
"strings"
"github.com/docker/docker/libnetwork/internal/addrset"
"github.com/docker/docker/libnetwork/types"
)
// PoolID is the pointer to the configured pools in each address space
type PoolID struct {
AddressSpace string
SubnetKey
}
// PoolData contains the configured pool data
type PoolData struct {
addrs *addrset.AddrSet
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
}
func (k SubnetKey) Is6() bool {
return k.Subnet.Addr().Is6()
}
// PoolIDFromString creates a new PoolID and populates the SubnetKey object
// reading it from the given string.
func PoolIDFromString(str string) (pID PoolID, err error) {
if str == "" {
return pID, types.InvalidParameterErrorf("invalid string form for subnetkey: %s", str)
}
p := strings.Split(str, "/")
if len(p) != 3 && len(p) != 5 {
return pID, types.InvalidParameterErrorf("invalid string form for subnetkey: %s", str)
}
pID.AddressSpace = p[0]
pID.Subnet, err = netip.ParsePrefix(p[1] + "/" + p[2])
if err != nil {
return pID, types.InvalidParameterErrorf("invalid string form for subnetkey: %s", str)
}
if len(p) == 5 {
pID.ChildSubnet, err = netip.ParsePrefix(p[3] + "/" + p[4])
if err != nil {
return pID, types.InvalidParameterErrorf("invalid string form for subnetkey: %s", str)
}
}
return pID, nil
}
// String returns the string form of the SubnetKey object
func (s *PoolID) String() string {
if s.ChildSubnet == (netip.Prefix{}) {
return s.AddressSpace + "/" + s.Subnet.String()
} else {
return s.AddressSpace + "/" + s.Subnet.String() + "/" + s.ChildSubnet.String()
}
}
// String returns the string form of the PoolData object
func (p *PoolData) String() string {
return fmt.Sprintf("PoolData[Children: %d]", len(p.children))
}
// mergeIter is used to iterate on both 'a' and 'b' at the same time while
// maintaining the total order that would arise if both were merged and then
// sorted. Both 'a' and 'b' have to be sorted beforehand.
type mergeIter struct {
a, b []netip.Prefix
ia, ib int
cmp func(a, b netip.Prefix) int
lastA bool
}
func newMergeIter(a, b []netip.Prefix, cmp func(a, b netip.Prefix) int) *mergeIter {
iter := &mergeIter{
a: a,
b: b,
cmp: cmp,
}
iter.lastA = iter.nextA()
return iter
}
func (it *mergeIter) Get() netip.Prefix {
if it.ia+it.ib >= len(it.a)+len(it.b) {
return netip.Prefix{}
}
if it.lastA {
return it.a[it.ia]
}
return it.b[it.ib]
}
func (it *mergeIter) Inc() {
if it.lastA {
it.ia++
} else {
it.ib++
}
it.lastA = it.nextA()
}
func (it *mergeIter) nextA() bool {
if it.ia < len(it.a) && it.ib < len(it.b) && it.cmp(it.a[it.ia], it.b[it.ib]) <= 0 {
return true
} else if it.ia < len(it.a) && it.ib >= len(it.b) {
return true
}
return false
}