bitmap_ipv4.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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. package bitmap
  5. // This allocator handles IPv4 assignments with a similar logic to the base bitmap, but a simpler
  6. // implementation due to the ability to just use uint32 for IPv4 addresses
  7. import (
  8. "encoding/binary"
  9. "errors"
  10. "fmt"
  11. "net"
  12. "sync"
  13. "github.com/bits-and-blooms/bitset"
  14. "github.com/coredhcp/coredhcp/plugins/allocators"
  15. )
  16. var (
  17. errNotInRange = errors.New("IPv4 address outside of allowed range")
  18. errInvalidIP = errors.New("invalid IPv4 address passed as input")
  19. )
  20. // IPv4Allocator allocates IPv4 addresses, tracking utilization with a bitmap
  21. type IPv4Allocator struct {
  22. start uint32
  23. end uint32
  24. // This bitset implementation isn't goroutine-safe, we protect it with a mutex for now
  25. // until we can swap for another concurrent implementation
  26. bitmap *bitset.BitSet
  27. l sync.Mutex
  28. }
  29. func (a *IPv4Allocator) toIP(offset uint32) net.IP {
  30. if offset > a.end-a.start {
  31. panic("BUG: offset out of bounds")
  32. }
  33. r := make(net.IP, net.IPv4len)
  34. binary.BigEndian.PutUint32(r, a.start+offset)
  35. return r
  36. }
  37. func (a *IPv4Allocator) toOffset(ip net.IP) (uint, error) {
  38. if ip.To4() == nil {
  39. return 0, errInvalidIP
  40. }
  41. intIP := binary.BigEndian.Uint32(ip.To4())
  42. if intIP < a.start || intIP > a.end {
  43. return 0, errNotInRange
  44. }
  45. return uint(intIP - a.start), nil
  46. }
  47. // Allocate reserves an IP for a client
  48. func (a *IPv4Allocator) Allocate(hint net.IPNet) (n net.IPNet, err error) {
  49. n.Mask = net.CIDRMask(32, 32)
  50. // This is just a hint, ignore any error with it
  51. hintOffset, _ := a.toOffset(hint.IP)
  52. a.l.Lock()
  53. defer a.l.Unlock()
  54. var next uint
  55. // First try the exact match
  56. if !a.bitmap.Test(hintOffset) {
  57. next = hintOffset
  58. } else {
  59. // Then any available address
  60. avail, ok := a.bitmap.NextClear(0)
  61. if !ok {
  62. return n, allocators.ErrNoAddrAvail
  63. }
  64. next = avail
  65. }
  66. a.bitmap.Set(next)
  67. n.IP = a.toIP(uint32(next))
  68. return
  69. }
  70. // Free releases the given IP
  71. func (a *IPv4Allocator) Free(n net.IPNet) error {
  72. offset, err := a.toOffset(n.IP)
  73. if err != nil {
  74. return errNotInRange
  75. }
  76. a.l.Lock()
  77. defer a.l.Unlock()
  78. if !a.bitmap.Test(uint(offset)) {
  79. return &allocators.ErrDoubleFree{Loc: n}
  80. }
  81. a.bitmap.Clear(offset)
  82. return nil
  83. }
  84. // NewIPv4Allocator creates a new allocator suitable for giving out IPv4 addresses
  85. func NewIPv4Allocator(start, end net.IP) (*IPv4Allocator, error) {
  86. if start.To4() == nil || end.To4() == nil {
  87. return nil, fmt.Errorf("invalid IPv4 addresses given to create the allocator: [%s,%s]", start, end)
  88. }
  89. alloc := IPv4Allocator{
  90. start: binary.BigEndian.Uint32(start.To4()),
  91. end: binary.BigEndian.Uint32(end.To4()),
  92. }
  93. if alloc.start > alloc.end {
  94. return nil, errors.New("no IPs in the given range to allocate")
  95. }
  96. alloc.bitmap = bitset.New(uint(alloc.end - alloc.start + 1))
  97. return &alloc, nil
  98. }