add all files from Hong

This commit is contained in:
zhangsz
2025-06-30 09:23:28 +08:00
parent ceb1fe2640
commit 9b7d32fbd9
69 changed files with 7280 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
// 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.
// This allocator only returns prefixes of a single size
// This is much simpler to implement (reduces the problem to an equivalent of
// single ip allocations), probably makes sense in cases where the available
// range is much larger than the expected number of clients. Also is what KEA
// does so at least it's not worse than that
package bitmap
import (
"errors"
"fmt"
"net"
"strconv"
"sync"
"github.com/bits-and-blooms/bitset"
"github.com/coredhcp/coredhcp/logger"
"github.com/coredhcp/coredhcp/plugins/allocators"
)
var log = logger.GetLogger("plugins/allocators/bitmap")
// Allocator is a prefix allocator allocating in chunks of a fixed size
// regardless of the size requested by the client.
// It consumes an amount of memory proportional to the total amount of available prefixes
type Allocator struct {
containing net.IPNet
page int
bitmap *bitset.BitSet
l sync.Mutex
}
// prefix must verify: containing.Mask.Size < prefix.Mask.Size < page
func (a *Allocator) toIndex(base net.IP) (uint, error) {
value, err := allocators.Offset(base, a.containing.IP, a.page)
if err != nil {
return 0, fmt.Errorf("Cannot compute prefix index: %w", err)
}
return uint(value), nil
}
func (a *Allocator) toPrefix(idx uint) (net.IP, error) {
return allocators.AddPrefixes(a.containing.IP, uint64(idx), uint64(a.page))
}
// Allocate reserves a maxsize-sized block and returns a block of size
// min(maxsize, hint.size)
func (a *Allocator) Allocate(hint net.IPNet) (ret net.IPNet, err error) {
// Ensure size is max(maxsize, hint.size)
reqSize, hintErr := hint.Mask.Size()
if reqSize < a.page || hintErr != 128 {
reqSize = a.page
}
ret.Mask = net.CIDRMask(reqSize, 128)
// Try to allocate the requested prefix
a.l.Lock()
defer a.l.Unlock()
if hint.IP.To16() != nil && a.containing.Contains(hint.IP) {
idx, hintErr := a.toIndex(hint.IP)
if hintErr == nil && !a.bitmap.Test(idx) {
a.bitmap.Set(idx)
ret.IP, err = a.toPrefix(idx)
return
}
}
// Find a free prefix
next, ok := a.bitmap.NextClear(0)
if !ok {
err = allocators.ErrNoAddrAvail
return
}
a.bitmap.Set(next)
ret.IP, err = a.toPrefix(next)
if err != nil {
// This violates the assumption that every index in the bitmap maps back to a valid prefix
err = fmt.Errorf("BUG: could not get prefix from allocation: %w", err)
a.bitmap.Clear(next)
}
return
}
// Free returns the given prefix to the available pool if it was taken.
func (a *Allocator) Free(prefix net.IPNet) error {
idx, err := a.toIndex(prefix.IP.Mask(prefix.Mask))
if err != nil {
return fmt.Errorf("Could not find prefix in pool: %w", err)
}
a.l.Lock()
defer a.l.Unlock()
if !a.bitmap.Test(idx) {
return &allocators.ErrDoubleFree{Loc: prefix}
}
a.bitmap.Clear(idx)
return nil
}
// NewBitmapAllocator creates a new allocator, allocating /`size` prefixes
// carved out of the given `pool` prefix
func NewBitmapAllocator(pool net.IPNet, size int) (*Allocator, error) {
poolSize, _ := pool.Mask.Size()
allocOrder := size - poolSize
if allocOrder < 0 {
return nil, errors.New("The size of allocated prefixes cannot be larger than the pool they're allocated from")
} else if allocOrder >= strconv.IntSize {
return nil, fmt.Errorf("A pool with more than 2^%d items is not representable", size-poolSize)
} else if allocOrder >= 32 {
log.Warningln("Using a pool of more than 2^32 elements may result in large memory consumption")
}
if !(1<<uint(allocOrder) <= bitset.Cap()) {
return nil, errors.New("Can't fit this pool using the bitmap allocator")
}
alloc := Allocator{
containing: pool,
page: size,
bitmap: bitset.New(1 << uint(allocOrder)),
}
return &alloc, nil
}

View File

@@ -0,0 +1,122 @@
// 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
}

View File

