Bläddra i källkod

libnet/ipam: use netip types internally

The netip types can be used as map keys, unlike net.IP and friends,
which is a very useful property to have for this application.

Signed-off-by: Cory Snider <csnider@mirantis.com>
Cory Snider 2 år sedan
förälder
incheckning
3c59ef247f

+ 108 - 97
libnetwork/ipam/allocator.go

@@ -3,10 +3,12 @@ package ipam
 import (
 	"fmt"
 	"net"
+	"net/netip"
 	"strings"
 
 	"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"
 )
@@ -24,17 +26,34 @@ type Allocator struct {
 
 // NewAllocator returns an instance of libnetwork ipam
 func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) {
-	return &Allocator{
-		local:  newAddrSpace(lcAs),
-		global: newAddrSpace(glAs),
-	}, nil
+	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.global, err = newAddrSpace(glAs)
+	if err != nil {
+		return nil, fmt.Errorf("could not construct global address space: %w", err)
+	}
+	return &a, nil
 }
 
-func newAddrSpace(predefined []*net.IPNet) *addrSpace {
-	return &addrSpace{
-		subnets:    map[string]*PoolData{},
-		predefined: predefined,
+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
@@ -67,36 +86,32 @@ func (a *Allocator) RequestPool(addressSpace, pool, subPool string, options map[
 		if subPool != "" {
 			return parseErr(ipamapi.ErrInvalidSubPool)
 		}
-		var nw *net.IPNet
-		nw, k.SubnetKey, err = aSpace.allocatePredefinedPool(v6)
+		k.Subnet, err = aSpace.allocatePredefinedPool(v6)
 		if err != nil {
 			return "", nil, nil, err
 		}
-		return k.String(), nw, nil, nil
+		return k.String(), toIPNet(k.Subnet), nil, nil
 	}
 
-	var (
-		nw, sub *net.IPNet
-	)
-	if _, nw, err = net.ParseCIDR(pool); err != nil {
+	if k.Subnet, err = netip.ParsePrefix(pool); err != nil {
 		return parseErr(ipamapi.ErrInvalidPool)
 	}
 
 	if subPool != "" {
 		var err error
-		_, sub, err = net.ParseCIDR(subPool)
+		k.ChildSubnet, err = netip.ParsePrefix(subPool)
 		if err != nil {
 			return parseErr(ipamapi.ErrInvalidSubPool)
 		}
-		k.ChildSubnet = subPool
 	}
 
-	k.SubnetKey, err = aSpace.allocateSubnet(nw, sub)
+	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, nil
+	return k.String(), toIPNet(k.Subnet), nil, nil
 }
 
 // ReleasePool releases the address pool identified by the passed id
@@ -112,7 +127,7 @@ func (a *Allocator) ReleasePool(poolID string) error {
 		return err
 	}
 
-	return aSpace.releaseSubnet(k.SubnetKey)
+	return aSpace.releaseSubnet(k.Subnet, k.ChildSubnet)
 }
 
 // Given the address space, returns the local or global PoolConfig based on whether the
@@ -127,13 +142,12 @@ func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) {
 	return nil, types.BadRequestErrorf("cannot find address space %s", as)
 }
 
-func newPoolData(pool *net.IPNet) *PoolData {
-	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--
 	}
 
@@ -142,23 +156,23 @@ func newPoolData(pool *net.IPNet) *PoolData {
 
 	// 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)
 	}
 
-	return &PoolData{Pool: pool, addrs: h, children: map[string]struct{}{}}
+	return &PoolData{addrs: h, children: map[netip.Prefix]struct{}{}}
 }
 
 // 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() []*net.IPNet {
