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:
parent
c75220eeff
commit
7301b98502
3 changed files with 253 additions and 240 deletions
253
libnetwork/ipam/address_space.go
Normal file
253
libnetwork/ipam/address_space.go
Normal 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()
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue