Compare commits
9 Commits
9ed4591f53
...
ee6d8b30fd
Author | SHA1 | Date |
---|---|---|
Johannes Kimmel | ee6d8b30fd | |
Johannes Kimmel | b7216e783d | |
Johannes Kimmel | c124d3c614 | |
Johannes Kimmel | 630e845504 | |
Johannes Kimmel | 4d8620ebd7 | |
Johannes Kimmel | 822a8039b1 | |
Johannes Kimmel | ddc7bf5a05 | |
Johannes Kimmel | 2f47568c4e | |
Johannes Kimmel | 939656149c |
15
index.html
15
index.html
|
@ -135,17 +135,20 @@
|
|||
</filter>
|
||||
</defs>
|
||||
|
||||
<g style="fill:gray; filter:url(#shadow);" transform="translate(0.1 0.1)">
|
||||
<g id="shadows" style="fill:gray; filter:url(#shadow);" transform="translate(0.1 0.1)">
|
||||
{{- range .Regions }}{{ with .Rect }}
|
||||
<rect x="{{.X}}" y="{{.Y}}" width="{{.W}}" height="{{.H}}" rx="0.2" />
|
||||
{{- end }}{{ end }}
|
||||
</g>
|
||||
{{- range .Regions }}
|
||||
<g style="fill:var(--cfg); stroke:white; stroke-width:0.1;">
|
||||
<title>{{.Prefix}}</title>\n", p
|
||||
<rect hx-get="/api/dealloc/{{.Prefix}}" {{ with .Rect }}x="{{.X}}" y="{{.Y}}" width="{{.W}}" height="{{.H}}"{{end}} rx="0.2"/>
|
||||
|
||||
<g id="regions" style="fill:var(--cfg); stroke:white; stroke-width:0.1;">
|
||||
{{- range .Regions }}
|
||||
<g id="svg-region-{{.Prefix}}" hx-get="/api/dealloc/{{.Prefix}}">
|
||||
<title>{{.Prefix}}</title>\n", p
|
||||
<rect {{ with .Rect }}x="{{.X}}" y="{{.Y}}" width="{{.W}}" height="{{.H}}"{{end}} rx="0.2"/>
|
||||
</g>
|
||||
{{- end }}
|
||||
</g>
|
||||
{{- end }}
|
||||
</svg>
|
||||
</div>
|
||||
{{- end }}
|
||||
|
|
|
@ -1,171 +1,71 @@
|
|||
package ipalloc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
Append(Operation) error
|
||||
Operations() (Scanner, error)
|
||||
}
|
||||
|
||||
type Scanner interface {
|
||||
Next() bool
|
||||
Operation() Operation
|
||||
Err() error
|
||||
}
|
||||
|
||||
type Operation interface {
|
||||
Prefix() netip.Prefix
|
||||
}
|
||||
|
||||
type op struct {
|
||||
prefix netip.Prefix
|
||||
}
|
||||
|
||||
func (o op) Prefix() netip.Prefix {
|
||||
return o.prefix
|
||||
}
|
||||
|
||||
type OpProvision struct{ op }
|
||||
|
||||
type OpAllocation struct{ op }
|
||||
|
||||
type OpDeallocation struct{ op }
|
||||
|
||||
type Memstore struct {
|
||||
ops []Operation
|
||||
}
|
||||
|
||||
func (m *Memstore) Append(op Operation) error {
|
||||
m.ops = append(m.ops, op)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memstore) Operations() (Scanner, error) {
|
||||
return &memscanner{opQueue: slices.Clip(m.ops)}, nil
|
||||
}
|
||||
|
||||
type memscanner struct {
|
||||
cur Operation
|
||||
opQueue []Operation
|
||||
}
|
||||
|
||||
func (m *memscanner) Next() bool {
|
||||
if len(m.opQueue) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
m.opQueue, m.cur = m.opQueue[1:], m.opQueue[0]
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *memscanner) Operation() Operation {
|
||||
return m.cur
|
||||
}
|
||||
|
||||
func (m *memscanner) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Filestore struct {
|
||||
filepath string
|
||||
}
|
||||
|
||||
func (fs *Filestore) Append(op Operation) error {
|
||||
f, err := os.OpenFile(fs.filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(f, op)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fs *Filestore) Operations() (Scanner, error) {
|
||||
f, err := os.OpenFile(fs.filepath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.Flock(int(f.Fd()), syscall.LOCK_SH); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newFileScanner(f), nil
|
||||
}
|
||||
|
||||
type fileScanner struct {
|
||||
err error
|
||||
cur Operation
|
||||
f *os.File
|
||||
s bufio.Scanner
|
||||
}
|
||||
|
||||
func newFileScanner(f *os.File) *fileScanner {
|
||||
return &fileScanner{
|
||||
err: nil,
|
||||
cur: nil,
|
||||
f: f,
|
||||
s: *bufio.NewScanner(f),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fileScanner) Next() bool {
|
||||
if f.f == nil {
|
||||
return false
|
||||
}
|
||||
if !f.s.Scan() {
|
||||
f.err = f.s.Err()
|
||||
if err := f.f.Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
log.Println(f.s.Text())
|
||||
f.cur = nil // TODO: parse
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *fileScanner) Operation() Operation {
|
||||
return f.cur
|
||||
}
|
||||
|
||||
func (f *fileScanner) Err() error {
|
||||
return f.err
|
||||
}
|
||||
|
||||
type tree struct {
|
||||
prefix netip.Prefix
|
||||
lo *tree
|
||||
hi *tree
|
||||
parent *tree
|
||||
prefix netip.Prefix
|
||||
lo *tree
|
||||
hi *tree
|
||||
parent *tree
|
||||
minfreelen int
|
||||
}
|
||||
|
||||
func (t *tree) Prefix() netip.Prefix {
|
||||
return t.prefix
|
||||
}
|
||||
|
||||
func (t *tree) Split() {
|
||||
lo, hi := SplitPrefix(t.prefix)
|
||||
|
||||
t.lo = &tree{prefix: lo, parent: t}
|
||||
t.hi = &tree{prefix: hi, parent: t}
|
||||
func (t *tree) Leaf() bool {
|
||||
return t != nil && t.lo == nil && t.hi == nil
|
||||
}
|
||||
func (t *tree) Full() bool {
|
||||
return t != nil && t.lo != nil && t.hi != nil
|
||||
}
|
||||
func (t *tree) Walk(f func(*tree) bool) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
t.lo.Walk(f)
|
||||
if !f(t) {
|
||||
return
|
||||
}
|
||||
t.hi.Walk(f)
|
||||
}
|
||||
func (t *tree) haveSpace() bool {
|
||||
return t != nil && !t.Full() && (t.parent == nil || !t.Leaf())
|
||||
}
|
||||
|
||||
func (t *tree) BestMatch(bits int) *tree {
|
||||
func (t *tree) Find(p netip.Prefix) *tree {
|
||||
best := t.FindBestPrefix(p)
|
||||
if best != nil && best.prefix == p {
|
||||
return best
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (t *tree) FindBestPrefix(p netip.Prefix) *tree {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
if t.prefix == p {
|
||||
return t
|
||||
}
|
||||
if !t.prefix.Overlaps(p) {
|
||||
return nil
|
||||
}
|
||||
if best := t.lo.FindBestPrefix(p); best != nil {
|
||||
return best
|
||||
}
|
||||
if best := t.hi.FindBestPrefix(p); best != nil {
|
||||
return best
|
||||
}
|
||||
return t
|
||||
}
|
||||
func (t *tree) FindBestPrefixLen(bits int) *tree {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -173,39 +73,38 @@ func (t *tree) BestMatch(bits int) *tree {
|
|||
// already taken
|
||||
return nil
|
||||
}
|
||||
//log.Println("testing", t.prefix)
|
||||
if t.minfreelen > bits {
|
||||
return nil
|
||||
}
|
||||
if t.prefix.Bits() >= bits {
|
||||
//log.Println("too long", t.prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
var best *tree
|
||||
if !t.Full() {
|
||||
best = t
|
||||
|
||||
lobest := t.lo.FindBestPrefixLen(bits)
|
||||
hibest := t.hi.FindBestPrefixLen(bits)
|
||||
switch {
|
||||
case lobest == nil:
|
||||
best = hibest
|
||||
case hibest == nil:
|
||||
best = lobest
|
||||
case lobest.minfreelen >= hibest.minfreelen:
|
||||
best = lobest
|
||||
default:
|
||||
best = hibest
|
||||
}
|
||||
|
||||
best = better(best, t.lo.BestMatch(bits))
|
||||
best = better(best, t.hi.BestMatch(bits))
|
||||
if best == nil {
|
||||
best = t
|
||||
}
|
||||
|
||||
return best
|
||||
}
|
||||
|
||||
func better(t1, t2 *tree) *tree {
|
||||
switch {
|
||||
case t1 == nil:
|
||||
return t2
|
||||
case t2 == nil:
|
||||
return t1
|
||||
case t1.prefix.Bits() >= t2.prefix.Bits():
|
||||
return t1
|
||||
default:
|
||||
return t2
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tree) Alloc(bits int) *tree {
|
||||
//log.Println("Searching space for", bits)
|
||||
best := t.BestMatch(bits)
|
||||
best := t.FindBestPrefixLen(bits)
|
||||
if best == nil {
|
||||
best = t
|
||||
}
|
||||
|
@ -213,69 +112,92 @@ func (t *tree) Alloc(bits int) *tree {
|
|||
return nil
|
||||
}
|
||||
|
||||
for best.prefix.Bits() < bits {
|
||||
lo, hi := SplitPrefix(best.prefix)
|
||||
//log.Println(lo, hi)
|
||||
switch {
|
||||
case best.lo == nil:
|
||||
best.lo = &tree{prefix: lo, parent: best}
|
||||
best = best.lo
|
||||
case best.hi == nil:
|
||||
best.hi = &tree{prefix: hi, parent: best}
|
||||
best = best.hi
|
||||
default:
|
||||
panic("unreachable")
|
||||
tofix := best
|
||||
|
||||
// the first insert can be into a new hi branch
|
||||
if best.lo != nil {
|
||||
best.hi = &tree{
|
||||
prefix: SplitPrefixHi(best.prefix),
|
||||
parent: best,
|
||||
minfreelen: best.prefix.Bits() + 2,
|
||||
}
|
||||
//log.Println("choosing", best.prefix)
|
||||
best = best.hi
|
||||
}
|
||||
|
||||
// the rest of the inserts are always into the lo branch
|
||||
for best.prefix.Bits() < bits {
|
||||
best.lo = &tree{
|
||||
prefix: SplitPrefixLo(best.prefix),
|
||||
parent: best,
|
||||
minfreelen: best.prefix.Bits() + 2,
|
||||
}
|
||||
best = best.lo
|
||||
}
|
||||
|
||||
best.fixMinFree()
|
||||
tofix.fixMinFreeAll()
|
||||
|
||||
return best
|
||||
}
|
||||
|
||||
func (t *tree) Insert(p netip.Prefix) *tree {
|
||||
var ret *tree
|
||||
|
||||
for t.prefix.Bits() < p.Bits() {
|
||||
lo, hi := SplitPrefix(t.prefix)
|
||||
//log.Println(lo, hi)
|
||||
switch {
|
||||
case lo.Overlaps(p):
|
||||
if t.lo != nil {
|
||||
t = t.lo
|
||||
} else {
|
||||
t.lo = &tree{prefix: lo, parent: t}
|
||||
t = t.lo
|
||||
ret = t
|
||||
}
|
||||
case hi.Overlaps(p):
|
||||
if t.hi != nil {
|
||||
t = t.hi
|
||||
} else {
|
||||
t.hi = &tree{prefix: hi, parent: t}
|
||||
t = t.hi
|
||||
ret = t
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
//log.Println("splitting to", best.prefix)
|
||||
func (t *tree) fixMinFree() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
if t.Leaf() {
|
||||
t.minfreelen = t.prefix.Addr().BitLen() + 1
|
||||
return
|
||||
}
|
||||
lofree := t.lo.calcfreelen(t)
|
||||
hifree := t.hi.calcfreelen(t)
|
||||
minfreelen := max(min(lofree, hifree), t.prefix.Bits()+1)
|
||||
|
||||
//if t.minfreelen != minfreelen {
|
||||
// log.Printf("lo:%v, hi:%v\n", t.lo == nil, t.hi == nil)
|
||||
// log.Printf("Fixing space for %q, lo: %d, hi: %d, old: %d, new: %d", t.prefix, lofree, hifree, t.minfreelen, minfreelen)
|
||||
//}
|
||||
|
||||
t.minfreelen = minfreelen
|
||||
}
|
||||
func (t *tree) fixMinFreeAll() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fixfree := t
|
||||
|
||||
for fixfree != nil {
|
||||
fixfree.fixMinFree()
|
||||
fixfree = fixfree.parent
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *tree) FindBest(p netip.Prefix) *tree {
|
||||
func (t *tree) calcfreelen(parent *tree) int {
|
||||
if t == nil {
|
||||
return parent.prefix.Bits() + 1
|
||||
}
|
||||
return t.minfreelen
|
||||
}
|
||||
|
||||
func (t *tree) Insert(p netip.Prefix) *tree {
|
||||
t = t.FindBestPrefix(p)
|
||||
|
||||
if !t.haveSpace() {
|
||||
return nil
|
||||
}
|
||||
if !t.prefix.Overlaps(p) {
|
||||
return nil
|
||||
}
|
||||
if best := t.lo.FindBest(p); best != nil {
|
||||
return best
|
||||
}
|
||||
if best := t.hi.FindBest(p); best != nil {
|
||||
return best
|
||||
|
||||
for t.prefix.Bits() < p.Bits() {
|
||||
if lo := SplitPrefixLo(t.prefix); lo.Overlaps(p) {
|
||||
t.lo = &tree{prefix: lo, parent: t}
|
||||
t = t.lo
|
||||
} else if hi := SplitPrefixHi(t.prefix); hi.Overlaps(p) {
|
||||
t.hi = &tree{prefix: hi, parent: t}
|
||||
t = t.hi
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
t.fixMinFreeAll()
|
||||
return t
|
||||
}
|
||||
|
||||
|
@ -310,46 +232,36 @@ func (t *tree) Dealloc(p netip.Prefix) error {
|
|||
}
|
||||
}
|
||||
|
||||
match.fixMinFree()
|
||||
match = match.parent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (t *tree) Find(p netip.Prefix) *tree {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
//log.Println("searching for", p, "in", t.prefix)
|
||||
if t.prefix == p {
|
||||
return t
|
||||
}
|
||||
if !t.prefix.Overlaps(p) {
|
||||
//log.Println(p, "does not overlap", t.prefix)
|
||||
return nil
|
||||
}
|
||||
l := t.lo.Find(p)
|
||||
if l != nil {
|
||||
return l
|
||||
}
|
||||
return t.hi.Find(p)
|
||||
}
|
||||
|
||||
func (t *tree) Leaf() bool {
|
||||
return t != nil && t.lo == nil && t.hi == nil
|
||||
}
|
||||
func (t *tree) Full() bool {
|
||||
return t != nil && t.lo != nil && t.hi != nil
|
||||
}
|
||||
func (t *tree) Dot() string {
|
||||
var buf strings.Builder
|
||||
|
||||
func (t *tree) Walk(f func(*tree) bool) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
t.lo.Walk(f)
|
||||
if !f(t) {
|
||||
return
|
||||
}
|
||||
t.hi.Walk(f)
|
||||
fmt.Fprintln(&buf, "strict digraph {")
|
||||
|
||||
t.Walk(func(t *tree) bool {
|
||||
printEdge := func(parent, child *tree) {
|
||||
if child == nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(&buf, "\t%q -> %q;\n", parent.prefix, child.prefix)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&buf, "\t%q [shape=none label=<<table border=\"0\" cellspacing=\"0\" cellborder=\"1\"><tr><td>prefix</td><td>%s</td></tr><tr><td>minfreelen</td><td>%d</td></tr></table>>];\n", t.prefix, t.prefix, t.minfreelen)
|
||||
|
||||
printEdge(t, t.lo)
|
||||
printEdge(t, t.hi)
|
||||
|
||||
return true
|
||||
})
|
||||
fmt.Fprintln(&buf, "}")
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
|
@ -441,6 +353,16 @@ func (db *DB) Root() netip.Prefix {
|
|||
return db.provisions.prefix
|
||||
}
|
||||
|
||||
//func (db *DB) Provision(p netip.Prefix) error {
|
||||
// db.Lock()
|
||||
// defer db.Unlock()
|
||||
// p = p.Masked()
|
||||
// if db.provisions.Provision(p) == nil {
|
||||
// return fmt.Errorf("Overlapping provisions: %s", p)
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
|
||||
type Treemap struct {
|
||||
ViewBox Rect
|
||||
Regions []Region
|
||||
|
@ -492,10 +414,8 @@ func placeIntoRect(p, into netip.Prefix, r Rect) Rect {
|
|||
if p.Bits() < into.Bits() {
|
||||
panic("unreachable")
|
||||
}
|
||||
lo, hi := SplitPrefix(into)
|
||||
|
||||
switch {
|
||||
case lo.Overlaps(p):
|
||||
if lo := SplitPrefixLo(into); lo.Overlaps(p) {
|
||||
var subr Rect
|
||||
if r.W > r.H {
|
||||
subr.Y = r.Y
|
||||
|
@ -509,7 +429,7 @@ func placeIntoRect(p, into netip.Prefix, r Rect) Rect {
|
|||
subr.W = r.W
|
||||
}
|
||||
return placeIntoRect(p, lo, subr)
|
||||
case hi.Overlaps(p):
|
||||
} else if hi := SplitPrefixHi(into); hi.Overlaps(p) {
|
||||
var subr Rect
|
||||
if r.W > r.H {
|
||||
subr.Y = r.Y
|
||||
|
@ -523,26 +443,23 @@ func placeIntoRect(p, into netip.Prefix, r Rect) Rect {
|
|||
subr.W = r.W
|
||||
}
|
||||
return placeIntoRect(p, hi, subr)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
//func (db *DB) Provision(p netip.Prefix) error {
|
||||
// db.Lock()
|
||||
// defer db.Unlock()
|
||||
// p = p.Masked()
|
||||
// if db.provisions.Provision(p) == nil {
|
||||
// return fmt.Errorf("Overlapping provisions: %s", p)
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func SplitPrefix(p netip.Prefix) (netip.Prefix, netip.Prefix) {
|
||||
func prepareSplit(p netip.Prefix) netip.Prefix {
|
||||
if p.IsSingleIP() {
|
||||
log.Fatal("can't split single ip prefix", p)
|
||||
panic(fmt.Sprint("can't split single ip prefix ", p))
|
||||
}
|
||||
p = p.Masked()
|
||||
return p.Masked()
|
||||
}
|
||||
func SplitPrefixLo(p netip.Prefix) netip.Prefix {
|
||||
p = prepareSplit(p)
|
||||
return netip.PrefixFrom(p.Addr(), p.Bits()+1)
|
||||
}
|
||||
func SplitPrefixHi(p netip.Prefix) netip.Prefix {
|
||||
p = prepareSplit(p)
|
||||
|
||||
bs := p.Addr().AsSlice()
|
||||
off := p.Bits() / 8
|
||||
|
@ -552,9 +469,8 @@ func SplitPrefix(p netip.Prefix) (netip.Prefix, netip.Prefix) {
|
|||
if !ok {
|
||||
log.Fatal("can't use slice as addr", bs)
|
||||
}
|
||||
|
||||
lo := netip.PrefixFrom(p.Addr(), p.Bits()+1)
|
||||
hi := netip.PrefixFrom(hiaddr, p.Bits()+1)
|
||||
|
||||
return lo, hi
|
||||
return netip.PrefixFrom(hiaddr, p.Bits()+1)
|
||||
}
|
||||
func splitPrefix(p netip.Prefix) (netip.Prefix, netip.Prefix) {
|
||||
return SplitPrefixLo(p), SplitPrefixHi(p)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ipalloc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
@ -15,13 +16,51 @@ func TestSplitPrefix(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range tests {
|
||||
lo, hi := SplitPrefix(netip.MustParsePrefix(test.in))
|
||||
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
|
||||
|
@ -42,10 +81,10 @@ func TestTreeAlloc(t *testing.T) {
|
|||
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) {
|
||||
|
@ -97,7 +136,9 @@ func TestTreeDealloc(t *testing.T) {
|
|||
|
||||
first := root.Alloc(2).prefix
|
||||
root.Alloc(2)
|
||||
root.Dealloc(first)
|
||||
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())
|
||||
|
@ -106,7 +147,7 @@ func TestTreeDealloc(t *testing.T) {
|
|||
t.Fatal(first)
|
||||
}
|
||||
}
|
||||
func TestFindBest(t *testing.T) {
|
||||
func TestFindBestPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
bits int
|
||||
best string
|
||||
|
@ -136,7 +177,7 @@ func TestFindBest(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range best {
|
||||
best := root.FindBest(netip.MustParsePrefix(test[0]))
|
||||
best := root.FindBestPrefix(netip.MustParsePrefix(test[0]))
|
||||
want := netip.MustParsePrefix(test[1])
|
||||
|
||||
if best.prefix != want {
|
||||
|
@ -167,6 +208,7 @@ func TestInsert(t *testing.T) {
|
|||
t.Fatal("unable to find", p, "in", root)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
p := netip.MustParsePrefix(test)
|
||||
insert := root.Insert(p)
|
||||
|
@ -175,3 +217,88 @@ func TestInsert(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package ipalloc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
Append(Operation) error
|
||||
Operations() (Scanner, error)
|
||||
}
|
||||
|
||||
type Scanner interface {
|
||||
Next() bool
|
||||
Operation() Operation
|
||||
Err() error
|
||||
}
|
||||
|
||||
type Operation interface {
|
||||
Prefix() netip.Prefix
|
||||
}
|
||||
|
||||
type op struct {
|
||||
prefix netip.Prefix
|
||||
}
|
||||
|
||||
func (o op) Prefix() netip.Prefix {
|
||||
return o.prefix
|
||||
}
|
||||
|
||||
type OpProvision struct{ op }
|
||||
|
||||
type OpAllocation struct{ op }
|
||||
|
||||
type OpDeallocation struct{ op }
|
||||
|
||||
type Memstore struct {
|
||||
ops []Operation
|
||||
}
|
||||
|
||||
func (m *Memstore) Append(op Operation) error {
|
||||
m.ops = append(m.ops, op)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memstore) Operations() (Scanner, error) {
|
||||
return &memscanner{opQueue: slices.Clip(m.ops)}, nil
|
||||
}
|
||||
|
||||
type memscanner struct {
|
||||
cur Operation
|
||||
opQueue []Operation
|
||||
}
|
||||
|
||||
func (m *memscanner) Next() bool {
|
||||
if len(m.opQueue) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
m.opQueue, m.cur = m.opQueue[1:], m.opQueue[0]
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *memscanner) Operation() Operation {
|
||||
return m.cur
|
||||
}
|
||||
|
||||
func (m *memscanner) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Filestore struct {
|
||||
filepath string
|
||||
}
|
||||
|
||||
func (fs *Filestore) Append(op Operation) error {
|
||||
f, err := os.OpenFile(fs.filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(f, op)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fs *Filestore) Operations() (Scanner, error) {
|
||||
f, err := os.OpenFile(fs.filepath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.Flock(int(f.Fd()), syscall.LOCK_SH); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newFileScanner(f), nil
|
||||
}
|
||||
|
||||
type fileScanner struct {
|
||||
err error
|
||||
cur Operation
|
||||
f *os.File
|
||||
s bufio.Scanner
|
||||
}
|
||||
|
||||
func newFileScanner(f *os.File) *fileScanner {
|
||||
return &fileScanner{
|
||||
err: nil,
|
||||
cur: nil,
|
||||
f: f,
|
||||
s: *bufio.NewScanner(f),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fileScanner) Next() bool {
|
||||
if f.f == nil {
|
||||
return false
|
||||
}
|
||||
if !f.s.Scan() {
|
||||
f.err = f.s.Err()
|
||||
if err := f.f.Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
log.Println(f.s.Text())
|
||||
f.cur = nil // TODO: parse
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *fileScanner) Operation() Operation {
|
||||
return f.cur
|
||||
}
|
||||
|
||||
func (f *fileScanner) Err() error {
|
||||
return f.err
|
||||
}
|
Loading…
Reference in New Issue