+func (aSpace *addrSpace) getPredefineds() []netip.Prefix {
 	i := aSpace.predefinedStartIndex
 	// defensive in case the list changed since last update
 	if i >= len(aSpace.predefined) {
@@ -178,37 +192,35 @@ func (aSpace *addrSpace) updatePredefinedStartIndex(amt int) {
 	aSpace.predefinedStartIndex = i
 }
 
-func (aSpace *addrSpace) allocatePredefinedPool(ipV6 bool) (*net.IPNet, SubnetKey, error) {
-	var v ipVersion
-	v = v4
-	if ipV6 {
-		v = v6
-	}
-
+func (aSpace *addrSpace) allocatePredefinedPool(ipV6 bool) (netip.Prefix, error) {
 	aSpace.Lock()
 	defer aSpace.Unlock()
 
 	for i, nw := range aSpace.getPredefineds() {
-		if v != getAddressVersion(nw.IP) {
+		if ipV6 != nw.Addr().Is6() {
 			continue
 		}
 		// Checks whether pool has already been allocated
-		if _, ok := aSpace.subnets[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(nw) {
 			aSpace.updatePredefinedStartIndex(i + 1)
-			k, err := aSpace.allocateSubnetL(nw, nil)
+			err := aSpace.allocateSubnetL(nw, netip.Prefix{})
 			if err != nil {
-				return nil, SubnetKey{}, err
+				return netip.Prefix{}, err
 			}
-			return nw, k, nil
+			return nw, nil
 		}
 	}
 
-	return nil, SubnetKey{}, 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
@@ -223,46 +235,52 @@ func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[s
 	if err != nil {
 		return nil, nil, err
 	}
-	return aSpace.requestAddress(k.SubnetKey, prefAddress, opts)
+	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)
+		}
+	}
+	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
 }
 
-func (aSpace *addrSpace) requestAddress(k SubnetKey, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
+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[k.Subnet]
+	p, ok := aSpace.subnets[nw]
 	if !ok {
-		return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%+v", k)
+		return netip.Addr{}, types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub)
 	}
 
-	if prefAddress != nil && !p.Pool.Contains(prefAddress) {
-		return nil, nil, ipamapi.ErrIPOutOfRange
+	if prefAddress != (netip.Addr{}) && !nw.Contains(prefAddress) {
+		return netip.Addr{}, ipamapi.ErrIPOutOfRange
 	}
 
-	var ipr *AddressRange
-	if k.ChildSubnet != "" {
-		if _, ok := p.children[k.ChildSubnet]; !ok {
-			return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%+v", k)
-		}
-		_, sub, err := net.ParseCIDR(k.ChildSubnet)
-		if err != nil {
-			return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%+v: %v", k, err)
-		}
-		ipr, err = getAddressRange(sub, p.Pool)
-		if err != nil {
-			return nil, nil, err
+	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(p.Pool, p.addrs, prefAddress, ipr, serial)
+	ip, err := getAddress(nw, p.addrs, prefAddress, sub, serial)
 	if err != nil {
-		return nil, nil, err
+		return netip.Addr{}, err
 	}
 
-	return &net.IPNet{IP: ip, Mask: p.Pool.Mask}, nil, nil
+	return ip, nil
 }
 
 // ReleaseAddress releases the address from the specified pool ID
@@ -278,79 +296,72 @@ func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
 		return err
 	}
 
-	return aSpace.releaseAddress(k.SubnetKey, address)
+	addr, ok := netip.AddrFromSlice(address)
+	if !ok {
+		return types.BadRequestErrorf("invalid address: %v", address)
+	}
+
+	return aSpace.releaseAddress(k.Subnet, k.ChildSubnet, addr.Unmap())
 }
 
-func (aSpace *addrSpace) releaseAddress(k SubnetKey, address net.IP) error {
+func (aSpace *addrSpace) releaseAddress(nw, sub netip.Prefix, address netip.Addr) error {
 	aSpace.Lock()
 	defer aSpace.Unlock()
 
-	p, ok := aSpace.subnets[k.Subnet]
+	p, ok := aSpace.subnets[nw]
 	if !ok {
-		return types.NotFoundErrorf("cannot find address pool for %+v", k)
+		return types.NotFoundErrorf("cannot find address pool for %v/%v", nw, sub)
 	}
-	if k.ChildSubnet != "" {
-		if _, ok := p.children[k.ChildSubnet]; !ok {
-			return types.NotFoundErrorf("cannot find address pool for poolID:%+v", k)
+	if sub != (netip.Prefix{}) {
+		if _, ok := p.children[sub]; !ok {
+			return types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub)
 		}
 	}
 
-	if address == nil {
-		return types.BadRequestErrorf("invalid address: nil")
+	if !address.IsValid() {
+		return types.BadRequestErrorf("invalid address")
 	}
 
-	if !p.Pool.Contains(address) {
+	if !nw.Contains(address) {
 		return ipamapi.ErrIPOutOfRange
 	}
 
-	mask := p.Pool.Mask
-
-	h, err := types.GetHostPartIP(address, mask)
-	if err != nil {
-		return types.InternalErrorf("failed to release address %s: %v", address, err)
-	}
-
 	defer logrus.Debugf("Released address Address:%v Sequence:%s", address, p.addrs)
 
-	return p.addrs.Unset(ipToUint64(h))
+	return p.addrs.Unset(hostID(address, uint(nw.Bits())))
 }
 
-func getAddress(nw *net.IPNet, bitmask *bitmap.Bitmap, 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
+		return ipbits.Add(base.Addr(), ordinal, 0), nil
 	case bitmap.ErrBitAllocated:
-		return nil, ipamapi.ErrIPAlreadyAllocated
+		return netip.Addr{}, ipamapi.ErrIPAlreadyAllocated
 	case bitmap.ErrNoBitAvailable:
-		return nil, ipamapi.ErrNoAvailableIPs
+		return netip.Addr{}, ipamapi.ErrNoAvailableIPs
 	default:
-		return nil, err
+		return netip.Addr{}, err
 	}
 }
 

+ 9 - 34
libnetwork/ipam/allocator_test.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"math/rand"
 	"net"
+	"net/netip"
 	"runtime"
 	"strconv"
 	"sync"
@@ -21,34 +22,8 @@ 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 := &PoolID{AddressSpace: "default", SubnetKey: SubnetKey{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())
@@ -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())
 	}
@@ -495,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)
@@ -619,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)
@@ -954,7 +929,7 @@ func TestRelease(t *testing.T) {
 	for i, inp := range toRelease {
 		ip0 := net.ParseIP(inp.address)
 		a.ReleaseAddress(pid, ip0)
-		bm := a.local.subnets[subnet].addrs
+		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())
 		}
@@ -976,8 +951,8 @@ func assertGetAddress(t *testing.T, subnet string) {
 		printTime = false
 	)
 
-	_, 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)
 
@@ -986,7 +961,7 @@ func assertGetAddress(t *testing.T, subnet string) {
 	start := time.Now()
 	run := 0
 	for err != ipamapi.ErrNoAvailableIPs {
-		_, err = getAddress(sub, bm, nil, nil, false)
+		_, err = getAddress(sub, bm, netip.Addr{}, netip.Prefix{}, false)
 		run++
 	}
 	if printTime {

+ 49 - 55
libnetwork/ipam/structures.go

@@ -2,7 +2,7 @@ package ipam
 
 import (
 	"fmt"
-	"net"
+	"net/netip"
 	"strings"
 	"sync"
 
@@ -19,9 +19,8 @@ type PoolID struct {
 
 // PoolData contains the configured pool data
 type PoolData struct {
-	Pool     *net.IPNet
 	addrs    *bitmap.Bitmap
-	children map[string]struct{}
+	children map[netip.Prefix]struct{}
 
 	// Whether to implicitly release the pool once it no longer has any children.
 	autoRelease bool
@@ -29,37 +28,25 @@ type PoolData struct {
 
 // SubnetKey is the composite key to an address pool within an address space.
 type SubnetKey struct {
-	Subnet, ChildSubnet string
+	Subnet, ChildSubnet netip.Prefix
 }
 
 // addrSpace contains the pool configurations for the address space
 type addrSpace struct {
 	// Master subnet pools, indexed by the value's stringified PoolData.Pool field.
-	subnets map[string]*PoolData
+	subnets map[netip.Prefix]*PoolData
 
 	// Predefined pool for the address space
-	predefined           []*net.IPNet
+	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 *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
@@ -75,104 +62,111 @@ func (s *PoolID) 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("Pool: %s, Children: %d",
-		p.Pool.String(), len(p.children))
+	return fmt.Sprintf("PoolData[Children: %d]", len(p.children))
 }
 
 // allocateSubnet adds the subnet k to the address space.
-func (aSpace *addrSpace) allocateSubnet(nw, sub *net.IPNet) (SubnetKey, error) {
+func (aSpace *addrSpace) allocateSubnet(nw, sub netip.Prefix) error {
 	aSpace.Lock()
 	defer aSpace.Unlock()
 
 	// Check if already allocated
-	if pool, ok := aSpace.subnets[nw.String()]; ok {
+	if pool, ok := aSpace.subnets[nw]; ok {
 		var childExists bool
-		if sub != nil {
-			_, childExists = pool.children[sub.String()]
+		if sub != (netip.Prefix{}) {
+			_, childExists = pool.children[sub]
 		}
-		if sub == nil || childExists {
+		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 SubnetKey{}, ipamapi.ErrPoolOverlap
+			return ipamapi.ErrPoolOverlap
 		}
 	}
 
 	return aSpace.allocateSubnetL(nw, sub)
 }
 
-func (aSpace *addrSpace) allocateSubnetL(nw, sub *net.IPNet) (SubnetKey, error) {
+func (aSpace *addrSpace) allocateSubnetL(nw, sub netip.Prefix) error {
 	// If master pool, check for overlap
-	if sub == nil {
+	if sub == (netip.Prefix{}) {
 		if aSpace.contains(nw) {
-			return SubnetKey{}, ipamapi.ErrPoolOverlap
+			return ipamapi.ErrPoolOverlap
 		}
-		k := SubnetKey{Subnet: nw.String()}
 		// This is a new master pool, add it along with corresponding bitmask
-		aSpace.subnets[k.Subnet] = newPoolData(nw)
-		return k, nil
+		aSpace.subnets[nw] = newPoolData(nw)
+		return nil
 	}
 
 	// This is a new non-master pool (subPool)
-
-	_, err := getAddressRange(sub, nw)
-	if err != nil {
-		return SubnetKey{}, err
+	if nw.Addr().BitLen() != sub.Addr().BitLen() {
+		return fmt.Errorf("pool and subpool are of incompatible address families")
 	}
 
-	k := SubnetKey{Subnet: nw.String(), ChildSubnet: sub.String()}
-
 	// Look for parent pool
-	pp, ok := aSpace.subnets[k.Subnet]
+	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[k.Subnet] = pp
+		aSpace.subnets[nw] = pp
 	}
-	pp.children[k.ChildSubnet] = struct{}{}
-	return k, nil
+	pp.children[sub] = struct{}{}
+	return nil
 }
 
-func (aSpace *addrSpace) releaseSubnet(k SubnetKey) error {
+func (aSpace *addrSpace) releaseSubnet(nw, sub netip.Prefix) error {
 	aSpace.Lock()
 	defer aSpace.Unlock()
 
-	p, ok := aSpace.subnets[k.Subnet]
+	p, ok := aSpace.subnets[nw]
 	if !ok {
 		return ipamapi.ErrBadPool
 	}
 
-	if k.ChildSubnet != "" {
-		if _, ok := p.children[k.ChildSubnet]; !ok {
+	if sub != (netip.Prefix{}) {
+		if _, ok := p.children[sub]; !ok {
 			return ipamapi.ErrBadPool
 		}
-		delete(p.children, k.ChildSubnet)
+		delete(p.children, sub)
 	} else {
 		p.autoRelease = true
 	}
 
 	if len(p.children) == 0 && p.autoRelease {
-		delete(aSpace.subnets, k.Subnet)
+		delete(aSpace.subnets, nw)
 	}
 
 	return nil
 }
 
 // contains checks whether nw is a superset or subset of any of the existing subnets in this address space.
-func (aSpace *addrSpace) contains(nw *net.IPNet) bool {
-	for _, v := range aSpace.subnets {
-		if nw.Contains(v.Pool.IP) || v.Pool.Contains(nw.IP) {
+func (aSpace *addrSpace) contains(nw netip.Prefix) bool {
+	for pool := range aSpace.subnets {
+		if nw.Contains(pool.Addr()) || pool.Contains(nw.Addr()) {
 			return true
 		}
 	}

+ 28 - 55
libnetwork/ipam/utils.go

@@ -1,75 +1,48 @@
 package ipam
 
 import (
-	"fmt"
 	"net"
+	"net/netip"
 
-	"github.com/docker/docker/libnetwork/types"
+	"github.com/docker/docker/libnetwork/ipbits"
 )
 
-type ipVersion int
-
-const (
-	v4 = 4
-	v6 = 6
-)
-
-func getAddressRange(nw, masterNw *net.IPNet) (*AddressRange, error) {
-	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)
+func toIPNet(p netip.Prefix) *net.IPNet {
+	if !p.IsValid() {
+		return nil
 	}
-	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)
+	return &net.IPNet{
+		IP:   p.Addr().AsSlice(),
+		Mask: net.CIDRMask(p.Bits(), p.Addr().BitLen()),
 	}
-	hIP, e := types.GetHostPartIP(bIP, masterNw.Mask)
-	if e != nil {
-		return nil, fmt.Errorf("failed to compute range's highest ip address: %v", e)
-	}
-	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)
+func toPrefix(n *net.IPNet) (netip.Prefix, bool) {
+	if ll := len(n.Mask); ll != net.IPv4len && ll != net.IPv6len {
+		return netip.Prefix{}, false
 	}
 
-	end := len(network.Mask)
-	addIntToIP(address[:end], ordinal)
-
-	return net.IP(address[:end])
-}
+	addr, ok := netip.AddrFromSlice(n.IP)
+	if !ok {
+		return netip.Prefix{}, false
+	}
 
-func getAddressVersion(ip net.IP) ipVersion {
-	if ip.To4() == nil {
-		return v6
+	ones, bits := n.Mask.Size()
+	if ones == 0 && bits == 0 {
+		return netip.Prefix{}, false
 	}
-	return v4
+
+	return netip.PrefixFrom(addr.Unmap(), ones), true
 }
 
-// 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
-	}
+func hostID(addr netip.Addr, bits uint) uint64 {
+	return ipbits.Field(addr, bits, uint(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)
-	}
-	return value
+// 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 - 0
libnetwork/ipbits/ipbits.go

@@ -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 - 0
libnetwork/ipbits/ipbits_test.go

@@ -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 - 0
libnetwork/ipbits/uint128.go

@@ -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
+}