cidrtree

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 29, 2023 License: MIT Imports: 7 Imported by: 7

README

package cidrtree

Go Reference GitHub release (latest SemVer) CI Coverage Status Stand With Ukraine

!!! ATTENTION, API HAS CHANGED

The API has changed from v0.1.4 to v0.2.0.

Overview

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

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.

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.

API

  import "github.com/gaissmai/cidrtree"

  type Table struct { // Has unexported fields.  }
    Table is an IPv4 and IPv6 routing table. The zero value is ready to use.

  func (t Table) LookupIP(ip netip.Addr) (lpm netip.Prefix, value any, ok bool)
  func (t Table) LookupCIDR(pfx netip.Prefix) (lpm netip.Prefix, value any, ok bool)

  func (t Table) Insert(pfx netip.Prefix, val any) *Table
  func (t Table) Delete(cidr netip.Prefix) (*Table, bool)
  func (t Table) Union(other *Table) *Table
  func (t Table) Clone() *Table

  func (t *Table) InsertMutable(pfx netip.Prefix, val any)
  func (t *Table) DeleteMutable(cidr netip.Prefix) bool
  func (t *Table) UnionMutable(other *Table)

  func (t Table) String() string
  func (t Table) Fprint(w io.Writer) error

  func (t Table) Walk(cb func(pfx netip.Prefix, val any) bool)

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

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 struct {
	// contains filtered or unexported fields
}

Table is an IPv4 and IPv6 routing table. The zero value is ready to use.

func (Table) Clone added in v0.2.0

func (t Table) Clone() *Table

Clone, deep cloning of the routing table.

func (Table) Delete added in v0.2.0

func (t Table) Delete(cidr netip.Prefix) (*Table, bool)

Delete removes the cdir if it exists, returns the new table and true, false if not found.

func (*Table) DeleteMutable added in v0.2.0

func (t *Table) DeleteMutable(cidr netip.Prefix) bool

DeleteMutable removes the cidr from table, returns true if it exists, false otherwise. If the original table does not need to be preserved then this is much faster than the immutable delete.

func (Table) Fprint added in v0.2.0

func (t Table) Fprint(w io.Writer) error

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.

Example
package main

import (
	"net/netip"
	"os"

	"github.com/gaissmai/cidrtree"
)

var input = []netip.Prefix{
	netip.MustParsePrefix("fe80::/10"),
	netip.MustParsePrefix("172.16.0.0/12"),
	netip.MustParsePrefix("10.0.0.0/24"),
	netip.MustParsePrefix("::1/128"),
	netip.MustParsePrefix("192.168.0.0/16"),
	netip.MustParsePrefix("10.0.0.0/8"),
	netip.MustParsePrefix("::/0"),
	netip.MustParsePrefix("10.0.1.0/24"),
	netip.MustParsePrefix("169.254.0.0/16"),
	netip.MustParsePrefix("2000::/3"),
	netip.MustParsePrefix("2001:db8::/32"),
	netip.MustParsePrefix("127.0.0.0/8"),
	netip.MustParsePrefix("127.0.0.1/32"),
	netip.MustParsePrefix("192.168.1.0/24"),
}

func main() {
	rtbl := new(cidrtree.Table)
	for _, cidr := range input {
		rtbl.InsertMutable(cidr, nil)
	}
	rtbl.Fprint(os.Stdout)

}
Output:

▼
├─ 10.0.0.0/8 (<nil>)
│  ├─ 10.0.0.0/24 (<nil>)
│  └─ 10.0.1.0/24 (<nil>)
├─ 127.0.0.0/8 (<nil>)
│  └─ 127.0.0.1/32 (<nil>)
├─ 169.254.0.0/16 (<nil>)
├─ 172.16.0.0/12 (<nil>)
└─ 192.168.0.0/16 (<nil>)
   └─ 192.168.1.0/24 (<nil>)
▼
└─ ::/0 (<nil>)
   ├─ ::1/128 (<nil>)
   ├─ 2000::/3 (<nil>)
   │  └─ 2001:db8::/32 (<nil>)
   └─ fe80::/10 (<nil>)

func (Table) Insert added in v0.2.0

func (t Table) Insert(pfx netip.Prefix, val any) *Table

Insert adds pfx to the table with value val, returning a new table. If pfx is already present in the table, its value is set to val.

func (*Table) InsertMutable added in v0.2.0

func (t *Table) InsertMutable(pfx netip.Prefix, val any)

InsertMutable adds pfx to the table with value val, changing the original table. If pfx is already present in the table, its value is set to val.

func (Table) LookupCIDR added in v0.2.0

func (t Table) LookupCIDR(pfx netip.Prefix) (lpm netip.Prefix, value any, ok bool)

LookupCIDR 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.

LookupCIDR does not allocate memory.

example:

▼
├─ 10.0.0.0/8
│  ├─ 10.0.0.0/24
│  └─ 10.0.1.0/24
├─ 127.0.0.0/8
│  └─ 127.0.0.1/32
├─ 169.254.0.0/16
├─ 172.16.0.0/12
└─ 192.168.0.0/16
   └─ 192.168.1.0/24
▼
└─ ::/0
   ├─ ::1/128
   ├─ 2000::/3
   │  └─ 2001:db8::/32
   ├─ fc00::/7
   ├─ fe80::/10
   └─ ff00::/8

    rtbl.LookupCIDR(42.0.0.0/8)         returns (netip.Prefix{}, <nil>,  false)
    rtbl.LookupCIDR(10.0.1.0/29)        returns (10.0.1.0/24,    <value>, true)
    rtbl.LookupCIDR(192.168.0.0/16)     returns (192.168.0.0/16, <value>, true)
    rtbl.LookupCIDR(2001:7c0:3100::/40) returns (2000::/3,       <value>, true)

func (Table) LookupIP added in v0.2.0

func (t Table) LookupIP(ip netip.Addr) (lpm netip.Prefix, value any, ok bool)

LookupIP 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.

LookupIP does not allocate memory.

example:

▼
├─ 10.0.0.0/8
│  ├─ 10.0.0.0/24
│  └─ 10.0.1.0/24
├─ 127.0.0.0/8
│  └─ 127.0.0.1/32
├─ 169.254.0.0/16
├─ 172.16.0.0/12
└─ 192.168.0.0/16
   └─ 192.168.1.0/24
▼
└─ ::/0
   ├─ ::1/128
   ├─ 2000::/3
   │  └─ 2001:db8::/32
   ├─ fc00::/7
   ├─ fe80::/10
   └─ ff00::/8

    rtbl.LookupIP(42.0.0.0)             returns (netip.Prefix{}, <nil>,  false)
    rtbl.LookupIP(10.0.1.17)            returns (10.0.1.0/24,    <value>, true)
    rtbl.LookupIP(2001:7c0:3100:1::111) returns (2000::/3,       <value>, true)

func (Table) String added in v0.2.0

func (t Table) String() string

String returns a hierarchical tree diagram of the ordered CIDRs as string, just a wrapper for [Tree.Fprint].

func (Table) Union added in v0.2.0

func (t Table) Union(other *Table) *Table

Union 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) UnionMutable added in v0.2.0

