mirror of
https://github.com/moby/moby.git
synced 2026-08-03 14:41:03 +00:00
357 lines
11 KiB
Go
357 lines
11 KiB
Go
package defaultipam
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/moby/moby/api/types/network"
|
|
"github.com/moby/moby/v2/daemon/internal/netiputil"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/internal/addrset"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/ipamapi"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/ipamutils"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
|
)
|
|
|
|
const (
|
|
// DriverName is the name of the built-in default IPAM driver.
|
|
DriverName = "default"
|
|
|
|
localAddressSpace = "LocalDefault"
|
|
globalAddressSpace = "GlobalDefault"
|
|
)
|
|
|
|
var _ ipamapi.PoolStatuser = &Allocator{}
|
|
|
|
// Register registers the default ipam driver with libnetwork. It takes
|
|
// two optional address pools respectively containing the list of user-defined
|
|
// address pools for 'local' and 'global' address spaces.
|
|
func Register(ic ipamapi.Registerer, lAddrPools, gAddrPools []*ipamutils.NetworkToSplit) error {
|
|
if len(gAddrPools) == 0 {
|
|
gAddrPools = ipamutils.GetGlobalScopeDefaultNetworks()
|
|
}
|
|
|
|
a, err := NewAllocator(lAddrPools, gAddrPools)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cps := &ipamapi.Capability{RequiresRequestReplay: true}
|
|
|
|
return ic.RegisterIpamDriverWithCapabilities(DriverName, a, cps)
|
|
}
|
|
|
|
// Allocator provides per address space ipv4/ipv6 bookkeeping
|
|
type Allocator struct {
|
|
// The address spaces
|
|
local4, local6, global4, global6 *addrSpace
|
|
}
|
|
|
|
// NewAllocator returns an instance of libnetwork ipam
|
|
func NewAllocator(lcAs, glAs []*ipamutils.NetworkToSplit) (*Allocator, error) {
|
|
var (
|
|
a Allocator
|
|
err error
|
|
lcAs4, lcAs6, glAs4, glAs6 []*ipamutils.NetworkToSplit
|
|
)
|
|
|
|
lcAs4, lcAs6, err = splitByIPFamily(lcAs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not construct local address space: %w", err)
|
|
}
|
|
|
|
glAs4, glAs6, err = splitByIPFamily(glAs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not construct global address space: %w", err)
|
|
}
|
|
|
|
a.local4, err = newAddrSpace(lcAs4)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not construct local v4 address space: %w", err)
|
|
}
|
|
a.local6, err = newAddrSpace(lcAs6)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not construct local v6 address space: %w", err)
|
|
}
|
|
a.global4, err = newAddrSpace(glAs4)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not construct global v4 address space: %w", err)
|
|
}
|
|
a.global6, err = newAddrSpace(glAs6)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not construct global v6 address space: %w", err)
|
|
}
|
|
return &a, nil
|
|
}
|
|
|
|
func splitByIPFamily(s []*ipamutils.NetworkToSplit) ([]*ipamutils.NetworkToSplit, []*ipamutils.NetworkToSplit, error) {
|
|
var v4, v6 []*ipamutils.NetworkToSplit
|
|
|
|
for i, n := range s {
|
|
if !n.Base.IsValid() || n.Size == 0 {
|
|
return []*ipamutils.NetworkToSplit{}, []*ipamutils.NetworkToSplit{}, fmt.Errorf("network at index %d (%v) is not in canonical form", i, n)
|
|
}
|
|
if n.Base.Bits() > n.Size {
|
|
return []*ipamutils.NetworkToSplit{}, []*ipamutils.NetworkToSplit{}, fmt.Errorf("network at index %d (%v) has a smaller prefix (/%d) than the target size of that pool (/%d)", i, n, n.Base.Bits(), n.Size)
|
|
}
|
|
|
|
n.Base, _ = n.Base.Addr().Unmap().Prefix(n.Base.Bits())
|
|
|
|
if n.Base.Addr().Is4() {
|
|
v4 = append(v4, n)
|
|
} else {
|
|
v6 = append(v6, n)
|
|
}
|
|
}
|
|
|
|
return v4, v6, 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 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.
|
|
// If requestedSubPool is not empty, it must be a valid IP address and length in CIDR notation which is a sub-range of requestedPool.
|
|
// requestedSubPool must be empty if requestedPool is empty.
|
|
func (a *Allocator) RequestPool(req ipamapi.PoolRequest) (ipamapi.AllocatedPool, error) {
|
|
log.G(context.TODO()).Debugf("RequestPool: %+v", req)
|
|
|
|
parseErr := func(err error) error {
|
|
return types.InternalErrorf("failed to parse pool request for address space %q pool %q subpool %q: %v", req.AddressSpace, req.Pool, req.SubPool, err)
|
|
}
|
|
|
|
if req.AddressSpace == "" {
|
|
return ipamapi.AllocatedPool{}, parseErr(ipamapi.ErrInvalidAddressSpace)
|
|
}
|
|
aSpace, err := a.getAddrSpace(req.AddressSpace, req.V6)
|
|
if err != nil {
|
|
return ipamapi.AllocatedPool{}, err
|
|
}
|
|
|
|
k := PoolID{AddressSpace: req.AddressSpace}
|
|
|
|
prefixLength := 0
|
|
|
|
if req.Pool != "" {
|
|
prefix, err := netip.ParsePrefix(req.Pool)
|
|
if err != nil {
|
|
return ipamapi.AllocatedPool{}, parseErr(ipamapi.ErrInvalidPool)
|
|
}
|
|
|
|
if prefix.Addr().IsUnspecified() {
|
|
// If the prefix is unspecified, we're only interested in the prefix size.
|
|
// We'll attempt to use the specified size to allocate a subnet from the
|
|
// predefined pools.
|
|
req.Pool = ""
|
|
|
|
if prefix.Bits() > 0 {
|
|
prefixLength = prefix.Bits()
|
|
}
|
|
} else {
|
|
k.Subnet = prefix
|
|
}
|
|
}
|
|
|
|
if req.Pool == "" && req.SubPool != "" {
|
|
return ipamapi.AllocatedPool{}, parseErr(ipamapi.ErrInvalidSubPool)
|
|
}
|
|
|
|
if req.Pool == "" {
|
|
if k.Subnet, err = aSpace.allocatePredefinedPool(req.Exclude, prefixLength); err != nil {
|
|
return ipamapi.AllocatedPool{}, err
|
|
}
|
|
return ipamapi.AllocatedPool{PoolID: k.String(), Pool: k.Subnet}, nil
|
|
}
|
|
|
|
if req.SubPool != "" {
|
|
if k.ChildSubnet, err = netip.ParsePrefix(req.SubPool); err != nil {
|
|
return ipamapi.AllocatedPool{}, types.InternalErrorf("invalid pool request: %v", ipamapi.ErrInvalidSubPool)
|
|
}
|
|
}
|
|
|
|
// This is a new non-master pool (subPool)
|
|
if k.Subnet.IsValid() && k.ChildSubnet.IsValid() && k.Subnet.Addr().BitLen() != k.ChildSubnet.Addr().BitLen() {
|
|
return ipamapi.AllocatedPool{}, types.InvalidParameterErrorf("pool and subpool are of incompatible address families")
|
|
}
|
|
|
|
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 ipamapi.AllocatedPool{}, types.ForbiddenErrorf("invalid pool request: %v", err)
|
|
}
|
|
|
|
return ipamapi.AllocatedPool{PoolID: k.String(), Pool: k.Subnet}, nil
|
|
}
|
|
|
|
// ReleasePool releases the address pool identified by the passed id
|
|
func (a *Allocator) ReleasePool(poolID string) error {
|
|
log.G(context.TODO()).Debugf("ReleasePool(%s)", poolID)
|
|
k, err := PoolIDFromString(poolID)
|
|
if err != nil {
|
|
return types.InvalidParameterErrorf("invalid pool id: %s", poolID)
|
|
}
|
|
|
|
aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
|
|
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, v6 bool) (*addrSpace, error) {
|
|
switch as {
|
|
case localAddressSpace:
|
|
if v6 {
|
|
return a.local6, nil
|
|
}
|
|
return a.local4, nil
|
|
case globalAddressSpace:
|
|
if v6 {
|
|
return a.global6, nil
|
|
}
|
|
return a.global4, nil
|
|
}
|
|
return nil, types.InvalidParameterErrorf("cannot find address space %s", as)
|
|
}
|
|
|
|
func newPoolData(pool netip.Prefix) *PoolData {
|
|
h := addrset.New(pool)
|
|
|
|
// Reserve the first address in the range for the:
|
|
// - IPv4 network address
|
|
// - Except in a /31 point-to-point link, https://datatracker.ietf.org/doc/html/rfc3021
|
|
// - IPv6 Subnet-Router anycast address, https://datatracker.ietf.org/doc/html/rfc4291#section-2.6.1
|
|
bits := pool.Addr().BitLen() - pool.Bits()
|
|
if !pool.Addr().Is4() || bits > 1 {
|
|
h.Add(pool.Addr())
|
|
}
|
|
|
|
// For IPv4, reserve the broadcast address.
|
|
// - Except in a /31 point-to-point link, https://datatracker.ietf.org/doc/html/rfc3021
|
|
if pool.Addr().Is4() && bits > 1 {
|
|
h.Add(netiputil.LastAddr(pool))
|
|
}
|
|
|
|
return &PoolData{addrs: h, children: map[netip.Prefix]struct{}{}}
|
|
}
|
|
|
|
// 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) {
|
|
log.G(context.TODO()).Debugf("RequestAddress(%s, %v, %v)", poolID, prefAddress, opts)
|
|
k, err := PoolIDFromString(poolID)
|
|
if err != nil {
|
|
return nil, nil, types.InvalidParameterErrorf("invalid pool id: %s", poolID)
|
|
}
|
|
|
|
aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
|
|
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.InvalidParameterErrorf("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
|
|
}
|
|
|
|
// ReleaseAddress releases the address from the specified pool ID
|
|
func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
|
|
log.G(context.TODO()).Debugf("ReleaseAddress(%s, %v)", poolID, address)
|
|
k, err := PoolIDFromString(poolID)
|
|
if err != nil {
|
|
return types.InvalidParameterErrorf("invalid pool id: %s", poolID)
|
|
}
|
|
|
|
aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr, ok := netip.AddrFromSlice(address)
|
|
if !ok {
|
|
return types.InvalidParameterErrorf("invalid address: %v", address)
|
|
}
|
|
|
|
return aSpace.releaseAddress(k.Subnet, k.ChildSubnet, addr.Unmap())
|
|
}
|
|
|
|
func getAddress(base netip.Prefix, addrSet *addrset.AddrSet, prefAddress netip.Addr, ipr netip.Prefix, serial bool) (netip.Addr, error) {
|
|
var (
|
|
addr netip.Addr
|
|
err error
|
|
)
|
|
|
|
log.G(context.TODO()).Debugf("Request address PoolID:%v %s Serial:%v PrefAddress:%v ", base, addrSet, serial, prefAddress)
|
|
|
|
if prefAddress.IsValid() {
|
|
err = addrSet.Add(prefAddress)
|
|
if err == nil {
|
|
addr = prefAddress
|
|
}
|
|
} else if ipr.IsValid() && ipr != base {
|
|
addr, err = addrSet.AddAnyInRange(ipr, serial)
|
|
} else {
|
|
addr, err = addrSet.AddAny(serial)
|
|
}
|
|
|
|
if err != nil {
|
|
if errors.Is(err, addrset.ErrAllocated) {
|
|
return netip.Addr{}, ipamapi.ErrIPAlreadyAllocated
|
|
}
|
|
if errors.Is(err, addrset.ErrNotAvailable) {
|
|
return netip.Addr{}, ipamapi.ErrNoAvailableIPs
|
|
}
|
|
return netip.Addr{}, err
|
|
}
|
|
return addr, nil
|
|
}
|
|
|
|
// PoolStatus returns the operational status of the specified IPAM pool.
|
|
func (a *Allocator) PoolStatus(poolID string) (network.SubnetStatus, error) {
|
|
k, err := PoolIDFromString(poolID)
|
|
if err != nil {
|
|
return network.SubnetStatus{}, types.InvalidParameterErrorf("invalid pool id: %s", poolID)
|
|
}
|
|
|
|
aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
|
|
if err != nil {
|
|
return network.SubnetStatus{}, err
|
|
}
|
|
|
|
return aSpace.allocationStatus(k.Subnet, k.ChildSubnet)
|
|
}
|
|
|
|
// IsBuiltIn returns true for builtin drivers
|
|
func (a *Allocator) IsBuiltIn() bool {
|
|
return true
|
|
}
|