Documentation ¶
Overview ¶
Package cidrtree implements fast lookup (longest-prefix-match) for IP routing tables (IPv4/IPv6).
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.
Index ¶
- type Table
- func (t Table[V]) Clone() *Table[V]
- func (t *Table[V]) Delete(pfx netip.Prefix) bool
- func (t Table[V]) DeleteImmutable(pfx netip.Prefix) (*Table[V], bool)
- func (t Table[V]) Fprint(w io.Writer) error
- func (t *Table[V]) Insert(pfx netip.Prefix, value V)
- func (t Table[V]) InsertImmutable(pfx netip.Prefix, value V) *Table[V]
- func (t Table[V]) Lookup(ip netip.Addr) (lpm netip.Prefix, value V, ok bool)
- func (t Table[V]) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value V, ok bool)
- func (t Table[V]) String() string
- func (t *Table[V]) Union(other Table[V])
- func (t Table[V]) UnionImmutable(other Table[V]) *Table[V]
- func (t Table[V]) Walk(cb func(pfx netip.Prefix, value V) bool)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Table ¶ added in v0.2.0
type Table[V any] struct { // contains filtered or unexported fields }
Table is an IPv4 and IPv6 routing table. The zero value is ready to use.
func (*Table[V]) Delete ¶ added in v0.2.0
Delete removes the prefix from table, returns true if it exists, false otherwise.
func (Table[V]) DeleteImmutable ¶ added in v0.3.0
DeleteImmutable removes the prefix if it exists, returns the new table and true, false if not found.
func (Table[V]) Fprint ¶ added in v0.2.0
Fprint writes an ordered CIDR tree diagram to w. If w is nil, Fprint panics.
The order from top to bottom is in ascending order of the start address and the subtree structure is determined by the CIDRs coverage.
func (*Table[V]) Insert ¶ added in v0.2.0
Insert adds pfx to the routing table with value of generic type V. If pfx is already present in the table, its value is set to the new value.
func (Table[V]) InsertImmutable ¶ added in v0.3.0
InsertImmutable adds pfx to the table with value of generic type V, returning a new table. If pfx is already present in the table, its value is set to the new value.
func (Table[V]) Lookup ¶ added in v0.3.0
Lookup returns the longest-prefix-match (lpm) for given ip. If the ip isn't covered by any CIDR, the zero value and false is returned.
Lookup does not allocate memory.
Example ¶
package main import ( "fmt" "net/netip" "os" "github.com/gaissmai/cidrtree" ) func mustAddr(s string) netip.Addr { return netip.MustParseAddr(s) } func mustPfx(s string) netip.Prefix { return netip.MustParsePrefix(s) } var input = []struct { cidr netip.Prefix nextHop netip.Addr }{ {mustPfx("fe80::/10"), mustAddr("::1%lo")}, {mustPfx("172.16.0.0/12"), mustAddr("8.8.8.8")}, {mustPfx("10.0.0.0/24"), mustAddr("8.8.8.8")}, {mustPfx("::1/128"), mustAddr("::1%eth0")}, {mustPfx("192.168.0.0/16"), mustAddr("9.9.9.9")}, {mustPfx("10.0.0.0/8"), mustAddr("9.9.9.9")}, {mustPfx("::/0"), mustAddr("2001:db8::1")}, {mustPfx("10.0.1.0/24"), mustAddr("10.0.0.0")}, {mustPfx("169.254.0.0/16"), mustAddr("10.0.0.0")}, {mustPfx("2000::/3"), mustAddr("2001:db8::1")}, {mustPfx("2001:db8::/32"), mustAddr("2001:db8::1")}, {mustPfx("127.0.0.0/8"), mustAddr("127.0.0.1")}, {mustPfx("127.0.0.1/32"), mustAddr("127.0.0.1")}, {mustPfx("192.168.1.0/24"), mustAddr("127.0.0.1")}, } func main() { rtbl := new(cidrtree.Table[netip.Addr]) for _, item := range input { rtbl.Insert(item.cidr, item.nextHop) } rtbl.Fprint(os.Stdout) fmt.Println() ip := mustAddr("42.0.0.0") lpm, value, ok := rtbl.Lookup(ip) fmt.Printf("Lookup: %-20v lpm: %-15v value: %11v, ok: %v\n", ip, lpm, value, ok) ip = mustAddr("10.0.1.17") lpm, value, ok = rtbl.Lookup(ip) fmt.Printf("Lookup: %-20v lpm: %-15v value: %11v, ok: %v\n", ip, lpm, value, ok) ip = mustAddr("2001:7c0:3100:1::111") lpm, value, ok = rtbl.Lookup(ip) fmt.Printf("Lookup: %-20v lpm: %-15v value: %11v, ok: %v\n", ip, lpm, value, ok) }
Output: ▼ ├─ 10.0.0.0/8 (9.9.9.9) │ ├─ 10.0.0.0/24 (8.8.8.8) │ └─ 10.0.1.0/24 (10.0.0.0) ├─ 127.0.0.0/8 (127.0.0.1) │ └─ 127.0.0.1/32 (127.0.0.1) ├─ 169.254.0.0/16 (10.0.0.0) ├─ 172.16.0.0/12 (8.8.8.8) └─ 192.168.0.0/16 (9.9.9.9) └─ 192.168.1.0/24 (127.0.0.1) ▼ └─ ::/0 (2001:db8::1) ├─ ::1/128 (::1%eth0) ├─ 2000::/3 (2001:db8::1) │ └─ 2001:db8::/32 (2001:db8::1) └─ fe80::/10 (::1%lo) Lookup: 42.0.0.0 lpm: invalid Prefix value: invalid IP, ok: false Lookup: 10.0.1.17 lpm: 10.0.1.0/24 value: 10.0.0.0, ok: true Lookup: 2001:7c0:3100:1::111 lpm: 2000::/3 value: 2001:db8::1, ok: true
func (Table[V]) LookupPrefix ¶ added in v0.3.0
LookupPrefix returns the longest-prefix-match (lpm) for given prefix. If the prefix isn't equal or covered by any CIDR in the table, the zero value and false is returned.
LookupPrefix does not allocate memory.
func (Table[V]) String ¶ added in v0.2.0
String returns a hierarchical tree diagram of the ordered CIDRs as string, just a wrapper for [Tree.Fprint].
func (*Table[V]) Union ¶ added in v0.2.0
Union combines two tables, changing the receiver table. If there are duplicate entries, the value is taken from the other table.
func (Table[V]) UnionImmutable ¶ added in v0.3.0
UnionImmutable combines any two tables immutable and returns the combined table. If there are duplicate entries, the value is taken from the other table.
func (Table[V]) Walk ¶ added in v0.2.0
Walk iterates the cidrtree in ascending order. The callback function is called with the prefix and value of the respective node and the depth in the tree. If callback returns `false`, the iteration is aborted.
Example ¶
package main import ( "fmt" "net/netip" "github.com/gaissmai/cidrtree" ) func mustAddr(s string) netip.Addr { return netip.MustParseAddr(s) } func mustPfx(s string) netip.Prefix { return netip.MustParsePrefix(s) } var input = []struct { cidr netip.Prefix nextHop netip.Addr }{ {mustPfx("fe80::/10"), mustAddr("::1%lo")}, {mustPfx("172.16.0.0/12"), mustAddr("8.8.8.8")}, {mustPfx("10.0.0.0/24"), mustAddr("8.8.8.8")}, {mustPfx("::1/128"), mustAddr("::1%eth0")}, {mustPfx("192.168.0.0/16"), mustAddr("9.9.9.9")}, {mustPfx("10.0.0.0/8"), mustAddr("9.9.9.9")}, {mustPfx("::/0"), mustAddr("2001:db8::1")}, {mustPfx("10.0.1.0/24"), mustAddr("10.0.0.0")}, {mustPfx("169.254.0.0/16"), mustAddr("10.0.0.0")}, {mustPfx("2000::/3"), mustAddr("2001:db8::1")}, {mustPfx("2001:db8::/32"), mustAddr("2001:db8::1")}, {mustPfx("127.0.0.0/8"), mustAddr("127.0.0.1")}, {mustPfx("127.0.0.1/32"), mustAddr("127.0.0.1")}, {mustPfx("192.168.1.0/24"), mustAddr("127.0.0.1")}, } func main() { cb := func(p netip.Prefix, val any) bool { fmt.Printf("%v (%v)\n", p, val) return true } rtbl := new(cidrtree.Table[any]) for _, item := range input { rtbl.Insert(item.cidr, item.nextHop) } rtbl.Walk(cb) }
Output: 10.0.0.0/8 (9.9.9.9) 10.0.0.0/24 (8.8.8.8) 10.0.1.0/24 (10.0.0.0) 127.0.0.0/8 (127.0.0.1) 127.0.0.1/32 (127.0.0.1) 169.254.0.0/16 (10.0.0.0) 172.16.0.0/12 (8.8.8.8) 192.168.0.0/16 (9.9.9.9) 192.168.1.0/24 (127.0.0.1) ::/0 (2001:db8::1) ::1/128 (::1%eth0) 2000::/3 (2001:db8::1) 2001:db8::/32 (2001:db8::1) fe80::/10 (::1%lo)