305 lines
7.1 KiB
Go
305 lines
7.1 KiB
Go
package ipalloc
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/netip"
|
|
"testing"
|
|
)
|
|
|
|
func TestSplitPrefix(t *testing.T) {
|
|
tests := []struct{ in, lo, hi string }{
|
|
{"0.0.0.0/0", "0.0.0.0/1", "128.0.0.0/1"},
|
|
{"0.0.0.0/29", "0.0.0.0/30", "0.0.0.4/30"},
|
|
{"0.0.0.0/31", "0.0.0.0/32", "0.0.0.1/32"},
|
|
{"0.0.0.1/0", "0.0.0.0/1", "128.0.0.0/1"},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
lo, hi := splitPrefix(netip.MustParsePrefix(test.in))
|
|
if lo != netip.MustParsePrefix(test.lo) || hi != netip.MustParsePrefix(test.hi) {
|
|
t.Fatal(test.in, lo, hi)
|
|
}
|
|
}
|
|
|
|
singles := []string{"0.0.0.0/32", "::/128"}
|
|
for _, test := range singles {
|
|
func() {
|
|
defer func() {
|
|
_ = recover()
|
|
}()
|
|
_, _ = splitPrefix(netip.MustParsePrefix(test))
|
|
t.Fatalf("Expected panic when splitting %q", test)
|
|
}()
|
|
}
|
|
|
|
}
|
|
|
|
func TestTreeAllocSmall(t *testing.T) {
|
|
tests := []struct {
|
|
bits int
|
|
best string
|
|
}{
|
|
{32, "0.0.0.0/32"},
|
|
{32, "0.0.0.1/32"},
|
|
{31, "0.0.0.2/31"},
|
|
{32, "0.0.0.4/32"},
|
|
}
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/28")}
|
|
|
|
for _, test := range tests {
|
|
//t.Log("searching for", test.bits)
|
|
best := root.Alloc(test.bits)
|
|
if best == nil {
|
|
log.Print(root.Dot())
|
|
t.Fatal(root, test.bits)
|
|
}
|
|
//t.Log(test, "->", best.prefix)
|
|
if best.prefix != netip.MustParsePrefix(test.best) {
|
|
log.Print(root.Dot())
|
|
t.Fatal(root, test.bits, best.prefix.String())
|
|
}
|
|
}
|
|
}
|
|
func TestTreeAlloc(t *testing.T) {
|
|
tests := []struct {
|
|
bits int
|
|
best string
|
|
}{
|
|
{32, "0.0.0.0/32"},
|
|
{32, "0.0.0.1/32"},
|
|
{1, "128.0.0.0/1"},
|
|
{2, "64.0.0.0/2"},
|
|
{31, "0.0.0.2/31"},
|
|
{32, "0.0.0.4/32"},
|
|
}
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/0")}
|
|
|
|
for _, test := range tests {
|
|
best := root.Alloc(test.bits)
|
|
if best == nil {
|
|
t.Fatal(root, test.bits)
|
|
}
|
|
if best.prefix != netip.MustParsePrefix(test.best) {
|
|
log.Print(root.Dot())
|
|
t.Fatal(root, test.bits, best.prefix.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTreeDealloc(t *testing.T) {
|
|
tests := []struct {
|
|
bits int
|
|
best string
|
|
}{
|
|
{32, "0.0.0.0/32"},
|
|
{32, "0.0.0.1/32"},
|
|
{1, "128.0.0.0/1"},
|
|
{2, "64.0.0.0/2"},
|
|
{31, "0.0.0.2/31"},
|
|
{32, "0.0.0.4/32"},
|
|
}
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/0")}
|
|
|
|
for _, test := range tests {
|
|
best := root.Alloc(test.bits)
|
|
if best == nil {
|
|
t.Fatal(root, test.bits)
|
|
}
|
|
if best.prefix != netip.MustParsePrefix(test.best) {
|
|
t.Fatal(root, test.bits, best.prefix.String())
|
|
}
|
|
}
|
|
|
|
deallocs := []string{
|
|
"0.0.0.0/32",
|
|
"0.0.0.1/32",
|
|
"128.0.0.0/1",
|
|
"64.0.0.0/2",
|
|
"0.0.0.2/31",
|
|
"0.0.0.4/32",
|
|
}
|
|
|
|
for _, test := range deallocs {
|
|
if err := root.Dealloc(netip.MustParsePrefix(test)); err != nil {
|
|
t.Fatal(test, err)
|
|
}
|
|
}
|
|
|
|
if !root.Leaf() {
|
|
root.Walk(func(t *tree) bool {
|
|
log.Println(t.prefix, t.Leaf())
|
|
return true
|
|
})
|
|
t.Fatalf("root not empty: %#v", root)
|
|
}
|
|
|
|
first := root.Alloc(2).prefix
|
|
root.Alloc(2)
|
|
if err := root.Dealloc(first); err != nil {
|
|
t.Fatal("failed to remove", first)
|
|
}
|
|
if first != root.Alloc(2).prefix {
|
|
root.Walk(func(t *tree) bool {
|
|
log.Println(t.prefix, t.Leaf())
|
|
return true
|
|
})
|
|
t.Fatal(first)
|
|
}
|
|
}
|
|
func TestFindBestPrefix(t *testing.T) {
|
|
tests := []struct {
|
|
bits int
|
|
best string
|
|
}{
|
|
{32, "0.0.0.0/32"},
|
|
{32, "0.0.0.1/32"},
|
|
{1, "128.0.0.0/1"},
|
|
{2, "64.0.0.0/2"},
|
|
{31, "0.0.0.2/31"},
|
|
{32, "0.0.0.4/32"},
|
|
}
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/0")}
|
|
|
|
for _, test := range tests {
|
|
best := root.Alloc(test.bits)
|
|
if best == nil {
|
|
t.Fatal(root, test.bits)
|
|
}
|
|
if best.prefix != netip.MustParsePrefix(test.best) {
|
|
t.Fatal(root, test.bits, best.prefix.String())
|
|
}
|
|
}
|
|
|
|
best := [][2]string{
|
|
{"128.0.0.0/32", "128.0.0.0/1"},
|
|
{"128.0.0.1/32", "128.0.0.0/1"},
|
|
}
|
|
|
|
for _, test := range best {
|
|
best := root.FindBestPrefix(netip.MustParsePrefix(test[0]))
|
|
want := netip.MustParsePrefix(test[1])
|
|
|
|
if best.prefix != want {
|
|
t.Fatal(best, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInsert(t *testing.T) {
|
|
tests := []string{
|
|
"0.0.0.1/32",
|
|
"0.0.0.0/32",
|
|
"128.0.0.0/1",
|
|
"64.0.0.0/2",
|
|
"0.0.0.4/32",
|
|
"0.0.0.2/31",
|
|
}
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/0")}
|
|
|
|
for _, test := range tests {
|
|
p := netip.MustParsePrefix(test)
|
|
insert := root.Insert(p)
|
|
if insert == nil {
|
|
t.Fatal(root, test)
|
|
}
|
|
match := root.Find(p)
|
|
if match.prefix != p {
|
|
t.Fatal("unable to find", p, "in", root)
|
|
}
|
|
}
|
|
|
|
for _, test := range tests {
|
|
p := netip.MustParsePrefix(test)
|
|
insert := root.Insert(p)
|
|
if insert != nil {
|
|
t.Fatal(root, test)
|
|
}
|
|
}
|
|
}
|
|
|
|
func expectAlloc(t *testing.T, root *tree, bits int, expect string) {
|
|
alloc := root.Alloc(bits)
|
|
if alloc == nil {
|
|
t.Log("graph:\n", root.Dot())
|
|
t.Fatal()
|
|
}
|
|
if alloc.prefix != netip.MustParsePrefix(expect) {
|
|
t.Logf("allocating %d bits failed: got %s, want %s", bits, alloc.prefix, expect)
|
|
t.Log("graph:\n", root.Dot())
|
|
t.Fatal()
|
|
}
|
|
}
|
|
func TestAllocDealloc(t *testing.T) {
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/30")}
|
|
expectAlloc(t, root, 31, "0.0.0.0/31")
|
|
expectAlloc(t, root, 32, "0.0.0.2/32")
|
|
if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil {
|
|
t.Fatalf("%s", err)
|
|
}
|
|
expectAlloc(t, root, 32, "0.0.0.3/32")
|
|
expectAlloc(t, root, 32, "0.0.0.0/32")
|
|
expectAlloc(t, root, 32, "0.0.0.1/32")
|
|
}
|
|
func TestAllocDealloc2(t *testing.T) {
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/29")}
|
|
expectAlloc(t, root, 31, "0.0.0.0/31")
|
|
expectAlloc(t, root, 31, "0.0.0.2/31")
|
|
expectAlloc(t, root, 32, "0.0.0.4/32")
|
|
if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil {
|
|
t.Fatalf("%s", err)
|
|
}
|
|
expectAlloc(t, root, 32, "0.0.0.5/32")
|
|
expectAlloc(t, root, 32, "0.0.0.0/32")
|
|
expectAlloc(t, root, 32, "0.0.0.1/32")
|
|
}
|
|
func TestAllocFull(t *testing.T) {
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/31")}
|
|
expectAlloc(t, root, 32, "0.0.0.0/32")
|
|
expectAlloc(t, root, 32, "0.0.0.1/32")
|
|
if alloc := root.Alloc(32); alloc != nil {
|
|
t.Fatalf("expected nil for allocation in full tree, got %v", alloc)
|
|
}
|
|
}
|
|
func TestFindNotAvailable(t *testing.T) {
|
|
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/31")}
|
|
if match := root.Find(netip.MustParsePrefix("0.0.0.0/32")); match != nil {
|
|
t.Fatalf("expected not to find %q in empty tree", match.prefix)
|
|
}
|
|
if match := root.Find(netip.MustParsePrefix("128.0.0.0/32")); match != nil {
|
|
t.Fatalf("expected not to find %q in empty tree", match.prefix)
|
|
}
|
|
if match := root.Find(netip.MustParsePrefix("::/32")); match != nil {
|
|
t.Fatalf("expected not to find %q in empty tree", match.prefix)
|
|
}
|
|
}
|
|
|
|
func benchRunN(b *testing.B, base netip.Addr, bits int) {
|
|
root := &tree{
|
|
prefix: netip.PrefixFrom(base, bits),
|
|
}
|
|
bitlen := base.BitLen()
|
|
for i := 0; i < b.N; i++ {
|
|
if alloc := root.Alloc(bitlen); alloc == nil {
|
|
root = &tree{
|
|
prefix: netip.PrefixFrom(base, bits),
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
func BenchmarkFillv4(b *testing.B) {
|
|
for bits := 24; bits >= 0; bits -= 8 {
|
|
b.Run(fmt.Sprintf("%s-%02d", b.Name(), bits), func(b *testing.B) {
|
|
benchRunN(b, netip.MustParseAddr("0.0.0.0"), bits)
|
|
})
|
|
}
|
|
}
|
|
func BenchmarkFillv6(b *testing.B) {
|
|
for bits := 96; bits >= 0; bits -= 16 {
|
|
b.Run(fmt.Sprintf("%s-%02d", b.Name(), bits), func(b *testing.B) {
|
|
benchRunN(b, netip.MustParseAddr("::"), bits)
|
|
})
|
|
}
|
|
}
|