diff --git a/libnetwork/ipam/allocator.go b/libnetwork/ipam/allocator.go index a001ab563a..46e5c52225 100644 --- a/libnetwork/ipam/allocator.go +++ b/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 } } diff --git a/libnetwork/ipam/allocator_test.go b/libnetwork/ipam/allocator_test.go index 8b18120c48..5cf4f29231 100644 --- a/libnetwork/ipam/allocator_test.go +++ b/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 { diff --git a/libnetwork/ipam/structures.go b/libnetwork/ipam/structures.go index eaf0acb17f..bec2596612 100644 --- a/libnetwork/ipam/structures.go +++ b/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 } } diff --git a/libnetwork/ipam/utils.go b/libnetwork/ipam/utils.go index 6288be6c60..4e7d72c2e0 100644 --- a/libnetwork/ipam/utils.go +++ b/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) - } - 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) - } - - 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 } diff --git a/libnetwork/ipbits/ipbits.go b/libnetwork/ipbits/ipbits.go new file mode 100644 index 0000000000..ab2c04ed31 --- /dev/null +++ b/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() + } +} diff --git a/libnetwork/ipbits/ipbits_test.go b/libnetwork/ipbits/ipbits_test.go new file mode 100644 index 0000000000..d23ea1ad8c --- /dev/null +++ b/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) + } + } +} diff --git a/libnetwork/ipbits/uint128.go b/libnetwork/ipbits/uint128.go new file mode 100644 index 0000000000..56700d03a0 --- /dev/null +++ b/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<>(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 +}