func (t *Table) UnionMutable(other *Table)

UnionMutable combines two tables, changing the receiver table. If there are duplicate entries, the value is taken from the other table.

func (Table) Walk added in v0.2.0

func (t Table) Walk(cb func(pfx netip.Prefix, val any) bool)

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"
)

var input = []netip.Prefix{
	netip.MustParsePrefix("fe80::/10"),
	netip.MustParsePrefix("172.16.0.0/12"),
	netip.MustParsePrefix("10.0.0.0/24"),
	netip.MustParsePrefix("::1/128"),
	netip.MustParsePrefix("192.168.0.0/16"),
	netip.MustParsePrefix("10.0.0.0/8"),
	netip.MustParsePrefix("::/0"),
	netip.MustParsePrefix("10.0.1.0/24"),
	netip.MustParsePrefix("169.254.0.0/16"),
	netip.MustParsePrefix("2000::/3"),
	netip.MustParsePrefix("2001:db8::/32"),
	netip.MustParsePrefix("127.0.0.0/8"),
	netip.MustParsePrefix("127.0.0.1/32"),
	netip.MustParsePrefix("192.168.1.0/24"),
}

func main() {
	cb := func(p netip.Prefix, val any) bool {
		fmt.Printf("%v (%v)\n", p, val)
		return true
	}

	rtbl := new(cidrtree.Table)
	for _, cidr := range input {
		rtbl.InsertMutable(cidr, nil)
	}
	rtbl.Walk(cb)

}
Output:

10.0.0.0/8 (<nil>)
10.0.0.0/24 (<nil>)
10.0.1.0/24 (<nil>)
127.0.0.0/8 (<nil>)
127.0.0.1/32 (<nil>)
169.254.0.0/16 (<nil>)
172.16.0.0/12 (<nil>)
192.168.0.0/16 (<nil>)
192.168.1.0/24 (<nil>)
::/0 (<nil>)
::1/128 (<nil>)
2000::/3 (<nil>)
2001:db8::/32 (<nil>)
fe80::/10 (<nil>)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL