bitmap.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. // Copyright 2018-present the CoreDHCP Authors. All rights reserved
  2. // This source code is licensed under the MIT license found in the
  3. // LICENSE file in the root directory of this source tree.
  4. // This allocator only returns prefixes of a single size
  5. // This is much simpler to implement (reduces the problem to an equivalent of
  6. // single ip allocations), probably makes sense in cases where the available
  7. // range is much larger than the expected number of clients. Also is what KEA
  8. // does so at least it's not worse than that
  9. package bitmap
  10. import (
  11. "errors"
  12. "fmt"
  13. "net"
  14. "strconv"
  15. "sync"
  16. "github.com/bits-and-blooms/bitset"
  17. "github.com/coredhcp/coredhcp/logger"
  18. "github.com/coredhcp/coredhcp/plugins/allocators"
  19. )
  20. var log = logger.GetLogger("plugins/allocators/bitmap")
  21. // Allocator is a prefix allocator allocating in chunks of a fixed size
  22. // regardless of the size requested by the client.
  23. // It consumes an amount of memory proportional to the total amount of available prefixes
  24. type Allocator struct {
  25. containing net.IPNet
  26. page int
  27. bitmap *bitset.BitSet
  28. l sync.Mutex
  29. }
  30. // prefix must verify: containing.Mask.Size < prefix.Mask.Size < page
  31. func (a *Allocator) toIndex(base net.IP) (uint, error) {
  32. value, err := allocators.Offset(base, a.containing.IP, a.page)
  33. if err != nil {
  34. return 0, fmt.Errorf("Cannot compute prefix index: %w", err)
  35. }
  36. return uint(value), nil
  37. }
  38. func (a *Allocator) toPrefix(idx uint) (net.IP, error) {
  39. return allocators.AddPrefixes(a.containing.IP, uint64(idx), uint64(a.page))
  40. }
  41. // Allocate reserves a maxsize-sized block and returns a block of size
  42. // min(maxsize, hint.size)
  43. func (a *Allocator) Allocate(hint net.IPNet) (ret net.IPNet, err error) {
  44. // Ensure size is max(maxsize, hint.size)
  45. reqSize, hintErr := hint.Mask.Size()
  46. if reqSize < a.page || hintErr != 128 {
  47. reqSize = a.page
  48. }
  49. ret.Mask = net.CIDRMask(reqSize, 128)
  50. // Try to allocate the requested prefix
  51. a.l.Lock()
  52. defer a.l.Unlock()
  53. if hint.IP.To16() != nil && a.containing.Contains(hint.IP) {
  54. idx, hintErr := a.toIndex(hint.IP)
  55. if hintErr == nil && !a.bitmap.Test(idx) {
  56. a.bitmap.Set(idx)
  57. ret.IP, err = a.toPrefix(idx)
  58. return
  59. }
  60. }
  61. // Find a free prefix
  62. next, ok := a.bitmap.NextClear(0)
  63. if !ok {
  64. err = allocators.ErrNoAddrAvail
  65. return
  66. }
  67. a.bitmap.Set(next)
  68. ret.IP, err = a.toPrefix(next)
  69. if err != nil {
  70. // This violates the assumption that every index in the bitmap maps back to a valid prefix
  71. err = fmt.Errorf("BUG: could not get prefix from allocation: %w", err)
  72. a.bitmap.Clear(next)
  73. }
  74. return
  75. }
  76. // Free returns the given prefix to the available pool if it was taken.
  77. func (a *Allocator) Free(prefix net.IPNet) error {
  78. idx, err := a.toIndex(prefix.IP.Mask(prefix.Mask))
  79. if err != nil {
  80. return fmt.Errorf("Could not find prefix in pool: %w", err)
  81. }
  82. a.l.Lock()
  83. defer a.l.Unlock()
  84. if !a.bitmap.Test(idx) {
  85. return &allocators.ErrDoubleFree{Loc: prefix}
  86. }
  87. a.bitmap.Clear(idx)
  88. return nil
  89. }
  90. // NewBitmapAllocator creates a new allocator, allocating /`size` prefixes
  91. // carved out of the given `pool` prefix
  92. func NewBitmapAllocator(pool net.IPNet, size int) (*Allocator, error) {
  93. poolSize, _ := pool.Mask.Size()
  94. allocOrder := size - poolSize
  95. if allocOrder < 0 {
  96. return nil, errors.New("The size of allocated prefixes cannot be larger than the pool they're allocated from")
  97. } else if allocOrder >= strconv.IntSize {
  98. return nil, fmt.Errorf("A pool with more than 2^%d items is not representable", size-poolSize)
  99. } else if allocOrder >= 32 {
  100. log.Warningln("Using a pool of more than 2^32 elements may result in large memory consumption")
  101. }
  102. if !(1<<uint(allocOrder) <= bitset.Cap()) {
  103. return nil, errors.New("Can't fit this pool using the bitmap allocator")
  104. }
  105. alloc := Allocator{
  106. containing: pool,
  107. page: size,
  108. bitmap: bitset.New(1 << uint(allocOrder)),
  109. }
  110. return &alloc, nil
  111. }