diff --git a/routingtable/filter/actions/accept_action.go b/routingtable/filter/actions/accept_action.go new file mode 100644 index 0000000000000000000000000000000000000000..6c0cc2acbfa3d75603ff33cc10025bdad09b3a23 --- /dev/null +++ b/routingtable/filter/actions/accept_action.go @@ -0,0 +1,13 @@ +package actions + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +type AcceptAction struct { +} + +func (*AcceptAction) Do(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) { + return pa, false +} diff --git a/routingtable/filter/actions/reject_action.go b/routingtable/filter/actions/reject_action.go new file mode 100644 index 0000000000000000000000000000000000000000..d740139626b99395db71a3b2e7c2cea1d04ebacf --- /dev/null +++ b/routingtable/filter/actions/reject_action.go @@ -0,0 +1,13 @@ +package actions + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +type RejectAction struct { +} + +func (*RejectAction) Do(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) { + return pa, true +} diff --git a/routingtable/filter/filter.go b/routingtable/filter/filter.go new file mode 100644 index 0000000000000000000000000000000000000000..f2b46997bd2d02a0c7e03d5bc0e4faccf180c9e7 --- /dev/null +++ b/routingtable/filter/filter.go @@ -0,0 +1,64 @@ +package filter + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable" +) + +type Filter struct { + routingtable.ClientManager + terms []*Term +} + +func NewFilter(terms []*Term) *Filter { + f := &Filter{ + terms: terms, + } + f.ClientManager = routingtable.NewClientManager(f) + + return f +} + +func (f *Filter) AddPath(p net.Prefix, pa *route.Path) error { + pa, rejected := f.processTerms(p, pa) + if rejected { + return nil + } + + for _, c := range f.Clients() { + c.AddPath(p, pa) + } + + return nil +} + +func (f *Filter) RemovePath(p net.Prefix, pa *route.Path) bool { + pa, rejected := f.processTerms(p, pa) + if rejected { + return false + } + + for _, c := range f.Clients() { + c.RemovePath(p, pa) + } + + return true +} + +func (f *Filter) UpdateNewClient(c routingtable.RouteTableClient) error { + return nil +} + +func (f *Filter) processTerms(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) { + modPath = pa + + for _, t := range f.terms { + modPath, reject = t.Process(p, modPath) + if reject { + return modPath, true + } + } + + return modPath, false +} diff --git a/routingtable/filter/filter_test.go b/routingtable/filter/filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8b33f125e0824a1e44b5dad7f4b8daf9ec323247 --- /dev/null +++ b/routingtable/filter/filter_test.go @@ -0,0 +1,187 @@ +package filter + +import ( + "testing" + + "github.com/bio-routing/bio-rd/routingtable/filter/actions" + "github.com/stretchr/testify/assert" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable" +) + +type clientMock struct { + routingtable.ClientManager + addPathCalled bool + removePathCalled bool + path *route.Path +} + +func (m *clientMock) AddPath(p net.Prefix, pa *route.Path) error { + m.path = pa + m.addPathCalled = true + return nil +} + +func (m *clientMock) RemovePath(p net.Prefix, pa *route.Path) bool { + m.path = pa + m.removePathCalled = true + return false +} + +func (m *clientMock) UpdateNewClient(c routingtable.RouteTableClient) error { + return nil +} + +func newClientMock() *clientMock { + m := &clientMock{} + m.ClientManager = routingtable.NewClientManager(m) + return m +} + +func TestAddPath(t *testing.T) { + tests := []struct { + name string + prefix net.Prefix + path *route.Path + term *Term + exptectCalled bool + expectModified bool + }{ + { + name: "accept", + prefix: net.NewPfx(0, 0), + path: &route.Path{}, + term: &Term{ + then: []Then{ + &actions.AcceptAction{}, + }, + }, + exptectCalled: true, + expectModified: false, + }, + { + name: "reject", + prefix: net.NewPfx(0, 0), + path: &route.Path{}, + term: &Term{ + then: []Then{ + &actions.RejectAction{}, + }, + }, + exptectCalled: false, + expectModified: false, + }, + { + name: "modified", + prefix: net.NewPfx(0, 0), + path: &route.Path{}, + term: &Term{ + then: []Then{ + &mockAction{}, + &actions.AcceptAction{}, + }, + }, + exptectCalled: true, + expectModified: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + m := newClientMock() + + f := NewFilter([]*Term{test.term}) + f.Register(m) + + f.AddPath(test.prefix, test.path) + assert.Equal(te, test.exptectCalled, m.addPathCalled, "called") + + if !test.exptectCalled { + return + } + + if m.path != test.path && !test.expectModified { + te.Fatal("expected path to be not modified but was") + } + + if m.path == test.path && test.expectModified { + te.Fatal("expected path to be modified but was same reference") + } + }) + } +} + +func TestRemovePath(t *testing.T) { + tests := []struct { + name string + prefix net.Prefix + path *route.Path + term *Term + exptectCalled bool + expectModified bool + }{ + { + name: "accept", + prefix: net.NewPfx(0, 0), + path: &route.Path{}, + term: &Term{ + then: []Then{ + &actions.AcceptAction{}, + }, + }, + exptectCalled: true, + expectModified: false, + }, + { + name: "reject", + prefix: net.NewPfx(0, 0), + path: &route.Path{}, + term: &Term{ + then: []Then{ + &actions.RejectAction{}, + }, + }, + exptectCalled: false, + expectModified: false, + }, + { + name: "modified", + prefix: net.NewPfx(0, 0), + path: &route.Path{}, + term: &Term{ + then: []Then{ + &mockAction{}, + &actions.AcceptAction{}, + }, + }, + exptectCalled: true, + expectModified: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + m := newClientMock() + + f := NewFilter([]*Term{test.term}) + f.Register(m) + + f.RemovePath(test.prefix, test.path) + assert.Equal(te, test.exptectCalled, m.removePathCalled, "called") + + if !test.exptectCalled { + return + } + + if m.path != test.path && !test.expectModified { + te.Fatal("expected path to be not modified but was") + } + + if m.path == test.path && test.expectModified { + te.Fatal("expected path to be modified but was same reference") + } + }) + } +} diff --git a/routingtable/filter/from.go b/routingtable/filter/from.go new file mode 100644 index 0000000000000000000000000000000000000000..2237fe4ea9fcc61e65f2ec8c59a400e619fc092f --- /dev/null +++ b/routingtable/filter/from.go @@ -0,0 +1,14 @@ +package filter + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +type From struct { + prefixList *PrefixList +} + +func (f *From) Matches(p net.Prefix, pa *route.Path) bool { + return f.prefixList.Matches(p) +} diff --git a/routingtable/filter/prefix_list.go b/routingtable/filter/prefix_list.go new file mode 100644 index 0000000000000000000000000000000000000000..b783a07a540b67acc7fd3384b582490f0bb5aea2 --- /dev/null +++ b/routingtable/filter/prefix_list.go @@ -0,0 +1,17 @@ +package filter + +import "github.com/bio-routing/bio-rd/net" + +type PrefixList struct { + allowed []net.Prefix +} + +func (f *PrefixList) Matches(p net.Prefix) bool { + for _, a := range f.allowed { + if !a.Contains(p) { + return false + } + } + + return true +} diff --git a/routingtable/filter/term.go b/routingtable/filter/term.go new file mode 100644 index 0000000000000000000000000000000000000000..85a88eb88945581c388f790bcf17631b7f1da9db --- /dev/null +++ b/routingtable/filter/term.go @@ -0,0 +1,52 @@ +package filter + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +// Term matches a path against a list of conditions and performs actions if it matches +type Term struct { + from []*From + then []Then +} + +// NewTerm creates a new term +func NewTerm(from []*From, then []Then) *Term { + t := &Term{ + from: from, + then: then, + } + + return t +} + +// Process processes a path returning if the path should be rejected and returns a possible modified version of the path +func (t *Term) Process(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) { + orig := pa + + if len(t.from) == 0 { + return t.processActions(p, pa) + } + + for _, f := range t.from { + if f.Matches(p, pa) { + return t.processActions(p, pa) + } + } + + return orig, true +} + +func (t *Term) processActions(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) { + modPath = pa + + for _, action := range t.then { + modPath, reject = action.Do(p, modPath) + if reject { + continue + } + } + + return modPath, reject +} diff --git a/routingtable/filter/term_test.go b/routingtable/filter/term_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ee55c712c21a055d75dc61468751e1c80b92f81b --- /dev/null +++ b/routingtable/filter/term_test.go @@ -0,0 +1,169 @@ +package filter + +import ( + "testing" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable/filter/actions" + "github.com/stretchr/testify/assert" +) + +type mockAction struct { +} + +func (*mockAction) Do(p net.Prefix, pa *route.Path) (*route.Path, bool) { + cp := *pa + cp.Type = route.OSPFPathType + + return &cp, false +} + +func TestProcess(t *testing.T) { + tests := []struct { + name string + prefix net.Prefix + path *route.Path + from []*From + then []Then + expectReject bool + expectModified bool + }{ + { + name: "empty from", + prefix: net.NewPfx(strAddr("100.64.0.1"), 8), + path: &route.Path{}, + from: []*From{}, + then: []Then{ + &actions.AcceptAction{}, + }, + expectReject: false, + expectModified: false, + }, + { + name: "from matches", + prefix: net.NewPfx(strAddr("100.64.0.1"), 8), + path: &route.Path{}, + from: []*From{ + { + &PrefixList{ + allowed: []net.Prefix{ + net.NewPfx(0, 0), + }, + }, + }, + }, + then: []Then{ + &actions.AcceptAction{}, + }, + expectReject: false, + expectModified: false, + }, + { + name: "from does not match", + prefix: net.NewPfx(strAddr("100.64.0.1"), 8), + path: &route.Path{}, + from: []*From{ + { + &PrefixList{ + allowed: []net.Prefix{ + net.NewPfx(0, 32), + }, + }, + }, + }, + then: []Then{ + &actions.AcceptAction{}, + }, + expectReject: true, + expectModified: false, + }, + { + name: "modified", + prefix: net.NewPfx(strAddr("100.64.0.1"), 8), + path: &route.Path{}, + from: []*From{ + { + &PrefixList{ + allowed: []net.Prefix{ + net.NewPfx(0, 0), + }, + }, + }, + }, + then: []Then{ + &mockAction{}, + }, + expectReject: false, + expectModified: true, + }, + { + name: "modified and accepted (2 actions)", + prefix: net.NewPfx(strAddr("100.64.0.1"), 8), + path: &route.Path{}, + from: []*From{ + { + &PrefixList{ + allowed: []net.Prefix{ + net.NewPfx(0, 0), + }, + }, + }, + }, + then: []Then{ + &mockAction{}, + &actions.AcceptAction{}, + }, + expectReject: false, + expectModified: true, + }, + { + name: "one of the prefix filters matches", + prefix: net.NewPfx(strAddr("100.64.0.1"), 8), + path: &route.Path{}, + from: []*From{ + { + &PrefixList{ + allowed: []net.Prefix{ + net.NewPfx(0, 32), + }, + }, + }, + { + &PrefixList{ + allowed: []net.Prefix{ + net.NewPfx(0, 0), + }, + }, + }, + }, + then: []Then{ + &actions.AcceptAction{}, + }, + expectReject: false, + expectModified: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + term := NewTerm(test.from, test.then) + + pa, reject := term.Process(test.prefix, test.path) + assert.Equal(te, test.expectReject, reject, "reject") + + if pa != test.path && !test.expectModified { + te.Fatal("expected path to be not modified but was") + } + + if pa == test.path && test.expectModified { + te.Fatal("expected path to be modified but was same reference") + } + }) + } +} + +func strAddr(s string) uint32 { + ret, _ := net.StrToAddr(s) + return ret +} diff --git a/routingtable/filter/then.go b/routingtable/filter/then.go new file mode 100644 index 0000000000000000000000000000000000000000..920daf43c1cdb121400168cc0ac17460bff42350 --- /dev/null +++ b/routingtable/filter/then.go @@ -0,0 +1,10 @@ +package filter + +import ( + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +type Then interface { + Do(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) +}