diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index 41a2113f8e6cb85cb0250d0333908071dc03fd79..d59149a9d50360b698edf8d9a2f2066fb8ef11de 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -1,5 +1,9 @@ package packet +import ( + "fmt" +) + const ( OctetLen = 8 MaxASNsSegment = 255 @@ -51,13 +55,14 @@ const ( MalformedASPath = 11 // Attribute Type Codes - OriginAttr = 1 - ASPathAttr = 2 - NextHopAttr = 3 - MEDAttr = 4 - LocalPrefAttr = 5 - AtomicAggrAttr = 6 - AggregatorAttr = 7 + OriginAttr = 1 + ASPathAttr = 2 + NextHopAttr = 3 + MEDAttr = 4 + LocalPrefAttr = 5 + AtomicAggrAttr = 6 + AggregatorAttr = 7 + LargeCommunityAttr = 32 // ORIGIN values IGP = 0 @@ -172,3 +177,13 @@ 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/decoder_test.go b/protocols/bgp/packet/decoder_test.go index 01d4e01596040cdab68d7cd5abdfb30b846fe3fa..4cbc8c110b507ce475c7b7f22ac3d73ba43992f1 100644 --- a/protocols/bgp/packet/decoder_test.go +++ b/protocols/bgp/packet/decoder_test.go @@ -852,7 +852,6 @@ func TestDecodeUpdateMsg(t *testing.T) { 5, // Attribute Type code (Local Pref) 4, // Length 0, 0, 1, 0, // Local Pref 256 - }, wantFail: false, expected: &BGPUpdate{ diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index 0390b6617944b5ea1933b542b97c29ac069c52c6..4dc6c49c9d50c94698b44eb953b11d9d5a8a4a39 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -84,6 +84,10 @@ func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err } case AtomicAggrAttr: // Nothing to do for 0 octet long attribute + case LargeCommunityAttr: + if err := pa.decodeLargeCommunities(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode large communities: %v", err) + } default: return nil, consumed, fmt.Errorf("Invalid Attribute Type Code: %v", pa.TypeCode) } @@ -210,6 +214,43 @@ func (pa *PathAttribute) decodeAggregator(buf *bytes.Buffer) error { return dumpNBytes(buf, pa.Length-p) } +func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { + length := pa.Length + recordLen := uint16(12) + count := length / recordLen + + coms := make([]LargeCommunity, count) + + for i := uint16(0); i < count; i++ { + com := LargeCommunity{} + + v, err := read4BytesAsUin32(buf) + if err != nil { + return err + } + com.GlobalAdministrator = v + + v, err = read4BytesAsUin32(buf) + if err != nil { + return err + } + com.DataPart1 = v + + v, err = read4BytesAsUin32(buf) + if err != nil { + return err + } + com.DataPart2 = v + + coms[i] = com + } + + pa.Value = coms + + dump := pa.Length - (count * recordLen) + return dumpNBytes(buf, dump) +} + func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) { bytesRead := 0 if pa.ExtendedLength { @@ -281,6 +322,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 { @@ -500,3 +550,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 read4BytesAsUin32(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 next hop: buf.Read read %d bytes", n) + } + + return fourBytesToUint32(b), nil +} diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index 15f78fe39a763b86eb31281cbee5e30e54a84585..34c62b49e0d569375a1b280e8ef6fc1f3088904a 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 @@ -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 diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go index c53696fb5e6895c165e56f75d38a3e441fd9449e..7681a127f17d6e4229d3ea79af22c55665a31831 100644 --- a/protocols/bgp/server/fsm.go +++ b/protocols/bgp/server/fsm.go @@ -843,6 +843,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/route/bgp.go b/route/bgp.go index e56b140b4bea79f00125a020c5f05b61aceb7279..1109a42d2b8ee12cd4d1b7719ee402109b33085f 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