libnet/ipam: put addrSpace into a separate file

`addrSpace` methods are currently scattered in two different files.
As upcoming work will rewrite some of these methods, better put them
into a separate file.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
Albin Kerouanton 2024-04-14 12:09:21 +02:00
parent c75220eeff
commit 7301b98502
3 changed files with 253 additions and 240 deletions

View file

@ -0,0 +1,253 @@
package ipam
import (
"context"
"fmt"
"net"
"net/netip"
"strings"
"sync"
"github.com/containerd/log"
"github.com/docker/docker/libnetwork/internal/netiputil"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/types"
)
// 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[netip.Prefix]*PoolData
// Predefined pool for the address space
predefined []netip.Prefix
predefinedStartIndex int
mu sync.Mutex
}
func newAddrSpace(predefined []*net.IPNet) (*addrSpace, error) {
pdf := make([]netip.Prefix, len(predefined))
for i, n := range predefined {
var ok bool
pdf[i], ok = netiputil.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
}
// allocateSubnet adds the subnet k to the address space.
func (aSpace *addrSpace) allocateSubnet(nw, sub netip.Prefix) error {
aSpace.mu.Lock()
defer aSpace.mu.Unlock()
// Check if already allocated
if pool, ok := aSpace.subnets[nw]; ok {
var childExists bool
if sub != (netip.Prefix{}) {
_, childExists = pool.children[sub]
}
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 ipamapi.ErrPoolOverlap
}
}
return aSpace.allocateSubnetL(nw, sub)
}
func (aSpace *addrSpace) allocateSubnetL(nw, sub netip.Prefix) error {
// If master pool, check for overlap
if sub == (netip.Prefix{}) {
if aSpace.overlaps(nw) {
return ipamapi.ErrPoolOverlap
}
// This is a new master pool, add it along with corresponding bitmask
aSpace.subnets[nw] = newPoolData(nw)
return nil
}
// This is a new non-master pool (subPool)
if nw.Addr().BitLen() != sub.Addr().BitLen() {
return fmt.Errorf("pool and subpool are of incompatible address families")
}
// Look for parent pool
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[nw] = pp
}
pp.children[sub] = struct{}{}
return nil
}
// overlaps reports whether nw contains any IP addresses in common with any of
// the existing subnets in this address space.
func (aSpace *addrSpace) overlaps(nw netip.Prefix) bool {
for pool := range aSpace.subnets {
if pool.Overlaps(nw) {
return true
}
}
return false
}
// 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.mu.Lock()
defer aSpace.mu.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.overlaps(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)
}
func (aSpace *addrSpace) releaseSubnet(nw, sub netip.Prefix) error {
aSpace.mu.Lock()
defer aSpace.mu.Unlock()
p, ok := aSpace.subnets[nw]
if !ok {
return ipamapi.ErrBadPool
}
if sub != (netip.Prefix{}) {
if _, ok := p.children[sub]; !ok {
return ipamapi.ErrBadPool
}
delete(p.children, sub)
} else {
p.autoRelease = true
}
if len(p.children) == 0 && p.autoRelease {
delete(aSpace.subnets, nw)
}
return nil
}
func (aSpace *addrSpace) requestAddress(nw, sub netip.Prefix, prefAddress netip.Addr, opts map[string]string) (netip.Addr, error) {
aSpace.mu.Lock()
defer aSpace.mu.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
}
func (aSpace *addrSpace) releaseAddress(nw, sub netip.Prefix, address netip.Addr) error {
aSpace.mu.Lock()
defer aSpace.mu.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.InvalidParameterErrorf("invalid address")
}
if !nw.Contains(address) {
return ipamapi.ErrIPOutOfRange
}
defer log.G(context.TODO()).Debugf("Released address Address:%v Sequence:%s", address, p.addrs)
return p.addrs.Unset(netiputil.HostID(address, uint(nw.Bits())))
}
func (aSpace *addrSpace) DumpDatabase() string {
aSpace.mu.Lock()
defer aSpace.mu.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()
}

View file

