diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index d59149a9d50360b698edf8d9a2f2066fb8ef11de..71c4236b18f515a2f07d65ebaa829edebb3dcba2 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -1,20 +1,17 @@ package packet -import ( - "fmt" -) - const ( OctetLen = 8 MaxASNsSegment = 255 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 @@ -177,13 +174,3 @@ type Aggretator struct { Addr uint32 ASN uint16 } - -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) -} 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 4dc6c49c9d50c94698b44eb953b11d9d5a8a4a39..e20fc48672d20de933b6efa3312382c55ea3f073 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -171,7 +171,7 @@ func (pa *PathAttribute) decodeNextHop(buf *bytes.Buffer) error { 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) + return fmt.Errorf("Unable to decode local pref: %v", err) } pa.Value = uint32(med) @@ -181,7 +181,7 @@ func (pa *PathAttribute) decodeMED(buf *bytes.Buffer) error { 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) + return fmt.Errorf("Unable to decode local pref: %v", err) } pa.Value = uint32(lpref) @@ -216,8 +216,7 @@ func (pa *PathAttribute) decodeAggregator(buf *bytes.Buffer) error { func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { length := pa.Length - recordLen := uint16(12) - count := length / recordLen + count := length / LargeCommunityLen coms := make([]LargeCommunity, count) @@ -247,7 +246,7 @@ func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { pa.Value = coms - dump := pa.Length - (count * recordLen) + dump := pa.Length - (count * LargeCommunityLen) return dumpNBytes(buf, dump) } @@ -363,6 +362,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 @@ -458,6 +459,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 @@ -539,6 +566,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, "(") } diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index 34c62b49e0d569375a1b280e8ef6fc1f3088904a..a56fba0d237d26128af87c500e12a62bb11217b7 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -1195,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