Skip to content

Commit 2bf9657

Browse files
committed
now with generic value
1 parent 4f0c06c commit 2bf9657

9 files changed

+318
-341
lines changed

CHANGES.md

-25
This file was deleted.

README.md

+15-16
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,39 @@
88

99
## !!! ATTENTION
1010

11-
API currently not stable!
11+
API is currently not stable!
1212

1313
## Overview
1414

1515
`package cidrtree` is a datastructure for IP routing tables (IPv4/IPv6) with fast lookup (longest prefix match).
1616

1717
The implementation is based on treaps, which have been augmented here for CIDRs. Treaps are randomized, self-balancing binary search trees. Due to the nature of treaps the lookups (readers) and the update (writer) can be easily decoupled. This is the perfect fit for a software router or firewall.
1818

19-
This package is a specialization of the more generic [interval package] of the same author,
20-
but explicit for CIDRs. It has a narrow focus with a specialized API for IP routing tables.
19+
This package is a specialization of the more generic [interval package] of the same author, but explicit for CIDRs. It has a narrow focus with a specialized API for IP routing tables.
2120

2221
[interval package]: https://github.com/gaissmai/interval
2322

2423
## API
2524
```go
2625
import "github.com/gaissmai/cidrtree"
2726

28-
type Table struct { // Has unexported fields. }
27+
type Table[V any] struct { // Has unexported fields. }
2928
Table is an IPv4 and IPv6 routing table. The zero value is ready to use.
3029

31-
func (t Table) Lookup(ip netip.Addr) (lpm netip.Prefix, value any, ok bool)
32-
func (t Table) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value any, ok bool)
30+
func (t Table[V]) Lookup(ip netip.Addr) (lpm netip.Prefix, value V, ok bool)
31+
func (t Table[V]) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value V, ok bool)
3332

34-
func (t *Table) Insert(pfx netip.Prefix, val any)
35-
func (t *Table) Delete(pfx netip.Prefix) bool
36-
func (t *Table) Union(other Table)
33+
func (t *Table[V]) Insert(pfx netip.Prefix, value V)
34+
func (t *Table[V]) Delete(pfx netip.Prefix) bool
35+
func (t *Table[V]) Union(other Table[V])
3736

38-
func (t Table) InsertImmutable(pfx netip.Prefix, val any) *Table
39-
func (t Table) DeleteImmutable(pfx netip.Prefix) (*Table, bool)
40-
func (t Table) UnionImmutable(other Table) *Table
41-
func (t Table) Clone() *Table
37+
func (t Table[V]) InsertImmutable(pfx netip.Prefix, value V) *Table[V]
38+
func (t Table[V]) DeleteImmutable(pfx netip.Prefix) (*Table[V], bool)
39+
func (t Table[V]) UnionImmutable(other Table[V]) *Table[V]
40+
func (t Table[V]) Clone() *Table[V]
4241

43-
func (t Table) String() string
44-
func (t Table) Fprint(w io.Writer) error
42+
func (t Table[V]) String() string
43+
func (t Table[V]) Fprint(w io.Writer) error
4544

46-
func (t Table) Walk(cb func(pfx netip.Prefix, val any) bool)
45+
func (t Table[V]) Walk(cb func(pfx netip.Prefix, value V) bool)
4746
```