@ -43,21 +43,6 @@ func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) {
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 = netiputil.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
@ -178,60 +163,6 @@ func newPoolData(pool netip.Prefix) *PoolData {
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.mu.Lock()
defer aSpace.mu.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.overlaps(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) {
log.G(context.TODO()).Debugf("RequestAddress(%s, %v, %v)", poolID, prefAddress, opts)
@ -262,36 +193,6 @@ func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[s
}, nil, nil
}
func (aSpace *addrSpace) requestAddress(nw, sub netip.Prefix, prefAddress netip.Addr, opts map[string]string) (netip.Addr, error) {
aSpace.mu.Lock()
defer aSpace.mu.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 {
log.G(context.TODO()).Debugf("ReleaseAddress(%s, %v)", poolID, address)
@ -313,33 +214,6 @@ func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
return aSpace.releaseAddress(k.Subnet, k.ChildSubnet, addr.Unmap())
}
func (aSpace *addrSpace) releaseAddress(nw, sub netip.Prefix, address netip.Addr) error {
aSpace.mu.Lock()
defer aSpace.mu.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.InvalidParameterErrorf("invalid address")
}
if !nw.Contains(address) {
return ipamapi.ErrIPOutOfRange
}
defer log.G(context.TODO()).Debugf("Released address Address:%v Sequence:%s", address, p.addrs)
return p.addrs.Unset(netiputil.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
@ -389,21 +263,6 @@ func (a *Allocator) DumpDatabase() string {
return b.String()
}
func (aSpace *addrSpace) DumpDatabase() string {
aSpace.mu.Lock()
defer aSpace.mu.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

View file

@ -4,10 +4,8 @@ import (
"fmt"
"net/netip"
"strings"
"sync"
"github.com/docker/docker/libnetwork/bitmap"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/types"
)
@ -31,18 +29,6 @@ type SubnetKey struct {
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[netip.Prefix]*PoolData
// Predefined pool for the address space
predefined []netip.Prefix
predefinedStartIndex int
mu sync.Mutex
}
// PoolIDFromString creates a new PoolID and populates the SubnetKey object
// reading it from the given string.
func PoolIDFromString(str string) (pID PoolID, err error) {
@ -82,88 +68,3 @@ func (s *PoolID) String() string {
func (p *PoolData) String() string {
return fmt.Sprintf("PoolData[Children: %d]", len(p.children))
}
// allocateSubnet adds the subnet k to the address space.
func (aSpace *addrSpace) allocateSubnet(nw, sub netip.Prefix) error {
aSpace.mu.Lock()
defer aSpace.mu.Unlock()
// Check if already allocated
if pool, ok := aSpace.subnets[nw]; ok {
var childExists bool
if sub != (netip.Prefix{}) {
_, childExists = pool.children[sub]
}
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 ipamapi.ErrPoolOverlap
}
}
return aSpace.allocateSubnetL(nw, sub)
}
func (aSpace *addrSpace) allocateSubnetL(nw, sub netip.Prefix) error {
// If master pool, check for overlap
if sub == (netip.Prefix{}) {
if aSpace.overlaps(nw) {
return ipamapi.ErrPoolOverlap
}
// This is a new master pool, add it along with corresponding bitmask
aSpace.subnets[nw] = newPoolData(nw)
return nil
}
// This is a new non-master pool (subPool)
if nw.Addr().BitLen() != sub.Addr().BitLen() {
return fmt.Errorf("pool and subpool are of incompatible address families")
}
// Look for parent pool
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[nw] = pp
}
pp.children[sub] = struct{}{}
return nil
}
func (aSpace *addrSpace) releaseSubnet(nw, sub netip.Prefix) error {
aSpace.mu.Lock()
defer aSpace.mu.Unlock()
p, ok := aSpace.subnets[nw]
if !ok {
return ipamapi.ErrBadPool
}
if sub != (netip.Prefix{}) {
if _, ok := p.children[sub]; !ok {
return ipamapi.ErrBadPool
}
delete(p.children, sub)
} else {
p.autoRelease = true
}
if len(p.children) == 0 && p.autoRelease {
delete(aSpace.subnets, nw)
}
return nil
}
// overlaps reports whether nw contains any IP addresses in common with any of
// the existing subnets in this address space.
func (aSpace *addrSpace) overlaps(nw netip.Prefix) bool {
for pool := range aSpace.subnets {
if pool.Overlaps(nw) {
return true
}
}
return false
}