diff --git a/routingtable/client_interface.go b/routingtable/client_interface.go new file mode 100644 index 0000000000000000000000000000000000000000..648bf40ae29738a7c9f85bf068183d2317d66120 --- /dev/null +++ b/routingtable/client_interface.go @@ -0,0 +1,12 @@ +package routingtable + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +// RouteTableClient is the interface that every type of RIB must implement +type RouteTableClient interface { + AddPath(*net.Prefix, *route.Path) error + RemovePath(*net.Prefix, *route.Path) error +} diff --git a/routingtable/client_manager.go b/routingtable/client_manager.go new file mode 100644 index 0000000000000000000000000000000000000000..08e2587aa0d9f50bcf59054765754cfe84166af0 --- /dev/null +++ b/routingtable/client_manager.go @@ -0,0 +1,21 @@ +package routingtable + +type ClientManager struct { + clients map[RouteTableClient]struct{} // Ensures a client registers at most once + routingTable *RoutingTable +} + +func (c *ClientManager) Register(client RouteTableClient) { + if c.clients == nil { + c.clients = make(map[RouteTableClient]struct{}, 0) + } + c.clients[client] = struct{}{} + //c.routingTable.updateNewClient(client) +} + +func (c *ClientManager) Unregister(client RouteTableClient) { + if _, ok := c.clients[client]; !ok { + return + } + delete(c.clients, client) +} diff --git a/routingtable/rib_interface.go b/routingtable/rib_interface.go new file mode 100644 index 0000000000000000000000000000000000000000..45afeb2605a13a3ca65bfb885950d0a3b2fc8692 --- /dev/null +++ b/routingtable/rib_interface.go @@ -0,0 +1,11 @@ +package routingtable + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +type RIB interface { + AddPath(*net.Prefix, *route.Path) + RemovePath(*net.Prefix, *route.Path) +} diff --git a/routingtable/table.go b/routingtable/table.go new file mode 100644 index 0000000000000000000000000000000000000000..6b8bcb6b39200b0710aa3de0899ce12c248ed846 --- /dev/null +++ b/routingtable/table.go @@ -0,0 +1,92 @@ +package routingtable + +import ( + "sync" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +// RoutingTable is a binary trie that stores prefixes and their paths +type RoutingTable struct { + root *node + mu sync.RWMutex +} + +// NewRoutingTable creates a new routing table +func NewRoutingTable() *RoutingTable { + return &RoutingTable{} +} + +// AddPath adds a path to the routing table +func (rt *RoutingTable) AddPath(pfx net.Prefix, p *route.Path) error { + rt.mu.Lock() + defer rt.mu.Unlock() + + if rt.root == nil { + rt.root = newNode(pfx, p, pfx.Pfxlen(), false) + return nil + } + + rt.root = rt.root.addPath(pfx, p) + return nil +} + +// RemovePath removes a path from the trie +func (rt *RoutingTable) RemovePath(pfx net.Prefix, p *route.Path) error { + rt.mu.Lock() + defer rt.mu.Unlock() + + rt.root.removePath(pfx, p) + return nil +} + +// LPM performs a longest prefix match for pfx on lpm +func (rt *RoutingTable) LPM(pfx net.Prefix) (res []*route.Route) { + rt.mu.RLock() + defer rt.mu.RUnlock() + + if rt.root == nil { + return nil + } + + rt.root.lpm(pfx, &res) + return res +} + +// Get get's the route for pfx from the LPM +func (rt *RoutingTable) Get(pfx net.Prefix) *route.Route { + rt.mu.RLock() + defer rt.mu.RUnlock() + + if rt.root == nil { + return nil + } + + res := rt.root.get(pfx) + if res == nil { + return nil + } + return res.route +} + +// GetLonger get's prefix pfx and all it's more specifics from the LPM +func (rt *RoutingTable) GetLonger(pfx net.Prefix) (res []*route.Route) { + rt.mu.RLock() + defer rt.mu.RUnlock() + + if rt.root == nil { + return []*route.Route{} + } + + return rt.root.get(pfx).dumpPfxs(res) +} + +// Dump dumps all routes in table rt into a slice +func (rt *RoutingTable) Dump() []*route.Route { + rt.mu.RLock() + defer rt.mu.RUnlock() + + res := make([]*route.Route, 0) + return rt.root.dump(res) +} diff --git a/routingtable/table_test.go b/routingtable/table_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a2eb10f76f93046fe71e98e59998ff52e15283b5 --- /dev/null +++ b/routingtable/table_test.go @@ -0,0 +1,451 @@ +package routingtable + +import ( + "testing" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/stretchr/testify/assert" +) + +func TestAddPath(t *testing.T) { + tests := []struct { + name string + routes []*route.Route + expected *node + }{ + { + name: "Insert first node", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + }, + expected: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + skip: 8, + }, + }, + { + name: "Insert duplicate node", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + }, + expected: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + skip: 8, + }, + }, + { + name: "Insert triangle", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), nil), + route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), nil), + }, + expected: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + skip: 8, + l: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), nil), + }, + h: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), nil), + }, + }, + }, + { + name: "Insert disjunct prefixes", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + }, + expected: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 7), nil), + skip: 7, + dummy: true, + l: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + }, + h: &node{ + route: route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + skip: 16, + }, + }, + }, + { + name: "Insert disjunct prefixes plus one child low", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + expected: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 7), nil), + skip: 7, + dummy: true, + l: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + l: &node{ + skip: 1, + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + l: &node{ + skip: 1, + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + }, + }, + }, + h: &node{ + route: route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + skip: 16, + }, + }, + }, + { + name: "Insert disjunct prefixes plus one child high", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.128"), 25), nil), + }, + expected: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 7), nil), + skip: 7, + dummy: true, + l: &node{ + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + l: &node{ + skip: 1, + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + l: &node{ + skip: 1, + route: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + }, + }, + }, + h: &node{ + route: route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + skip: 16, + h: &node{ + route: route.NewRoute(net.NewPfx(strAddr("11.100.123.128"), 25), nil), + }, + }, + }, + }, + } + + for _, test := range tests { + rt := NewRoutingTable() + for _, route := range test.routes { + rt.AddPath(route.Prefix(), nil) + } + + assert.Equal(t, test.expected, rt.root) + } +} + +func TestGet(t *testing.T) { + tests := []struct { + name string + routes []*route.Route + needle net.Prefix + expected *route.Route + }{ + { + name: "Test 1: Search pfx and dump route + more specifics", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + needle: net.NewPfx(strAddr("10.0.0.0"), 8), + expected: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + }, + { + name: "Test 2: Search pfx and don't dump more specifics", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + needle: net.NewPfx(strAddr("10.0.0.0"), 8), + expected: route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + }, + { + name: "Test 3: Empty table", + routes: []*route.Route{}, + needle: net.NewPfx(strAddr("10.0.0.0"), 32), + expected: nil, + }, + { + name: "Test 4: Get Dummy", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + }, + needle: net.NewPfx(strAddr("10.0.0.0"), 7), + expected: nil, + }, + { + name: "Test 5", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + needle: net.NewPfx(strAddr("11.100.123.0"), 24), + expected: route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + }, + { + name: "Test 4: Get nonexistent #1", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + }, + needle: net.NewPfx(strAddr("10.0.0.0"), 10), + expected: nil, + }, + { + name: "Test 4: Get nonexistent #2", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + }, + needle: net.NewPfx(strAddr("10.0.0.0"), 10), + expected: nil, + }, + } + + for _, test := range tests { + rt := NewRoutingTable() + for _, route := range test.routes { + rt.AddPath(route.Prefix(), nil) + } + p := rt.Get(test.needle) + + if p == nil { + if test.expected != nil { + t.Errorf("Unexpected nil result for test %q", test.name) + } + continue + } + + assert.Equal(t, test.expected, p) + } +} +func TestGetLonger(t *testing.T) { + tests := []struct { + name string + routes []*route.Route + needle net.Prefix + expected []*route.Route + }{ + { + name: "Test 1: Search pfx and dump route + more specifics", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + needle: net.NewPfx(strAddr("10.0.0.0"), 8), + expected: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + }, + }, + } + + for _, test := range tests { + rt := NewRoutingTable() + for _, route := range test.routes { + rt.AddPath(route.Prefix(), nil) + } + p := rt.GetLonger(test.needle) + + if p == nil { + if test.expected != nil { + t.Errorf("Unexpected nil result for test %q", test.name) + } + continue + } + + assert.Equal(t, test.expected, p) + } +} +func TestLPM(t *testing.T) { + tests := []struct { + name string + routes []*route.Route + needle net.Prefix + expected []*route.Route + }{ + { + name: "LPM for non-existent route", + routes: []*route.Route{}, + needle: net.NewPfx(strAddr("10.0.0.0"), 32), + expected: nil, + }, + { + name: "Positive LPM test", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 + expected: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + }, + }, + { + name: "Exact match", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + needle: net.NewPfx(strAddr("10.0.0.0"), 10), + expected: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + }, + } + + for _, test := range tests { + rt := NewRoutingTable() + for _, route := range test.routes { + rt.AddPath(route.Prefix(), nil) + } + assert.Equal(t, test.expected, rt.LPM(test.needle)) + } +} + +func TestRemovePath(t *testing.T) { + tests := []struct { + name string + routes []*route.Route + removePfx net.Prefix + removePath *route.Path + expected []*route.Route + }{ + { + name: "Remove a path that is the only one for a prefix", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + }, + removePfx: net.NewPfx(strAddr("10.0.0.0"), 8), + removePath: &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }, + expected: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + }, + }, + { + name: "Remove a path that is one of two for a prefix", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 1000, + }, + }), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 2000, + }, + }), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + }, + removePfx: net.NewPfx(strAddr("10.0.0.0"), 8), + removePath: &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 1000, + }, + }, + expected: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 2000, + }, + }), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, + }), + }, + }, + } + + for _, test := range tests { + rt := NewRoutingTable() + for _, route := range test.routes { + for _, p := range route.Paths() { + rt.AddPath(route.Prefix(), p) + } + } + + rt.RemovePath(test.removePfx, test.removePath) + + rtExpected := NewRoutingTable() + for _, route := range test.expected { + for _, p := range route.Paths() { + rtExpected.AddPath(route.Prefix(), p) + } + } + + assert.Equal(t, rtExpected.Dump(), rt.Dump()) + } +} + +func strAddr(s string) uint32 { + ret, _ := net.StrToAddr(s) + return ret +} diff --git a/routingtable/trie.go b/routingtable/trie.go new file mode 100644 index 0000000000000000000000000000000000000000..5fdc4b8cb73357740e777fd6606ffcea129972ba --- /dev/null +++ b/routingtable/trie.go @@ -0,0 +1,229 @@ +package routingtable + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +// node is a node in the compressed trie that is used to implement a routing table +type node struct { + skip uint8 + dummy bool + route *route.Route + l *node + h *node +} + +func getBitUint32(x uint32, pos uint8) bool { + return ((x) & (1 << (32 - pos))) != 0 +} + +func newNode(pfx net.Prefix, path *route.Path, skip uint8, dummy bool) *node { + n := &node{ + route: route.NewRoute(pfx, path), + skip: skip, + dummy: dummy, + } + return n +} + +func (n *node) removePath(pfx net.Prefix, p *route.Path) { + if n == nil { + return + } + + if n.route.Prefix() == pfx { + if n.dummy { + return + } + + n.route.RemovePath(p) + if len(n.route.Paths()) == 0 { + // FIXME: Can this node actually be removed from the trie entirely? + n.dummy = true + } + + return + } + + b := getBitUint32(pfx.Addr(), n.route.Pfxlen()+1) + if !b { + n.l.removePath(pfx, p) + return + } + n.h.removePath(pfx, p) + return +} + +func (n *node) lpm(needle net.Prefix, res *[]*route.Route) { + if n == nil { + return + } + + currentPfx := n.route.Prefix() + if currentPfx == needle && !n.dummy { + *res = append(*res, n.route) + return + } + + if !currentPfx.Contains(needle) { + return + } + + if !n.dummy { + *res = append(*res, n.route) + } + n.l.lpm(needle, res) + n.h.lpm(needle, res) +} + +func (n *node) dumpPfxs(res []*route.Route) []*route.Route { + if n == nil { + return nil + } + + if !n.dummy { + res = append(res, n.route) + } + + if n.l != nil { + res = n.l.dumpPfxs(res) + } + + if n.h != nil { + res = n.h.dumpPfxs(res) + } + + return res +} + +func (n *node) get(pfx net.Prefix) *node { + if n == nil { + return nil + } + + if n.route.Prefix() == pfx { + if n.dummy { + return nil + } + return n + } + + if n.route.Pfxlen() > pfx.Pfxlen() { + return nil + } + + b := getBitUint32(pfx.Addr(), n.route.Pfxlen()+1) + if !b { + return n.l.get(pfx) + } + return n.h.get(pfx) +} + +func (n *node) addPath(pfx net.Prefix, p *route.Path) *node { + currentPfx := n.route.Prefix() + if currentPfx == pfx { + n.route.AddPath(p) + n.dummy = false + return n + } + + // is pfx NOT a subnet of this node? + if !currentPfx.Contains(pfx) { + if pfx.Contains(currentPfx) { + return n.insertBefore(pfx, p, n.route.Pfxlen()-n.skip-1) + } + + return n.newSuperNode(pfx, p) + } + + // pfx is a subnet of this node + b := getBitUint32(pfx.Addr(), n.route.Pfxlen()+1) + if !b { + return n.insertLow(pfx, p, currentPfx.Pfxlen()) + } + return n.insertHigh(pfx, p, n.route.Pfxlen()) +} + +func (n *node) insertLow(pfx net.Prefix, p *route.Path, parentPfxLen uint8) *node { + if n.l == nil { + n.l = newNode(pfx, p, pfx.Pfxlen()-parentPfxLen-1, false) + return n + } + n.l = n.l.addPath(pfx, p) + return n +} + +func (n *node) insertHigh(pfx net.Prefix, p *route.Path, parentPfxLen uint8) *node { + if n.h == nil { + n.h = newNode(pfx, p, pfx.Pfxlen()-parentPfxLen-1, false) + return n + } + n.h = n.h.addPath(pfx, p) + return n +} + +func (n *node) newSuperNode(pfx net.Prefix, p *route.Path) *node { + superNet := pfx.GetSupernet(n.route.Prefix()) + + pfxLenDiff := n.route.Pfxlen() - superNet.Pfxlen() + skip := n.skip - pfxLenDiff + + pseudoNode := newNode(superNet, nil, skip, true) + pseudoNode.insertChildren(n, pfx, p) + return pseudoNode +} + +func (n *node) insertChildren(old *node, newPfx net.Prefix, newPath *route.Path) { + // Place the old node + b := getBitUint32(old.route.Prefix().Addr(), n.route.Pfxlen()+1) + if !b { + n.l = old + n.l.skip = old.route.Pfxlen() - n.route.Pfxlen() - 1 + } else { + n.h = old + n.h.skip = old.route.Pfxlen() - n.route.Pfxlen() - 1 + } + + // Place the new Prefix + newNode := newNode(newPfx, newPath, newPfx.Pfxlen()-n.route.Pfxlen()-1, false) + b = getBitUint32(newPfx.Addr(), n.route.Pfxlen()+1) + if !b { + n.l = newNode + } else { + n.h = newNode + } +} + +func (n *node) insertBefore(pfx net.Prefix, p *route.Path, parentPfxLen uint8) *node { + tmp := n + + pfxLenDiff := n.route.Pfxlen() - pfx.Pfxlen() + skip := n.skip - pfxLenDiff + new := newNode(pfx, p, skip, false) + + b := getBitUint32(pfx.Addr(), parentPfxLen) + if !b { + new.l = tmp + new.l.skip = tmp.route.Pfxlen() - pfx.Pfxlen() - 1 + } else { + new.h = tmp + new.h.skip = tmp.route.Pfxlen() - pfx.Pfxlen() - 1 + } + + return new +} + +func (n *node) dump(res []*route.Route) []*route.Route { + if n == nil { + return res + } + + if !n.dummy { + res = append(res, n.route) + } + + res = n.l.dump(res) + res = n.h.dump(res) + return res +} diff --git a/routingtable/trie_test.go b/routingtable/trie_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7bbf46e11dcc2a19e3af36a012e81b65be326cad --- /dev/null +++ b/routingtable/trie_test.go @@ -0,0 +1,34 @@ +package routingtable + +import ( + "testing" +) + +func TestGetBitUint32(t *testing.T) { + tests := []struct { + name string + input uint32 + offset uint8 + expected bool + }{ + { + name: "test 1", + input: 167772160, // 10.0.0.0 + offset: 8, + expected: false, + }, + { + name: "test 2", + input: 184549376, // 11.0.0.0 + offset: 8, + expected: true, + }, + } + + for _, test := range tests { + b := getBitUint32(test.input, test.offset) + if b != test.expected { + t.Errorf("%s: Unexpected failure: Bit %d of %d is %v. Expected %v", test.name, test.offset, test.input, b, test.expected) + } + } +}