ipam/host-local: support multiple IP ranges
This change allows the host-local allocator to allocate multiple IPs. This is intended to enable dual-stack, but is not limited to only two subnets or separate address families.
This commit is contained in:
159
plugins/ipam/host-local/backend/allocator/range.go
Normal file
159
plugins/ipam/host-local/backend/allocator/range.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package allocator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
)
|
||||
|
||||
// Canonicalize takes a given range and ensures that all information is consistent,
|
||||
// filling out Start, End, and Gateway with sane values if missing
|
||||
func (r *Range) Canonicalize() error {
|
||||
if err := canonicalizeIP(&r.Subnet.IP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Can't create an allocator for a network with no addresses, eg
|
||||
// a /32 or /31
|
||||
ones, masklen := r.Subnet.Mask.Size()
|
||||
if ones > masklen-2 {
|
||||
return fmt.Errorf("Network %s too small to allocate from", (*net.IPNet)(&r.Subnet).String())
|
||||
}
|
||||
|
||||
if len(r.Subnet.IP) != len(r.Subnet.Mask) {
|
||||
return fmt.Errorf("IPNet IP and Mask version mismatch")
|
||||
}
|
||||
|
||||
// If the gateway is nil, claim .1
|
||||
if r.Gateway == nil {
|
||||
r.Gateway = ip.NextIP(r.Subnet.IP)
|
||||
} else {
|
||||
if err := canonicalizeIP(&r.Gateway); err != nil {
|
||||
return err
|
||||
}
|
||||
subnet := (net.IPNet)(r.Subnet)
|
||||
if !subnet.Contains(r.Gateway) {
|
||||
return fmt.Errorf("gateway %s not in network %s", r.Gateway.String(), subnet.String())
|
||||
}
|
||||
}
|
||||
|
||||
// RangeStart: If specified, make sure it's sane (inside the subnet),
|
||||
// otherwise use the first free IP (i.e. .1) - this will conflict with the
|
||||
// gateway but we skip it in the iterator
|
||||
if r.RangeStart != nil {
|
||||
if err := canonicalizeIP(&r.RangeStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.IPInRange(r.RangeStart); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
r.RangeStart = ip.NextIP(r.Subnet.IP)
|
||||
}
|
||||
|
||||
// RangeEnd: If specified, verify sanity. Otherwise, add a sensible default
|
||||
// (e.g. for a /24: .254 if IPv4, ::255 if IPv6)
|
||||
if r.RangeEnd != nil {
|
||||
if err := canonicalizeIP(&r.RangeEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.IPInRange(r.RangeEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
r.RangeEnd = lastIP(r.Subnet)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValidIP checks if a given ip is a valid, allocatable address in a given Range
|
||||
func (r *Range) IPInRange(addr net.IP) error {
|
||||
if err := canonicalizeIP(&addr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subnet := (net.IPNet)(r.Subnet)
|
||||
|
||||
if len(addr) != len(r.Subnet.IP) {
|
||||
return fmt.Errorf("IP %s is not the same protocol as subnet %s",
|
||||
addr, subnet.String())
|
||||
}
|
||||
|
||||
if !subnet.Contains(addr) {
|
||||
return fmt.Errorf("%s not in network %s", addr, subnet.String())
|
||||
}
|
||||
|
||||
// We ignore nils here so we can use this function as we initialize the range.
|
||||
if r.RangeStart != nil {
|
||||
if ip.Cmp(addr, r.RangeStart) < 0 {
|
||||
return fmt.Errorf("%s is in network %s but before start %s",
|
||||
addr, (*net.IPNet)(&r.Subnet).String(), r.RangeStart)
|
||||
}
|
||||
}
|
||||
|
||||
if r.RangeEnd != nil {
|
||||
if ip.Cmp(addr, r.RangeEnd) > 0 {
|
||||
return fmt.Errorf("%s is in network %s but after end %s",
|
||||
addr, (*net.IPNet)(&r.Subnet).String(), r.RangeEnd)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Overlaps returns true if there is any overlap between ranges
|
||||
func (r *Range) Overlaps(r1 *Range) bool {
|
||||
// different familes
|
||||
if len(r.RangeStart) != len(r1.RangeStart) {
|
||||
return false
|
||||
}
|
||||
|
||||
return r.IPInRange(r1.RangeStart) == nil ||
|
||||
r.IPInRange(r1.RangeEnd) == nil ||
|
||||
r1.IPInRange(r.RangeStart) == nil ||
|
||||
r1.IPInRange(r.RangeEnd) == nil
|
||||
}
|
||||
|
||||
// canonicalizeIP makes sure a provided ip is in standard form
|
||||
func canonicalizeIP(ip *net.IP) error {
|
||||
if ip.To4() != nil {
|
||||
*ip = ip.To4()
|
||||
return nil
|
||||
} else if ip.To16() != nil {
|
||||
*ip = ip.To16()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("IP %s not v4 nor v6", *ip)
|
||||
}
|
||||
|
||||
// Determine the last IP of a subnet, excluding the broadcast if IPv4
|
||||
func lastIP(subnet types.IPNet) net.IP {
|
||||
var end net.IP
|
||||
for i := 0; i < len(subnet.IP); i++ {
|
||||
end = append(end, subnet.IP[i]|^subnet.Mask[i])
|
||||
}
|
||||
if subnet.IP.To4() != nil {
|
||||
end[3]--
|
||||
}
|
||||
|
||||
return end
|
||||
}
|
Reference in New Issue
Block a user