github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/libnetwork/ipam/allocator.go (about)

     1  package ipam
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/netip"
     8  	"strings"
     9  
    10  	"github.com/containerd/log"
    11  	"github.com/docker/docker/libnetwork/bitmap"
    12  	"github.com/docker/docker/libnetwork/internal/netiputil"
    13  	"github.com/docker/docker/libnetwork/ipamapi"
    14  	"github.com/docker/docker/libnetwork/ipbits"
    15  	"github.com/docker/docker/libnetwork/types"
    16  )
    17  
    18  const (
    19  	localAddressSpace  = "LocalDefault"
    20  	globalAddressSpace = "GlobalDefault"
    21  )
    22  
    23  // Allocator provides per address space ipv4/ipv6 book keeping
    24  type Allocator struct {
    25  	// The address spaces
    26  	local, global *addrSpace
    27  }
    28  
    29  // NewAllocator returns an instance of libnetwork ipam
    30  func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) {
    31  	var (
    32  		a   Allocator
    33  		err error
    34  	)
    35  	a.local, err = newAddrSpace(lcAs)
    36  	if err != nil {
    37  		return nil, fmt.Errorf("could not construct local address space: %w", err)
    38  	}
    39  	a.global, err = newAddrSpace(glAs)
    40  	if err != nil {
    41  		return nil, fmt.Errorf("could not construct global address space: %w", err)
    42  	}
    43  	return &a, nil
    44  }
    45  
    46  func newAddrSpace(predefined []*net.IPNet) (*addrSpace, error) {
    47  	pdf := make([]netip.Prefix, len(predefined))
    48  	for i, n := range predefined {
    49  		var ok bool
    50  		pdf[i], ok = netiputil.ToPrefix(n)
    51  		if !ok {
    52  			return nil, fmt.Errorf("network at index %d (%v) is not in canonical form", i, n)
    53  		}
    54  	}
    55  	return &addrSpace{
    56  		subnets:    map[netip.Prefix]*PoolData{},
    57  		predefined: pdf,
    58  	}, nil
    59  }
    60  
    61  // GetDefaultAddressSpaces returns the local and global default address spaces
    62  func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) {
    63  	return localAddressSpace, globalAddressSpace, nil
    64  }
    65  
    66  // RequestPool returns an address pool along with its unique id.
    67  // addressSpace must be a valid address space name and must not be the empty string.
    68  // If requestedPool is the empty string then the default predefined pool for addressSpace will be used, otherwise pool must be a valid IP address and length in CIDR notation.
    69  // If requestedSubPool is not empty, it must be a valid IP address and length in CIDR notation which is a sub-range of requestedPool.
    70  // requestedSubPool must be empty if requestedPool is empty.
    71  func (a *Allocator) RequestPool(addressSpace, requestedPool, requestedSubPool string, _ map[string]string, v6 bool) (poolID string, pool *net.IPNet, meta map[string]string, err error) {
    72  	log.G(context.TODO()).Debugf("RequestPool(%s, %s, %s, _, %t)", addressSpace, requestedPool, requestedSubPool, v6)
    73  
    74  	parseErr := func(err error) error {
    75  		return types.InternalErrorf("failed to parse pool request for address space %q pool %q subpool %q: %v", addressSpace, requestedPool, requestedSubPool, err)
    76  	}
    77  
    78  	if addressSpace == "" {
    79  		return "", nil, nil, parseErr(ipamapi.ErrInvalidAddressSpace)
    80  	}
    81  	aSpace, err := a.getAddrSpace(addressSpace)
    82  	if err != nil {
    83  		return "", nil, nil, err
    84  	}
    85  	if requestedPool == "" && requestedSubPool != "" {
    86  		return "", nil, nil, parseErr(ipamapi.ErrInvalidSubPool)
    87  	}
    88  
    89  	k := PoolID{AddressSpace: addressSpace}
    90  	if requestedPool == "" {
    91  		k.Subnet, err = aSpace.allocatePredefinedPool(v6)
    92  		if err != nil {
    93  			return "", nil, nil, err
    94  		}
    95  		return k.String(), netiputil.ToIPNet(k.Subnet), nil, nil
    96  	}
    97  
    98  	if k.Subnet, err = netip.ParsePrefix(requestedPool); err != nil {
    99  		return "", nil, nil, parseErr(ipamapi.ErrInvalidPool)
   100  	}
   101  
   102  	if requestedSubPool != "" {
   103  		k.ChildSubnet, err = netip.ParsePrefix(requestedSubPool)
   104  		if err != nil {
   105  			return "", nil, nil, parseErr(ipamapi.ErrInvalidSubPool)
   106  		}
   107  	}
   108  
   109  	k.Subnet, k.ChildSubnet = k.Subnet.Masked(), k.ChildSubnet.Masked()
   110  	// Prior to https://github.com/moby/moby/pull/44968, libnetwork would happily accept a ChildSubnet with a bigger
   111  	// mask than its parent subnet. In such case, it was producing IP addresses based on the parent subnet, and the
   112  	// child subnet was not allocated from the address pool. Following condition take care of restoring this behavior
   113  	// for networks created before upgrading to v24.0.
   114  	if k.ChildSubnet.IsValid() && k.ChildSubnet.Bits() < k.Subnet.Bits() {
   115  		k.ChildSubnet = k.Subnet
   116  	}
   117  
   118  	err = aSpace.allocateSubnet(k.Subnet, k.ChildSubnet)
   119  	if err != nil {
   120  		return "", nil, nil, err
   121  	}
   122  
   123  	return k.String(), netiputil.ToIPNet(k.Subnet), nil, nil
   124  }
   125  
   126  // ReleasePool releases the address pool identified by the passed id
   127  func (a *Allocator) ReleasePool(poolID string) error {
   128  	log.G(context.TODO()).Debugf("ReleasePool(%s)", poolID)
   129  	k, err := PoolIDFromString(poolID)
   130  	if err != nil {
   131  		return types.InvalidParameterErrorf("invalid pool id: %s", poolID)
   132  	}
   133  
   134  	aSpace, err := a.getAddrSpace(k.AddressSpace)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	return aSpace.releaseSubnet(k.Subnet, k.ChildSubnet)
   140  }
   141  
   142  // Given the address space, returns the local or global PoolConfig based on whether the
   143  // address space is local or global. AddressSpace locality is registered with IPAM out of band.
   144  func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) {
   145  	switch as {
   146  	case localAddressSpace:
   147  		return a.local, nil
   148  	case globalAddressSpace:
   149  		return a.global, nil
   150  	}
   151  	return nil, types.InvalidParameterErrorf("cannot find address space %s", as)
   152  }
   153  
   154  func newPoolData(pool netip.Prefix) *PoolData {
   155  	ones, bits := pool.Bits(), pool.Addr().BitLen()
   156  	numAddresses := uint64(1 << uint(bits-ones))
   157  
   158  	// Allow /64 subnet
   159  	if pool.Addr().Is6() && numAddresses == 0 {
   160  		numAddresses--
   161  	}
   162  
   163  	// Generate the new address masks.
   164  	h := bitmap.New(numAddresses)
   165  
   166  	// Pre-reserve the network address on IPv4 networks large
   167  	// enough to have one (i.e., anything bigger than a /31.
   168  	if !(pool.Addr().Is4() && numAddresses <= 2) {
   169  		h.Set(0)
   170  	}
   171  
   172  	// Pre-reserve the broadcast address on IPv4 networks large
   173  	// enough to have one (i.e., anything bigger than a /31).
   174  	if pool.Addr().Is4() && numAddresses > 2 {
   175  		h.Set(numAddresses - 1)
   176  	}
   177  
   178  	return &PoolData{addrs: h, children: map[netip.Prefix]struct{}{}}
   179  }
   180  
   181  // getPredefineds returns the predefined subnets for the address space.
   182  //
   183  // It should not be called concurrently with any other method on the addrSpace.
   184  func (aSpace *addrSpace) getPredefineds() []netip.Prefix {
   185  	i := aSpace.predefinedStartIndex
   186  	// defensive in case the list changed since last update
   187  	if i >= len(aSpace.predefined) {
   188  		i = 0
   189  	}
   190  	return append(aSpace.predefined[i:], aSpace.predefined[:i]...)
   191  }
   192  
   193  // updatePredefinedStartIndex rotates the predefined subnet list by amt.
   194  //
   195  // It should not be called concurrently with any other method on the addrSpace.
   196  func (aSpace *addrSpace) updatePredefinedStartIndex(amt int) {
   197  	i := aSpace.predefinedStartIndex + amt
   198  	if i < 0 || i >= len(aSpace.predefined) {
   199  		i = 0
   200  	}
   201  	aSpace.predefinedStartIndex = i
   202  }
   203  
   204  func (aSpace *addrSpace) allocatePredefinedPool(ipV6 bool) (netip.Prefix, error) {
   205  	aSpace.Lock()
   206  	defer aSpace.Unlock()
   207  
   208  	for i, nw := range aSpace.getPredefineds() {
   209  		if ipV6 != nw.Addr().Is6() {
   210  			continue
   211  		}
   212  		// Checks whether pool has already been allocated
   213  		if _, ok := aSpace.subnets[nw]; ok {
   214  			continue
   215  		}
   216  		// Shouldn't be necessary, but check prevents IP collisions should
   217  		// predefined pools overlap for any reason.
   218  		if !aSpace.overlaps(nw) {
   219  			aSpace.updatePredefinedStartIndex(i + 1)
   220  			err := aSpace.allocateSubnetL(nw, netip.Prefix{})
   221  			if err != nil {
   222  				return netip.Prefix{}, err
   223  			}
   224  			return nw, nil
   225  		}
   226  	}
   227  
   228  	v := 4
   229  	if ipV6 {
   230  		v = 6
   231  	}
   232  	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)
   233  }
   234  
   235  // RequestAddress returns an address from the specified pool ID
   236  func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
   237  	log.G(context.TODO()).Debugf("RequestAddress(%s, %v, %v)", poolID, prefAddress, opts)
   238  	k, err := PoolIDFromString(poolID)
   239  	if err != nil {
   240  		return nil, nil, types.InvalidParameterErrorf("invalid pool id: %s", poolID)
   241  	}
   242  
   243  	aSpace, err := a.getAddrSpace(k.AddressSpace)
   244  	if err != nil {
   245  		return nil, nil, err
   246  	}
   247  	var pref netip.Addr
   248  	if prefAddress != nil {
   249  		var ok bool
   250  		pref, ok = netip.AddrFromSlice(prefAddress)
   251  		if !ok {
   252  			return nil, nil, types.InvalidParameterErrorf("invalid preferred address: %v", prefAddress)
   253  		}
   254  	}
   255  	p, err := aSpace.requestAddress(k.Subnet, k.ChildSubnet, pref.Unmap(), opts)
   256  	if err != nil {
   257  		return nil, nil, err
   258  	}
   259  	return &net.IPNet{
   260  		IP:   p.AsSlice(),
   261  		Mask: net.CIDRMask(k.Subnet.Bits(), k.Subnet.Addr().BitLen()),
   262  	}, nil, nil
   263  }
   264  
   265  func (aSpace *addrSpace) requestAddress(nw, sub netip.Prefix, prefAddress netip.Addr, opts map[string]string) (netip.Addr, error) {
   266  	aSpace.Lock()
   267  	defer aSpace.Unlock()
   268  
   269  	p, ok := aSpace.subnets[nw]
   270  	if !ok {
   271  		return netip.Addr{}, types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub)
   272  	}
   273  
   274  	if prefAddress != (netip.Addr{}) && !nw.Contains(prefAddress) {
   275  		return netip.Addr{}, ipamapi.ErrIPOutOfRange
   276  	}
   277  
   278  	if sub != (netip.Prefix{}) {
   279  		if _, ok := p.children[sub]; !ok {
   280  			return netip.Addr{}, types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub)
   281  		}
   282  	}
   283  
   284  	// In order to request for a serial ip address allocation, callers can pass in the option to request
   285  	// IP allocation serially or first available IP in the subnet
   286  	serial := opts[ipamapi.AllocSerialPrefix] == "true"
   287  	ip, err := getAddress(nw, p.addrs, prefAddress, sub, serial)
   288  	if err != nil {
   289  		return netip.Addr{}, err
   290  	}
   291  
   292  	return ip, nil
   293  }
   294  
   295  // ReleaseAddress releases the address from the specified pool ID
   296  func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
   297  	log.G(context.TODO()).Debugf("ReleaseAddress(%s, %v)", poolID, address)
   298  	k, err := PoolIDFromString(poolID)
   299  	if err != nil {
   300  		return types.InvalidParameterErrorf("invalid pool id: %s", poolID)
   301  	}
   302  
   303  	aSpace, err := a.getAddrSpace(k.AddressSpace)
   304  	if err != nil {
   305  		return err
   306  	}
   307  
   308  	addr, ok := netip.AddrFromSlice(address)
   309  	if !ok {
   310  		return types.InvalidParameterErrorf("invalid address: %v", address)
   311  	}
   312  
   313  	return aSpace.releaseAddress(k.Subnet, k.ChildSubnet, addr.Unmap())
   314  }
   315  
   316  func (aSpace *addrSpace) releaseAddress(nw, sub netip.Prefix, address netip.Addr) error {
   317  	aSpace.Lock()
   318  	defer aSpace.Unlock()
   319  
   320  	p, ok := aSpace.subnets[nw]
   321  	if !ok {
   322  		return types.NotFoundErrorf("cannot find address pool for %v/%v", nw, sub)
   323  	}
   324  	if sub != (netip.Prefix{}) {
   325  		if _, ok := p.children[sub]; !ok {
   326  			return types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub)
   327  		}
   328  	}
   329  
   330  	if !address.IsValid() {
   331  		return types.InvalidParameterErrorf("invalid address")
   332  	}
   333  
   334  	if !nw.Contains(address) {
   335  		return ipamapi.ErrIPOutOfRange
   336  	}
   337  
   338  	defer log.G(context.TODO()).Debugf("Released address Address:%v Sequence:%s", address, p.addrs)
   339  
   340  	return p.addrs.Unset(netiputil.HostID(address, uint(nw.Bits())))
   341  }
   342  
   343  func getAddress(base netip.Prefix, bitmask *bitmap.Bitmap, prefAddress netip.Addr, ipr netip.Prefix, serial bool) (netip.Addr, error) {
   344  	var (
   345  		ordinal uint64
   346  		err     error
   347  	)
   348  
   349  	log.G(context.TODO()).Debugf("Request address PoolID:%v %s Serial:%v PrefAddress:%v ", base, bitmask, serial, prefAddress)
   350  
   351  	if bitmask.Unselected() == 0 {
   352  		return netip.Addr{}, ipamapi.ErrNoAvailableIPs
   353  	}
   354  	if ipr == (netip.Prefix{}) && prefAddress == (netip.Addr{}) {
   355  		ordinal, err = bitmask.SetAny(serial)
   356  	} else if prefAddress != (netip.Addr{}) {
   357  		ordinal = netiputil.HostID(prefAddress, uint(base.Bits()))
   358  		err = bitmask.Set(ordinal)
   359  	} else {
   360  		start, end := netiputil.SubnetRange(base, ipr)
   361  		ordinal, err = bitmask.SetAnyInRange(start, end, serial)
   362  	}
   363  
   364  	switch err {
   365  	case nil:
   366  		// Convert IP ordinal for this subnet into IP address
   367  		return ipbits.Add(base.Addr(), ordinal, 0), nil
   368  	case bitmap.ErrBitAllocated:
   369  		return netip.Addr{}, ipamapi.ErrIPAlreadyAllocated
   370  	case bitmap.ErrNoBitAvailable:
   371  		return netip.Addr{}, ipamapi.ErrNoAvailableIPs
   372  	default:
   373  		return netip.Addr{}, err
   374  	}
   375  }
   376  
   377  // DumpDatabase dumps the internal info
   378  func (a *Allocator) DumpDatabase() string {
   379  	aspaces := map[string]*addrSpace{
   380  		localAddressSpace:  a.local,
   381  		globalAddressSpace: a.global,
   382  	}
   383  
   384  	var b strings.Builder
   385  	for _, as := range []string{localAddressSpace, globalAddressSpace} {
   386  		fmt.Fprintf(&b, "\n### %s\n", as)
   387  		b.WriteString(aspaces[as].DumpDatabase())
   388  	}
   389  	return b.String()
   390  }
   391  
   392  func (aSpace *addrSpace) DumpDatabase() string {
   393  	aSpace.Lock()
   394  	defer aSpace.Unlock()
   395  
   396  	var b strings.Builder
   397  	for k, config := range aSpace.subnets {
   398  		fmt.Fprintf(&b, "%v: %v\n", k, config)
   399  		fmt.Fprintf(&b, "  Bitmap: %v\n", config.addrs)
   400  		for k := range config.children {
   401  			fmt.Fprintf(&b, "  - Subpool: %v\n", k)
   402  		}
   403  	}
   404  	return b.String()
   405  }
   406  
   407  // IsBuiltIn returns true for builtin drivers
   408  func (a *Allocator) IsBuiltIn() bool {
   409  	return true
   410  }