| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- // Copyright 2018-present the CoreDHCP Authors. All rights reserved
- // This source code is licensed under the MIT license found in the
- // LICENSE file in the root directory of this source tree.
- package bitmap
- // This allocator handles IPv4 assignments with a similar logic to the base bitmap, but a simpler
- // implementation due to the ability to just use uint32 for IPv4 addresses
- import (
- "encoding/binary"
- "errors"
- "fmt"
- "net"
- "sync"
- "github.com/bits-and-blooms/bitset"
- "github.com/coredhcp/coredhcp/plugins/allocators"
- )
- var (
- errNotInRange = errors.New("IPv4 address outside of allowed range")
- errInvalidIP = errors.New("invalid IPv4 address passed as input")
- )
- // IPv4Allocator allocates IPv4 addresses, tracking utilization with a bitmap
- type IPv4Allocator struct {
- start uint32
- end uint32
- // This bitset implementation isn't goroutine-safe, we protect it with a mutex for now
- // until we can swap for another concurrent implementation
- bitmap *bitset.BitSet
- l sync.Mutex
- }
- func (a *IPv4Allocator) toIP(offset uint32) net.IP {
- if offset > a.end-a.start {
- panic("BUG: offset out of bounds")
- }
- r := make(net.IP, net.IPv4len)
- binary.BigEndian.PutUint32(r, a.start+offset)
- return r
- }
- func (a *IPv4Allocator) toOffset(ip net.IP) (uint, error) {
- if ip.To4() == nil {
- return 0, errInvalidIP
- }
- intIP := binary.BigEndian.Uint32(ip.To4())
- if intIP < a.start || intIP > a.end {
- return 0, errNotInRange
- }
- return uint(intIP - a.start), nil
- }
- // Allocate reserves an IP for a client
- func (a *IPv4Allocator) Allocate(hint net.IPNet) (n net.IPNet, err error) {
- n.Mask = net.CIDRMask(32, 32)
- // This is just a hint, ignore any error with it
- hintOffset, _ := a.toOffset(hint.IP)
- a.l.Lock()
- defer a.l.Unlock()
- var next uint
- // First try the exact match
- if !a.bitmap.Test(hintOffset) {
- next = hintOffset
- } else {
- // Then any available address
- avail, ok := a.bitmap.NextClear(0)
- if !ok {
- return n, allocators.ErrNoAddrAvail
- }
- next = avail
- }
- a.bitmap.Set(next)
- n.IP = a.toIP(uint32(next))
- return
- }
- // Free releases the given IP
- func (a *IPv4Allocator) Free(n net.IPNet) error {
- offset, err := a.toOffset(n.IP)
- if err != nil {
- return errNotInRange
- }
- a.l.Lock()
- defer a.l.Unlock()
- if !a.bitmap.Test(uint(offset)) {
- return &allocators.ErrDoubleFree{Loc: n}
- }
- a.bitmap.Clear(offset)
- return nil
- }
- // NewIPv4Allocator creates a new allocator suitable for giving out IPv4 addresses
- func NewIPv4Allocator(start, end net.IP) (*IPv4Allocator, error) {
- if start.To4() == nil || end.To4() == nil {
- return nil, fmt.Errorf("invalid IPv4 addresses given to create the allocator: [%s,%s]", start, end)
- }
- alloc := IPv4Allocator{
- start: binary.BigEndian.Uint32(start.To4()),
- end: binary.BigEndian.Uint32(end.To4()),
- }
- if alloc.start > alloc.end {
- return nil, errors.New("no IPs in the given range to allocate")
- }
- alloc.bitmap = bitset.New(uint(alloc.end - alloc.start + 1))
- return &alloc, nil
- }
|