diff --git a/main.go b/main.go index 3e0598bce10567ead6b30ea7f9e1caeebeafdcf3..1952d771d9713831e851f7c7388b532ba1f4f7f1 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,10 @@ func main() { err := b.Start(&config.Global{ Listen: true, + LocalAddressList: []net.IP{ + net.IPv4(169, 254, 100, 1), + net.IPv4(169, 254, 200, 0), + }, }) if err != nil { logrus.Fatalf("Unable to start BGP server: %v", err) diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index 7cd77c21d0a6e1ce87e8f3e3926433b336a8cd25..b32980dbdb3f702a69af0ff521db0cb66584bd4b 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -6,11 +6,12 @@ const ( BGP4Version = 4 MinOpenLen = 29 - MarkerLen = 16 - HeaderLen = 19 - MinLen = 19 - MaxLen = 4096 - NLRIMaxLen = 5 + MarkerLen = 16 + HeaderLen = 19 + MinLen = 19 + MaxLen = 4096 + NLRIMaxLen = 5 + LargeCommunityLen = 12 OpenMsg = 1 UpdateMsg = 2 @@ -55,17 +56,17 @@ const ( AdministrativeReset = 4 // Attribute Type Codes - OriginAttr = 1 - ASPathAttr = 2 - NextHopAttr = 3 - MEDAttr = 4 - LocalPrefAttr = 5 - AtomicAggrAttr = 6 - AggregatorAttr = 7 - CommunitiesAttr = 8 - - AS4PathAttr = 17 - AS4AggregatorAttr = 18 + OriginAttr = 1 + ASPathAttr = 2 + NextHopAttr = 3 + MEDAttr = 4 + LocalPrefAttr = 5 + AtomicAggrAttr = 6 + AggregatorAttr = 7 + CommunitiesAttr = 8 + AS4PathAttr = 17 + AS4AggregatorAttr = 18 + LargeCommunityAttr = 32 // ORIGIN values IGP = 0 diff --git a/protocols/bgp/packet/large_community.go b/protocols/bgp/packet/large_community.go new file mode 100644 index 0000000000000000000000000000000000000000..135c86398700e200ba3a4f42e75ca3df33f30ed6 --- /dev/null +++ b/protocols/bgp/packet/large_community.go @@ -0,0 +1,46 @@ +package packet + +import ( + "fmt" + "strconv" + "strings" +) + +type LargeCommunity struct { + GlobalAdministrator uint32 + DataPart1 uint32 + DataPart2 uint32 +} + +func (c LargeCommunity) String() string { + return fmt.Sprintf("(%d,%d,%d)", c.GlobalAdministrator, c.DataPart1, c.DataPart2) +} + +func ParseCommunityString(s string) (com LargeCommunity, err error) { + s = strings.Trim(s, "()") + t := strings.Split(s, ",") + + if len(t) != 3 { + return com, fmt.Errorf("can not parse large community %s", s) + } + + v, err := strconv.ParseUint(t[0], 10, 32) + if err != nil { + return com, err + } + com.GlobalAdministrator = uint32(v) + + v, err = strconv.ParseUint(t[1], 10, 32) + if err != nil { + return com, err + } + com.DataPart1 = uint32(v) + + v, err = strconv.ParseUint(t[2], 10, 32) + if err != nil { + return com, err + } + com.DataPart2 = uint32(v) + + return com, err +} diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index 753395d2c67a844863e067f27ec45bed1a29cda2..a9fcdd74e5eaab291cce876a8ad597c03ca0c2e4 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -96,6 +96,10 @@ func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err if err := pa.decodeAS4Aggregator(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Aggregator: %v", err) } + case LargeCommunityAttr: + if err := pa.decodeLargeCommunities(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode large communities: %v", err) + } default: if err := pa.decodeUnknown(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to decode unknown attribute: %v", err) @@ -176,41 +180,15 @@ func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer) error { } func (pa *PathAttribute) decodeNextHop(buf *bytes.Buffer) error { - addr := [4]byte{} - - p := uint16(0) - n, err := buf.Read(addr[:]) - if err != nil { - return err - } - if n != 4 { - return fmt.Errorf("Unable to read next hop: buf.Read read %d bytes", n) - } - - pa.Value = fourBytesToUint32(addr) - p += 4 - - return dumpNBytes(buf, pa.Length-p) + return pa.decodeUint32(buf, "next hop") } func (pa *PathAttribute) decodeMED(buf *bytes.Buffer) error { - med, err := pa.decodeUint32(buf) - if err != nil { - return fmt.Errorf("Unable to recode local pref: %v", err) - } - - pa.Value = uint32(med) - return nil + return pa.decodeUint32(buf, "MED") } func (pa *PathAttribute) decodeLocalPref(buf *bytes.Buffer) error { - lpref, err := pa.decodeUint32(buf) - if err != nil { - return fmt.Errorf("Unable to recode local pref: %v", err) - } - - pa.Value = uint32(lpref) - return nil + return pa.decodeUint32(buf, "local pref") } func (pa *PathAttribute) decodeAggregator(buf *bytes.Buffer) error { @@ -260,23 +238,64 @@ func (pa *PathAttribute) decodeCommunities(buf *bytes.Buffer) error { return nil } -func (pa *PathAttribute) decodeAS4Path(buf *bytes.Buffer) error { - as4Path, err := pa.decodeUint32(buf) - if err != nil { - return fmt.Errorf("Unable to decode AS4Path: %v", err) +func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { + length := pa.Length + count := length / LargeCommunityLen + + coms := make([]LargeCommunity, count) + + for i := uint16(0); i < count; i++ { + com := LargeCommunity{} + + v, err := read4BytesAsUint32(buf) + if err != nil { + return err + } + com.GlobalAdministrator = v + + v, err = read4BytesAsUint32(buf) + if err != nil { + return err + } + com.DataPart1 = v + + v, err = read4BytesAsUint32(buf) + if err != nil { + return err + } + com.DataPart2 = v + + coms[i] = com } - pa.Value = as4Path - return nil + pa.Value = coms + + dump := pa.Length - (count * LargeCommunityLen) + return dumpNBytes(buf, dump) +} + +func (pa *PathAttribute) decodeAS4Path(buf *bytes.Buffer) error { + return pa.decodeUint32(buf, "AS4Path") } func (pa *PathAttribute) decodeAS4Aggregator(buf *bytes.Buffer) error { - as4Aggregator, err := pa.decodeUint32(buf) + return pa.decodeUint32(buf, "AS4Aggregator") +} + +func (pa *PathAttribute) decodeUint32(buf *bytes.Buffer, attrName string) error { + v, err := read4BytesAsUint32(buf) if err != nil { - return fmt.Errorf("Unable to decode AS4Aggregator: %v", err) + return fmt.Errorf("Unable to decode %s: %v", attrName, err) + } + + pa.Value = v + + p := uint16(4) + err = dumpNBytes(buf, pa.Length-p) + if err != nil { + return fmt.Errorf("dumpNBytes failed: %v", err) } - pa.Value = as4Aggregator return nil } @@ -300,24 +319,6 @@ func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) { return bytesRead, nil } -func (pa *PathAttribute) decodeUint32(buf *bytes.Buffer) (uint32, error) { - var v uint32 - - p := uint16(0) - err := decode(buf, []interface{}{&v}) - if err != nil { - return 0, err - } - - p += 4 - err = dumpNBytes(buf, pa.Length-p) - if err != nil { - return 0, fmt.Errorf("dumpNBytes failed: %v", err) - } - - return v, nil -} - func (pa *PathAttribute) ASPathString() (ret string) { for _, p := range pa.Value.(ASPath) { if p.Type == ASSet { @@ -351,6 +352,15 @@ func (pa *PathAttribute) ASPathLen() (ret uint16) { return } +func (a *PathAttribute) LargeCommunityString() string { + s := "" + for _, com := range a.Value.([]LargeCommunity) { + s += com.String() + " " + } + + return strings.TrimRight(s, " ") +} + // dumpNBytes is used to dump n bytes of buf. This is useful in case an path attributes // length doesn't match a fixed length's attributes length (e.g. ORIGIN is always an octet) func dumpNBytes(buf *bytes.Buffer, n uint16) error { @@ -383,6 +393,8 @@ func (pa *PathAttribute) serialize(buf *bytes.Buffer) uint8 { pathAttrLen = pa.serializeAtomicAggregate(buf) case AggregatorAttr: pathAttrLen = pa.serializeAggregator(buf) + case LargeCommunityAttr: + pathAttrLen = pa.serializeLargeCommunities(buf) } return pathAttrLen @@ -478,6 +490,32 @@ func (pa *PathAttribute) serializeAggregator(buf *bytes.Buffer) uint8 { return 5 } +func (pa *PathAttribute) serializeLargeCommunities(buf *bytes.Buffer) uint8 { + coms := pa.Value.([]LargeCommunity) + if len(coms) == 0 { + return 0 + } + + attrFlags := uint8(0) + attrFlags = setOptional(attrFlags) + attrFlags = setTransitive(attrFlags) + attrFlags = setPartial(attrFlags) + buf.WriteByte(attrFlags) + buf.WriteByte(LargeCommunityAttr) + + length := uint8(LargeCommunityLen * len(coms)) + + buf.WriteByte(length) + + for _, com := range coms { + buf.Write(convert.Uint32Byte(com.GlobalAdministrator)) + buf.Write(convert.Uint32Byte(com.DataPart1)) + buf.Write(convert.Uint32Byte(com.DataPart2)) + } + + return length +} + /*func (pa *PathAttribute) PrependASPath(prepend []uint32) { if pa.TypeCode != ASPathAttr { return @@ -563,6 +601,24 @@ func ParseASPathStr(asPathString string) (*PathAttribute, error) { }, nil } +func LargeCommunityAttributeForString(s string) (*PathAttribute, error) { + strs := strings.Split(s, " ") + coms := make([]LargeCommunity, len(strs)) + + var err error + for i, str := range strs { + coms[i], err = ParseCommunityString(str) + if err != nil { + return nil, err + } + } + + return &PathAttribute{ + TypeCode: LargeCommunityAttr, + Value: coms, + }, nil +} + func isBeginOfASSet(asPathPart string) bool { return strings.Contains(asPathPart, "(") } @@ -574,3 +630,16 @@ func isEndOfASSset(asPathPart string) bool { func fourBytesToUint32(address [4]byte) uint32 { return uint32(address[0])<<24 + uint32(address[1])<<16 + uint32(address[2])<<8 + uint32(address[3]) } + +func read4BytesAsUint32(buf *bytes.Buffer) (uint32, error) { + b := [4]byte{} + n, err := buf.Read(b[:]) + if err != nil { + return 0, err + } + if n != 4 { + return 0, fmt.Errorf("Unable to read as uint32. Expected 4 bytes but got only %d", n) + } + + return fourBytesToUint32(b), nil +} diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index a547178a7c2422ba5dc1b653735d590f3b69973b..47e00b35a2ce63580c5f669a20485732875c6231 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -589,6 +589,74 @@ func TestDecodeAggregator(t *testing.T) { } } +func TestDecodeLargeCommunity(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "two valid large communities", + input: []byte{ + 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, // (1, 2, 3), (4, 5, 6) + }, + wantFail: false, + expected: &PathAttribute{ + Length: 24, + Value: []LargeCommunity{ + { + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + { + GlobalAdministrator: 4, + DataPart1: 5, + DataPart2: 6, + }, + }, + }, + }, + { + name: "Empty input", + input: []byte{}, + wantFail: false, + expected: &PathAttribute{ + Length: 0, + Value: []LargeCommunity{}, + }, + }, + } + + for _, test := range tests { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeLargeCommunities(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + continue + } + t.Errorf("Expected error did not happen for test %q", test.name) + continue + } + + if err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + assert.Equal(t, test.expected, pa) + } +} + func TestSetLength(t *testing.T) { tests := []struct { name string @@ -695,7 +763,7 @@ func TestDecodeUint32(t *testing.T) { pa := &PathAttribute{ Length: l, } - res, err := pa.decodeUint32(bytes.NewBuffer(test.input)) + err := pa.decodeUint32(bytes.NewBuffer(test.input), "test") if test.wantFail { if err != nil { @@ -710,7 +778,7 @@ func TestDecodeUint32(t *testing.T) { continue } - assert.Equal(t, test.expected, res) + assert.Equal(t, test.expected, pa.Value) } } @@ -756,6 +824,40 @@ func TestASPathString(t *testing.T) { } } +func TestLargeCommunityString(t *testing.T) { + tests := []struct { + name string + pa *PathAttribute + expected string + }{ + { + name: "two attributes", + pa: &PathAttribute{ + Value: []LargeCommunity{ + { + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + { + GlobalAdministrator: 4, + DataPart1: 5, + DataPart2: 6, + }, + }, + }, + expected: "(1,2,3) (4,5,6)", + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + res := test.pa.LargeCommunityString() + assert.Equal(te, test.expected, res) + }) + } +} + func TestSetOptional(t *testing.T) { tests := []struct { name string @@ -1093,6 +1195,62 @@ func TestSerializeASPath(t *testing.T) { } } +func TestSerializeLargeCommunities(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "2 large communities", + input: &PathAttribute{ + TypeCode: LargeCommunityAttr, + Value: []LargeCommunity{ + { + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + { + GlobalAdministrator: 4, + DataPart1: 5, + DataPart2: 6, + }, + }, + }, + expected: []byte{ + 0xe0, // Attribute flags + 32, // Type + 24, // Length + 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, // Communities (1, 2, 3), (4, 5, 6) + }, + expectedLen: 24, + }, + { + name: "empty list of communities", + input: &PathAttribute{ + TypeCode: LargeCommunityAttr, + Value: []LargeCommunity{}, + }, + expected: []byte{}, + expectedLen: 0, + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + buf := bytes.NewBuffer([]byte{}) + n := test.input.serializeLargeCommunities(buf) + if n != test.expectedLen { + t.Fatalf("Unexpected length for test %q: %d", test.name, n) + } + + assert.Equal(t, test.expected, buf.Bytes()) + }) + } +} + func TestSerialize(t *testing.T) { tests := []struct { name string diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go index 6e37f14ea9ae53866beb699cc7748e158faf0523..1d83b2d39819e429d9d32b557df9d97d28e07bac 100644 --- a/protocols/bgp/server/fsm.go +++ b/protocols/bgp/server/fsm.go @@ -792,7 +792,7 @@ func (fsm *FSM) established() int { case recvMsg := <-fsm.msgRecvCh: msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg)) if err != nil { - log.WithError(err).Errorf("Failed to decode BGP message %v\n", recvMsg.msg) + log.WithError(err).Errorf("Failed to decode BGP message %v\n", recvMsg.msg) switch bgperr := err.(type) { case packet.BGPError: sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode) @@ -845,6 +845,8 @@ func (fsm *FSM) established() int { case packet.ASPathAttr: path.BGPPath.ASPath = pa.ASPathString() path.BGPPath.ASPathLen = pa.ASPathLen() + case packet.LargeCommunityAttr: + path.BGPPath.LargeCommunities = pa.LargeCommunityString() } } fsm.adjRIBIn.AddPath(pfx, path) diff --git a/protocols/bgp/server/update_helper.go b/protocols/bgp/server/update_helper.go new file mode 100644 index 0000000000000000000000000000000000000000..22bfeec52ad72e603583452c9bc9f70be314f4a7 --- /dev/null +++ b/protocols/bgp/server/update_helper.go @@ -0,0 +1,60 @@ +package server + +import ( + "fmt" + "strings" + + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + "github.com/bio-routing/bio-rd/route" +) + +func pathAttribues(p *route.Path, fsm *FSM) (*packet.PathAttribute, error) { + asPathPA, err := packet.ParseASPathStr(strings.TrimRight(fmt.Sprintf("%d %s", fsm.localASN, p.BGPPath.ASPath), " ")) + if err != nil { + return nil, fmt.Errorf("Unable to parse AS path: %v", err) + } + + origin := &packet.PathAttribute{ + TypeCode: packet.OriginAttr, + Value: p.BGPPath.Origin, + Next: asPathPA, + } + + nextHop := &packet.PathAttribute{ + TypeCode: packet.NextHopAttr, + Value: p.BGPPath.NextHop, + } + asPathPA.Next = nextHop + + localPref := &packet.PathAttribute{ + TypeCode: packet.LocalPrefAttr, + Value: p.BGPPath.LocalPref, + } + nextHop.Next = localPref + + if p.BGPPath != nil { + err := addOptionalPathAttribues(p, localPref) + + if err != nil { + return nil, err + } + } + + return origin, nil +} + +func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) error { + current := parent + + if len(p.BGPPath.LargeCommunities) > 0 { + largeCommunities, err := packet.LargeCommunityAttributeForString(p.BGPPath.LargeCommunities) + if err != nil { + return fmt.Errorf("Could not create large community attribute: %v", err) + } + + current.Next = largeCommunities + current = largeCommunities + } + + return nil +} diff --git a/protocols/bgp/server/update_sender.go b/protocols/bgp/server/update_sender.go index c33f2ce01882e0ba1326962fce7396c9005d9aae..54c1d1b0cfa28c9094015d50483af1fd12bfd2e6 100644 --- a/protocols/bgp/server/update_sender.go +++ b/protocols/bgp/server/update_sender.go @@ -28,31 +28,18 @@ func newUpdateSender(fsm *FSM) *UpdateSender { // AddPath serializes a new path and sends out a BGP update message func (u *UpdateSender) AddPath(pfx net.Prefix, p *route.Path) error { - asPathPA, err := packet.ParseASPathStr(asPathString(u.iBGP, u.fsm.localASN, p.BGPPath.ASPath)) + pathAttrs, err := pathAttribues(p, u.fsm) if err != nil { - return fmt.Errorf("Unable to parse AS path: %v", err) + log.Errorf("Unable to create BGP Update: %v", err) + return nil } - update := &packet.BGPUpdate{ - PathAttributes: &packet.PathAttribute{ - TypeCode: packet.OriginAttr, - Value: p.BGPPath.Origin, - Next: &packet.PathAttribute{ - TypeCode: packet.ASPathAttr, - Value: asPathPA.Value, - Next: &packet.PathAttribute{ - TypeCode: packet.NextHopAttr, - Value: p.BGPPath.NextHop, - Next: &packet.PathAttribute{ - TypeCode: packet.LocalPrefAttr, - Value: p.BGPPath.LocalPref, - }, - }, - }, - }, - NLRI: &packet.NLRI{ - IP: pfx.Addr(), - Pfxlen: pfx.Pfxlen(), + update := &packet.BGPUpdateAddPath{ + PathAttributes: pathAttrs, + NLRI: &packet.NLRIAddPath{ + PathIdentifier: p.BGPPath.PathIdentifier, + IP: pfx.Addr(), + Pfxlen: pfx.Pfxlen(), }, } diff --git a/protocols/bgp/server/update_sender_add_path.go b/protocols/bgp/server/update_sender_add_path.go index a0fe2b3046174cfd5abd929366f631fb07c4d4fc..84d6796046b8b3380a6afae2f7ab1257b59a699c 100644 --- a/protocols/bgp/server/update_sender_add_path.go +++ b/protocols/bgp/server/update_sender_add_path.go @@ -27,32 +27,17 @@ func newUpdateSenderAddPath(fsm *FSM) *UpdateSenderAddPath { // AddPath serializes a new path and sends out a BGP update message func (u *UpdateSenderAddPath) AddPath(pfx net.Prefix, p *route.Path) error { - asPathPA, err := packet.ParseASPathStr(asPathString(u.iBGP, u.fsm.localASN, p.BGPPath.ASPath)) + pathAttrs, err := pathAttribues(p, u.fsm) if err != nil { - return fmt.Errorf("Unable to parse AS path: %v", err) + log.Errorf("Unable to create BGP Update: %v", err) + return nil } - update := &packet.BGPUpdateAddPath{ - PathAttributes: &packet.PathAttribute{ - TypeCode: packet.OriginAttr, - Value: p.BGPPath.Origin, - Next: &packet.PathAttribute{ - TypeCode: packet.ASPathAttr, - Value: asPathPA.Value, - Next: &packet.PathAttribute{ - TypeCode: packet.NextHopAttr, - Value: p.BGPPath.NextHop, - Next: &packet.PathAttribute{ - TypeCode: packet.LocalPrefAttr, - Value: p.BGPPath.LocalPref, - }, - }, - }, - }, - NLRI: &packet.NLRIAddPath{ - PathIdentifier: p.BGPPath.PathIdentifier, - IP: pfx.Addr(), - Pfxlen: pfx.Pfxlen(), + update := &packet.BGPUpdate{ + PathAttributes: pathAttrs, + NLRI: &packet.NLRI{ + IP: pfx.Addr(), + Pfxlen: pfx.Pfxlen(), }, } diff --git a/route/bgp.go b/route/bgp.go index 847964f913645397ffbab534c73cafd3f7b0d9c9..a19c4cde94873f5761fd579737bacde3fd240729 100644 --- a/route/bgp.go +++ b/route/bgp.go @@ -10,16 +10,17 @@ import ( // BGPPath represents a set of BGP path attributes type BGPPath struct { - PathIdentifier uint32 - NextHop uint32 - LocalPref uint32 - ASPath string - ASPathLen uint16 - Origin uint8 - MED uint32 - EBGP bool - BGPIdentifier uint32 - Source uint32 + PathIdentifier uint32 + NextHop uint32 + LocalPref uint32 + ASPath string + ASPathLen uint16 + Origin uint8 + MED uint32 + EBGP bool + BGPIdentifier uint32 + Source uint32 + LargeCommunities string } // ECMP determines if routes b and c are euqal in terms of ECMP diff --git a/routingtable/filter/actions/add_large_community_action.go b/routingtable/filter/actions/add_large_community_action.go new file mode 100644 index 0000000000000000000000000000000000000000..17169c315817509c8c6c1a2b382ab39c3b5f0428 --- /dev/null +++ b/routingtable/filter/actions/add_large_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 AddLargeCommunityAction struct { + communities []*packet.LargeCommunity +} + +func NewAddLargeCommunityAction(coms []*packet.LargeCommunity) *AddLargeCommunityAction { + return &AddLargeCommunityAction{ + communities: coms, + } +} + +func (a *AddLargeCommunityAction) 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.LargeCommunities = modified.BGPPath.LargeCommunities + " " + com.String() + } + modified.BGPPath.LargeCommunities = strings.TrimLeft(modified.BGPPath.LargeCommunities, " ") + + return modified, false +} diff --git a/routingtable/filter/actions/add_large_community_action_test.go b/routingtable/filter/actions/add_large_community_action_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d2c597fd5cbe8011b3eeb7b74443a09c726d6eb7 --- /dev/null +++ b/routingtable/filter/actions/add_large_community_action_test.go @@ -0,0 +1,75 @@ +package actions + +import ( + "testing" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + "github.com/bio-routing/bio-rd/route" + "github.com/stretchr/testify/assert" +) + +func TestAddingLargeCommunities(t *testing.T) { + tests := []struct { + name string + current string + communities []*packet.LargeCommunity + expected string + }{ + { + name: "add one to empty", + communities: []*packet.LargeCommunity{ + &packet.LargeCommunity{ + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + }, + expected: "(1,2,3)", + }, + { + name: "add one to existing", + current: "(5,6,7)", + communities: []*packet.LargeCommunity{ + &packet.LargeCommunity{ + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + }, + expected: "(5,6,7) (1,2,3)", + }, + { + name: "add two to existing", + current: "(5,6,7)", + communities: []*packet.LargeCommunity{ + &packet.LargeCommunity{ + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + &packet.LargeCommunity{ + GlobalAdministrator: 7, + DataPart1: 8, + DataPart2: 9, + }, + }, + expected: "(5,6,7) (1,2,3) (7,8,9)", + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + p := &route.Path{ + BGPPath: &route.BGPPath{ + LargeCommunities: test.current, + }, + } + + a := NewAddLargeCommunityAction(test.communities) + modPath, _ := a.Do(net.Prefix{}, p) + + assert.Equal(te, test.expected, modPath.BGPPath.LargeCommunities) + }) + } +} diff --git a/routingtable/filter/large_community_filter.go b/routingtable/filter/large_community_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..0f9d474e69728ba682cde441e819a0d08bd7cc77 --- /dev/null +++ b/routingtable/filter/large_community_filter.go @@ -0,0 +1,15 @@ +package filter + +import ( + "strings" + + "github.com/bio-routing/bio-rd/protocols/bgp/packet" +) + +type LargeCommunityFilter struct { + community *packet.LargeCommunity +} + +func (f *LargeCommunityFilter) Matches(communityString string) bool { + return strings.Contains(communityString, f.community.String()) +} diff --git a/routingtable/filter/term_condition.go b/routingtable/filter/term_condition.go index 8bc87485d58bd9215d122e4af9792c7c0c1c2b82..003b9dcbc021e75cd28d3039efbad2d00bf1d109 100644 --- a/routingtable/filter/term_condition.go +++ b/routingtable/filter/term_condition.go @@ -6,8 +6,9 @@ import ( ) type TermCondition struct { - prefixLists []*PrefixList - routeFilters []*RouteFilter + prefixLists []*PrefixList + routeFilters []*RouteFilter + largeCommunityFilters []*LargeCommunityFilter } func NewTermCondition(prefixLists []*PrefixList, routeFilters []*RouteFilter) *TermCondition { @@ -18,7 +19,7 @@ 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) + return f.matchesAnyPrefixList(p) || f.machtchesAnyRouteFilter(p) || f.machtchesAnyLageCommunityFilter(pa) } func (t *TermCondition) matchesAnyPrefixList(p net.Prefix) bool { @@ -40,3 +41,17 @@ func (t *TermCondition) machtchesAnyRouteFilter(p net.Prefix) bool { return false } + +func (t *TermCondition) machtchesAnyLageCommunityFilter(pa *route.Path) bool { + if pa.BGPPath == nil { + return false + } + + for _, l := range t.largeCommunityFilters { + if l.Matches(pa.BGPPath.LargeCommunities) { + return true + } + } + + return false +} diff --git a/routingtable/filter/term_condition_test.go b/routingtable/filter/term_condition_test.go index fba97b673bea6901983e6bba509358fe8bf504f3..6e9b89f20eb226faebdd3856fa4bc61a97388a4e 100644 --- a/routingtable/filter/term_condition_test.go +++ b/routingtable/filter/term_condition_test.go @@ -4,17 +4,20 @@ import ( "testing" "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" "github.com/stretchr/testify/assert" ) func TestMatches(t *testing.T) { tests := []struct { - name string - prefix net.Prefix - prefixLists []*PrefixList - routeFilters []*RouteFilter - expected bool + name string + prefix net.Prefix + bgpPath *route.BGPPath + prefixLists []*PrefixList + routeFilters []*RouteFilter + largeCommunityFilters []*LargeCommunityFilter + expected bool }{ { name: "one prefix matches in prefix list, no route filters set", @@ -105,16 +108,50 @@ func TestMatches(t *testing.T) { }, expected: true, }, + { + name: "large community matches", + prefix: net.NewPfx(strAddr("10.0.0.0"), 24), + bgpPath: &route.BGPPath{ + LargeCommunities: "(1,2,0) (1,2,3)", + }, + largeCommunityFilters: []*LargeCommunityFilter{ + { + &packet.LargeCommunity{ + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + }, + }, + expected: true, + }, + { + name: "large community does not match", + prefix: net.NewPfx(strAddr("10.0.0.0"), 24), + bgpPath: &route.BGPPath{}, + 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 := NewTermCondition(test.prefixLists, test.routeFilters) + f.largeCommunityFilters = test.largeCommunityFilters + + pa := &route.Path{ + BGPPath: test.bgpPath, + } - assert.Equal(te, test.expected, f.Matches(test.prefix, &route.Path{})) + assert.Equal(te, test.expected, f.Matches(test.prefix, pa)) }) } }