diff --git a/rt/route.go b/rt/route.go index f902748611fdad60b03a31e1194c35930c27587c..f309d3c108241ab2e62d0c5536b6275b7a35ddf8 100644 --- a/rt/route.go +++ b/rt/route.go @@ -23,11 +23,12 @@ type Route struct { } func NewRoute(pfx *net.Prefix, paths []*Path) *Route { - return &Route{ - pfx: pfx, - activePaths: make([]*Path, 0), - paths: paths, + r := &Route{ + pfx: pfx, + paths: paths, } + + return r } func (r *Route) Pfxlen() uint8 { @@ -95,7 +96,7 @@ func (r *Route) AddPaths(paths []*Path) { } func (r *Route) bestPaths() { - var best []*Path + best := []*Path{} protocol := getBestProtocol(r.paths) switch protocol { diff --git a/rt/route_test.go b/rt/route_test.go index ecd88a04168a225a513f07f6d2fb2b96b05d2f22..3bac4e87cf7907e57fce7af3ac8192370f048bec 100644 --- a/rt/route_test.go +++ b/rt/route_test.go @@ -26,8 +26,7 @@ func TestNewRoute(t *testing.T) { }, }, expected: &Route{ - pfx: net.NewPfx(158798889, 24), - activePaths: make([]*Path, 0), + pfx: net.NewPfx(158798889, 24), paths: []*Path{ { Type: 2, diff --git a/rt/routing_table.go b/rt/routing_table.go index c86526e966c28329a45381f59bf1741d358887da..6ce2785cdbc5bb6fdcbe2342b17e037ae2147180 100644 --- a/rt/routing_table.go +++ b/rt/routing_table.go @@ -4,11 +4,13 @@ import ( "github.com/bio-routing/bio-rd/net" ) +// RT represents a routing table type RT struct { root *node nodes uint64 } +// node is a node in the compressed trie that is used to implement a routing table type node struct { skip uint8 dummy bool @@ -32,31 +34,32 @@ func newNode(route *Route, skip uint8, dummy bool) *node { } // LPM performs a longest prefix match for pfx on lpm -func (lpm *RT) LPM(pfx *net.Prefix) (res []*Route) { - if lpm.root == nil { +func (rt *RT) LPM(pfx *net.Prefix) (res []*Route) { + if rt.root == nil { return nil } - lpm.root.lpm(pfx, &res) + rt.root.lpm(pfx, &res) return res } // RemovePath removes a path from the trie -func (lpm *RT) RemovePath(route *Route) { - lpm.root.removePath(route) +func (rt *RT) RemovePath(route *Route) { + rt.root.removePath(route) } -func (lpm *RT) RemovePfx(pfx *net.Prefix) { - lpm.root.removePfx(pfx) +// RemovePfx removes a prefix from the rt including all it's paths +func (rt *RT) RemovePfx(pfx *net.Prefix) { + rt.root.removePfx(pfx) } // Get get's prefix pfx from the LPM -func (lpm *RT) Get(pfx *net.Prefix, moreSpecifics bool) (res []*Route) { - if lpm.root == nil { +func (rt *RT) Get(pfx *net.Prefix, moreSpecifics bool) (res []*Route) { + if rt.root == nil { return nil } - node := lpm.root.get(pfx) + node := rt.root.get(pfx) if moreSpecifics { return node.dumpPfxs(res) } @@ -71,13 +74,14 @@ func (lpm *RT) Get(pfx *net.Prefix, moreSpecifics bool) (res []*Route) { } // Insert inserts a route into the LPM -func (lpm *RT) Insert(route *Route) { - if lpm.root == nil { - lpm.root = newNode(route, route.Pfxlen(), false) +func (rt *RT) Insert(route *Route) { + if rt.root == nil { + route.bestPaths() + rt.root = newNode(route, route.Pfxlen(), false) return } - lpm.root = lpm.root.insert(route) + rt.root = rt.root.insert(route) } func (n *node) removePath(route *Route) { @@ -204,6 +208,7 @@ func (n *node) insert(route *Route) *node { // is pfx NOT a subnet of this node? if !n.route.Prefix().Contains(route.Prefix()) { + route.bestPaths() if route.Prefix().Contains(n.route.Prefix()) { return n.insertBefore(route, n.route.Pfxlen()-n.skip-1) } @@ -221,6 +226,7 @@ func (n *node) insert(route *Route) *node { func (n *node) insertLow(route *Route, parentPfxLen uint8) *node { if n.l == nil { + route.bestPaths() n.l = newNode(route, route.Pfxlen()-parentPfxLen-1, false) return n } @@ -230,6 +236,7 @@ func (n *node) insertLow(route *Route, parentPfxLen uint8) *node { func (n *node) insertHigh(route *Route, parentPfxLen uint8) *node { if n.h == nil { + route.bestPaths() n.h = newNode(route, route.Pfxlen()-parentPfxLen-1, false) return n } @@ -288,9 +295,10 @@ func (n *node) insertBefore(route *Route, parentPfxLen uint8) *node { return new } -func (lpm *RT) Dump() []*Route { +// Dump dumps all routes in table rt into a slice +func (rt *RT) Dump() []*Route { res := make([]*Route, 0) - return lpm.root.dump(res) + return rt.root.dump(res) } func (n *node) dump(res []*Route) []*Route { diff --git a/rt/routing_table_test.go b/rt/routing_table_test.go index 602c1da960ec9be66196decf59eb47e2794a5bfc..6a80752c9d9047bf94e7a2ff9de3e8c59ce6eaf6 100644 --- a/rt/routing_table_test.go +++ b/rt/routing_table_test.go @@ -52,18 +52,24 @@ func TestRemovePath(t *testing.T) { }), }, expected: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, + { + pfx: net.NewPfx(strAddr("10.0.0.0"), 9), + paths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, }, - }), - NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, + }, + { + pfx: net.NewPfx(strAddr("10.128.0.0"), 9), + paths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, }, - }), + }, }, }, { @@ -107,14 +113,25 @@ func TestRemovePath(t *testing.T) { }), }, expected: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 2000, + { + pfx: net.NewPfx(strAddr("10.0.0.0"), 8), + paths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 2000, + }, }, }, - }), + activePaths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 2000, + }, + }, + }, + }, NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), []*Path{ { Type: BGPPathType, @@ -132,16 +149,16 @@ func TestRemovePath(t *testing.T) { } for _, test := range tests { - lpm := New() + rt := New() for _, route := range test.routes { - lpm.Insert(route) + rt.Insert(route) } for _, route := range test.remove { - lpm.RemovePath(route) + rt.RemovePath(route) } - res := lpm.Dump() + res := rt.Dump() assert.Equal(t, test.expected, res) } } @@ -219,6 +236,371 @@ func TestRemovePfx(t *testing.T) { } } +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) + } + } +} + +func TestLPM(t *testing.T) { + tests := []struct { + name string + routes []*Route + needle *net.Prefix + expected []*Route + }{ + { + name: "LPM for non-existent route", + routes: []*Route{}, + needle: net.NewPfx(strAddr("10.0.0.0"), 32), + expected: nil, + }, + { + name: "Positive LPM test", + routes: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 + expected: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + }, + }, + /*{ + name: "Exact match", + routes: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + needle: net.NewPfx(strAddr("10.0.0.0"), 10), + expected: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + }, + },*/ + } + + for _, test := range tests { + rt := New() + for _, route := range test.routes { + rt.Insert(route) + } + assert.Equal(t, test.expected, rt.LPM(test.needle)) + } +} + +func TestGet(t *testing.T) { + tests := []struct { + name string + moreSpecifics bool + routes []*Route + needle *net.Prefix + expected []*Route + }{ + { + name: "Test 1: Search pfx and dump route + more specifics", + moreSpecifics: true, + routes: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + 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), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + }, + }, + { + name: "Test 2: Search pfx and don't dump more specifics", + routes: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + 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{}, + needle: net.NewPfx(strAddr("10.0.0.0"), 32), + expected: nil, + }, + { + name: "Test 4: Get Dummy", + routes: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + 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{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), + 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{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + 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{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + 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 := New() + for _, route := range test.routes { + rt.Insert(route) + } + p := rt.Get(test.needle, test.moreSpecifics) + + 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 TestInsert(t *testing.T) { + tests := []struct { + name string + routes []*Route + expected *node + }{ + { + name: "Insert first node", + routes: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + }, + expected: &node{ + route: &Route{ + pfx: net.NewPfx(strAddr("10.0.0.0"), 8), + activePaths: []*Path{}, + }, + skip: 8, + }, + }, + { + name: "Insert duplicate node", + routes: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), + }, + expected: &node{ + route: &Route{ + pfx: net.NewPfx(strAddr("10.0.0.0"), 8), + activePaths: []*Path{}, + }, + skip: 8, + }, + }, + /*{ + name: "Insert triangle", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + net.NewPfx(167772160, 9), // 10.0.0.0 + net.NewPfx(176160768, 9), // 10.128.0.0 + }, + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 + }, + skip: 8, + l: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 9), // 10.0.0.0 + }, + }, + h: &node{ + route: &Route{ + pfx: net.NewPfx(176160768, 9), // 10.128.0.0 + }, + }, + }, + }, + { + name: "Insert disjunct prefixes", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + net.NewPfx(191134464, 24), // 11.100.123.0/24 + }, + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 + }, + skip: 7, + dummy: true, + l: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 + }, + }, + h: &node{ + route: &Route{ + pfx: net.NewPfx(191134464, 24), // 10.0.0.0/8 + }, + skip: 16, + }, + }, + }, + { + name: "Insert disjunct prefixes plus one child low", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + net.NewPfx(191134464, 24), // 11.100.123.0/24 + net.NewPfx(167772160, 12), // 10.0.0.0 + net.NewPfx(167772160, 10), // 10.0.0.0 + }, + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 + }, + skip: 7, + dummy: true, + l: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 + }, + l: &node{ + skip: 1, + route: &Route{ + pfx: net.NewPfx(167772160, 10), // 10.0.0.0/10 + }, + l: &node{ + skip: 1, + route: &Route{ + pfx: net.NewPfx(167772160, 12), // 10.0.0.0 + }, + }, + }, + }, + h: &node{ + route: &Route{ + pfx: net.NewPfx(191134464, 24), // 10.0.0.0/8 + }, + skip: 16, + }, + }, + }, + { + name: "Insert disjunct prefixes plus one child high", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + net.NewPfx(191134464, 24), // 11.100.123.0/24 + net.NewPfx(167772160, 12), // 10.0.0.0 + net.NewPfx(167772160, 10), // 10.0.0.0 + net.NewPfx(191134592, 25), // 11.100.123.128/25 + }, + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 + }, + skip: 7, + dummy: true, + l: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 + }, + l: &node{ + skip: 1, + route: &Route{ + pfx: net.NewPfx(167772160, 10), // 10.0.0.0/10 + }, + l: &node{ + skip: 1, + route: &Route{ + pfx: net.NewPfx(167772160, 12), // 10.0.0.0 + }, + }, + }, + }, + h: &node{ + route: &Route{ + pfx: net.NewPfx(191134464, 24), //11.100.123.0/24 + }, + skip: 16, + h: &node{ + route: &Route{ + pfx: net.NewPfx(191134592, 25), //11.100.123.128/25 + }, + }, + }, + }, + },*/ + } + + for _, test := range tests { + rt := New() + for _, route := range test.routes { + rt.Insert(route) + } + + assert.Equal(t, test.expected, rt.root) + } +} + func strAddr(s string) uint32 { ret, _ := net.StrToAddr(s) return ret