577 строки
13 KiB
Go
577 строки
13 KiB
Go
// Copyright 2017 Microsoft. All rights reserved.
|
|
// MIT License
|
|
|
|
package ipam
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/Azure/azure-container-networking/platform"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
// Default address space IDs.
|
|
LocalDefaultAddressSpaceId = "local"
|
|
GlobalDefaultAddressSpaceId = "global"
|
|
)
|
|
|
|
const (
|
|
// Address space scopes.
|
|
LocalScope = iota
|
|
GlobalScope
|
|
)
|
|
|
|
var (
|
|
// Azure VNET well-known host IDs.
|
|
defaultGatewayHostId = net.ParseIP("::1")
|
|
dnsPrimaryHostId = net.ParseIP("::2")
|
|
dnsSecondaryHostId = net.ParseIP("::3")
|
|
|
|
// Azure DNS host proxy well-known address.
|
|
dnsHostProxyAddress = net.ParseIP("168.63.129.16")
|
|
)
|
|
|
|
// Represents the key to an address pool.
|
|
type addressPoolId struct {
|
|
AsId string
|
|
Subnet string
|
|
ChildSubnet string
|
|
}
|
|
|
|
// Represents a set of non-overlapping address pools.
|
|
type addressSpace struct {
|
|
Id string
|
|
Scope int
|
|
Pools map[string]*addressPool
|
|
epoch int
|
|
}
|
|
|
|
// Represents a subnet and the set of addresses in it.
|
|
type addressPool struct {
|
|
as *addressSpace
|
|
Id string
|
|
IfName string
|
|
Subnet net.IPNet
|
|
Gateway net.IP
|
|
Addresses map[string]*addressRecord
|
|
addrsByID map[string]*addressRecord
|
|
IsIPv6 bool
|
|
Priority int
|
|
RefCount int
|
|
epoch int
|
|
}
|
|
|
|
// AddressPoolInfo contains information about an address pool.
|
|
type AddressPoolInfo struct {
|
|
Subnet net.IPNet
|
|
Gateway net.IP
|
|
DnsServers []net.IP
|
|
UnhealthyAddrs []net.IP
|
|
IsIPv6 bool
|
|
Available int
|
|
Capacity int
|
|
}
|
|
|
|
// Represents an IP address in a pool.
|
|
type addressRecord struct {
|
|
ID string
|
|
Addr net.IP
|
|
InUse bool
|
|
unhealthy bool
|
|
epoch int
|
|
}
|
|
|
|
//
|
|
// AddressPoolId
|
|
//
|
|
|
|
// Creates a new address pool ID object.
|
|
func NewAddressPoolId(asId string, subnet string, childSubnet string) *addressPoolId {
|
|
return &addressPoolId{
|
|
AsId: asId,
|
|
Subnet: subnet,
|
|
ChildSubnet: childSubnet,
|
|
}
|
|
}
|
|
|
|
// Creates a new pool ID from a string representation.
|
|
func NewAddressPoolIdFromString(s string) (*addressPoolId, error) {
|
|
var pid addressPoolId
|
|
|
|
p := strings.Split(s, "|")
|
|
if len(p) > 3 {
|
|
return nil, errInvalidPoolID
|
|
}
|
|
|
|
pid.AsId = p[0]
|
|
if len(p) >= 2 {
|
|
pid.Subnet = p[1]
|
|
}
|
|
if len(p) == 3 {
|
|
pid.ChildSubnet = p[2]
|
|
}
|
|
|
|
return &pid, nil
|
|
}
|
|
|
|
// Returns the string representation of a pool ID.
|
|
func (pid *addressPoolId) String() string {
|
|
s := fmt.Sprintf("%s|%s", pid.AsId, pid.Subnet)
|
|
if pid.ChildSubnet != "" {
|
|
s = fmt.Sprintf("%s|%s", s, pid.ChildSubnet)
|
|
}
|
|
return s
|
|
}
|
|
|
|
//
|
|
// AddressSpace
|
|
//
|
|
|
|
// Creates a new addressSpace object.
|
|
func (am *addressManager) newAddressSpace(id string, scope int) (*addressSpace, error) {
|
|
if scope != LocalScope && scope != GlobalScope {
|
|
return nil, errInvalidScope
|
|
}
|
|
|
|
return &addressSpace{
|
|
Id: id,
|
|
Scope: scope,
|
|
Pools: make(map[string]*addressPool),
|
|
}, nil
|
|
}
|
|
|
|
// Returns the address space with the given ID.
|
|
func (am *addressManager) getAddressSpace(id string) (*addressSpace, error) {
|
|
as := am.AddrSpaces[id]
|
|
if as == nil {
|
|
return nil, errInvalidAddressSpace
|
|
}
|
|
|
|
return as, nil
|
|
}
|
|
|
|
// Sets a new or updates an existing address space.
|
|
func (am *addressManager) setAddressSpace(as *addressSpace) error {
|
|
as1, ok := am.AddrSpaces[as.Id]
|
|
if !ok {
|
|
am.AddrSpaces[as.Id] = as
|
|
} else {
|
|
// merges the address set refreshed from the source
|
|
// and the ones we have currently in this address space
|
|
logger.Info("merging address space")
|
|
as1.merge(as)
|
|
}
|
|
|
|
// Notify NetPlugin of external interfaces.
|
|
if am.netApi != nil {
|
|
for _, ap := range as.Pools {
|
|
am.netApi.AddExternalInterface(ap.IfName, ap.Subnet.String(), "") //nolint
|
|
}
|
|
}
|
|
|
|
am.save()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Merges a new address space to an existing one.
|
|
func (as *addressSpace) merge(newas *addressSpace) {
|
|
// The new epoch after the merge.
|
|
// epoch is essentially the count of invocations
|
|
// used to ensure if certain addresses refreshed from the source
|
|
// are still relevant
|
|
as.epoch++
|
|
|
|
// Add new pools and addresses.
|
|
for pk, pv := range newas.Pools {
|
|
ap := as.Pools[pk]
|
|
|
|
if ap == nil {
|
|
// This is a new address pool.
|
|
// Merge it to the existing address space.
|
|
as.Pools[pk] = pv
|
|
pv.as = as
|
|
pv.epoch = as.epoch
|
|
} else {
|
|
// This pool already exists.
|
|
// Compare address records one by one.
|
|
for ak, av := range pv.Addresses {
|
|
ar := ap.Addresses[ak]
|
|
|
|
if ar == nil {
|
|
// This is a new address record.
|
|
// Merge it to the existing address pool.
|
|
ap.Addresses[ak] = av
|
|
av.epoch = as.epoch
|
|
} else {
|
|
// This address record already exists.
|
|
ar.epoch = as.epoch
|
|
ar.unhealthy = false
|
|
}
|
|
|
|
delete(pv.Addresses, ak)
|
|
}
|
|
|
|
pv.as = nil
|
|
}
|
|
|
|
delete(newas.Pools, pk)
|
|
}
|
|
|
|
// Cleanup stale pools and addresses from the old epoch.
|
|
// Those currently in use will be deleted after they are released.
|
|
for pk, pv := range as.Pools {
|
|
if pv.epoch < as.epoch {
|
|
// This pool may have stale addresses.
|
|
for ak, av := range pv.Addresses {
|
|
if av.epoch == as.epoch {
|
|
// Pool has at least one valid or in-use address.
|
|
pv.epoch = as.epoch
|
|
} else if av.InUse {
|
|
// Address is no longer valid, but still in use.
|
|
pv.epoch = as.epoch
|
|
av.unhealthy = true
|
|
} else {
|
|
// This address is no longer available.
|
|
delete(pv.Addresses, ak)
|
|
}
|
|
}
|
|
|
|
// Delete the pool if it has no addresses left.
|
|
if pv.epoch < as.epoch && !pv.isInUse() {
|
|
pv.as = nil
|
|
delete(as.Pools, pk)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Creates a new addressPool object.
|
|
func (as *addressSpace) newAddressPool(ifName string, priority int, subnet *net.IPNet) (*addressPool, error) {
|
|
id := subnet.String()
|
|
|
|
pool, ok := as.Pools[id]
|
|
if ok {
|
|
return pool, errAddressPoolExists
|
|
}
|
|
|
|
v6 := (subnet.IP.To4() == nil)
|
|
|
|
pool = &addressPool{
|
|
as: as,
|
|
Id: id,
|
|
IfName: ifName,
|
|
Subnet: *subnet,
|
|
Gateway: platform.GenerateAddress(subnet, defaultGatewayHostId),
|
|
Addresses: make(map[string]*addressRecord),
|
|
addrsByID: make(map[string]*addressRecord),
|
|
IsIPv6: v6,
|
|
Priority: priority,
|
|
epoch: as.epoch,
|
|
}
|
|
|
|
as.Pools[id] = pool
|
|
|
|
return pool, nil
|
|
}
|
|
|
|
// Returns the address pool with the given pool ID.
|
|
func (as *addressSpace) getAddressPool(poolId string) (*addressPool, error) {
|
|
ap := as.Pools[poolId]
|
|
if ap == nil {
|
|
return nil, fmt.Errorf("Pool id %v not found :%w", poolId, errInvalidPoolID)
|
|
}
|
|
|
|
return ap, nil
|
|
}
|
|
|
|
// Requests a new address pool from the address space.
|
|
func (as *addressSpace) requestPool(poolId string, subPoolId string, options map[string]string, v6 bool) (*addressPool, error) {
|
|
var ap *addressPool
|
|
var err error
|
|
|
|
logger.Info("Requesting pool with", zap.String("poolId", poolId), zap.Any("options", options), zap.Any("v6", v6))
|
|
|
|
if poolId != "" {
|
|
// Return the specific address pool requested.
|
|
// Note sharing of pools is allowed when specifically requested.
|
|
ap = as.Pools[poolId]
|
|
if ap == nil {
|
|
err = errAddressPoolNotFound
|
|
}
|
|
} else {
|
|
// Return any available address pool.
|
|
ifName := options[OptInterfaceName]
|
|
|
|
for _, pool := range as.Pools {
|
|
logger.Info("Checking pool", zap.String("id", pool.Id))
|
|
|
|
// Skip if pool is already in use.
|
|
if pool.isInUse() {
|
|
logger.Info("Pool is in use", zap.String("id", pool.Id))
|
|
|
|
// in case the pool is actually not in use,
|
|
// attempt to release it
|
|
as.releasePool(pool.Id)
|
|
if pool.isInUse() {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Pick a pool from the same address family.
|
|
if pool.IsIPv6 != v6 {
|
|
logger.Info("Pool is of a different address family")
|
|
continue
|
|
}
|
|
|
|
// Skip if pool is not on the requested interface.
|
|
if ifName != "" && ifName != pool.IfName {
|
|
logger.Info("Pool is not on the requested interface")
|
|
continue
|
|
}
|
|
|
|
logger.Info("Pool matches requirements", zap.String("id", pool.Id))
|
|
|
|
if ap == nil {
|
|
ap = pool
|
|
continue
|
|
}
|
|
|
|
// Prefer the pool with the highest priority.
|
|
if pool.Priority > ap.Priority {
|
|
logger.Info("Pool is preferred because of priority")
|
|
ap = pool
|
|
}
|
|
|
|
// Prefer the pool with the highest number of addresses.
|
|
if len(pool.Addresses) > len(ap.Addresses) {
|
|
logger.Info("Pool is preferred because of capacity")
|
|
ap = pool
|
|
}
|
|
}
|
|
|
|
if ap == nil {
|
|
err = ErrNoAvailableAddressPools
|
|
}
|
|
}
|
|
|
|
if ap != nil && ap.RefCount == 0 {
|
|
ap.RefCount = 1
|
|
}
|
|
|
|
logger.Info("Pool request completed with pool", zap.Any("ap", ap), zap.Error(err))
|
|
|
|
return ap, err
|
|
}
|
|
|
|
// Releases a previously requested address pool back to its address space.
|
|
func (as *addressSpace) releasePool(poolId string) error {
|
|
var addressesInUse bool
|
|
|
|
ap, ok := as.Pools[poolId]
|
|
if !ok {
|
|
return errAddressPoolNotFound
|
|
}
|
|
|
|
if addressesInUse = ap.IsAnyRecordInUse(); addressesInUse {
|
|
logger.Info("Skip releasing pool with due to address being in use", zap.String("poolId", poolId))
|
|
} else {
|
|
logger.Info("Releasing pool as there are no allocations", zap.String("poolId", poolId))
|
|
ap.RefCount = 0
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddressPool
|
|
//
|
|
// Returns address pool information.
|
|
func (ap *addressPool) getInfo() *AddressPoolInfo {
|
|
var available int
|
|
var unhealthyAddrs []net.IP
|
|
|
|
for _, ar := range ap.Addresses {
|
|
if !ar.InUse {
|
|
available++
|
|
}
|
|
if ar.unhealthy {
|
|
unhealthyAddrs = append(unhealthyAddrs, ar.Addr)
|
|
}
|
|
}
|
|
|
|
info := &AddressPoolInfo{
|
|
Subnet: ap.Subnet,
|
|
Gateway: ap.Gateway,
|
|
DnsServers: []net.IP{dnsHostProxyAddress},
|
|
UnhealthyAddrs: unhealthyAddrs,
|
|
IsIPv6: ap.IsIPv6,
|
|
Available: available,
|
|
Capacity: len(ap.Addresses),
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// Returns if an address pool is currently in use.
|
|
func (ap *addressPool) isInUse() bool {
|
|
return ap.RefCount > 0
|
|
}
|
|
|
|
// Returns if any address in the pool is currently in use.
|
|
func (ap *addressPool) IsAnyRecordInUse() bool {
|
|
for _, address := range ap.Addresses {
|
|
if address.InUse || address.ID != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Creates a new addressRecord object.
|
|
func (ap *addressPool) newAddressRecord(addr *net.IP) (*addressRecord, error) {
|
|
id := addr.String()
|
|
|
|
if !ap.Subnet.Contains(*addr) {
|
|
return nil, errInvalidAddress
|
|
}
|
|
|
|
ar, ok := ap.Addresses[id]
|
|
if ok {
|
|
return ar, errAddressExists
|
|
}
|
|
|
|
ar = &addressRecord{
|
|
Addr: *addr,
|
|
epoch: ap.epoch,
|
|
}
|
|
|
|
ap.Addresses[id] = ar
|
|
|
|
return ar, nil
|
|
}
|
|
|
|
// Requests a new address from the address pool.
|
|
func (ap *addressPool) requestAddress(address string, options map[string]string) (string, error) {
|
|
var ar *addressRecord
|
|
var addr *net.IPNet
|
|
id := options[OptAddressID]
|
|
|
|
logger.Info("Requesting address with", zap.String("address", address), zap.Any("options", options))
|
|
|
|
if address != "" {
|
|
// Return the specific address requested.
|
|
ar = ap.Addresses[address]
|
|
if ar == nil {
|
|
logger.Error("Address request failed with", zap.Error(errAddressNotFound))
|
|
return "", errAddressNotFound
|
|
}
|
|
if ar.InUse {
|
|
// Return the same address if IDs match.
|
|
if id == "" || id != ar.ID {
|
|
logger.Error("Address request failed with", zap.Error(errAddressInUse))
|
|
return "", errAddressInUse
|
|
}
|
|
}
|
|
} else if options[OptAddressType] == OptAddressTypeGateway {
|
|
// Return the pre-assigned gateway address.
|
|
ar = &addressRecord{
|
|
Addr: ap.Gateway,
|
|
}
|
|
id = ""
|
|
} else if id != "" {
|
|
// Return the address with the matching identifier.
|
|
ar = ap.addrsByID[id]
|
|
}
|
|
|
|
// If no address was found, return any available address.
|
|
if ar == nil {
|
|
for _, ar = range ap.Addresses {
|
|
if !ar.InUse && ar.ID == "" {
|
|
break
|
|
}
|
|
ar = nil
|
|
}
|
|
|
|
if ar == nil {
|
|
logger.Error("Address request failed with", zap.Error(errNoAvailableAddresses))
|
|
return "", errNoAvailableAddresses
|
|
}
|
|
}
|
|
|
|
if id != "" {
|
|
ap.addrsByID[id] = ar
|
|
ar.ID = id
|
|
}
|
|
|
|
ar.InUse = true
|
|
|
|
// Return address in CIDR notation.
|
|
addr = &net.IPNet{
|
|
IP: ar.Addr,
|
|
Mask: ap.Subnet.Mask,
|
|
}
|
|
|
|
logger.Info("Address request completed with", zap.Any("address", addr))
|
|
|
|
return addr.String(), nil
|
|
}
|
|
|
|
// Releases a previously requested address back to its address pool.
|
|
func (ap *addressPool) releaseAddress(address string, options map[string]string) error {
|
|
var ar *addressRecord
|
|
var id string
|
|
var err error
|
|
|
|
logger.Info("Releasing address with", zap.String("address", address), zap.Any("options", options))
|
|
defer func() { logger.Error("Address release completed with", zap.String("address", address), zap.Error(err)) }()
|
|
|
|
if options != nil {
|
|
id = options[OptAddressID]
|
|
}
|
|
|
|
if address != "" {
|
|
// Release the specific address.
|
|
ar = ap.Addresses[address]
|
|
|
|
// Release the pre-assigned gateway address.
|
|
if ar == nil && address == ap.Gateway.String() {
|
|
return nil
|
|
}
|
|
} else if id != "" {
|
|
// Release the address with the matching ID.
|
|
ar = ap.addrsByID[id]
|
|
if ar != nil {
|
|
address = ar.Addr.String()
|
|
}
|
|
}
|
|
|
|
// Fail if an address record with a matching ID is not found.
|
|
if ar == nil || (id != "" && id != ar.ID) {
|
|
logger.Info("Address not found. Not Returning error")
|
|
return nil
|
|
}
|
|
|
|
if !ar.InUse {
|
|
logger.Info("Address not in use. Not Returning error")
|
|
return nil
|
|
}
|
|
|
|
ar.InUse = false
|
|
|
|
if id != "" && ar.ID == id {
|
|
delete(ap.addrsByID, ar.ID)
|
|
ar.ID = ""
|
|
}
|
|
|
|
// Delete address record if it is no longer available.
|
|
if ar.epoch < ap.as.epoch {
|
|
logger.Info("Deleting Address record from address pool as metadata doesn't have this address")
|
|
delete(ap.Addresses, address)
|
|
}
|
|
|
|
return nil
|
|
}
|