| 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" | |
| ) | |
| const ( | |
| localAddressSpace = "LocalDefault" | |
| globalAddressSpace = "GlobalDefault" | |
| ) | |
| // Allocator provides per address space ipv4/ipv6 book keeping | |
| type Allocator struct { | |
| // The address spaces | |
| local, global *addrSpace | |
| } | |
| // NewAllocator returns an instance of libnetwork ipam | |
| func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) { | |
| 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, 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 | |
| func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) { | |
| return localAddressSpace, globalAddressSpace, nil | |
| } | |
| // RequestPool returns an address pool along with its unique id. | |
| // addressSpace must be a valid address space name and must not be the empty string. | |
| // If pool 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. | |
| // If subPool is not empty, it must be a valid IP address and length in CIDR notation which is a sub-range of pool. | |
| // subPool must be empty if pool is empty. | |
| 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) | |
| 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) | |
| } | |
| if addressSpace == "" { | |
| return parseErr(ipamapi.ErrInvalidAddressSpace) | |
| } | |
| aSpace, err := a.getAddrSpace(addressSpace) | |
| if err != nil { | |
| return "", nil, nil, err | |
| } | |
| k := PoolID{AddressSpace: addressSpace} | |
| 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() | |
| // Prior to https://github.com/moby/moby/pull/44968, libnetwork would happily accept a ChildSubnet with a bigger | |
| // mask than its parent subnet. In such case, it was producing IP addresses based on the parent subnet, and the | |
| // child subnet was not allocated from the address pool. Following condition take care of restoring this behavior | |
| // for networks created before upgrading to v24.0. | |
| if k.ChildSubnet.IsValid() && k.ChildSubnet.Bits() < k.Subnet.Bits() { | |
| k.ChildSubnet = k.Subnet | |
| } | |
| err = aSpace.allocateSubnet(k.Subnet, k.ChildSubnet) | |
| if err != nil { | |
| return "", nil, nil, err | |
| } | |
| 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 := PoolID{} | |
| if err := k.FromString(poolID); err != nil { | |
| return types.BadRequestErrorf("invalid pool id: %s", poolID) | |
| } | |
| aSpace, err := a.getAddrSpace(k.AddressSpace) | |
| if err != nil { | |
| return err | |
| } | |
| 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) { | |
| switch as { | |
| case localAddressSpace: | |
| return a.local, nil | |
| case globalAddressSpace: | |
| return a.global, nil | |
| } | |
| return nil, types.BadRequestErrorf("cannot find address space %s", as) | |
| } | |
| func newPoolData(pool netip.Prefix) *PoolData { | |
| ones, bits := pool.Bits(), pool.Addr().BitLen() | |
| numAddresses := uint64(1 << uint(bits-ones)) | |
| // Allow /64 subnet | |
| if pool.Addr().Is6() && numAddresses == 0 { | |
| numAddresses-- | |
| } | |
| // Generate the new address masks. | |
| 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 !(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 pool.Addr().Is4() && numAddresses > 2 { | |
| h.Set(numAddresses - 1) | |
| } | |
| 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() []netip.Prefix { | |
| i := aSpace.predefinedStartIndex | |
| // defensive in case the list changed since last update | |
| if i >= len(aSpace.predefined) { | |
| i = 0 | |
| } | |
| return append(aSpace.predefined[i:], aSpace.predefined[:i]...) | |
| } | |
| // 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 | |
| } | |
| aSpace.predefinedStartIndex = i | |
| } | |
| func (aSpace *addrSpace) allocatePredefinedPool(ipV6 bool) (netip.Prefix, error) { | |
| aSpace.Lock() | |
| 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[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) | |
| err := aSpace.allocateSubnetL(nw, netip.Prefix{}) | |
| if err != nil { | |
| return netip.Prefix{}, err | |
| } | |
| return nw, nil | |
| } | |
| } | |
| 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 := PoolID{} | |
| if err := k.FromString(poolID); err != nil { | |
| return nil, nil, types.BadRequestErrorf("invalid pool id: %s", poolID) | |
| } | |
| aSpace, err := a.getAddrSpace(k.AddressSpace) | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| 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(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 := PoolID{} | |
| if err := k.FromString(poolID); err != nil { | |
| return types.BadRequestErrorf("invalid pool id: %s", poolID) | |
| } | |
| aSpace, err := a.getAddrSpace(k.AddressSpace) | |
| if err != nil { | |
| return err | |
| } | |
| 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(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 !address.IsValid() { | |
| return types.BadRequestErrorf("invalid address") | |
| } | |
| if !nw.Contains(address) { | |
| return ipamapi.ErrIPOutOfRange | |
| } | |
| defer logrus.Debugf("Released address Address:%v Sequence:%s", address, p.addrs) | |
| return p.addrs.Unset(hostID(address, uint(nw.Bits()))) | |
| } | |
| func getAddress(base netip.Prefix, bitmask *bitmap.Bitmap, prefAddress netip.Addr, ipr netip.Prefix, serial bool) (netip.Addr, error) { | |
| var ( | |
| ordinal uint64 | |
| err error | |
| ) | |
| logrus.Debugf("Request address PoolID:%v %s Serial:%v PrefAddress:%v ", base, bitmask, serial, prefAddress) | |
| if bitmask.Unselected() == 0 { | |
| return netip.Addr{}, ipamapi.ErrNoAvailableIPs | |
| } | |
| if ipr == (netip.Prefix{}) && prefAddress == (netip.Addr{}) { | |
| ordinal, err = bitmask.SetAny(serial) | |
| } else if prefAddress != (netip.Addr{}) { | |
| ordinal = hostID(prefAddress, uint(base.Bits())) | |
| err = bitmask.Set(ordinal) | |
| } else { | |
| 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 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 netip.Addr{}, err | |
| } | |
| } | |
| // DumpDatabase dumps the internal info | |
| func (a *Allocator) DumpDatabase() string { | |
| aspaces := map[string]*addrSpace{ | |
| localAddressSpace: a.local, | |
| globalAddressSpace: a.global, | |
| } | |
| 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() | |
| } | |
| 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) | |
| } | |
| } | |
| return b.String() | |
| } | |
| // IsBuiltIn returns true for builtin drivers | |
| func (a *Allocator) IsBuiltIn() bool { | |
| return true | |
| } |