github.com/cilium/cilium@v1.16.2/pkg/ipam/service/ipallocator/allocator.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  // Copyright The Kubernetes Authors.
     4  
     5  package ipallocator
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"math/big"
    11  	"net"
    12  
    13  	"github.com/cilium/cilium/pkg/ipam/service/allocator"
    14  )
    15  
    16  // Interface manages the allocation of IP addresses out of a range. Interface
    17  // should be threadsafe.
    18  type Interface interface {
    19  	Allocate(net.IP) error
    20  	AllocateNext() (net.IP, error)
    21  	Release(net.IP) error
    22  	ForEach(func(net.IP))
    23  	CIDR() net.IPNet
    24  	Has(ip net.IP) bool
    25  }
    26  
    27  var (
    28  	ErrFull              = errors.New("range is full")
    29  	ErrAllocated         = errors.New("provided IP is already allocated")
    30  	ErrMismatchedNetwork = errors.New("the provided network does not match the current range")
    31  )
    32  
    33  type ErrNotInRange struct {
    34  	ValidRange string
    35  }
    36  
    37  func (e *ErrNotInRange) Error() string {
    38  	return fmt.Sprintf("provided IP is not in the valid range. The range of valid IPs is %s", e.ValidRange)
    39  }
    40  
    41  // Range is a contiguous block of IPs that can be allocated atomically.
    42  //
    43  // The internal structure of the range is:
    44  //
    45  //	For CIDR 10.0.0.0/24
    46  //	254 addresses usable out of 256 total (minus base and broadcast IPs)
    47  //	  The number of usable addresses is r.max
    48  //
    49  //	CIDR base IP          CIDR broadcast IP
    50  //	10.0.0.0                     10.0.0.255
    51  //	|                                     |
    52  //	0 1 2 3 4 5 ...         ... 253 254 255
    53  //	  |                              |
    54  //	r.base                     r.base + r.max
    55  //	  |                              |
    56  //	offset #0 of r.allocated   last offset of r.allocated
    57  type Range struct {
    58  	net *net.IPNet
    59  	// base is a cached version of the start IP in the CIDR range as a *big.Int
    60  	base *big.Int
    61  	// max is the maximum size of the usable addresses in the range
    62  	max int
    63  
    64  	alloc allocator.Interface
    65  }
    66  
    67  // NewCIDRRange creates a Range over a net.IPNet, calling allocator.NewAllocationMap to construct
    68  // the backing store.
    69  func NewCIDRRange(cidr *net.IPNet) *Range {
    70  	base := bigForIP(cidr.IP)
    71  	max := maximum(0, int(RangeSize(cidr)-2)) // don't use the network broadcast,
    72  
    73  	return &Range{
    74  		net:   cidr,
    75  		base:  base.Add(base, big.NewInt(1)), // don't use the network base
    76  		max:   max,
    77  		alloc: allocator.NewAllocationMap(int(max), cidr.String()),
    78  	}
    79  }
    80  
    81  func maximum(a, b int) int {
    82  	if a > b {
    83  		return a
    84  	}
    85  	return b
    86  }
    87  
    88  // Free returns the count of IP addresses left in the range.
    89  func (r *Range) Free() int {
    90  	return r.alloc.Free()
    91  }
    92  
    93  // Used returns the count of IP addresses used in the range.
    94  func (r *Range) Used() int {
    95  	return r.max - r.alloc.Free()
    96  }
    97  
    98  // CIDR returns the CIDR covered by the range.
    99  func (r *Range) CIDR() net.IPNet {
   100  	return *r.net
   101  }
   102  
   103  // Allocate attempts to reserve the provided IP. ErrNotInRange or
   104  // ErrAllocated will be returned if the IP is not valid for this range
   105  // or has already been reserved.  ErrFull will be returned if there
   106  // are no addresses left.
   107  func (r *Range) Allocate(ip net.IP) error {
   108  	ok, offset := r.contains(ip)
   109  	if !ok {
   110  		return &ErrNotInRange{r.net.String()}
   111  	}
   112  
   113  	allocated, err := r.alloc.Allocate(offset)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	if !allocated {
   118  		return ErrAllocated
   119  	}
   120  	return nil
   121  }
   122  
   123  // AllocateNext reserves one of the IPs from the pool. ErrFull may
   124  // be returned if there are no addresses left.
   125  func (r *Range) AllocateNext() (net.IP, error) {
   126  	offset, ok, err := r.alloc.AllocateNext()
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	if !ok {
   131  		return nil, ErrFull
   132  	}
   133  	return addIPOffset(r.base, offset), nil
   134  }
   135  
   136  // Release releases the IP back to the pool. Releasing an
   137  // unallocated IP or an IP out of the range is a no-op and
   138  // returns no error.
   139  func (r *Range) Release(ip net.IP) {
   140  	ok, offset := r.contains(ip)
   141  	if ok {
   142  		r.alloc.Release(offset)
   143  	}
   144  }
   145  
   146  // ForEach calls the provided function for each allocated IP.
   147  func (r *Range) ForEach(fn func(net.IP)) {
   148  	r.alloc.ForEach(func(offset int) {
   149  		ip, _ := GetIndexedIP(r.net, offset+1) // +1 because Range doesn't store IP 0
   150  		fn(ip)
   151  	})
   152  }
   153  
   154  // Has returns true if the provided IP is already allocated and a call
   155  // to Allocate(ip) would fail with ErrAllocated.
   156  func (r *Range) Has(ip net.IP) bool {
   157  	ok, offset := r.contains(ip)
   158  	if !ok {
   159  		return false
   160  	}
   161  
   162  	return r.alloc.Has(offset)
   163  }
   164  
   165  // Snapshot saves the current state of the pool.
   166  func (r *Range) Snapshot() (string, []byte, error) {
   167  	snapshottable, ok := r.alloc.(allocator.Snapshottable)
   168  	if !ok {
   169  		return "", nil, fmt.Errorf("not a snapshottable allocator")
   170  	}
   171  	str, data := snapshottable.Snapshot()
   172  	return str, data, nil
   173  }
   174  
   175  // Restore restores the pool to the previously captured state. ErrMismatchedNetwork
   176  // is returned if the provided IPNet range doesn't exactly match the previous range.
   177  func (r *Range) Restore(net *net.IPNet, data []byte) error {
   178  	if !net.IP.Equal(r.net.IP) || net.Mask.String() != r.net.Mask.String() {
   179  		return ErrMismatchedNetwork
   180  	}
   181  	snapshottable, ok := r.alloc.(allocator.Snapshottable)
   182  	if !ok {
   183  		return fmt.Errorf("not a snapshottable allocator")
   184  	}
   185  	if err := snapshottable.Restore(net.String(), data); err != nil {
   186  		return fmt.Errorf("restoring snapshot encountered: %w", err)
   187  	}
   188  	return nil
   189  }
   190  
   191  // contains returns true and the offset if the ip is in the range, and false
   192  // and nil otherwise. The first and last addresses of the CIDR are omitted.
   193  func (r *Range) contains(ip net.IP) (bool, int) {
   194  	if !r.net.Contains(ip) {
   195  		return false, 0
   196  	}
   197  
   198  	offset := calculateIPOffset(r.base, ip)
   199  	if offset < 0 || offset >= r.max {
   200  		return false, 0
   201  	}
   202  	return true, offset
   203  }
   204  
   205  // bigForIP creates a big.Int based on the provided net.IP
   206  func bigForIP(ip net.IP) *big.Int {
   207  	// NOTE: Convert to 16-byte representation so we can
   208  	// handle v4 and v6 values the same way.
   209  	return big.NewInt(0).SetBytes(ip.To16())
   210  }
   211  
   212  // addIPOffset adds the provided integer offset to a base big.Int representing a net.IP
   213  // NOTE: If you started with a v4 address and overflow it, you get a v6 result.
   214  func addIPOffset(base *big.Int, offset int) net.IP {
   215  	r := big.NewInt(0).Add(base, big.NewInt(int64(offset))).Bytes()
   216  	r = append(make([]byte, 16), r...)
   217  	return net.IP(r[len(r)-16:])
   218  }
   219  
   220  // calculateIPOffset calculates the integer offset of ip from base such that
   221  // base + offset = ip. It requires ip >= base.
   222  func calculateIPOffset(base *big.Int, ip net.IP) int {
   223  	return int(big.NewInt(0).Sub(bigForIP(ip), base).Int64())
   224  }
   225  
   226  // RangeSize returns the size of a range in valid addresses.
   227  func RangeSize(subnet *net.IPNet) int64 {
   228  	ones, bits := subnet.Mask.Size()
   229  	if bits == 32 && (bits-ones) >= 31 || bits == 128 && (bits-ones) >= 127 {
   230  		return 0
   231  	}
   232  	// For IPv6, the max size will be limited to 65536
   233  	// This is due to the allocator keeping track of all the
   234  	// allocated IP's in a bitmap. This will keep the size of
   235  	// the bitmap to 64k.
   236  	if bits == 128 && (bits-ones) >= 16 {
   237  		return int64(1) << uint(16)
   238  	} else {
   239  		return int64(1) << uint(bits-ones)
   240  	}
   241  }
   242  
   243  // GetIndexedIP returns a net.IP that is subnet.IP + index in the contiguous IP space.
   244  func GetIndexedIP(subnet *net.IPNet, index int) (net.IP, error) {
   245  	ip := addIPOffset(bigForIP(subnet.IP), index)
   246  	if !subnet.Contains(ip) {
   247  		return nil, fmt.Errorf("can't generate IP with index %d from subnet. subnet too small. subnet: %q", index, subnet)
   248  	}
   249  	return ip, nil
   250  }