@@ -0,0 +1,63 @@
// 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
import (
"net"
"testing"
)
func getv4Allocator() *IPv4Allocator {
alloc, err := NewIPv4Allocator(net.IPv4(192, 0, 2, 0), net.IPv4(192, 0, 2, 255))
if err != nil {
panic(err)
}
return alloc
}
func Test4Alloc(t *testing.T) {
alloc := getv4Allocator()
net1, err := alloc.Allocate(net.IPNet{})
if err != nil {
t.Fatal(err)
}
net2, err := alloc.Allocate(net.IPNet{})
if err != nil {
t.Fatal(err)
}
if net1.IP.Equal(net2.IP) {
t.Fatal("That address was already allocated")
}
err = alloc.Free(net1)
if err != nil {
t.Fatal(err)
}
err = alloc.Free(net1)
if err == nil {
t.Fatal("Expected DoubleFree error")
}
}
func Test4OutOfPool(t *testing.T) {
alloc := getv4Allocator()
hint := net.IPv4(198, 51, 100, 5)
res, err := alloc.Allocate(net.IPNet{IP: hint, Mask: net.CIDRMask(32, 32)})
if err != nil {
t.Fatalf("Failed to allocate with invalid hint: %v", err)
}
_, prefix, _ := net.ParseCIDR("192.0.2.0/24")
if !prefix.Contains(res.IP) {
t.Fatal("Obtained prefix outside of range: ", res)
}
if prefLen, totalLen := res.Mask.Size(); prefLen != 32 || totalLen != 32 {
t.Fatalf("Prefixes have wrong size %d/%d", prefLen, totalLen)
}
}

View File

@@ -0,0 +1,137 @@
// 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
import (
"math"
"math/rand"
"net"
"testing"
"github.com/bits-and-blooms/bitset"
)
func getAllocator(bits int) *Allocator {
_, prefix, err := net.ParseCIDR("2001:db8::/56")
if err != nil {
panic(err)
}
alloc, err := NewBitmapAllocator(*prefix, 56+bits)
if err != nil {
panic(err)
}
return alloc
}
func TestAlloc(t *testing.T) {
alloc := getAllocator(8)
net, err := alloc.Allocate(net.IPNet{})
if err != nil {
t.Fatal(err)
}
err = alloc.Free(net)
if err != nil {
t.Fatal(err)
}
err = alloc.Free(net)
if err == nil {
t.Fatal("Expected DoubleFree error")
}
}
func TestExhaust(t *testing.T) {
_, prefix, _ := net.ParseCIDR("2001:db8::/62")
alloc, _ := NewBitmapAllocator(*prefix, 64)
allocd := []net.IPNet{}
for i := 0; i < 4; i++ {
net, err := alloc.Allocate(net.IPNet{Mask: net.CIDRMask(64, 128)})
if err != nil {
t.Fatalf("Error before exhaustion: %v", err)
}
allocd = append(allocd, net)
}
_, err := alloc.Allocate(net.IPNet{})
if err == nil {
t.Fatalf("Successfully allocated more prefixes than there are in the pool")
}
err = alloc.Free(allocd[1])
if err != nil {
t.Fatalf("Could not free: %v", err)
}
net, err := alloc.Allocate(allocd[1])
if err != nil {
t.Fatalf("Could not reallocate after free: %v", err)
}
if !net.IP.Equal(allocd[1].IP) || net.Mask.String() != allocd[1].Mask.String() {
t.Fatalf("Did not obtain the right network after free: got %v, expected %v", net, allocd[1])
}
}
func TestOutOfPool(t *testing.T) {
alloc := getAllocator(8)
_, prefix, _ := net.ParseCIDR("fe80:abcd::/48")
res, err := alloc.Allocate(*prefix)
if err != nil {
t.Fatalf("Failed to allocate with invalid hint: %v", err)
}
if !alloc.containing.Contains(res.IP) {
t.Fatal("Obtained prefix outside of range: ", res)
}
if prefLen, totalLen := res.Mask.Size(); prefLen != 64 || totalLen != 128 {
t.Fatalf("Prefixes have wrong size %d/%d", prefLen, totalLen)
}
}
func prefixSizeForAllocs(allocs int) int {
return int(math.Ceil(math.Log2(float64(allocs))))
}
// Benchmark parallel Allocate, when the bitmap is mostly empty and we're allocating few values
// compared to the available allocations
func BenchmarkParallelAllocInitiallyEmpty(b *testing.B) {
// Run with -race to debug concurrency issues
alloc := getAllocator(prefixSizeForAllocs(b.N) + 2) // Use max 25% of the bitmap (initially empty)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if net, err := alloc.Allocate(net.IPNet{}); err != nil {
b.Logf("Could not allocate (got %v and an error): %v", net, err)
b.Fail()
}
}
})
}
func BenchmarkParallelAllocPartiallyFilled(b *testing.B) {
// We'll make a bitmap with 2x the number of allocs we want to make.
// Then randomly fill it to about 50% utilization
alloc := getAllocator(prefixSizeForAllocs(b.N) + 1)
// Build a replacement bitmap that we'll put in the allocator, with approx. 50% of values filled
newbmap := make([]uint64, alloc.bitmap.Len())
for i := uint(0); i < alloc.bitmap.Len(); i++ {
newbmap[i] = rand.Uint64()
}
alloc.bitmap = bitset.From(newbmap)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if net, err := alloc.Allocate(net.IPNet{}); err != nil {
b.Logf("Could not allocate (got %v and an error): %v", net, err)
b.Fail()
}
}
})
}