diff --git a/protocols/bgp/packet/community.go b/protocols/bgp/packet/community.go index 6f4e2a67ab3b074117211e28a84d6da2188da3d9..05b3d8fcb15d295df75853067eea5e124b1e47b2 100644 --- a/protocols/bgp/packet/community.go +++ b/protocols/bgp/packet/community.go @@ -13,7 +13,7 @@ const ( func CommunityStringForUint32(v uint32) string { e1 := v >> 16 - e2 := v - e1<<16 + e2 := v & 0x0000FFFF return fmt.Sprintf("(%d,%d)", e1, e2) } diff --git a/routingtable/filter/actions/add_community_action.go b/routingtable/filter/actions/add_community_action.go new file mode 100644 index 0000000000000000000000000000000000000000..01ad05f62936574bb576d55fe1c7bcf23697a38f --- /dev/null +++ b/routingtable/filter/actions/add_community_action.go @@ -0,0 +1,34 @@ +package actions + +import ( + "strings" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + "github.com/bio-routing/bio-rd/route" +) + +type AddCommunityAction struct { + communities []uint32 +} + +func NewAddCommunityAction(coms []uint32) *AddCommunityAction { + return &AddCommunityAction{ + communities: coms, + } +} + +func (a *AddCommunityAction) Do(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) { + if pa.BGPPath == nil || len(a.communities) == 0 { + return pa, false + } + + modified := pa.Copy() + + for _, com := range a.communities { + modified.BGPPath.Communities = modified.BGPPath.Communities + " " + packet.CommunityStringForUint32(com) + } + modified.BGPPath.Communities = strings.TrimLeft(modified.BGPPath.Communities, " ") + + return modified, false +} diff --git a/routingtable/filter/actions/add_community_action_test.go b/routingtable/filter/actions/add_community_action_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0687e86c4bc80b8648d172cb125449962dea9cf6 --- /dev/null +++ b/routingtable/filter/actions/add_community_action_test.go @@ -0,0 +1,57 @@ +package actions + +import ( + "testing" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/stretchr/testify/assert" +) + +func TestAddingCommunities(t *testing.T) { + tests := []struct { + name string + current string + communities []uint32 + expected string + }{ + { + name: "add one to empty", + communities: []uint32{ + 65538, + }, + expected: "(1,2)", + }, + { + name: "add one to existing", + current: "(1,2)", + communities: []uint32{ + 196612, + }, + expected: "(1,2) (3,4)", + }, + { + name: "add two to existing", + current: "(1,2)", + communities: []uint32{ + 196612, 327686, + }, + expected: "(1,2) (3,4) (5,6)", + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + p := &route.Path{ + BGPPath: &route.BGPPath{ + Communities: test.current, + }, + } + + a := NewAddCommunityAction(test.communities) + modPath, _ := a.Do(net.Prefix{}, p) + + assert.Equal(te, test.expected, modPath.BGPPath.Communities) + }) + } +} diff --git a/routingtable/filter/community_filter.go b/routingtable/filter/community_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..9d384e54ad0a58ee622e3d2b0bb1b857c867b0c3 --- /dev/null +++ b/routingtable/filter/community_filter.go @@ -0,0 +1,15 @@ +package filter + +import ( + "strings" + + "github.com/bio-routing/bio-rd/protocols/bgp/packet" +) + +type CommunityFilter struct { + community uint32 +} + +func (f *CommunityFilter) Matches(communityString string) bool { + return strings.Contains(communityString, packet.CommunityStringForUint32(f.community)) +} diff --git a/routingtable/filter/term_condition.go b/routingtable/filter/term_condition.go index 003b9dcbc021e75cd28d3039efbad2d00bf1d109..fe19e461701dc1a453a3ddee17a38547d08936a6 100644 --- a/routingtable/filter/term_condition.go +++ b/routingtable/filter/term_condition.go @@ -8,6 +8,7 @@ import ( type TermCondition struct { prefixLists []*PrefixList routeFilters []*RouteFilter + communityFilters []*CommunityFilter largeCommunityFilters []*LargeCommunityFilter } @@ -19,10 +20,17 @@ func NewTermCondition(prefixLists []*PrefixList, routeFilters []*RouteFilter) *T } func (f *TermCondition) Matches(p net.Prefix, pa *route.Path) bool { - return f.matchesAnyPrefixList(p) || f.machtchesAnyRouteFilter(p) || f.machtchesAnyLageCommunityFilter(pa) + return f.matchesPrefixListFilters(p) && + f.machtchesRouteFilters(p) && + f.machtchesCommunityFilters(pa) && + f.machtchesLargeCommunityFilters(pa) } -func (t *TermCondition) matchesAnyPrefixList(p net.Prefix) bool { +func (t *TermCondition) matchesPrefixListFilters(p net.Prefix) bool { + if len(t.prefixLists) == 0 { + return true + } + for _, l := range t.prefixLists { if l.Matches(p) { return true @@ -32,7 +40,11 @@ func (t *TermCondition) matchesAnyPrefixList(p net.Prefix) bool { return false } -func (t *TermCondition) machtchesAnyRouteFilter(p net.Prefix) bool { +func (t *TermCondition) machtchesRouteFilters(p net.Prefix) bool { + if len(t.routeFilters) == 0 { + return true + } + for _, l := range t.routeFilters { if l.Matches(p) { return true @@ -42,7 +54,29 @@ func (t *TermCondition) machtchesAnyRouteFilter(p net.Prefix) bool { return false } -func (t *TermCondition) machtchesAnyLageCommunityFilter(pa *route.Path) bool { +func (t *TermCondition) machtchesCommunityFilters(pa *route.Path) bool { + if len(t.communityFilters) == 0 { + return true + } + + if pa.BGPPath == nil { + return false + } + + for _, l := range t.communityFilters { + if l.Matches(pa.BGPPath.Communities) { + return true + } + } + + return false +} + +func (t *TermCondition) machtchesLargeCommunityFilters(pa *route.Path) bool { + if len(t.largeCommunityFilters) == 0 { + return true + } + if pa.BGPPath == nil { return false } diff --git a/routingtable/filter/term_condition_test.go b/routingtable/filter/term_condition_test.go index 6e9b89f20eb226faebdd3856fa4bc61a97388a4e..cca90950aa5a443c042c8061f77a08d3f4f7a0c4 100644 --- a/routingtable/filter/term_condition_test.go +++ b/routingtable/filter/term_condition_test.go @@ -16,6 +16,7 @@ func TestMatches(t *testing.T) { bgpPath *route.BGPPath prefixLists []*PrefixList routeFilters []*RouteFilter + communityFilters []*CommunityFilter largeCommunityFilters []*LargeCommunityFilter expected bool }{ @@ -25,8 +26,7 @@ func TestMatches(t *testing.T) { prefixLists: []*PrefixList{ NewPrefixList(net.NewPfx(strAddr("127.0.0.1"), 8)), }, - routeFilters: []*RouteFilter{}, - expected: true, + expected: true, }, { name: "one prefix in prefix list and no match, no route filters set", @@ -34,8 +34,7 @@ func TestMatches(t *testing.T) { prefixLists: []*PrefixList{ NewPrefixList(net.NewPfx(0, 32)), }, - routeFilters: []*RouteFilter{}, - expected: false, + expected: false, }, { name: "one prefix of 2 matches in prefix list, no route filters set", @@ -44,22 +43,19 @@ func TestMatches(t *testing.T) { NewPrefixList(net.NewPfx(strAddr("10.0.0.0"), 8)), NewPrefixList(net.NewPfx(strAddr("127.0.0.1"), 8)), }, - routeFilters: []*RouteFilter{}, - expected: true, + expected: true, }, { - name: "no prefixes in prefix list, only route filter matches", - prefix: net.NewPfx(strAddr("10.0.0.0"), 24), - prefixLists: []*PrefixList{}, + name: "no prefixes in prefix list, only route filter matches", + prefix: net.NewPfx(strAddr("10.0.0.0"), 24), routeFilters: []*RouteFilter{ NewRouteFilter(net.NewPfx(strAddr("10.0.0.0"), 8), Longer()), }, expected: true, }, { - name: "no prefixes in prefix list, one route filter matches", - prefix: net.NewPfx(strAddr("10.0.0.0"), 24), - prefixLists: []*PrefixList{}, + name: "no prefixes in prefix list, one route filter matches", + prefix: net.NewPfx(strAddr("10.0.0.0"), 24), routeFilters: []*RouteFilter{ NewRouteFilter(net.NewPfx(strAddr("8.0.0.0"), 8), Longer()), NewRouteFilter(net.NewPfx(strAddr("10.0.0.0"), 8), Longer()), @@ -67,9 +63,8 @@ func TestMatches(t *testing.T) { expected: true, }, { - name: "no prefixes in prefix list, one of many route filters matches", - prefix: net.NewPfx(strAddr("127.0.0.1"), 8), - prefixLists: []*PrefixList{}, + name: "no prefixes in prefix list, one of many route filters matches", + prefix: net.NewPfx(strAddr("127.0.0.1"), 8), routeFilters: []*RouteFilter{ NewRouteFilter(net.NewPfx(strAddr("10.0.0.0"), 8), Longer()), }, @@ -87,7 +82,7 @@ func TestMatches(t *testing.T) { expected: false, }, { - name: "one prefix in prefixlist, one route fitler, only prefix list matches", + name: "one prefix in prefixlist, one route filter, only prefix list matches", prefix: net.NewPfx(strAddr("8.8.8.0"), 24), prefixLists: []*PrefixList{ NewPrefixList(net.NewPfx(strAddr("8.0.0.0"), 8)), @@ -95,10 +90,10 @@ func TestMatches(t *testing.T) { routeFilters: []*RouteFilter{ NewRouteFilter(net.NewPfx(strAddr("10.0.0.0"), 8), Longer()), }, - expected: true, + expected: false, }, { - name: "one prefix in prefixlist, one route fitler, only route filter matches", + name: "one prefix in prefixlist, one route filter, only route filter matches", prefix: net.NewPfx(strAddr("10.0.0.0"), 24), prefixLists: []*PrefixList{ NewPrefixList(net.NewPfx(strAddr("8.0.0.0"), 8)), @@ -106,8 +101,38 @@ func TestMatches(t *testing.T) { routeFilters: []*RouteFilter{ NewRouteFilter(net.NewPfx(strAddr("10.0.0.0"), 8), Longer()), }, + expected: false, + }, + { + name: "community matches", + prefix: net.NewPfx(strAddr("10.0.0.0"), 24), + bgpPath: &route.BGPPath{ + Communities: "(1,2) (3,4) (5,6)", + }, + communityFilters: []*CommunityFilter{ + &CommunityFilter{196612}, // (3,4) + }, expected: true, }, + { + name: "community does not match", + prefix: net.NewPfx(strAddr("10.0.0.0"), 24), + bgpPath: &route.BGPPath{ + Communities: "(1,2) (3,4) (5,6)", + }, + communityFilters: []*CommunityFilter{ + &CommunityFilter{196608}, // (3,0) + }, + expected: false, + }, + { + name: "community filter, bgp path is nil", + prefix: net.NewPfx(strAddr("10.0.0.0"), 24), + communityFilters: []*CommunityFilter{ + &CommunityFilter{196608}, // (3,0) + }, + expected: false, + }, { name: "large community matches", prefix: net.NewPfx(strAddr("10.0.0.0"), 24), @@ -140,11 +165,26 @@ func TestMatches(t *testing.T) { }, expected: false, }, + { + name: "large community filter, bgp path is nil", + prefix: net.NewPfx(strAddr("10.0.0.0"), 24), + largeCommunityFilters: []*LargeCommunityFilter{ + { + &packet.LargeCommunity{ + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + }, + }, + expected: false, + }, } for _, test := range tests { t.Run(test.name, func(te *testing.T) { f := NewTermCondition(test.prefixLists, test.routeFilters) + f.communityFilters = test.communityFilters f.largeCommunityFilters = test.largeCommunityFilters pa := &route.Path{