diff --git a/route/route.go b/route/route.go index b537da336474d668b493c56e8ab84b0adfcd07e0..b3de6203022600719a165b58e31f92f00d27f305 100644 --- a/route/route.go +++ b/route/route.go @@ -80,7 +80,7 @@ func (r *Route) AddPath(p *Path) { r.paths = append(r.paths, p) } -// RemovePath removes path `rm` from route `r`. Returns true if removed path was last one. False otherwise. +// RemovePath removes path `rm` from route `r`. Returns length of path list after removing path `rm` func (r *Route) RemovePath(p *Path) int { if p == nil { return len(r.paths) diff --git a/routingtable/adjRIBIn/adj_rib_in.go b/routingtable/adjRIBIn/adj_rib_in.go new file mode 100644 index 0000000000000000000000000000000000000000..446d71e02982a6bd903b9890c15e8501af6f19a1 --- /dev/null +++ b/routingtable/adjRIBIn/adj_rib_in.go @@ -0,0 +1,46 @@ +package adjRIBIn + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable" +) + +// AdjRIBIn represents an Adjacency RIB In as described in RFC4271 +type AdjRIBIn struct { + rt *routingtable.RoutingTable + routingtable.ClientManager +} + +// NewAdjRIBIn creates a new Adjacency RIB In +func NewAdjRIBIn() *AdjRIBIn { + return &AdjRIBIn{ + rt: routingtable.NewRoutingTable(), + } +} + +// AddPath replaces the path for prefix `pfx`. If the prefix doesn't exist it is added. +func (a *AdjRIBIn) AddPath(pfx net.Prefix, p *route.Path) error { + oldPaths := a.rt.ReplacePath(pfx, p) + + for _, oldPath := range oldPaths { + for _, client := range a.ClientManager.Clients() { + client.RemovePath(pfx, oldPath) + } + } + + return nil +} + +// RemovePath removes the path for prefix `pfx` +func (a *AdjRIBIn) RemovePath(pfx net.Prefix, p *route.Path) error { + if !a.rt.RemovePath(pfx, p) { + return nil + } + + for _, client := range a.ClientManager.Clients() { + client.RemovePath(pfx, p) + } + + return nil +} diff --git a/routingtable/adjRIBIn/adj_rib_in_test.go b/routingtable/adjRIBIn/adj_rib_in_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a6c8989ee760a17ac1c2556533ade447cbcfdb45 --- /dev/null +++ b/routingtable/adjRIBIn/adj_rib_in_test.go @@ -0,0 +1,220 @@ +package adjRIBIn + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +type RTMockClient struct { + removePathParams struct { + pfx net.Prefix + path *route.Path + } +} + +func NewRTMockClient() *RTMockClient { + return &RTMockClient{} +} + +func (m *RTMockClient) AddPath(pfx net.Prefix, p *route.Path) error { + + return nil +} + +// RemovePath removes the path for prefix `pfx` +func (m *RTMockClient) RemovePath(pfx net.Prefix, p *route.Path) error { + m.removePathParams.pfx = pfx + m.removePathParams.path = p + return nil +} + +func TestAddPath(t *testing.T) { + tests := []struct { + name string + routes []*route.Route + removePfx net.Prefix + removePath *route.Path + expected []*route.Route + }{ + { + name: "Add route", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 100, + }, + }), + }, + removePfx: net.NewPfx(0, 0), + removePath: nil, + expected: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 100, + }, + }), + }, + }, + { + name: "Overwrite routes", + routes: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 100, + }, + }), + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 200, + }, + }), + }, + removePfx: net.NewPfx(strAddr("10.0.0.0"), 8), + removePath: &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 100, + }, + }, + expected: []*route.Route{ + route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + LocalPref: 200, + }, + }), + }, + }, + } + + for _, test := range tests { + adjRIBIn := NewAdjRIBIn() + mc := NewRTMockClient() + adjRIBIn.ClientManager.Register(mc) + + for _, route := range test.routes { + adjRIBIn.AddPath(route.Prefix(), route.Paths()[0]) + } + + if mc.removePathParams.pfx != test.removePfx { + t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, mc.removePathParams.pfx.String(), test.removePfx.String()) + } + + assert.Equal(t, test.removePath, mc.removePathParams.path) + assert.Equal(t, test.expected, adjRIBIn.rt.Dump()) + } +} + +func TestRemovePath(t *testing.T) { + tests := []struct { + name string + routes []*route.Route + removePfx net.Prefix + removePath *route.Path + expected []*route.Route + wantPropagation bool + }{ + { + name: "Remove an existing route", + 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{}, + }), + }, + wantPropagation: true, + }, + { + name: "Remove non existing route", + routes: []*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{}, + }), + }, + 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{}, + }), + }, + wantPropagation: false, + }, + } + + for _, test := range tests { + adjRIBIn := NewAdjRIBIn() + for _, route := range test.routes { + adjRIBIn.AddPath(route.Prefix(), route.Paths()[0]) + } + + mc := NewRTMockClient() + adjRIBIn.ClientManager.Register(mc) + adjRIBIn.RemovePath(test.removePfx, test.removePath) + + if test.wantPropagation { + if mc.removePathParams.pfx != test.removePfx { + t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, mc.removePathParams.pfx.String(), test.removePfx.String()) + } + + if mc.removePathParams.path != test.removePath { + t.Errorf("Test %q failed: Call to RemovePath did not propagate path properly: Got: %v Want: %v", test.name, mc.removePathParams.path, test.removePath) + } + } else { + if mc.removePathParams.pfx != net.NewPfx(0, 0) || mc.removePathParams.path != nil { + t.Errorf("Test %q failed: Call to RemovePath propagated unexpectedly", test.name) + } + } + + assert.Equal(t, test.expected, adjRIBIn.rt.Dump()) + } +} + +func strAddr(s string) uint32 { + ret, _ := net.StrToAddr(s) + return ret +} diff --git a/routingtable/client_interface.go b/routingtable/client_interface.go index 648bf40ae29738a7c9f85bf068183d2317d66120..7c235c7a668f03d7b9bdf05dab95db01fb7f9eb2 100644 --- a/routingtable/client_interface.go +++ b/routingtable/client_interface.go @@ -7,6 +7,6 @@ import ( // 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 + AddPath(net.Prefix, *route.Path) error + RemovePath(net.Prefix, *route.Path) error } diff --git a/routingtable/client_manager.go b/routingtable/client_manager.go index 08e2587aa0d9f50bcf59054765754cfe84166af0..8f93a94ba8bdca3bf577ec405f75017d8d081012 100644 --- a/routingtable/client_manager.go +++ b/routingtable/client_manager.go @@ -1,10 +1,12 @@ package routingtable +// ClientManager manages clients of routing tables (observer pattern) type ClientManager struct { clients map[RouteTableClient]struct{} // Ensures a client registers at most once routingTable *RoutingTable } +// Register registers a client for updates func (c *ClientManager) Register(client RouteTableClient) { if c.clients == nil { c.clients = make(map[RouteTableClient]struct{}, 0) @@ -13,9 +15,19 @@ func (c *ClientManager) Register(client RouteTableClient) { //c.routingTable.updateNewClient(client) } +// Unregister unregisters a client func (c *ClientManager) Unregister(client RouteTableClient) { if _, ok := c.clients[client]; !ok { return } delete(c.clients, client) } + +// Clients returns a list of registered clients +func (c *ClientManager) Clients() (ret []RouteTableClient) { + for rtc := range c.clients { + ret = append(ret, rtc) + } + + return ret +} diff --git a/routingtable/table.go b/routingtable/table.go index 6b8bcb6b39200b0710aa3de0899ce12c248ed846..fce8a4f1a26ac25ca5b124ec2b17810c698fe3ec 100644 --- a/routingtable/table.go +++ b/routingtable/table.go @@ -23,6 +23,10 @@ func (rt *RoutingTable) AddPath(pfx net.Prefix, p *route.Path) error { rt.mu.Lock() defer rt.mu.Unlock() + return rt.addPath(pfx, p) +} + +func (rt *RoutingTable) addPath(pfx net.Prefix, p *route.Path) error { if rt.root == nil { rt.root = newNode(pfx, p, pfx.Pfxlen(), false) return nil @@ -32,13 +36,55 @@ func (rt *RoutingTable) AddPath(pfx net.Prefix, p *route.Path) error { return nil } +// ReplacePath replaces all paths for prefix `pfx` with path `p` +func (rt *RoutingTable) ReplacePath(pfx net.Prefix, p *route.Path) []*route.Path { + rt.mu.Lock() + defer rt.mu.Unlock() + + r := rt.get(pfx) + if r == nil { + rt.addPath(pfx, p) + return nil + } + + oldPaths := r.Paths() + rt.removePaths(pfx, oldPaths) + + rt.addPath(pfx, p) + return oldPaths +} + // RemovePath removes a path from the trie -func (rt *RoutingTable) RemovePath(pfx net.Prefix, p *route.Path) error { +func (rt *RoutingTable) RemovePath(pfx net.Prefix, p *route.Path) bool { rt.mu.Lock() defer rt.mu.Unlock() - rt.root.removePath(pfx, p) - return nil + return rt.removePath(pfx, p) +} + +func (rt *RoutingTable) removePath(pfx net.Prefix, p *route.Path) bool { + return rt.root.removePath(pfx, p) +} + +func (rt *RoutingTable) removePaths(pfx net.Prefix, paths []*route.Path) { + for _, p := range paths { + rt.removePath(pfx, p) + } +} + +// RemovePfx removes all paths for prefix `pfx` +func (rt *RoutingTable) RemovePfx(pfx net.Prefix) []*route.Path { + rt.mu.Lock() + defer rt.mu.Unlock() + + r := rt.get(pfx) + if r == nil { + return nil + } + + oldPaths := r.Paths() + rt.removePaths(pfx, oldPaths) + return oldPaths } // LPM performs a longest prefix match for pfx on lpm @@ -59,6 +105,10 @@ func (rt *RoutingTable) Get(pfx net.Prefix) *route.Route { rt.mu.RLock() defer rt.mu.RUnlock() + return rt.get(pfx) +} + +func (rt *RoutingTable) get(pfx net.Prefix) *route.Route { if rt.root == nil { return nil } diff --git a/routingtable/trie.go b/routingtable/trie.go index 5fdc4b8cb73357740e777fd6606ffcea129972ba..730d10155284863b49c475185e34fc9db3ca0798 100644 --- a/routingtable/trie.go +++ b/routingtable/trie.go @@ -27,9 +27,9 @@ func newNode(pfx net.Prefix, path *route.Path, skip uint8, dummy bool) *node { return n } -func (n *node) removePath(pfx net.Prefix, p *route.Path) { +func (n *node) removePath(pfx net.Prefix, p *route.Path) (success bool) { if n == nil { - return + return false } if n.route.Prefix() == pfx { @@ -37,22 +37,21 @@ func (n *node) removePath(pfx net.Prefix, p *route.Path) { return } - n.route.RemovePath(p) + nPaths := len(n.route.Paths()) + nPathsAfterDel := 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 + return nPathsAfterDel < nPaths } b := getBitUint32(pfx.Addr(), n.route.Pfxlen()+1) if !b { - n.l.removePath(pfx, p) - return + return n.l.removePath(pfx, p) } - n.h.removePath(pfx, p) - return + return n.h.removePath(pfx, p) } func (n *node) lpm(needle net.Prefix, res *[]*route.Route) {