diff --git a/rt/bgp.go b/rt/bgp.go new file mode 100644 index 0000000000000000000000000000000000000000..7e71427dcf111932d199950eb3d93cedd77f296f --- /dev/null +++ b/rt/bgp.go @@ -0,0 +1,143 @@ +package rt + +import ( + "sync" + + log "github.com/sirupsen/logrus" +) + +type BGPPath struct { + PathIdentifier uint32 + NextHop uint32 + LocalPref uint32 + ASPath string + ASPathLen uint16 + Origin uint8 + MED uint32 + EBGP bool + Source uint32 +} + +type BGPPathManager struct { + paths map[BGPPath]*BGPPathCounter + mu sync.Mutex +} + +type BGPPathCounter struct { + usageCount uint64 + path *BGPPath +} + +func NewBGPPathManager() *BGPPathManager { + m := &BGPPathManager{} + return m +} + +func (m *BGPPathManager) pathExists(p BGPPath) bool { + if _, ok := m.paths[p]; !ok { + return false + } + + return true +} + +func (m *BGPPathManager) AddPath(p BGPPath) *BGPPath { + m.mu.Lock() + defer m.mu.Unlock() + + if !m.pathExists(p) { + m.paths[p] = &BGPPathCounter{ + path: &p, + } + } + + m.paths[p].usageCount++ + return m.paths[p].path +} + +func (m *BGPPathManager) RemovePath(p BGPPath) { + m.mu.Lock() + defer m.mu.Unlock() + + if !m.pathExists(p) { + log.Fatalf("Tried to remove non-existent BGPPath: %v", p) + return + } + + m.paths[p].usageCount-- + if m.paths[p].usageCount == 0 { + delete(m.paths, p) + } +} + +func (r *Route) bgpPathSelection() (res []*Path) { + // TODO: Implement next hop lookup and compare IGP metrics + if len(r.paths) == 1 { + copy(res, r.paths) + return res + } + + for _, p := range r.paths { + if p.Type != BGPPathType { + continue + } + + if len(res) == 0 { + res = append(res, p) + continue + } + + if res[0].BGPPath.ecmp(p.BGPPath) { + res = append(res, p) + continue + } + + if !res[0].BGPPath.better(p.BGPPath) { + continue + } + + res = []*Path{p} + } + + return res +} + +func (b *BGPPath) better(c *BGPPath) bool { + if c.LocalPref < b.LocalPref { + return false + } + + if c.LocalPref > b.LocalPref { + return true + } + + if c.ASPathLen > b.ASPathLen { + return false + } + + if c.ASPathLen < b.ASPathLen { + return true + } + + if c.Origin > b.Origin { + return false + } + + if c.Origin < b.Origin { + return true + } + + if c.MED > b.MED { + return false + } + + if c.MED < b.MED { + return true + } + + return false +} + +func (b *BGPPath) ecmp(c *BGPPath) bool { + return b.LocalPref == c.LocalPref && b.ASPathLen == c.ASPathLen && b.Origin == c.Origin && b.MED == c.MED +} diff --git a/rt/lpm_test.go.dis b/rt/lpm_test.go.dis new file mode 100644 index 0000000000000000000000000000000000000000..90f9e450ba42b376a81b239f90285aca497902e7 --- /dev/null +++ b/rt/lpm_test.go.dis @@ -0,0 +1,673 @@ +package rt + +import ( + "testing" + + "github.com/taktv6/tbgp/net" + + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + l := New() + if l == nil { + t.Errorf("New() returned nil") + } +} + +func TestRemove(t *testing.T) { + tests := []struct { + name string + routes []*Route + remove []*net.Prefix + expected []*Route + }{ + { + name: "Test 1", + routes: []*Route{ + NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 + NewRoute(net.NewPfx(167772160, 9)), // 10.0.0.0 + NewRoute(net.NewPfx(176160768, 9)), // 10.128.0.0 + }, + remove: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + }, + expected: []*Route{ + NewRoute(net.NewPfx(167772160, 9)), // 10.0.0.0 + NewRoute(net.NewPfx(176160768, 9)), // 10.128.0.0 + }, + }, + { + name: "Test 2", + routes: []*Route{ + NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0/8 + NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 + NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0/12 + NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0/10 + }, + remove: []*net.Prefix{ + net.NewPfx(167772160, 7), // 10.0.0.0/7 + }, + expected: []*Route{ + NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0/8 + NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0/10 + NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0/12 + NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 + }, + }, + { + name: "Test 3", + remove: []*net.Prefix{ + NewRoute(net.NewPfx(167772160, 7)), // 10.0.0.0/7 + }, + expected: []*Route{}, + }, + { + name: "Test 4", + prefixes: []*net.Prefix{ + NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 + NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 + NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0 + NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0 + NewRoute(net.NewPfx(191134592, 25)), // 11.100.123.128/25 + }, + remove: []*net.Prefix{ + NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 + }, + expected: []*Route{ + NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 + NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0 + NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0 + NewRoute(net.NewPfx(191134592, 25)), // 11.100.123.128/25 + }, + }, + { + name: "Test 5", + prefixes: []*net.Prefix{ + NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 + NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 + NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0 + NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0 + NewRoute(net.NewPfx(191134592, 25)), // 11.100.123.128/25 + }, + remove: []*net.Prefix{ + NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0/12 + }, + expected: []*Route{ + NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 + NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0 + NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 + NewRoute(net.NewPfx(191134592, 25)), // 11.100.123.128/25 + }, + }, + } + + for _, test := range tests { + lpm := New() + for _, route := range test.routes { + lpm.Insert(route) + } + + for _, pfx := range test.remove { + lpm.Remove(pfx) + } + + res := lpm.Dump() + assert.Equal(t, test.expected, res) + } +} + +func TestInsert(t *testing.T) { + tests := []struct { + name string + prefixes []*net.Prefix + expected *node + }{ + { + name: "Insert first node", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + }, + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8)}, // 10.0.0.0/8 + skip: 8, + }, + }, + { + name: "Insert double node", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + net.NewPfx(167772160, 8), // 10.0.0.0/8 + net.NewPfx(167772160, 8), // 10.0.0.0/8 + net.NewPfx(167772160, 8), // 10.0.0.0/8 + }, + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 + }, + 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 { + l := New() + for _, pfx := range test.prefixes { + l.Insert(&Route{pfx: pfx}) + } + + assert.Equal(t, test.expected, l.root) + } +} + +func TestLPM(t *testing.T) { + tests := []struct { + name string + prefixes []*net.Prefix + needle *net.Prefix + expected []*net.Prefix + }{ + { + name: "Test 1", + 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 + }, + needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 + expected: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + net.NewPfx(167772160, 10), // 10.0.0.0 + net.NewPfx(167772160, 12), // 10.0.0.0 + }, + }, + { + name: "Test 2", + prefixes: []*net.Prefix{}, + needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 + expected: nil, + }, + { + name: "Test 3", + 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 + }, + needle: net.NewPfx(167772160, 10), // 10.0.0.0/10 + expected: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + net.NewPfx(167772160, 10), // 10.0.0.0 + }, + }, + } + + for _, test := range tests { + lpm := New() + for _, pfx := range test.prefixes { + lpm.Insert(&Route{pfx: pfx}) + } + assert.Equal(t, test.expected, lpm.LPM(test.needle)) + } +} + +func TestGet(t *testing.T) { + tests := []struct { + name string + moreSpecifics bool + prefixes []*net.Prefix + needle *net.Prefix + expected []*net.Prefix + }{ + { + name: "Test 1: Search pfx and dump more specifics", + moreSpecifics: true, + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + net.NewPfx(191134464, 24), // 11.100.123.0/24 + net.NewPfx(167772160, 12), // 10.0.0.0/12 + net.NewPfx(167772160, 10), // 10.0.0.0/10 + }, + needle: net.NewPfx(167772160, 8), // 10.0.0.0/8 + expected: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + net.NewPfx(167772160, 10), // 10.0.0.0 + net.NewPfx(167772160, 12), // 10.0.0.0 + }, + }, + { + name: "Test 2: Search pfx and don't dump more specifics", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + 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 + }, + needle: net.NewPfx(167772160, 8), // 10.0.0.0/8 + expected: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + }, + }, + { + name: "Test 3", + prefixes: []*net.Prefix{}, + needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 + expected: nil, + }, + { + name: "Test 4: Get Dummy", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + net.NewPfx(191134464, 24), // 11.100.123.0/24 + }, + needle: net.NewPfx(167772160, 7), // 10.0.0.0/7 + expected: nil, + }, + { + name: "Test 5", + 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 + }, + needle: net.NewPfx(191134464, 24), // 10.0.0.0/8 + expected: []*net.Prefix{ + net.NewPfx(191134464, 24), // 11.100.123.0/24 + }, + }, + { + name: "Test 4: Get nonexistent #1", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0 + net.NewPfx(191134464, 24), // 11.100.123.0/24 + }, + needle: net.NewPfx(167772160, 10), // 10.0.0.0/10 + expected: nil, + }, + { + name: "Test 4: Get nonexistent #2", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + net.NewPfx(167772160, 12), // 10.0.0.0/12 + }, + needle: net.NewPfx(167772160, 10), // 10.0.0.0/10 + expected: nil, + }, + } + + for _, test := range tests { + lpm := New() + for _, pfx := range test.prefixes { + lpm.Insert(&Route{pfx: pfx}) + } + p := lpm.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 TestNewSuperNode(t *testing.T) { + tests := []struct { + name string + a *net.Prefix + b *net.Prefix + expected *node + }{ + { + name: "Test 1", + a: net.NewPfx(167772160, 8), // 10.0.0.0/8 + b: 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), //11.100.123.0/24 + }, + skip: 16, + }, + }, + }, + } + + for _, test := range tests { + n := newNode(&Route{pfx: test.a}, test.a.Pfxlen(), false) + n = n.newSuperNode(&Route{pfx: test.b}) + assert.Equal(t, test.expected, n) + } +} + +func TestDumpPfxs(t *testing.T) { + tests := []struct { + name string + prefixes []*net.Prefix + expected []*net.Prefix + }{ + + { + name: "Test 1: Empty node", + expected: nil, + }, + { + name: "Test 2: ", + prefixes: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + net.NewPfx(191134464, 24), // 11.100.123.0/24 + net.NewPfx(167772160, 12), // 10.0.0.0/12 + net.NewPfx(167772160, 10), // 10.0.0.0/10 + }, + expected: []*net.Prefix{ + net.NewPfx(167772160, 8), // 10.0.0.0/8 + net.NewPfx(167772160, 10), // 10.0.0.0/10 + net.NewPfx(167772160, 12), // 10.0.0.0/12 + net.NewPfx(191134464, 24), // 11.100.123.0/24 + }, + }, + } + + for _, test := range tests { + lpm := New() + for _, pfx := range test.prefixes { + lpm.Insert(&Route{pfx: pfx}) + } + + res := make([]*Route, 0) + r := lpm.root.dumpPfxs(res) + assert.Equal(t, test.expected, r) + } +} + +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 TestInsertChildren(t *testing.T) { + tests := []struct { + name string + base *net.Prefix + old *net.Prefix + new *net.Prefix + expected *node + }{ + { + name: "Test 1", + base: net.NewPfx(167772160, 8), //10.0.0.0/8 + old: net.NewPfx(167772160, 9), //10.0.0.0/9 + new: net.NewPfx(176160768, 9), //10.128.0.0/9 + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8), + }, + skip: 8, + dummy: true, + l: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 9), + }, + }, + h: &node{ + route: &Route{ + pfx: net.NewPfx(176160768, 9), + }, + }, + }, + }, + { + name: "Test 2", + base: net.NewPfx(167772160, 8), //10.0.0.0/8 + old: net.NewPfx(176160768, 9), //10.128.0.0/9 + new: net.NewPfx(167772160, 9), //10.0.0.0/9 + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8), + }, + skip: 8, + dummy: true, + l: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 9), + }, + }, + h: &node{ + route: &Route{ + pfx: net.NewPfx(176160768, 9), + }, + }, + }, + }, + } + + for _, test := range tests { + n := newNode(&Route{pfx: test.base}, test.base.Pfxlen(), true) + old := newNode(&Route{pfx: test.old}, test.old.Pfxlen(), false) + n.insertChildren(old, &Route{pfx: test.new}) + assert.Equal(t, test.expected, n) + } +} + +func TestInsertBefore(t *testing.T) { + tests := []struct { + name string + a *net.Prefix + b *net.Prefix + expected *node + }{ + { + name: "Test 1", + a: net.NewPfx(167772160, 10), // 10.0.0.0 + b: net.NewPfx(167772160, 8), // 10.0.0.0 + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 8), // 10.0.0.0, + }, + l: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 10), // 10.0.0.0 + }, + skip: 1, + }, + skip: 8, + }, + }, + { + name: "Test 2", + a: net.NewPfx(184549376, 8), // 11.0.0.0/8 + b: net.NewPfx(167772160, 7), // 10.0.0.0/7 + expected: &node{ + route: &Route{ + pfx: net.NewPfx(167772160, 7), // 10.0.0.0, + }, + h: &node{ + route: &Route{ + pfx: net.NewPfx(184549376, 8), // 10.0.0.0 + }, + skip: 0, + }, + skip: 7, + }, + }, + } + + for _, test := range tests { + n := newNode(&Route{pfx: test.a}, test.a.Pfxlen(), false) + n = n.insertBefore(&Route{pfx: test.b}, test.b.Pfxlen()) + assert.Equal(t, test.expected, n) + } +} diff --git a/rt/route.go b/rt/route.go new file mode 100644 index 0000000000000000000000000000000000000000..42dd416244a2b46f7080fbc10ea7852f3b5f7760 --- /dev/null +++ b/rt/route.go @@ -0,0 +1,125 @@ +package rt + +import ( + net "github.com/bio-routing/bio-rd/net" +) + +// Path Types +const StaticPathType = 1 +const BGPPathType = 2 +const OSPFPathType = 3 +const ISISPathType = 4 + +type Path struct { + Type uint8 + StaticPath *StaticPath + BGPPath *BGPPath +} + +type Route struct { + pfx *net.Prefix + activePaths []*Path + paths []*Path +} + +func NewRoute(pfx *net.Prefix, paths []*Path) *Route { + return &Route{ + pfx: pfx, + activePaths: make([]*Path, 0), + paths: paths, + } +} + +func (r *Route) Pfxlen() uint8 { + return r.pfx.Pfxlen() +} + +func (r *Route) Prefix() *net.Prefix { + return r.pfx +} + +func (r *Route) Remove(rm *Route) (final bool) { + for _, del := range rm.paths { + r.paths = removePath(r.paths, del) + } + + return len(r.paths) == 0 +} + +func removePath(paths []*Path, remove *Path) []*Path { + i := -1 + for j := range paths { + if paths[j].Equal(remove) { + i = j + break + } + } + + if i < 0 { + return paths + } + + copy(paths[i:], paths[i+1:]) + return paths[:len(paths)-1] +} + +func (p *Path) Equal(q *Path) bool { + if p == nil || q == nil { + return false + } + + if p.Type != q.Type { + return false + } + + switch p.Type { + case BGPPathType: + if *p.BGPPath != *q.BGPPath { + return false + } + } + + return true +} + +func (r *Route) AddPath(p *Path) { + r.paths = append(r.paths, p) + r.bestPaths() +} + +func (r *Route) AddPaths(paths []*Path) { + for _, p := range paths { + r.paths = append(r.paths, p) + } + r.bestPaths() +} + +func (r *Route) bestPaths() { + var best []*Path + protocol := getBestProtocol(r.paths) + + switch protocol { + case StaticPathType: + best = r.staticPathSelection() + case BGPPathType: + best = r.bgpPathSelection() + } + + r.activePaths = best +} + +func getBestProtocol(paths []*Path) uint8 { + best := uint8(0) + for _, p := range paths { + if best == 0 { + best = p.Type + continue + } + + if p.Type > best { + best = p.Type + } + } + + return best +} diff --git a/rt/route_test.go b/rt/route_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d4633694903eae24d79a35a0803c4b07edfa5073 --- /dev/null +++ b/rt/route_test.go @@ -0,0 +1,369 @@ +package rt + +import ( + "testing" + + net "github.com/bio-routing/bio-rd/net" + "github.com/stretchr/testify/assert" +) + +func TestNewRoute(t *testing.T) { + tests := []struct { + name string + pfx *net.Prefix + paths []*Path + expected *Route + }{ + { + name: "Test #1", + pfx: net.NewPfx(158798889, 24), + paths: []*Path{ + { + Type: 2, + StaticPath: &StaticPath{ + NextHop: 56963289, + }, + }, + }, + expected: &Route{ + pfx: net.NewPfx(158798889, 24), + activePaths: make([]*Path, 0), + paths: []*Path{ + { + Type: 2, + StaticPath: &StaticPath{ + NextHop: 56963289, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + res := NewRoute(test.pfx, test.paths) + assert.Equal(t, test.expected, res) + } +} + +func TestPfxlen(t *testing.T) { + tests := []struct { + name string + pfx *net.Prefix + expected uint8 + }{ + { + name: "Test #1", + pfx: net.NewPfx(158798889, 24), + expected: 24, + }, + } + + for _, test := range tests { + r := NewRoute(test.pfx, nil) + res := r.Pfxlen() + assert.Equal(t, test.expected, res) + } +} + +func TestPrefix(t *testing.T) { + tests := []struct { + name string + pfx *net.Prefix + expected *net.Prefix + }{ + { + name: "Test #1", + pfx: net.NewPfx(158798889, 24), + expected: net.NewPfx(158798889, 24), + }, + } + + for _, test := range tests { + r := NewRoute(test.pfx, nil) + res := r.Prefix() + assert.Equal(t, test.expected, res) + } +} + +func TestRouteRemovePath(t *testing.T) { + tests := []struct { + name string + paths []*Path + remove *Path + expected []*Path + }{ + { + name: "Remove middle", + paths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 300, + }, + }, + }, + remove: &Path{ + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + expected: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 300, + }, + }, + }, + }, + { + name: "Remove non-existent", + paths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 10, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 20, + }, + }, + }, + remove: &Path{ + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 50, + }, + }, + expected: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 10, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 20, + }, + }, + }, + }, + } + + for _, test := range tests { + res := removePath(test.paths, test.remove) + assert.Equal(t, test.expected, res) + } +} + +func TestEqual(t *testing.T) { + tests := []struct { + name string + pathA *Path + pathB *Path + expected bool + }{ + { + name: "Unequal types", + pathA: &Path{ + Type: 1, + }, + pathB: &Path{ + Type: 2, + }, + expected: false, + }, + { + name: "Unequal attributes", + pathA: &Path{ + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + pathB: &Path{ + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + expected: false, + }, + { + name: "Equal", + pathA: &Path{ + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + pathB: &Path{ + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + expected: true, + }, + } + + for _, test := range tests { + res := test.pathA.Equal(test.pathB) + assert.Equal(t, test.expected, res) + } +} + +func TestAddPath(t *testing.T) { + tests := []struct { + name string + route *Route + new *Path + expected *Route + }{ + { + name: "Add a new best path", + route: &Route{ + paths: []*Path{ + { + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + }, + }, + new: &Path{ + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + expected: &Route{ + activePaths: []*Path{ + { + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + }, + paths: []*Path{ + { + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + { + Type: 2, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + test.route.AddPath(test.new) + assert.Equal(t, test.expected, test.route) + } +} + +func TestAddPaths(t *testing.T) { + tests := []struct { + name string + route *Route + new []*Path + expected *Route + }{ + { + name: "Add 2 new paths including a new best path", + route: &Route{ + paths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + }, + }, + new: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 50, + }, + }, + }, + expected: &Route{ + activePaths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + }, + paths: []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 100, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 200, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 50, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + test.route.AddPaths(test.new) + assert.Equal(t, test.expected, test.route) + } +} diff --git a/rt/routing_table.go b/rt/routing_table.go new file mode 100644 index 0000000000000000000000000000000000000000..0ca9812a3a681890e346d7664eaea8e8bf7d6350 --- /dev/null +++ b/rt/routing_table.go @@ -0,0 +1,312 @@ +package rt + +import ( + "github.com/bio-routing/bio-rd/net" +) + +type LPM struct { + root *node + nodes uint64 +} + +type node struct { + skip uint8 + dummy bool + route *Route + l *node + h *node +} + +// New creates a new empty LPM +func New() *LPM { + return &LPM{} +} + +func newNode(route *Route, skip uint8, dummy bool) *node { + n := &node{ + route: route, + skip: skip, + dummy: dummy, + } + return n +} + +// LPM performs a longest prefix match for pfx on lpm +func (lpm *LPM) LPM(pfx *net.Prefix) (res []*Route) { + if lpm.root == nil { + return nil + } + + lpm.root.lpm(pfx, &res) + return res +} + +// RemovePath removes a path from the trie +func (lpm *LPM) RemovePath(route *Route) { + lpm.root.removePath(route) +} + +func (lpm *LPM) RemovePfx(pfx *net.Prefix) { + lpm.root.removePfx(pfx) +} + +// Get get's prefix pfx from the LPM +func (lpm *LPM) Get(pfx *net.Prefix, moreSpecifics bool) (res []*Route) { + if lpm.root == nil { + return nil + } + + node := lpm.root.get(pfx) + if moreSpecifics { + return node.dumpPfxs(res) + } + + if node == nil { + return nil + } + + return []*Route{ + node.route, + } +} + +// Insert inserts a route into the LPM +func (lpm *LPM) Insert(route *Route) { + if lpm.root == nil { + lpm.root = newNode(route, route.Pfxlen(), false) + return + } + + lpm.root = lpm.root.insert(route) +} + +func (n *node) removePath(route *Route) { + if n == nil { + return + } + + if *n.route.Prefix() == *route.Prefix() { + if n.dummy { + return + } + + if n.route.Remove(route) { + // FIXME: Can this node actually be removed from the trie entirely? + n.dummy = true + } + + return + } + + b := getBitUint32(route.Prefix().Addr(), n.route.Pfxlen()+1) + if !b { + n.l.removePath(route) + return + } + n.h.removePath(route) + return +} + +func (n *node) removePfx(pfx *net.Prefix) { + if n == nil { + return + } + + if *n.route.Prefix() == *pfx { + if n.dummy { + return + } + + n.dummy = true + + return + } + + b := getBitUint32(pfx.Addr(), n.route.Pfxlen()+1) + if !b { + n.l.removePfx(pfx) + return + } + n.h.removePfx(pfx) + return +} + +func (n *node) lpm(needle *net.Prefix, res *[]*Route) { + if n == nil { + return + } + + if *n.route.Prefix() == *needle && !n.dummy { + *res = append(*res, n.route) + return + } + + if !n.route.Prefix().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 { + 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) insert(route *Route) *node { + if *n.route.Prefix() == *route.Prefix() { + n.route.AddPaths(route.paths) + n.dummy = false + return n + } + + // is pfx NOT a subnet of this node? + if !n.route.Prefix().Contains(route.Prefix()) { + if route.Prefix().Contains(n.route.Prefix()) { + return n.insertBefore(route, n.route.Pfxlen()-n.skip-1) + } + + return n.newSuperNode(route) + } + + // pfx is a subnet of this node + b := getBitUint32(route.Prefix().Addr(), n.route.Pfxlen()+1) + if !b { + return n.insertLow(route, n.route.Prefix().Pfxlen()) + } + return n.insertHigh(route, n.route.Pfxlen()) +} + +func (n *node) insertLow(route *Route, parentPfxLen uint8) *node { + if n.l == nil { + n.l = newNode(route, route.Pfxlen()-parentPfxLen-1, false) + return n + } + n.l = n.l.insert(route) + return n +} + +func (n *node) insertHigh(route *Route, parentPfxLen uint8) *node { + if n.h == nil { + n.h = newNode(route, route.Pfxlen()-parentPfxLen-1, false) + return n + } + n.h = n.h.insert(route) + return n +} + +func (n *node) newSuperNode(route *Route) *node { + superNet := route.Prefix().GetSupernet(n.route.Prefix()) + + pfxLenDiff := n.route.Pfxlen() - superNet.Pfxlen() + skip := n.skip - pfxLenDiff + + pseudoNode := newNode(NewRoute(superNet, nil), skip, true) + pseudoNode.insertChildren(n, route) + return pseudoNode +} + +func (n *node) insertChildren(old *node, new *Route) { + // 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(new, new.Pfxlen()-n.route.Pfxlen()-1, false) + b = getBitUint32(new.Prefix().Addr(), n.route.Pfxlen()+1) + if !b { + n.l = newNode + } else { + n.h = newNode + } +} + +func (n *node) insertBefore(route *Route, parentPfxLen uint8) *node { + tmp := n + + pfxLenDiff := n.route.Pfxlen() - route.Pfxlen() + skip := n.skip - pfxLenDiff + new := newNode(route, skip, false) + + b := getBitUint32(route.Prefix().Addr(), parentPfxLen) + if !b { + new.l = tmp + new.l.skip = tmp.route.Pfxlen() - route.Pfxlen() - 1 + } else { + new.h = tmp + new.h.skip = tmp.route.Pfxlen() - route.Pfxlen() - 1 + } + + return new +} + +func (lpm *LPM) Dump() []*Route { + res := make([]*Route, 0) + return lpm.root.dump(res) +} + +func (n *node) dump(res []*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 +} + +func getBitUint32(x uint32, pos uint8) bool { + return ((x) & (1 << (32 - pos))) != 0 +} diff --git a/rt/routing_table_test.go b/rt/routing_table_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a1de4f03d372cef1dcb7af517f20b27f350547f7 --- /dev/null +++ b/rt/routing_table_test.go @@ -0,0 +1,152 @@ +package rt + +import ( + "testing" + + net "github.com/bio-routing/bio-rd/net" + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + l := New() + if l == nil { + t.Errorf("New() returned nil") + } +} + +func TestRemovePath(t *testing.T) { + tests := []struct { + name string + routes []*Route + remove []*Route + expected []*Route + }{ + { + name: "Remove a path that is the only one for a prefix", + routes: []*Route{ + NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), + NewRoute(net.NewPfx(167772160, 9), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.0.0.0/9 + NewRoute(net.NewPfx(176160768, 9), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.128.0.0/9 + }, + remove: []*Route{ + NewRoute(net.NewPfx(167772160, 8), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.0.0.0 + }, + expected: []*Route{ + NewRoute(net.NewPfx(167772160, 9), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.0.0.0 + NewRoute(net.NewPfx(176160768, 9), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.128.0.0 + }, + }, + { + name: "Remove a path that is one of two for a prefix", + routes: []*Route{ + NewRoute(net.NewPfx(167772160, 8), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 1000, + }, + }, + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 2000, + }, + }, + }), // 10.0.0.0/8 + NewRoute(net.NewPfx(167772160, 9), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.0.0.0/9 + NewRoute(net.NewPfx(176160768, 9), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.128.0.0/9 + }, + remove: []*Route{ + NewRoute(net.NewPfx(167772160, 8), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 1000, + }, + }, + }), // 10.0.0.0 + }, + expected: []*Route{ + NewRoute(net.NewPfx(167772160, 8), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 2000, + }, + }, + }), // 10.0.0.0/8 + NewRoute(net.NewPfx(167772160, 9), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.0.0.0/9 + NewRoute(net.NewPfx(176160768, 9), []*Path{ + { + Type: BGPPathType, + BGPPath: &BGPPath{}, + }, + }), // 10.128.0.0/9 + }, + }, + } + + for _, test := range tests { + lpm := New() + for _, route := range test.routes { + lpm.Insert(route) + } + + for _, route := range test.remove { + lpm.RemovePath(route) + } + + res := lpm.Dump() + assert.Equal(t, test.expected, res) + } +} + +func strAddr(s string) uint32 { + ret, _ := net.StrToAddr(s) + return ret +} diff --git a/rt/static.go b/rt/static.go new file mode 100644 index 0000000000000000000000000000000000000000..72720a6548427ce7d0a1d88fbd9039731f96698e --- /dev/null +++ b/rt/static.go @@ -0,0 +1,22 @@ +package rt + +type StaticPath struct { + NextHop uint32 +} + +func (r *Route) staticPathSelection() (res []*Path) { + if len(r.paths) == 1 { + copy(res, r.paths) + return res + } + + for _, p := range r.paths { + if p.Type != StaticPathType { + continue + } + + res = append(res, p) + } + + return +}