bench_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var intMap = map[int]string{
2525

2626
func BenchmarkLookup(b *testing.B) {
2727
for k := 1; k <= 100_000; k *= 10 {
28-
rt := new(cidrtree.Table)
28+
rt := new(cidrtree.Table[any])
2929
cidrs := shuffleFullTable(k)
3030
for _, cidr := range cidrs {
3131
rt.Insert(cidr, nil)
@@ -45,7 +45,7 @@ func BenchmarkLookup(b *testing.B) {
4545

4646
func BenchmarkLookupPrefix(b *testing.B) {
4747
for k := 1; k <= 100_000; k *= 10 {
48-
rt := new(cidrtree.Table)
48+
rt := new(cidrtree.Table[any])
4949
cidrs := shuffleFullTable(k)
5050
for _, cidr := range cidrs {
5151
rt.Insert(cidr, nil)
@@ -64,7 +64,7 @@ func BenchmarkLookupPrefix(b *testing.B) {
6464

6565
func BenchmarkClone(b *testing.B) {
6666
for k := 1; k <= 100_000; k *= 10 {
67-
rt := new(cidrtree.Table)
67+
rt := new(cidrtree.Table[any])
6868
for _, cidr := range shuffleFullTable(k) {
6969
rt.Insert(cidr, nil)
7070
}
@@ -80,7 +80,7 @@ func BenchmarkClone(b *testing.B) {
8080

8181
func BenchmarkInsert(b *testing.B) {
8282
for k := 1; k <= 100_000; k *= 10 {
83-
rt := new(cidrtree.Table)
83+
rt := new(cidrtree.Table[any])
8484
cidrs := shuffleFullTable(k)
8585
for _, cidr := range cidrs {
8686
rt.Insert(cidr, nil)
@@ -99,7 +99,7 @@ func BenchmarkInsert(b *testing.B) {
9999

100100
func BenchmarkDelete(b *testing.B) {
101101
for k := 1; k <= 100_000; k *= 10 {
102-
rt := new(cidrtree.Table)
102+
rt := new(cidrtree.Table[any])
103103
cidrs := shuffleFullTable(k)
104104
for _, cidr := range cidrs {
105105
rt.Insert(cidr, nil)

debug.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
// fprintBST writes a horizontal tree diagram of the binary search tree (BST) to w.
1111
//
1212
// Note: This is for debugging purposes only.
13-
func (t Table) fprintBST(w io.Writer) error {
13+
func (t Table[V]) fprintBST(w io.Writer) error {
1414
if t.root4 != nil {
1515
if _, err := fmt.Fprint(w, "R "); err != nil {
1616
return err
@@ -33,7 +33,7 @@ func (t Table) fprintBST(w io.Writer) error {
3333
}
3434

3535
// fprintBST recursive helper.
36-
func (n *node) fprintBST(w io.Writer, pad string) error {
36+
func (n *node[V]) fprintBST(w io.Writer, pad string) error {
3737
// stringify this node
3838
_, err := fmt.Fprintf(w, "%v [prio:%.4g] [subtree maxUpper: %v]\n", n.cidr, float64(n.prio)/math.MaxUint64, n.maxUpper.cidr)
3939
if err != nil {
@@ -80,7 +80,7 @@ func (n *node) fprintBST(w io.Writer, pad string) error {
8080
// If the skip function is not nil, a true return value defines which nodes must be skipped in the statistics.
8181
//
8282
// Note: This is for debugging and testing purposes only during development.
83-
func (t Table) statistics(skip func(netip.Prefix, any, int) bool) (size int, maxDepth int, average, deviation float64) {
83+
func (t Table[V]) statistics(skip func(netip.Prefix, any, int) bool) (size int, maxDepth int, average, deviation float64) {
8484
// key is depth, value is the sum of nodes with this depth
8585
depths := make(map[int]int)
8686

@@ -120,7 +120,7 @@ func (t Table) statistics(skip func(netip.Prefix, any, int) bool) (size int, max
120120
}
121121

122122
// walkWithDepth in ascending prefix order.
123-
func (n *node) walkWithDepth(cb func(netip.Prefix, any, int) bool, depth int) bool {
123+
func (n *node[V]) walkWithDepth(cb func(netip.Prefix, any, int) bool, depth int) bool {
124124
if n == nil {
125125
return true
126126
}

example_test.go

+63-60
Original file line numberDiff line numberDiff line change
@@ -8,73 +8,76 @@ import (
88
"github.com/gaissmai/cidrtree"
99
)
1010

11-
func addr(s string) netip.Addr {
11+
func mustAddr(s string) netip.Addr {
1212
return netip.MustParseAddr(s)
1313
}
1414

15-
func prfx(s string) netip.Prefix {
15+
func mustPfx(s string) netip.Prefix {
1616
return netip.MustParsePrefix(s)
1717
}
1818

19-
var input = []netip.Prefix{
20-
prfx("fe80::/10"),
21-
prfx("172.16.0.0/12"),
22-
prfx("10.0.0.0/24"),
23-
prfx("::1/128"),
24-
prfx("192.168.0.0/16"),
25-
prfx("10.0.0.0/8"),
26-
prfx("::/0"),
27-
prfx("10.0.1.0/24"),
28-
prfx("169.254.0.0/16"),
29-
prfx("2000::/3"),
30-
prfx("2001:db8::/32"),
31-
prfx("127.0.0.0/8"),
32-
prfx("127.0.0.1/32"),
33-
prfx("192.168.1.0/24"),
19+
var input = []struct {
20+
cidr netip.Prefix
21+
nextHop netip.Addr
22+
}{
23+
{mustPfx("fe80::/10"), mustAddr("::1%lo")},
24+
{mustPfx("172.16.0.0/12"), mustAddr("8.8.8.8")},
25+
{mustPfx("10.0.0.0/24"), mustAddr("8.8.8.8")},
26+
{mustPfx("::1/128"), mustAddr("::1%eth0")},
27+
{mustPfx("192.168.0.0/16"), mustAddr("9.9.9.9")},
28+
{mustPfx("10.0.0.0/8"), mustAddr("9.9.9.9")},
29+
{mustPfx("::/0"), mustAddr("2001:db8::1")},
30+
{mustPfx("10.0.1.0/24"), mustAddr("10.0.0.0")},
31+
{mustPfx("169.254.0.0/16"), mustAddr("10.0.0.0")},
32+
{mustPfx("2000::/3"), mustAddr("2001:db8::1")},
33+
{mustPfx("2001:db8::/32"), mustAddr("2001:db8::1")},
34+
{mustPfx("127.0.0.0/8"), mustAddr("127.0.0.1")},
35+
{mustPfx("127.0.0.1/32"), mustAddr("127.0.0.1")},
36+
{mustPfx("192.168.1.0/24"), mustAddr("127.0.0.1")},
3437
}
3538

3639
func ExampleTable_Lookup() {
37-
rtbl := new(cidrtree.Table)
38-
for _, cidr := range input {
39-
rtbl.Insert(cidr, nil)
40+
rtbl := new(cidrtree.Table[netip.Addr])
41+
for _, item := range input {
42+
rtbl.Insert(item.cidr, item.nextHop)
4043
}
4144
rtbl.Fprint(os.Stdout)
4245

4346
fmt.Println()
4447

45-
ip := addr("42.0.0.0")
48+
ip := mustAddr("42.0.0.0")
4649
lpm, value, ok := rtbl.Lookup(ip)
47-
fmt.Printf("Lookup: %-20v lpm: %-15v value: %v, ok: %v\n", ip, lpm, value, ok)
50+
fmt.Printf("Lookup: %-20v lpm: %-15v value: %11v, ok: %v\n", ip, lpm, value, ok)
4851

49-
ip = addr("10.0.1.17")
52+
ip = mustAddr("10.0.1.17")
5053
lpm, value, ok = rtbl.Lookup(ip)
51-
fmt.Printf("Lookup: %-20v lpm: %-15v value: %v, ok: %v\n", ip, lpm, value, ok)
54+
fmt.Printf("Lookup: %-20v lpm: %-15v value: %11v, ok: %v\n", ip, lpm, value, ok)
5255

53-
ip = addr("2001:7c0:3100:1::111")
56+
ip = mustAddr("2001:7c0:3100:1::111")
5457
lpm, value, ok = rtbl.Lookup(ip)
55-
fmt.Printf("Lookup: %-20v lpm: %-15v value: %v, ok: %v\n", ip, lpm, value, ok)
58+
fmt.Printf("Lookup: %-20v lpm: %-15v value: %11v, ok: %v\n", ip, lpm, value, ok)
5659

5760
// Output:
5861
// ▼
59-
// ├─ 10.0.0.0/8 (<nil>)
60-
// │ ├─ 10.0.0.0/24 (<nil>)
61-
// │ └─ 10.0.1.0/24 (<nil>)
62-
// ├─ 127.0.0.0/8 (<nil>)
63-
// │ └─ 127.0.0.1/32 (<nil>)
64-
// ├─ 169.254.0.0/16 (<nil>)
65-
// ├─ 172.16.0.0/12 (<nil>)
66-
// └─ 192.168.0.0/16 (<nil>)
67-
// └─ 192.168.1.0/24 (<nil>)
62+
// ├─ 10.0.0.0/8 (9.9.9.9)
63+
// │ ├─ 10.0.0.0/24 (8.8.8.8)
64+
// │ └─ 10.0.1.0/24 (10.0.0.0)
65+
// ├─ 127.0.0.0/8 (127.0.0.1)
66+
// │ └─ 127.0.0.1/32 (127.0.0.1)
67+
// ├─ 169.254.0.0/16 (10.0.0.0)
68+
// ├─ 172.16.0.0/12 (8.8.8.8)
69+
// └─ 192.168.0.0/16 (9.9.9.9)
70+
// └─ 192.168.1.0/24 (127.0.0.1)
6871
// ▼
69-
// └─ ::/0 (<nil>)
70-
// ├─ ::1/128 (<nil>)
71-
// ├─ 2000::/3 (<nil>)
72-
// │ └─ 2001:db8::/32 (<nil>)
73-
// └─ fe80::/10 (<nil>)
72+
// └─ ::/0 (2001:db8::1)
73+
// ├─ ::1/128 (::1%eth0)
74+
// ├─ 2000::/3 (2001:db8::1)
75+
// │ └─ 2001:db8::/32 (2001:db8::1)
76+
// └─ fe80::/10 (::1%lo)
7477
//
75-
// Lookup: 42.0.0.0 lpm: invalid Prefix value: <nil>, ok: false
76-
// Lookup: 10.0.1.17 lpm: 10.0.1.0/24 value: <nil>, ok: true
77-
// Lookup: 2001:7c0:3100:1::111 lpm: 2000::/3 value: <nil>, ok: true
78+
// Lookup: 42.0.0.0 lpm: invalid Prefix value: invalid IP, ok: false
79+
// Lookup: 10.0.1.17 lpm: 10.0.1.0/24 value: 10.0.0.0, ok: true
80+
// Lookup: 2001:7c0:3100:1::111 lpm: 2000::/3 value: 2001:db8::1, ok: true
7881
}
7982

8083
func ExampleTable_Walk() {
@@ -83,25 +86,25 @@ func ExampleTable_Walk() {
8386
return true
8487
}
8588

86-
rtbl := new(cidrtree.Table)
87-
for _, cidr := range input {
88-
rtbl.Insert(cidr, nil)
89+
rtbl := new(cidrtree.Table[any])
90+
for _, item := range input {
91+
rtbl.Insert(item.cidr, item.nextHop)
8992
}
9093
rtbl.Walk(cb)
9194

9295
// Output:
93-
// 10.0.0.0/8 (<nil>)
94-
// 10.0.0.0/24 (<nil>)
95-
// 10.0.1.0/24 (<nil>)
96-
// 127.0.0.0/8 (<nil>)
97-
// 127.0.0.1/32 (<nil>)
98-
// 169.254.0.0/16 (<nil>)
99-
// 172.16.0.0/12 (<nil>)
100-
// 192.168.0.0/16 (<nil>)
101-
// 192.168.1.0/24 (<nil>)
102-
// ::/0 (<nil>)
103-
// ::1/128 (<nil>)
104-
// 2000::/3 (<nil>)
105-
// 2001:db8::/32 (<nil>)
106-
// fe80::/10 (<nil>)
96+
// 10.0.0.0/8 (9.9.9.9)
97+
// 10.0.0.0/24 (8.8.8.8)
98+
// 10.0.1.0/24 (10.0.0.0)
99+
// 127.0.0.0/8 (127.0.0.1)
100+
// 127.0.0.1/32 (127.0.0.1)
101+
// 169.254.0.0/16 (10.0.0.0)
102+
// 172.16.0.0/12 (8.8.8.8)
103+
// 192.168.0.0/16 (9.9.9.9)
104+
// 192.168.1.0/24 (127.0.0.1)
105+
// ::/0 (2001:db8::1)
106+
// ::1/128 (::1%eth0)
107+
// 2000::/3 (2001:db8::1)
108+
// 2001:db8::/32 (2001:db8::1)
109+
// fe80::/10 (::1%lo)
107110
}

0 commit comments

Comments
 (0)