mirror of https://github.com/k3s-io/k3s
239 lines
6.8 KiB
Go
239 lines
6.8 KiB
Go
/*
|
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
|
|
|
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 ipallocator
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
"k8s.io/kubernetes/pkg/registry/service/allocator"
|
|
)
|
|
|
|
// Interface manages the allocation of IP addresses out of a range. Interface
|
|
// should be threadsafe.
|
|
type Interface interface {
|
|
Allocate(net.IP) error
|
|
AllocateNext() (net.IP, error)
|
|
Release(net.IP) error
|
|
}
|
|
|
|
var (
|
|
ErrFull = errors.New("range is full")
|
|
ErrNotInRange = errors.New("provided IP is not in the valid range")
|
|
ErrAllocated = errors.New("provided IP is already allocated")
|
|
ErrMismatchedNetwork = errors.New("the provided network does not match the current range")
|
|
)
|
|
|
|
// Range is a contiguous block of IPs that can be allocated atomically.
|
|
//
|
|
// The internal structure of the range is:
|
|
//
|
|
// For CIDR 10.0.0.0/24
|
|
// 254 addresses usable out of 256 total (minus base and broadcast IPs)
|
|
// The number of usable addresses is r.max
|
|
//
|
|
// CIDR base IP CIDR broadcast IP
|
|
// 10.0.0.0 10.0.0.255
|
|
// | |
|
|
// 0 1 2 3 4 5 ... ... 253 254 255
|
|
// | |
|
|
// r.base r.base + r.max
|
|
// | |
|
|
// offset #0 of r.allocated last offset of r.allocated
|
|
type Range struct {
|
|
net *net.IPNet
|
|
// base is a cached version of the start IP in the CIDR range as a *big.Int
|
|
base *big.Int
|
|
// max is the maximum size of the usable addresses in the range
|
|
max int
|
|
|
|
alloc allocator.Interface
|
|
}
|
|
|
|
// NewAllocatorCIDRRange creates a Range over a net.IPNet, calling allocatorFactory to construct the backing store.
|
|
func NewAllocatorCIDRRange(cidr *net.IPNet, allocatorFactory allocator.AllocatorFactory) *Range {
|
|
max := RangeSize(cidr)
|
|
base := bigForIP(cidr.IP)
|
|
rangeSpec := cidr.String()
|
|
|
|
r := Range{
|
|
net: cidr,
|
|
base: base.Add(base, big.NewInt(1)), // don't use the network base
|
|
max: maximum(0, int(max-2)), // don't use the network broadcast,
|
|
}
|
|
r.alloc = allocatorFactory(r.max, rangeSpec)
|
|
return &r
|
|
}
|
|
|
|
// Helper that wraps NewAllocatorCIDRRange, for creating a range backed by an in-memory store.
|
|
func NewCIDRRange(cidr *net.IPNet) *Range {
|
|
return NewAllocatorCIDRRange(cidr, func(max int, rangeSpec string) allocator.Interface {
|
|
return allocator.NewAllocationMap(max, rangeSpec)
|
|
})
|
|
}
|
|
|
|
func maximum(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Free returns the count of IP addresses left in the range.
|
|
func (r *Range) Free() int {
|
|
return r.alloc.Free()
|
|
}
|
|
|
|
// Allocate attempts to reserve the provided IP. ErrNotInRange or
|
|
// ErrAllocated will be returned if the IP is not valid for this range
|
|
// or has already been reserved. ErrFull will be returned if there
|
|
// are no addresses left.
|
|
func (r *Range) Allocate(ip net.IP) error {
|
|
ok, offset := r.contains(ip)
|
|
if !ok {
|
|
return ErrNotInRange
|
|
}
|
|
|
|
allocated, err := r.alloc.Allocate(offset)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !allocated {
|
|
return ErrAllocated
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AllocateNext reserves one of the IPs from the pool. ErrFull may
|
|
// be returned if there are no addresses left.
|
|
func (r *Range) AllocateNext() (net.IP, error) {
|
|
offset, ok, err := r.alloc.AllocateNext()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
return nil, ErrFull
|
|
}
|
|
return addIPOffset(r.base, offset), nil
|
|
}
|
|
|
|
// Release releases the IP back to the pool. Releasing an
|
|
// unallocated IP or an IP out of the range is a no-op and
|
|
// returns no error.
|
|
func (r *Range) Release(ip net.IP) error {
|
|
ok, offset := r.contains(ip)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return r.alloc.Release(offset)
|
|
}
|
|
|
|
// Has returns true if the provided IP is already allocated and a call
|
|
// to Allocate(ip) would fail with ErrAllocated.
|
|
func (r *Range) Has(ip net.IP) bool {
|
|
ok, offset := r.contains(ip)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
return r.alloc.Has(offset)
|
|
}
|
|
|
|
// Snapshot saves the current state of the pool.
|
|
func (r *Range) Snapshot(dst *api.RangeAllocation) error {
|
|
snapshottable, ok := r.alloc.(allocator.Snapshottable)
|
|
if !ok {
|
|
return fmt.Errorf("not a snapshottable allocator")
|
|
}
|
|
rangeString, data := snapshottable.Snapshot()
|
|
dst.Range = rangeString
|
|
dst.Data = data
|
|
return nil
|
|
}
|
|
|
|
// Restore restores the pool to the previously captured state. ErrMismatchedNetwork
|
|
// is returned if the provided IPNet range doesn't exactly match the previous range.
|
|
func (r *Range) Restore(net *net.IPNet, data []byte) error {
|
|
if !net.IP.Equal(r.net.IP) || net.Mask.String() != r.net.Mask.String() {
|
|
return ErrMismatchedNetwork
|
|
}
|
|
snapshottable, ok := r.alloc.(allocator.Snapshottable)
|
|
if !ok {
|
|
return fmt.Errorf("not a snapshottable allocator")
|
|
}
|
|
snapshottable.Restore(net.String(), data)
|
|
return nil
|
|
}
|
|
|
|
// contains returns true and the offset if the ip is in the range, and false
|
|
// and nil otherwise. The first and last addresses of the CIDR are omitted.
|
|
func (r *Range) contains(ip net.IP) (bool, int) {
|
|
if !r.net.Contains(ip) {
|
|
return false, 0
|
|
}
|
|
|
|
offset := calculateIPOffset(r.base, ip)
|
|
if offset < 0 || offset >= r.max {
|
|
return false, 0
|
|
}
|
|
return true, offset
|
|
}
|
|
|
|
// bigForIP creates a big.Int based on the provided net.IP
|
|
func bigForIP(ip net.IP) *big.Int {
|
|
b := ip.To4()
|
|
if b == nil {
|
|
b = ip.To16()
|
|
}
|
|
return big.NewInt(0).SetBytes(b)
|
|
}
|
|
|
|
// addIPOffset adds the provided integer offset to a base big.Int representing a
|
|
// net.IP
|
|
func addIPOffset(base *big.Int, offset int) net.IP {
|
|
return net.IP(big.NewInt(0).Add(base, big.NewInt(int64(offset))).Bytes())
|
|
}
|
|
|
|
// calculateIPOffset calculates the integer offset of ip from base such that
|
|
// base + offset = ip. It requires ip >= base.
|
|
func calculateIPOffset(base *big.Int, ip net.IP) int {
|
|
return int(big.NewInt(0).Sub(bigForIP(ip), base).Int64())
|
|
}
|
|
|
|
// RangeSize returns the size of a range in valid addresses.
|
|
func RangeSize(subnet *net.IPNet) int64 {
|
|
ones, bits := subnet.Mask.Size()
|
|
if (bits - ones) >= 31 {
|
|
panic("masks greater than 31 bits are not supported")
|
|
}
|
|
max := int64(1) << uint(bits-ones)
|
|
return max
|
|
}
|
|
|
|
// GetIndexedIP returns a net.IP that is subnet.IP + index in the contiguous IP space.
|
|
func GetIndexedIP(subnet *net.IPNet, index int) (net.IP, error) {
|
|
ip := addIPOffset(bigForIP(subnet.IP), index)
|
|
if !subnet.Contains(ip) {
|
|
return nil, fmt.Errorf("can't generate IP with index %d from subnet. subnet too small. subnet: %q", index, subnet)
|
|
}
|
|
return ip, nil
|
|
}
|