github.com/cilium/cilium@v1.16.2/pkg/ipam/cidrset/cidr_set.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  // Copyright The Kubernetes Authors.
     4  
     5  package cidrset
     6  
     7  import (
     8  	"encoding/binary"
     9  	"errors"
    10  	"fmt"
    11  	"math/big"
    12  	"math/bits"
    13  	"net"
    14  	"net/netip"
    15  
    16  	"go4.org/netipx"
    17  
    18  	"github.com/cilium/cilium/pkg/lock"
    19  )
    20  
    21  // CidrSet manages a set of CIDR ranges from which blocks of IPs can
    22  // be allocated from.
    23  type CidrSet struct {
    24  	lock.Mutex
    25  	// clusterCIDR is the CIDR assigned to the cluster
    26  	clusterCIDR *net.IPNet
    27  	// clusterMaskSize is the mask size, in bits, assigned to the cluster
    28  	// caches the mask size to avoid the penalty of calling clusterCIDR.Mask.Size()
    29  	clusterMaskSize int
    30  	// nodeMask is the network mask assigned to the nodes
    31  	nodeMask net.IPMask
    32  	// nodeMaskSize is the mask size, in bits,assigned to the nodes
    33  	// caches the mask size to avoid the penalty of calling nodeMask.Size()
    34  	nodeMaskSize int
    35  	// maxCIDRs is the maximum number of CIDRs that can be allocated
    36  	maxCIDRs int
    37  	// allocatedCIDRs counts the number of CIDRs allocated
    38  	allocatedCIDRs int
    39  	// nextCandidate points to the next CIDR that should be free
    40  	nextCandidate int
    41  	// used is a bitmap used to track the CIDRs allocated
    42  	used big.Int
    43  }
    44  
    45  const (
    46  	// The subnet mask size cannot be greater than 16 more than the cluster mask size
    47  	// TODO: https://github.com/kubernetes/kubernetes/issues/44918
    48  	// clusterSubnetMaxDiff limited to 16 due to the uncompressed bitmap
    49  	// Due to this limitation the subnet mask for IPv6 cluster cidr needs to be >= 48
    50  	// as default mask size for IPv6 is 64.
    51  	clusterSubnetMaxDiff = 16
    52  	// halfIPv6Len is the half of the IPv6 length
    53  	halfIPv6Len = net.IPv6len / 2
    54  	// the default subnet mask should be lower or equal to the max ipv4 netmask
    55  	maxSubNetMaskSizeIPv4 = 32
    56  	// the default subnet mask should be lower or equal to the max ipv6 netmask
    57  	maxSubNetMaskSizeIPv6 = 128
    58  )
    59  
    60  var (
    61  	// ErrCIDRRangeNoCIDRsRemaining occurs when there is no more space
    62  	// to allocate CIDR ranges.
    63  	ErrCIDRRangeNoCIDRsRemaining = errors.New(
    64  		"CIDR allocation failed; there are no remaining CIDRs left to allocate in the accepted range")
    65  	// ErrCIDRSetSubNetTooBig occurs when the subnet mask size is too
    66  	// big compared to the CIDR mask size.
    67  	ErrCIDRSetSubNetTooBig = errors.New(
    68  		"New CIDR set failed; the node CIDR size is too big")
    69  	// ErrSubNetMaskSizeInvalid occurs when the subnet mask size  is invalid:
    70  	// bigger than 32 for IPv4 and bigger than 128 for IPv6
    71  	ErrSubNetMaskSizeInvalid = fmt.Errorf(
    72  		"SubNetMask is invalid, should be lower or equal to %d for IPv4 and to %d for IPv6",
    73  		maxSubNetMaskSizeIPv4, maxSubNetMaskSizeIPv6)
    74  )
    75  
    76  // NewCIDRSet creates a new CidrSet.
    77  func NewCIDRSet(clusterCIDR *net.IPNet, subNetMaskSize int) (*CidrSet, error) {
    78  	clusterMask := clusterCIDR.Mask
    79  	clusterMaskSize, bits := clusterMask.Size()
    80  
    81  	if clusterCIDR.IP.To4() == nil {
    82  		if subNetMaskSize > maxSubNetMaskSizeIPv6 {
    83  			return nil, ErrSubNetMaskSizeInvalid
    84  		}
    85  		if subNetMaskSize-clusterMaskSize > clusterSubnetMaxDiff {
    86  			return nil, ErrCIDRSetSubNetTooBig
    87  		}
    88  	} else if subNetMaskSize > maxSubNetMaskSizeIPv4 {
    89  		return nil, ErrSubNetMaskSizeInvalid
    90  	}
    91  	maxCIDRs := 1 << uint32(subNetMaskSize-clusterMaskSize)
    92  	return &CidrSet{
    93  		clusterCIDR:     clusterCIDR,
    94  		nodeMask:        net.CIDRMask(subNetMaskSize, bits),
    95  		clusterMaskSize: clusterMaskSize,
    96  		maxCIDRs:        maxCIDRs,
    97  		nodeMaskSize:    subNetMaskSize,
    98  	}, nil
    99  }
   100  
   101  func (s *CidrSet) String() string {
   102  	return fmt.Sprintf("clusterCIDR: %s, nodeMask: %d", s.clusterCIDR.String(), s.nodeMaskSize)
   103  }
   104  
   105  func (s *CidrSet) indexToCIDRBlock(index int) *net.IPNet {
   106  	var ip []byte
   107  	switch /*v4 or v6*/ {
   108  	case s.clusterCIDR.IP.To4() != nil:
   109  		{
   110  			j := uint32(index) << uint32(32-s.nodeMaskSize)
   111  			ipInt := (binary.BigEndian.Uint32(s.clusterCIDR.IP)) | j
   112  			ip = make([]byte, net.IPv4len)
   113  			binary.BigEndian.PutUint32(ip, ipInt)
   114  		}
   115  	case s.clusterCIDR.IP.To16() != nil:
   116  		{
   117  			// leftClusterIP      |     rightClusterIP
   118  			// 2001:0DB8:1234:0000:0000:0000:0000:0000
   119  			const v6NBits = 128
   120  			const halfV6NBits = v6NBits / 2
   121  			leftClusterIP := binary.BigEndian.Uint64(s.clusterCIDR.IP[:halfIPv6Len])
   122  			rightClusterIP := binary.BigEndian.Uint64(s.clusterCIDR.IP[halfIPv6Len:])
   123  
   124  			ip = make([]byte, net.IPv6len)
   125  
   126  			if s.nodeMaskSize <= halfV6NBits {
   127  				// We only care about left side IP
   128  				leftClusterIP |= uint64(index) << uint(halfV6NBits-s.nodeMaskSize)
   129  			} else {
   130  				if s.clusterMaskSize < halfV6NBits {
   131  					// see how many bits are needed to reach the left side
   132  					btl := uint(s.nodeMaskSize - halfV6NBits)
   133  					indexMaxBit := uint(64 - bits.LeadingZeros64(uint64(index)))
   134  					if indexMaxBit > btl {
   135  						leftClusterIP |= uint64(index) >> btl
   136  					}
   137  				}
   138  				// the right side will be calculated the same way either the
   139  				// subNetMaskSize affects both left and right sides
   140  				rightClusterIP |= uint64(index) << uint(v6NBits-s.nodeMaskSize)
   141  			}
   142  			binary.BigEndian.PutUint64(ip[:halfIPv6Len], leftClusterIP)
   143  			binary.BigEndian.PutUint64(ip[halfIPv6Len:], rightClusterIP)
   144  		}
   145  	}
   146  	return &net.IPNet{
   147  		IP:   ip,
   148  		Mask: s.nodeMask,
   149  	}
   150  }
   151  
   152  // IsFull returns true if CidrSet does not have any more available CIDRs.
   153  func (s *CidrSet) IsFull() bool {
   154  	s.Lock()
   155  	defer s.Unlock()
   156  	return s.allocatedCIDRs == s.maxCIDRs
   157  }
   158  
   159  // AllocateNext allocates the next free CIDR range. This will set the range
   160  // as occupied and return the allocated range.
   161  func (s *CidrSet) AllocateNext() (*net.IPNet, error) {
   162  	s.Lock()
   163  	defer s.Unlock()
   164  
   165  	if s.allocatedCIDRs == s.maxCIDRs {
   166  		return nil, ErrCIDRRangeNoCIDRsRemaining
   167  	}
   168  	candidate := s.nextCandidate
   169  	var i int
   170  	for i = 0; i < s.maxCIDRs; i++ {
   171  		if s.used.Bit(candidate) == 0 {
   172  			break
   173  		}
   174  		candidate = (candidate + 1) % s.maxCIDRs
   175  	}
   176  
   177  	s.nextCandidate = (candidate + 1) % s.maxCIDRs
   178  	s.used.SetBit(&s.used, candidate, 1)
   179  	s.allocatedCIDRs++
   180  
   181  	return s.indexToCIDRBlock(candidate), nil
   182  }
   183  
   184  // InRange returns true if the given CIDR is inside the range of the allocatable
   185  // CidrSet.
   186  func (s *CidrSet) InRange(cidr *net.IPNet) bool {
   187  	return s.clusterCIDR.Contains(cidr.IP.Mask(s.clusterCIDR.Mask)) || cidr.Contains(s.clusterCIDR.IP.Mask(cidr.Mask))
   188  }
   189  
   190  // IsClusterCIDR returns true if the given CIDR is equal to this CidrSet's cluster CIDR.
   191  func (s *CidrSet) IsClusterCIDR(cidr netip.Prefix) bool {
   192  	clusterPrefix, _ := netipx.FromStdIPNet(s.clusterCIDR)
   193  	return cidr == clusterPrefix
   194  }
   195  
   196  // Prefix returns the CidrSet's prefix.
   197  func (s *CidrSet) Prefix() netip.Prefix {
   198  	prefix, _ := netipx.FromStdIPNet(s.clusterCIDR)
   199  	return prefix
   200  }
   201  
   202  func (s *CidrSet) getBeginningAndEndIndices(cidr *net.IPNet) (begin, end int, err error) {
   203  	if cidr == nil {
   204  		return -1, -1, fmt.Errorf("error getting indices for cluster cidr %v, cidr is nil", s.clusterCIDR)
   205  	}
   206  	begin, end = 0, s.maxCIDRs-1
   207  	cidrMask := cidr.Mask
   208  	maskSize, _ := cidrMask.Size()
   209  	var ipSize int
   210  
   211  	if !s.InRange(cidr) {
   212  		return -1, -1, fmt.Errorf("cidr %v is out the range of cluster cidr %v", cidr, s.clusterCIDR)
   213  	}
   214  
   215  	if s.clusterMaskSize < maskSize {
   216  
   217  		ipSize = net.IPv4len
   218  		if cidr.IP.To4() == nil {
   219  			ipSize = net.IPv6len
   220  		}
   221  		begin, err = s.getIndexForIP(cidr.IP.Mask(s.nodeMask))
   222  		if err != nil {
   223  			return -1, -1, err
   224  		}
   225  		ip := make([]byte, ipSize)
   226  		if cidr.IP.To4() != nil {
   227  			ipInt := binary.BigEndian.Uint32(cidr.IP) | (^binary.BigEndian.Uint32(cidr.Mask))
   228  			binary.BigEndian.PutUint32(ip, ipInt)
   229  		} else {
   230  			// ipIntLeft          |         ipIntRight
   231  			// 2001:0DB8:1234:0000:0000:0000:0000:0000
   232  			ipIntLeft := binary.BigEndian.Uint64(cidr.IP[:net.IPv6len/2]) | (^binary.BigEndian.Uint64(cidr.Mask[:net.IPv6len/2]))
   233  			ipIntRight := binary.BigEndian.Uint64(cidr.IP[net.IPv6len/2:]) | (^binary.BigEndian.Uint64(cidr.Mask[net.IPv6len/2:]))
   234  			binary.BigEndian.PutUint64(ip[:net.IPv6len/2], ipIntLeft)
   235  			binary.BigEndian.PutUint64(ip[net.IPv6len/2:], ipIntRight)
   236  		}
   237  		end, err = s.getIndexForIP(net.IP(ip).Mask(s.nodeMask))
   238  		if err != nil {
   239  			return -1, -1, err
   240  		}
   241  	}
   242  	return begin, end, nil
   243  }
   244  
   245  // IsAllocated verifies if the given CIDR is allocated
   246  func (s *CidrSet) IsAllocated(cidr *net.IPNet) (bool, error) {
   247  	begin, end, err := s.getBeginningAndEndIndices(cidr)
   248  	if err != nil {
   249  		return false, err
   250  	}
   251  	s.Lock()
   252  	defer s.Unlock()
   253  	for i := begin; i <= end; i++ {
   254  		if s.used.Bit(i) == 0 {
   255  			return false, nil
   256  		}
   257  	}
   258  	return true, nil
   259  }
   260  
   261  // Release releases the given CIDR range.
   262  func (s *CidrSet) Release(cidr *net.IPNet) error {
   263  	begin, end, err := s.getBeginningAndEndIndices(cidr)
   264  	if err != nil {
   265  		return err
   266  	}
   267  	s.Lock()
   268  	defer s.Unlock()
   269  	for i := begin; i <= end; i++ {
   270  		// Only change the counters if we change the bit to prevent
   271  		// double counting.
   272  		if s.used.Bit(i) != 0 {
   273  			s.used.SetBit(&s.used, i, 0)
   274  			s.allocatedCIDRs--
   275  		}
   276  	}
   277  	return nil
   278  }
   279  
   280  // Occupy marks the given CIDR range as used. Occupy succeeds even if the CIDR
   281  // range was previously used.
   282  func (s *CidrSet) Occupy(cidr *net.IPNet) (err error) {
   283  	begin, end, err := s.getBeginningAndEndIndices(cidr)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	s.Lock()
   288  	defer s.Unlock()
   289  	for i := begin; i <= end; i++ {
   290  		// Only change the counters if we change the bit to prevent
   291  		// double counting.
   292  		if s.used.Bit(i) == 0 {
   293  			s.used.SetBit(&s.used, i, 1)
   294  			s.allocatedCIDRs++
   295  		}
   296  	}
   297  
   298  	return nil
   299  }
   300  
   301  func (s *CidrSet) getIndexForIP(ip net.IP) (int, error) {
   302  	if ip.To4() != nil {
   303  		cidrIndex := (binary.BigEndian.Uint32(s.clusterCIDR.IP) ^ binary.BigEndian.Uint32(ip.To4())) >> uint32(32-s.nodeMaskSize)
   304  		if cidrIndex >= uint32(s.maxCIDRs) {
   305  			return 0, fmt.Errorf("CIDR: %v/%v is out of the range of CIDR allocator", ip, s.nodeMaskSize)
   306  		}
   307  		return int(cidrIndex), nil
   308  	}
   309  	if ip.To16() != nil {
   310  		bigIP := big.NewInt(0).SetBytes(s.clusterCIDR.IP)
   311  		bigIP = bigIP.Xor(bigIP, big.NewInt(0).SetBytes(ip))
   312  		cidrIndexBig := bigIP.Rsh(bigIP, uint(net.IPv6len*8-s.nodeMaskSize))
   313  		cidrIndex := cidrIndexBig.Uint64()
   314  		if cidrIndex >= uint64(s.maxCIDRs) {
   315  			return 0, fmt.Errorf("CIDR: %v/%v is out of the range of CIDR allocator", ip, s.nodeMaskSize)
   316  		}
   317  		return int(cidrIndex), nil
   318  	}
   319  
   320  	return 0, fmt.Errorf("invalid IP: %v", ip)
   321  }