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