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 }