github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/libnetwork/ipam/allocator.go (about) 1 package ipam 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "net/netip" 8 "strings" 9 10 "github.com/containerd/log" 11 "github.com/Prakhar-Agarwal-byte/moby/libnetwork/bitmap" 12 "github.com/Prakhar-Agarwal-byte/moby/libnetwork/ipamapi" 13 "github.com/Prakhar-Agarwal-byte/moby/libnetwork/ipbits" 14 "github.com/Prakhar-Agarwal-byte/moby/libnetwork/types" 15 ) 16 17 const ( 18 localAddressSpace = "LocalDefault" 19 globalAddressSpace = "GlobalDefault" 20 ) 21 22 // Allocator provides per address space ipv4/ipv6 book keeping 23 type Allocator struct { 24 // The address spaces 25 local, global *addrSpace 26 } 27 28 // NewAllocator returns an instance of libnetwork ipam 29 func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) { 30 var ( 31 a Allocator 32 err error 33 ) 34 a.local, err = newAddrSpace(lcAs) 35 if err != nil { 36 return nil, fmt.Errorf("could not construct local address space: %w", err) 37 } 38 a.global, err = newAddrSpace(glAs) 39 if err != nil { 40 return nil, fmt.Errorf("could not construct global address space: %w", err) 41 } 42 return &a, nil 43 } 44 45 func newAddrSpace(predefined []*net.IPNet) (*addrSpace, error) { 46 pdf := make([]netip.Prefix, len(predefined)) 47 for i, n := range predefined { 48 var ok bool 49 pdf[i], ok = toPrefix(n) 50 if !ok { 51 return nil, fmt.Errorf("network at index %d (%v) is not in canonical form", i, n) 52 } 53 } 54 return &addrSpace{ 55 subnets: map[netip.Prefix]*PoolData{}, 56 predefined: pdf, 57 }, nil 58 } 59 60 // GetDefaultAddressSpaces returns the local and global default address spaces 61 func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) { 62 return localAddressSpace, globalAddressSpace, nil 63 } 64 65 // RequestPool returns an address pool along with its unique id. 66 // addressSpace must be a valid address space name and must not be the empty string. 67 // If requestedPool is the empty string then the default predefined pool for addressSpace will be used, otherwise pool must be a valid IP address and length in CIDR notation. 68 // If requestedSubPool is not empty, it must be a valid IP address and length in CIDR notation which is a sub-range of requestedPool. 69 // requestedSubPool must be empty if requestedPool is empty. 70 func (a *Allocator) RequestPool(addressSpace, requestedPool, requestedSubPool string, _ map[string]string, v6 bool) (poolID string, pool *net.IPNet, meta map[string]string, err error) { 71 log.G(context.TODO()).Debugf("RequestPool(%s, %s, %s, _, %t)", addressSpace, requestedPool, requestedSubPool, v6) 72 73 parseErr := func(err error) error { 74 return types.InternalErrorf("failed to parse pool request for address space %q pool %q subpool %q: %v", addressSpace, requestedPool, requestedSubPool, err) 75 } 76 77 if addressSpace == "" { 78 return "", nil, nil, parseErr(ipamapi.ErrInvalidAddressSpace) 79 } 80 aSpace, err := a.getAddrSpace(addressSpace) 81 if err != nil { 82 return "", nil, nil, err 83 } 84 if requestedPool == "" && requestedSubPool != "" { 85 return "", nil, nil, parseErr(ipamapi.ErrInvalidSubPool) 86 } 87 88 k := PoolID{AddressSpace: addressSpace} 89 if requestedPool == "" { 90 k.Subnet, err = aSpace.allocatePredefinedPool(v6) 91 if err != nil { 92 return "", nil, nil, err 93 } 94 return k.String(), toIPNet(k.Subnet), nil, nil 95 } 96 97 if k.Subnet, err = netip.ParsePrefix(requestedPool); err != nil { 98 return "", nil, nil, parseErr(ipamapi.ErrInvalidPool) 99 } 100 101 if requestedSubPool != "" { 102 k.ChildSubnet, err = netip.ParsePrefix(requestedSubPool) 103 if err != nil { 104 return "", nil, nil, parseErr(ipamapi.ErrInvalidSubPool) 105 } 106 } 107 108 k.Subnet, k.ChildSubnet = k.Subnet.Masked(), k.ChildSubnet.Masked() 109 // Prior to https://github.com/moby/moby/pull/44968, libnetwork would happily accept a ChildSubnet with a bigger 110 // mask than its parent subnet. In such case, it was producing IP addresses based on the parent subnet, and the 111 // child subnet was not allocated from the address pool. Following condition take care of restoring this behavior 112 // for networks created before upgrading to v24.0. 113 if k.ChildSubnet.IsValid() && k.ChildSubnet.Bits() < k.Subnet.Bits() { 114 k.ChildSubnet = k.Subnet 115 } 116 117 err = aSpace.allocateSubnet(k.Subnet, k.ChildSubnet) 118 if err != nil { 119 return "", nil, nil, err 120 } 121 122 return k.String(), toIPNet(k.Subnet), nil, nil 123 } 124 125 // ReleasePool releases the address pool identified by the passed id 126 func (a *Allocator) ReleasePool(poolID string) error { 127 log.G(context.TODO()).Debugf("ReleasePool(%s)", poolID) 128 k, err := PoolIDFromString(poolID) 129 if err != nil { 130 return types.InvalidParameterErrorf("invalid pool id: %s", poolID) 131 } 132 133 aSpace, err := a.getAddrSpace(k.AddressSpace) 134 if err != nil { 135 return err 136 } 137 138 return aSpace.releaseSubnet(k.Subnet, k.ChildSubnet) 139 } 140 141 // Given the address space, returns the local or global PoolConfig based on whether the 142 // address space is local or global. AddressSpace locality is registered with IPAM out of band. 143 func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) { 144 switch as { 145 case localAddressSpace: 146 return a.local, nil 147 case globalAddressSpace: 148 return a.global, nil 149 } 150 return nil, types.InvalidParameterErrorf("cannot find address space %s", as) 151 } 152 153 func newPoolData(pool netip.Prefix) *PoolData { 154 ones, bits := pool.Bits(), pool.Addr().BitLen() 155 numAddresses := uint64(1 << uint(bits-ones)) 156 157 // Allow /64 subnet 158 if pool.Addr().Is6() && numAddresses == 0 { 159 numAddresses-- 160 } 161 162 // Generate the new address masks. 163 h := bitmap.New(numAddresses) 164 165 // Pre-reserve the network address on IPv4 networks large 166 // enough to have one (i.e., anything bigger than a /31. 167 if !(pool.Addr().Is4() && numAddresses <= 2) { 168 h.Set(0) 169 } 170 171 // Pre-reserve the broadcast address on IPv4 networks large 172 // enough to have one (i.e., anything bigger than a /31). 173 if pool.Addr().Is4() && numAddresses > 2 { 174 h.Set(numAddresses - 1) 175 } 176 177 return &PoolData{addrs: h, children: map[netip.Prefix]struct{}{}} 178 } 179 180 // getPredefineds returns the predefined subnets for the address space. 181 // 182 // It should not be called concurrently with any other method on the addrSpace. 183 func (aSpace *addrSpace) getPredefineds() []netip.Prefix { 184 i := aSpace.predefinedStartIndex 185 // defensive in case the list changed since last update 186 if i >= len(aSpace.predefined) { 187 i = 0 188 } 189 return append(aSpace.predefined[i:], aSpace.predefined[:i]...) 190 } 191 192 // updatePredefinedStartIndex rotates the predefined subnet list by amt. 193 // 194 // It should not be called concurrently with any other method on the addrSpace. 195 func (aSpace *addrSpace) updatePredefinedStartIndex(amt int) { 196 i := aSpace.predefinedStartIndex + amt 197 if i < 0 || i >= len(aSpace.predefined) { 198 i = 0 199 } 200 aSpace.predefinedStartIndex = i 201 } 202 203 func (aSpace *addrSpace) allocatePredefinedPool(ipV6 bool) (netip.Prefix, error) { 204 aSpace.Lock() 205 defer aSpace.Unlock() 206 207 for i, nw := range aSpace.getPredefineds() { 208 if ipV6 != nw.Addr().Is6() { 209 continue 210 } 211 // Checks whether pool has already been allocated 212 if _, ok := aSpace.subnets[nw]; ok { 213 continue 214 } 215 // Shouldn't be necessary, but check prevents IP collisions should 216 // predefined pools overlap for any reason. 217 if !aSpace.contains(nw) { 218 aSpace.updatePredefinedStartIndex(i + 1) 219 err := aSpace.allocateSubnetL(nw, netip.Prefix{}) 220 if err != nil { 221 return netip.Prefix{}, err 222 } 223 return nw, nil 224 } 225 } 226 227 v := 4 228 if ipV6 { 229 v = 6 230 } 231 return netip.Prefix{}, types.NotFoundErrorf("could not find an available, non-overlapping IPv%d address pool among the defaults to assign to the network", v) 232 } 233 234 // RequestAddress returns an address from the specified pool ID 235 func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) { 236 log.G(context.TODO()).Debugf("RequestAddress(%s, %v, %v)", poolID, prefAddress, opts) 237 k, err := PoolIDFromString(poolID) 238 if err != nil { 239 return nil, nil, types.InvalidParameterErrorf("invalid pool id: %s", poolID) 240 } 241 242 aSpace, err := a.getAddrSpace(k.AddressSpace) 243 if err != nil { 244 return nil, nil, err 245 } 246 var pref netip.Addr 247 if prefAddress != nil { 248 var ok bool 249 pref, ok = netip.AddrFromSlice(prefAddress) 250 if !ok { 251 return nil, nil, types.InvalidParameterErrorf("invalid preferred address: %v", prefAddress) 252 } 253 } 254 p, err := aSpace.requestAddress(k.Subnet, k.ChildSubnet, pref.Unmap(), opts) 255 if err != nil { 256 return nil, nil, err 257 } 258 return &net.IPNet{ 259 IP: p.AsSlice(), 260 Mask: net.CIDRMask(k.Subnet.Bits(), k.Subnet.Addr().BitLen()), 261 }, nil, nil 262 } 263 264 func (aSpace *addrSpace) requestAddress(nw, sub netip.Prefix, prefAddress netip.Addr, opts map[string]string) (netip.Addr, error) { 265 aSpace.Lock() 266 defer aSpace.Unlock() 267 268 p, ok := aSpace.subnets[nw] 269 if !ok { 270 return netip.Addr{}, types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub) 271 } 272 273 if prefAddress != (netip.Addr{}) && !nw.Contains(prefAddress) { 274 return netip.Addr{}, ipamapi.ErrIPOutOfRange 275 } 276 277 if sub != (netip.Prefix{}) { 278 if _, ok := p.children[sub]; !ok { 279 return netip.Addr{}, types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub) 280 } 281 } 282 283 // In order to request for a serial ip address allocation, callers can pass in the option to request 284 // IP allocation serially or first available IP in the subnet 285 serial := opts[ipamapi.AllocSerialPrefix] == "true" 286 ip, err := getAddress(nw, p.addrs, prefAddress, sub, serial) 287 if err != nil { 288 return netip.Addr{}, err 289 } 290 291 return ip, nil 292 } 293 294 // ReleaseAddress releases the address from the specified pool ID 295 func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error { 296 log.G(context.TODO()).Debugf("ReleaseAddress(%s, %v)", poolID, address) 297 k, err := PoolIDFromString(poolID) 298 if err != nil { 299 return types.InvalidParameterErrorf("invalid pool id: %s", poolID) 300 } 301 302 aSpace, err := a.getAddrSpace(k.AddressSpace) 303 if err != nil { 304 return err 305 } 306 307 addr, ok := netip.AddrFromSlice(address) 308 if !ok { 309 return types.InvalidParameterErrorf("invalid address: %v", address) 310 } 311 312 return aSpace.releaseAddress(k.Subnet, k.ChildSubnet, addr.Unmap()) 313 } 314 315 func (aSpace *addrSpace) releaseAddress(nw, sub netip.Prefix, address netip.Addr) error { 316 aSpace.Lock() 317 defer aSpace.Unlock() 318 319 p, ok := aSpace.subnets[nw] 320 if !ok { 321 return types.NotFoundErrorf("cannot find address pool for %v/%v", nw, sub) 322 } 323 if sub != (netip.Prefix{}) { 324 if _, ok := p.children[sub]; !ok { 325 return types.NotFoundErrorf("cannot find address pool for poolID:%v/%v", nw, sub) 326 } 327 } 328 329 if !address.IsValid() { 330 return types.InvalidParameterErrorf("invalid address") 331 } 332 333 if !nw.Contains(address) { 334 return ipamapi.ErrIPOutOfRange 335 } 336 337 defer log.G(context.TODO()).Debugf("Released address Address:%v Sequence:%s", address, p.addrs) 338 339 return p.addrs.Unset(hostID(address, uint(nw.Bits()))) 340 } 341 342 func getAddress(base netip.Prefix, bitmask *bitmap.Bitmap, prefAddress netip.Addr, ipr netip.Prefix, serial bool) (netip.Addr, error) { 343 var ( 344 ordinal uint64 345 err error 346 ) 347 348 log.G(context.TODO()).Debugf("Request address PoolID:%v %s Serial:%v PrefAddress:%v ", base, bitmask, serial, prefAddress) 349 350 if bitmask.Unselected() == 0 { 351 return netip.Addr{}, ipamapi.ErrNoAvailableIPs 352 } 353 if ipr == (netip.Prefix{}) && prefAddress == (netip.Addr{}) { 354 ordinal, err = bitmask.SetAny(serial) 355 } else if prefAddress != (netip.Addr{}) { 356 ordinal = hostID(prefAddress, uint(base.Bits())) 357 err = bitmask.Set(ordinal) 358 } else { 359 start, end := subnetRange(base, ipr) 360 ordinal, err = bitmask.SetAnyInRange(start, end, serial) 361 } 362 363 switch err { 364 case nil: 365 // Convert IP ordinal for this subnet into IP address 366 return ipbits.Add(base.Addr(), ordinal, 0), nil 367 case bitmap.ErrBitAllocated: 368 return netip.Addr{}, ipamapi.ErrIPAlreadyAllocated 369 case bitmap.ErrNoBitAvailable: 370 return netip.Addr{}, ipamapi.ErrNoAvailableIPs 371 default: 372 return netip.Addr{}, err 373 } 374 } 375 376 // DumpDatabase dumps the internal info 377 func (a *Allocator) DumpDatabase() string { 378 aspaces := map[string]*addrSpace{ 379 localAddressSpace: a.local, 380 globalAddressSpace: a.global, 381 } 382 383 var b strings.Builder 384 for _, as := range []string{localAddressSpace, globalAddressSpace} { 385 fmt.Fprintf(&b, "\n### %s\n", as) 386 b.WriteString(aspaces[as].DumpDatabase()) 387 } 388 return b.String() 389 } 390 391 func (aSpace *addrSpace) DumpDatabase() string { 392 aSpace.Lock() 393 defer aSpace.Unlock() 394 395 var b strings.Builder 396 for k, config := range aSpace.subnets { 397 fmt.Fprintf(&b, "%v: %v\n", k, config) 398 fmt.Fprintf(&b, " Bitmap: %v\n", config.addrs) 399 for k := range config.children { 400 fmt.Fprintf(&b, " - Subpool: %v\n", k) 401 } 402 } 403 return b.String() 404 } 405 406 // IsBuiltIn returns true for builtin drivers 407 func (a *Allocator) IsBuiltIn() bool { 408 return true 409 }