Fix preferred ip allocation in ipam
- also provided a new utility to compute the host part ip address which is resilient to input passed in different representations. Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
parent
5c926bb344
commit
b2ff78548a
|
@ -21,7 +21,7 @@ const (
|
||||||
minNetSizeV6Eff = 96
|
minNetSizeV6Eff = 96
|
||||||
// The size of the host subnet used internally, it's the most granular sequence addresses
|
// The size of the host subnet used internally, it's the most granular sequence addresses
|
||||||
defaultInternalHostSize = 16
|
defaultInternalHostSize = 16
|
||||||
// datastore keyes for ipam obkects
|
// datastore keyes for ipam objects
|
||||||
dsConfigKey = "ipam-config" // ipam-config/<domain>/<map of subent configs>
|
dsConfigKey = "ipam-config" // ipam-config/<domain>/<map of subent configs>
|
||||||
dsDataKey = "ipam-data" // ipam-data/<domain>/<subnet>/<child-sudbnet>/<bitmask>
|
dsDataKey = "ipam-data" // ipam-data/<domain>/<subnet>/<child-sudbnet>/<bitmask>
|
||||||
)
|
)
|
||||||
|
@ -80,8 +80,7 @@ func NewAllocator(ds datastore.DataStore) (*Allocator, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load address bitmask for configured subnet %s because of %s", v.Subnet.String(), err.Error())
|
return fmt.Errorf("failed to load address bitmask for configured subnet %s because of %s", v.Subnet.String(), err.Error())
|
||||||
}
|
}
|
||||||
a.insertAddressMasks(k, subnetList)
|
return a.insertAddressMasks(k, subnetList)
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
|
@ -417,27 +416,47 @@ func (a *Allocator) request(addrSpace AddressSpace, req *AddressRequest, version
|
||||||
|
|
||||||
// Release allows releasing the address from the specified address space
|
// Release allows releasing the address from the specified address space
|
||||||
func (a *Allocator) Release(addrSpace AddressSpace, address net.IP) {
|
func (a *Allocator) Release(addrSpace AddressSpace, address net.IP) {
|
||||||
|
var (
|
||||||
|
space *bitseq.Handle
|
||||||
|
sub *net.IPNet
|
||||||
|
)
|
||||||
|
|
||||||
if address == nil {
|
if address == nil {
|
||||||
|
log.Debugf("Requested to remove nil address from address space %s", addrSpace)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ver := getAddressVersion(address)
|
ver := getAddressVersion(address)
|
||||||
if ver == v4 {
|
if ver == v4 {
|
||||||
address = address.To4()
|
address = address.To4()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the subnet containing the address
|
||||||
for _, subKey := range a.getSubnetList(addrSpace, ver) {
|
for _, subKey := range a.getSubnetList(addrSpace, ver) {
|
||||||
a.Lock()
|
sub = subKey.canonicalChildSubnet()
|
||||||
space := a.addresses[subKey]
|
|
||||||
a.Unlock()
|
|
||||||
sub := subKey.canonicalChildSubnet()
|
|
||||||
if sub.Contains(address) {
|
if sub.Contains(address) {
|
||||||
// Retrieve correspondent ordinal in the subnet
|
a.Lock()
|
||||||
ordinal := ipToUint32(getHostPortionIP(address, sub))
|
space = a.addresses[subKey]
|
||||||
// Release it
|
a.Unlock()
|
||||||
if err := space.Unset(ordinal); err != nil {
|
break
|
||||||
log.Warnf("Failed to release address %s because of internal error: %s", address.String(), err.Error())
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if space == nil {
|
||||||
|
log.Debugf("Could not find subnet on address space %s containing %s on release", addrSpace, address.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve correspondent ordinal in the subnet
|
||||||
|
hostPart, err := types.GetHostPartIP(address, sub.Mask)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to release address %s on address space %s because of internal error: %v", address.String(), addrSpace, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ordinal := ipToUint32(hostPart)
|
||||||
|
|
||||||
|
// Release it
|
||||||
|
if err := space.Unset(ordinal); err != nil {
|
||||||
|
log.Warnf("Failed to release address %s on address space %s because of internal error: %v", address.String(), addrSpace, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,7 +487,7 @@ func (a *Allocator) reserveAddress(addrSpace AddressSpace, subnet *net.IPNet, pr
|
||||||
bitmask, ok := a.addresses[key]
|
bitmask, ok := a.addresses[key]
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Printf("\nDid not find a bitmask for subnet key: %s", key.String())
|
log.Warnf("Did not find a bitmask for subnet key: %s", key.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
address, err := a.getAddress(key.canonicalChildSubnet(), bitmask, prefAddress, ver)
|
address, err := a.getAddress(key.canonicalChildSubnet(), bitmask, prefAddress, ver)
|
||||||
|
@ -511,7 +530,12 @@ func (a *Allocator) getAddress(subnet *net.IPNet, bitmask *bitseq.Handle, prefAd
|
||||||
if prefAddress == nil {
|
if prefAddress == nil {
|
||||||
ordinal, err = bitmask.SetAny()
|
ordinal, err = bitmask.SetAny()
|
||||||
} else {
|
} else {
|
||||||
err = bitmask.Set(ipToUint32(getHostPortionIP(prefAddress, subnet)))
|
hostPart, e := types.GetHostPartIP(prefAddress, subnet.Mask)
|
||||||
|
if e != nil {
|
||||||
|
return nil, fmt.Errorf("failed to allocate preferred address %s: %v", prefAddress.String(), e)
|
||||||
|
}
|
||||||
|
ordinal = ipToUint32(types.GetMinimalIP(hostPart))
|
||||||
|
err = bitmask.Set(ordinal)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrNoAvailableIPs
|
return nil, ErrNoAvailableIPs
|
||||||
|
@ -549,7 +573,7 @@ func generateAddress(ordinal uint32, network *net.IPNet) net.IP {
|
||||||
var address [16]byte
|
var address [16]byte
|
||||||
|
|
||||||
// Get network portion of IP
|
// Get network portion of IP
|
||||||
if network.IP.To4() != nil {
|
if getAddressVersion(network.IP) == v4 {
|
||||||
copy(address[:], network.IP.To4())
|
copy(address[:], network.IP.To4())
|
||||||
} else {
|
} else {
|
||||||
copy(address[:], network.IP)
|
copy(address[:], network.IP)
|
||||||
|
@ -592,12 +616,3 @@ func ipToUint32(ip []byte) uint32 {
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given an address and subnet, returns the host portion address
|
|
||||||
func getHostPortionIP(address net.IP, subnet *net.IPNet) net.IP {
|
|
||||||
hostPortion := make([]byte, len(address))
|
|
||||||
for i := 0; i < len(subnet.Mask); i++ {
|
|
||||||
hostPortion[i] = address[i] &^ subnet.Mask[i]
|
|
||||||
}
|
|
||||||
return hostPortion
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,15 +3,31 @@ package ipam
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/libnetwork/bitseq"
|
"github.com/docker/libnetwork/bitseq"
|
||||||
|
"github.com/docker/libnetwork/config"
|
||||||
|
"github.com/docker/libnetwork/datastore"
|
||||||
_ "github.com/docker/libnetwork/netutils"
|
_ "github.com/docker/libnetwork/netutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ds datastore.DataStore
|
||||||
|
|
||||||
|
// enable w/ upper case
|
||||||
|
func testMain(m *testing.M) {
|
||||||
|
var err error
|
||||||
|
ds, err = datastore.NewDataStore(&config.DatastoreCfg{Embedded: false, Client: config.DatastoreClientCfg{Provider: "consul", Address: "127.0.0.1:8500"}})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
func getAllocator(t *testing.T, subnet *net.IPNet) *Allocator {
|
func getAllocator(t *testing.T, subnet *net.IPNet) *Allocator {
|
||||||
a, err := NewAllocator(nil)
|
a, err := NewAllocator(ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -273,7 +289,32 @@ func TestGetInternalSubnets(t *testing.T) {
|
||||||
for _, d := range input {
|
for _, d := range input {
|
||||||
assertInternalSubnet(t, d.internalHostSize, d.parentSubnet, d.firstIntSubnet, d.lastIntSubnet)
|
assertInternalSubnet(t, d.internalHostSize, d.parentSubnet, d.firstIntSubnet, d.lastIntSubnet)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSameAddress(t *testing.T) {
|
||||||
|
a, err := NewAllocator(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addSpace := AddressSpace("giallo")
|
||||||
|
_, subnet, _ := net.ParseCIDR("192.168.100.0/24")
|
||||||
|
if err := a.AddSubnet(addSpace, &SubnetInfo{Subnet: subnet}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP("192.168.100.250")
|
||||||
|
req := &AddressRequest{Subnet: *subnet, Address: ip}
|
||||||
|
|
||||||
|
_, err = a.Request(addSpace, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = a.Request(addSpace, req)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAddress(t *testing.T) {
|
func TestGetAddress(t *testing.T) {
|
||||||
|
|
|
@ -197,6 +197,59 @@ func CompareIPNet(a, b *net.IPNet) bool {
|
||||||
return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
|
return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMinimalIP returns the address in its shortest form
|
||||||
|
func GetMinimalIP(ip net.IP) net.IP {
|
||||||
|
if ip != nil && ip.To4() != nil {
|
||||||
|
return ip.To4()
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMinimalIPNet returns a copy of the passed IP Network with congruent ip and mask notation
|
||||||
|
func GetMinimalIPNet(nw *net.IPNet) *net.IPNet {
|
||||||
|
if nw == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(nw.IP) == 16 && nw.IP.To4() != nil {
|
||||||
|
m := nw.Mask
|
||||||
|
if len(m) == 16 {
|
||||||
|
m = m[12:16]
|
||||||
|
}
|
||||||
|
return &net.IPNet{IP: nw.IP.To4(), Mask: m}
|
||||||
|
}
|
||||||
|
return nw
|
||||||
|
}
|
||||||
|
|
||||||
|
var v4inV6MaskPrefix = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||||
|
|
||||||
|
// GetHostPartIP returns the host portion of the ip address identified by the mask.
|
||||||
|
// IP address representation is not modified. If address and mask are not compatible
|
||||||
|
// an error is returned.
|
||||||
|
func GetHostPartIP(ip net.IP, mask net.IPMask) (net.IP, error) {
|
||||||
|
// Find the effective starting of address and mask
|
||||||
|
is := 0
|
||||||
|
ms := 0
|
||||||
|
if len(ip) == net.IPv6len && ip.To4() != nil {
|
||||||
|
is = 12
|
||||||
|
}
|
||||||
|
if len(ip[is:]) == net.IPv4len && len(mask) == net.IPv6len && bytes.Equal(mask[:12], v4inV6MaskPrefix) {
|
||||||
|
ms = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if address and mask are semantically compatible
|
||||||
|
if len(ip[is:]) != len(mask[ms:]) {
|
||||||
|
return nil, fmt.Errorf("cannot compute host portion ip address as ip and mask are not compatible: (%#v, %#v)", ip, mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute host portion
|
||||||
|
out := GetIPCopy(ip)
|
||||||
|
for i := 0; i < len(mask[ms:]); i++ {
|
||||||
|
out[is+i] &= ^mask[ms+i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NEXTHOP indicates a StaticRoute with an IP next hop.
|
// NEXTHOP indicates a StaticRoute with an IP next hop.
|
||||||
NEXTHOP = iota
|
NEXTHOP = iota
|
||||||
|
|
|
@ -2,6 +2,7 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -109,3 +110,61 @@ func TestErrorConstructors(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUtilGetHostPortionIP(t *testing.T) {
|
||||||
|
input := []struct {
|
||||||
|
ip net.IP
|
||||||
|
mask net.IPMask
|
||||||
|
host net.IP
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{ // ip in v4Inv6 representation, mask in v4 representation
|
||||||
|
ip: net.IPv4(172, 28, 30, 1),
|
||||||
|
mask: []byte{0xff, 0xff, 0xff, 0},
|
||||||
|
host: net.IPv4(0, 0, 0, 1),
|
||||||
|
},
|
||||||
|
{ // ip and mask in v4Inv6 representation
|
||||||
|
ip: net.IPv4(172, 28, 30, 2),
|
||||||
|
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
|
||||||
|
host: net.IPv4(0, 0, 0, 2),
|
||||||
|
},
|
||||||
|
{ // ip in v4 representation, mask in v4Inv6 representation
|
||||||
|
ip: net.IPv4(172, 28, 30, 3)[12:],
|
||||||
|
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
|
||||||
|
host: net.IPv4(0, 0, 0, 3)[12:],
|
||||||
|
},
|
||||||
|
{ // ip and mask in v4 representation
|
||||||
|
ip: net.IPv4(172, 28, 30, 4)[12:],
|
||||||
|
mask: []byte{0xff, 0xff, 0xff, 0},
|
||||||
|
host: net.IPv4(0, 0, 0, 4)[12:],
|
||||||
|
},
|
||||||
|
{ // ip and mask as v6
|
||||||
|
ip: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"),
|
||||||
|
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
|
||||||
|
host: net.ParseIP("0::AB:00CD"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range input {
|
||||||
|
h, err := GetHostPartIP(i.ip, i.mask)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !i.host.Equal(h) {
|
||||||
|
t.Fatalf("Failed to return expected host ip. Expected: %s. Got: %s", i.host, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ip as v6 and mask as v4 are not compatible
|
||||||
|
if _, err := GetHostPartIP(net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"), []byte{0xff, 0xff, 0xff, 0}); err == nil {
|
||||||
|
t.Fatalf("Unexpected success")
|
||||||
|
}
|
||||||
|
// ip as v4 and non conventional mask
|
||||||
|
if _, err := GetHostPartIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0}); err == nil {
|
||||||
|
t.Fatalf("Unexpected success")
|
||||||
|
}
|
||||||
|
// ip as v4 and non conventional mask
|
||||||
|
if _, err := GetHostPartIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}); err == nil {
|
||||||
|
t.Fatalf("Unexpected success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue