diff options
-rw-r--r-- | policy/policy.go | 174 | ||||
-rw-r--r-- | policy/policy_test.go | 244 | ||||
-rw-r--r-- | table/path.go | 10 |
3 files changed, 428 insertions, 0 deletions
diff --git a/policy/policy.go b/policy/policy.go index b701bd4a..977116fd 100644 --- a/policy/policy.go +++ b/policy/policy.go @@ -95,6 +95,13 @@ func NewPolicy(pd config.PolicyDefinition, ds config.DefinedSets) *Policy { conditions = append(conditions, asc) } + // CommunityCondition + communitySetName := statement.Conditions.BgpConditions.MatchCommunitySet + cc := NewCommunityCondition(communitySetName, ds.BgpDefinedSets.CommunitySetList) + if cc != nil { + conditions = append(conditions, cc) + } + action := &RoutingActions{ AcceptRoute: false, } @@ -452,6 +459,173 @@ func (c *AsPathCondition) evaluate(path table.Path) bool { return false } +type CommunityCondition struct { + DefaultCondition + CommunityList []*CommunityElement +} + +const ( + COMMUNITY_INTERNET string = "INTERNET" + COMMUNITY_NO_EXPORT string = "NO_EXPORT" + COMMUNITY_NO_ADVERTISE string = "NO_ADVERTISE" + COMMUNITY_NO_EXPORT_SUBCONFED string = "NO_EXPORT_SUBCONFED" +) + +const ( + COMMUNITY_INTERNET_VAL uint32 = 0x00000000 + COMMUNITY_NO_EXPORT_VAL = 0xFFFFFF01 + COMMUNITY_NO_ADVERTISE_VAL = 0xFFFFFF02 + COMMUNITY_NO_EXPORT_SUBCONFED_VAL = 0xFFFFFF03 +) + +type CommunityElement struct { + community uint32 + communityStr string + isRegExp bool + communityRegExp *regexp.Regexp +} + +// create CommunityCondition object +// CommunityCondition supports uint and string like 65000:100 +// and also supports regular expressions that are available in golang. +// if GoBGP can't parse the regular expression, it return nil and an error message is logged. +func NewCommunityCondition(communitySetName string, defAsPathSetList []config.CommunitySet) *CommunityCondition { + + // check format + regUint, _ := regexp.Compile("^([0-9]+)$") + regString, _ := regexp.Compile("([0-9]+):([0-9]+)") + regWellKnown, _ := regexp.Compile("^(" + + COMMUNITY_INTERNET + "|" + + COMMUNITY_NO_EXPORT + "|" + + COMMUNITY_NO_ADVERTISE + "|" + + COMMUNITY_NO_EXPORT_SUBCONFED + ")$") + + communityList := make([]*CommunityElement, 0) + for _, asPathSet := range defAsPathSetList { + if asPathSet.CommunitySetName == communitySetName { + for _, as := range asPathSet.CommunityMembers { + + e := &CommunityElement{ + isRegExp: false, + } + + if regUint.MatchString(as) { + // specified by Uint + community, err := strconv.ParseUint(as, 10, 32) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Type": "Community Condition", + }).Error("failed to parse the community value.") + return nil + } + + e.community = uint32(community) + e.communityStr = as + + } else if regString.MatchString(as) { + // specified by string containing ":" + group := regString.FindStringSubmatch(as) + asn, errAsn := strconv.ParseUint(group[1], 10, 16) + val, errVal := strconv.ParseUint(group[2], 10, 16) + + if errAsn != nil || errVal != nil { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Type": "Community Condition", + }).Error("failed to parser as number or community value.") + return nil + } + e.community = uint32(asn<<16 | val) + e.communityStr = as + + } else if regWellKnown.MatchString(as) { + // specified by well known community name + e.communityStr = as + switch as { + case COMMUNITY_INTERNET: + e.community = COMMUNITY_INTERNET_VAL + case COMMUNITY_NO_EXPORT: + e.community = COMMUNITY_NO_EXPORT_VAL + case COMMUNITY_NO_ADVERTISE: + e.community = COMMUNITY_NO_ADVERTISE_VAL + case COMMUNITY_NO_EXPORT_SUBCONFED: + e.community = COMMUNITY_NO_EXPORT_SUBCONFED_VAL + } + + } else { + // specified by regular expression + e.isRegExp = true + e.communityStr = as + reg, err := regexp.Compile(as) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Type": "Community Condition", + }).Error("Regular expression can't be compiled.") + return nil + } + e.communityRegExp = reg + } + communityList = append(communityList, e) + } + + c := &CommunityCondition{ + CommunityList: communityList, + } + return c + } + } + return nil +} + +// compare community in the message's attribute with +// the one in the condition. +func (c *CommunityCondition) evaluate(path table.Path) bool { + + communities := path.GetCommunities() + + if len(communities) == 0 { + return false + } + + // create community string in advance. + strCommunities := make([]string, len(communities)) + for i, c := range communities { + upper := strconv.FormatUint(uint64(c&0xFFFF0000>>16), 10) + lower := strconv.FormatUint(uint64(c&0x0000FFFF), 10) + strCommunities[i] = upper + ":" + lower + } + + matched := false + idx := -1 + for _, member := range c.CommunityList { + if member.isRegExp { + for i, c := range strCommunities { + if member.communityRegExp.MatchString(c) { + matched = true + idx = i + break + } + } + } else { + for i, c := range communities { + if c == member.community { + matched = true + idx = i + break + } + } + } + + if matched { + log.Debugf("community matched : community=%s)", strCommunities[idx]) + return true + } + } + return false +} + type Actions interface { apply(table.Path) table.Path } diff --git a/policy/policy_test.go b/policy/policy_test.go index 3aa50763..8a0467bb 100644 --- a/policy/policy_test.go +++ b/policy/policy_test.go @@ -22,6 +22,8 @@ import ( "github.com/osrg/gobgp/table" "github.com/stretchr/testify/assert" "net" + "strconv" + "strings" "testing" ) @@ -918,4 +920,246 @@ func TestAsPathConditionWithOtherCondition(t *testing.T) { assert.Equal(t, ROUTE_TYPE_REJECT, pType) assert.Equal(t, nil, newPath) +} + +func TestConditionConditionEvaluate(t *testing.T) { + + log.SetLevel(log.DebugLevel) + + strToCom := func(s string) uint32 { + elem := strings.Split(s, ":") + asn, _ := strconv.ParseUint(elem[0], 10, 16) + val, _ := strconv.ParseUint(elem[1], 10, 16) + return uint32(asn<<16 | val) + } + + // setup + // create path + peer := &table.PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65005}), + bgp.NewAsPathParam(1, []uint16{65001, 65010, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam1) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + strToCom("65001:100"), + strToCom("65001:200"), + strToCom("65001:300"), + strToCom("65001:400"), + 0x00000000, + 0xFFFFFF01, + 0xFFFFFF02, + 0xFFFFFF03}) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []bgp.NLRInfo{*bgp.NewNLRInfo(24, "10.10.0.101")} + withdrawnRoutes := []bgp.WithdrawnRoute{} + updateMsg1 := bgp.NewBGPUpdateMessage(withdrawnRoutes, pathAttributes, nlri) + table.UpdatePathAttrs4ByteAs(updateMsg1.Body.(*bgp.BGPUpdate)) + path1 := table.ProcessMessage(updateMsg1, peer)[0] + + // create match condition + comSet1 := config.CommunitySet{ + CommunitySetName: "comset1", + CommunityMembers: []string{"65001:10", "65001:50", "65001:100"}, + } + + comSet2 := config.CommunitySet{ + CommunitySetName: "comset2", + CommunityMembers: []string{"65001:200"}, + } + + comSet3 := config.CommunitySet{ + CommunitySetName: "comset3", + CommunityMembers: []string{"4259905936"}, + } + + comSet4 := config.CommunitySet{ + CommunitySetName: "comset4", + CommunityMembers: []string{"^[0-9]*:300$"}, + } + + comSet5 := config.CommunitySet{ + CommunitySetName: "comset5", + CommunityMembers: []string{"INTERNET"}, + } + + comSet6 := config.CommunitySet{ + CommunitySetName: "comset6", + CommunityMembers: []string{"NO_EXPORT"}, + } + + comSet7 := config.CommunitySet{ + CommunitySetName: "comset7", + CommunityMembers: []string{"NO_ADVERTISE"}, + } + + comSet8 := config.CommunitySet{ + CommunitySetName: "comset8", + CommunityMembers: []string{"NO_EXPORT_SUBCONFED"}, + } + + comSetList := []config.CommunitySet{comSet1, comSet2, comSet3, + comSet4, comSet5, comSet6, comSet7, comSet8} + p1 := NewCommunityCondition("comset1", comSetList) + p2 := NewCommunityCondition("comset2", comSetList) + p3 := NewCommunityCondition("comset3", comSetList) + p4 := NewCommunityCondition("comset4", comSetList) + p5 := NewCommunityCondition("comset5", comSetList) + p6 := NewCommunityCondition("comset6", comSetList) + p7 := NewCommunityCondition("comset7", comSetList) + p8 := NewCommunityCondition("comset8", comSetList) + + // test + assert.Equal(t, true, p1.evaluate(path1)) + assert.Equal(t, true, p2.evaluate(path1)) + assert.Equal(t, true, p3.evaluate(path1)) + assert.Equal(t, true, p4.evaluate(path1)) + assert.Equal(t, true, p5.evaluate(path1)) + assert.Equal(t, true, p6.evaluate(path1)) + assert.Equal(t, true, p7.evaluate(path1)) + assert.Equal(t, true, p8.evaluate(path1)) + +} + +func TestConditionConditionEvaluateWithOtherCondition(t *testing.T) { + + log.SetLevel(log.DebugLevel) + + strToCom := func(s string) uint32 { + elem := strings.Split(s, ":") + asn, _ := strconv.ParseUint(elem[0], 10, 16) + val, _ := strconv.ParseUint(elem[1], 10, 16) + return uint32(asn<<16 | val) + } + + // setup + // create path + peer := &table.PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65004, 65005}), + bgp.NewAsPathParam(1, []uint16{65001, 65000, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + strToCom("65001:100"), + strToCom("65001:200"), + strToCom("65001:300"), + strToCom("65001:400"), + 0x00000000, + 0xFFFFFF01, + 0xFFFFFF02, + 0xFFFFFF03}) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []bgp.NLRInfo{*bgp.NewNLRInfo(24, "10.10.0.101")} + withdrawnRoutes := []bgp.WithdrawnRoute{} + updateMsg := bgp.NewBGPUpdateMessage(withdrawnRoutes, pathAttributes, nlri) + table.UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := table.ProcessMessage(updateMsg, peer)[0] + + // create policy + asPathSet := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathSetMembers: []string{"65004$"}, + } + + comSet1 := config.CommunitySet{ + CommunitySetName: "comset1", + CommunityMembers: []string{"65001:10", "65001:50", "65001:100"}, + } + + comSet2 := config.CommunitySet{ + CommunitySetName: "comset2", + CommunityMembers: []string{"65050:\\d+"}, + } + + prefixSet := config.PrefixSet{ + PrefixSetName: "ps1", + PrefixList: []config.Prefix{ + config.Prefix{ + Address: net.ParseIP("10.11.1.0"), + Masklength: 16, + MasklengthRange: "21..24", + }}, + } + + neighborSet := config.NeighborSet{ + NeighborSetName: "ns1", + NeighborInfoList: []config.NeighborInfo{ + config.NeighborInfo{ + Address: net.ParseIP("10.2.1.1"), + }}, + } + + ds := config.DefinedSets{ + PrefixSetList: []config.PrefixSet{prefixSet}, + NeighborSetList: []config.NeighborSet{neighborSet}, + BgpDefinedSets: config.BgpDefinedSets{ + AsPathSetList: []config.AsPathSet{asPathSet}, + CommunitySetList: []config.CommunitySet{comSet1, comSet2}, + }, + } + + s1 := config.Statement{ + Name: "statement1", + Conditions: config.Conditions{ + MatchPrefixSet: "ps1", + MatchNeighborSet: "ns1", + BgpConditions: config.BgpConditions{ + MatchAsPathSet: "asset1", + MatchCommunitySet: "comset1", + }, + MatchSetOptions: config.MATCH_SET_OPTIONS_TYPE_ANY, + }, + Actions: config.Actions{ + AcceptRoute: false, + RejectRoute: true, + }, + } + + s2 := config.Statement{ + Name: "statement1", + Conditions: config.Conditions{ + MatchPrefixSet: "ps1", + MatchNeighborSet: "ns1", + BgpConditions: config.BgpConditions{ + MatchAsPathSet: "asset1", + MatchCommunitySet: "comset2", + }, + MatchSetOptions: config.MATCH_SET_OPTIONS_TYPE_ANY, + }, + Actions: config.Actions{ + AcceptRoute: false, + RejectRoute: true, + }, + } + + pd1 := config.PolicyDefinition{"pd1", []config.Statement{s1}} + pd2 := config.PolicyDefinition{"pd2", []config.Statement{s2}} + pl := config.RoutingPolicy{ + DefinedSets: ds, + PolicyDefinitionList: []config.PolicyDefinition{pd1, pd2}, + } + + //test + df := pl.DefinedSets + p := NewPolicy(pl.PolicyDefinitionList[0], df) + match, pType, newPath := p.Apply(path) + assert.Equal(t, true, match) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, nil, newPath) + + df = pl.DefinedSets + p = NewPolicy(pl.PolicyDefinitionList[1], df) + match, pType, newPath = p.Apply(path) + assert.Equal(t, false, match) + assert.Equal(t, ROUTE_TYPE_NONE, pType) + assert.Equal(t, nil, newPath) + }
\ No newline at end of file diff --git a/table/path.go b/table/path.go index 6c2995a8..305a80e1 100644 --- a/table/path.go +++ b/table/path.go @@ -36,6 +36,7 @@ type Path interface { GetAsPathLen() int GetAsList() []uint32 GetAsSeqList() []uint32 + GetCommunities() []uint32 setSource(source *PeerInfo) GetSource() *PeerInfo GetSourceAs() uint32 @@ -391,6 +392,15 @@ func (pd *PathDefault) getAsListofSpecificType(getAsSeq, getAsSet bool) []uint32 return asList } +func (pd *PathDefault) GetCommunities() []uint32 { + communityList := []uint32{} + if _, attr := pd.getPathAttr(bgp.BGP_ATTR_TYPE_COMMUNITIES); attr != nil { + communities := attr.(*bgp.PathAttributeCommunities) + communityList = append(communityList, communities.Value...) + } + return communityList +} + // create Path object based on route family func CreatePath(source *PeerInfo, nlri bgp.AddrPrefixInterface, attrs []bgp.PathAttributeInterface, isWithdraw bool, now time.Time) (Path, error) { |