diff --git a/libnetwork/ipam/allocator.go b/libnetwork/ipam/allocator.go index 529ec44467..46e5c52225 100644 --- a/libnetwork/ipam/allocator.go +++ b/libnetwork/ipam/allocator.go @@ -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 diff --git a/libnetwork/ipam/allocator_test.go b/libnetwork/ipam/allocator_test.go index ab539aea57..5cf4f29231 100644 --- a/libnetwork/ipam/allocator_test.go +++ b/libnetwork/ipam/allocator_test.go @@ -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) } diff --git a/libnetwork/ipam/parallel_test.go b/libnetwork/ipam/parallel_test.go index 6e96ebabdc..d34a859909 100644 --- a/libnetwork/ipam/parallel_test.go +++ b/libnetwork/ipam/parallel_test.go @@ -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< 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 +}