Skip to content

Commit

Permalink
bgp: add extended community support
Browse files Browse the repository at this point in the history
BGP Extended Communities (RFC 4360) are a fundamental function for
features like MP-BGP EVPN, VPLS or Flow-Spec.

This commit adds the basic functionality for EC which is required for Flow-Spec.

Signed-off-by: Tom Siewert <[email protected]>
  • Loading branch information
sinuscosinustan committed Dec 16, 2023
1 parent c567987 commit 8305ad6
Show file tree
Hide file tree
Showing 10 changed files with 506 additions and 92 deletions.
30 changes: 16 additions & 14 deletions protocols/bgp/packet/bgp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ const (
BGP4Version = 4
MinOpenLen = 29

MarkerLen = 16
HeaderLen = 19
MinLen = 19
MaxLen = 4096
MinUpdateLen = 4
NLRIMaxLen = 5
AFILen = 2
SAFILen = 1
CommunityLen = 4
LargeCommunityLen = 12
IPv4Len = 4
IPv6Len = 16
ClusterIDLen = 4
MarkerLen = 16
HeaderLen = 19
MinLen = 19
MaxLen = 4096
MinUpdateLen = 4
NLRIMaxLen = 5
AFILen = 2
SAFILen = 1
CommunityLen = 4
LargeCommunityLen = 12
ExtendedCommunityLen = 8
IPv4Len = 4
IPv6Len = 16
ClusterIDLen = 4

// BGP message types
OpenMsg = 1
Expand Down Expand Up @@ -78,6 +79,7 @@ const (
ClusterListAttr = 10
MultiProtocolReachNLRIAttr = 14
MultiProtocolUnreachNLRIAttr = 15
ExtendedCommunitiesAttr = 16
AS4PathAttr = 17
AS4AggregatorAttr = 18
LargeCommunitiesAttr = 32
Expand Down Expand Up @@ -207,4 +209,4 @@ func PeerRoleName(pr uint8) string {
default:
return "Unknown"
}
}
}
50 changes: 49 additions & 1 deletion protocols/bgp/packet/path_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ func decodePathAttr(buf *bytes.Buffer, opt *DecodeOptions) (pa *PathAttribute, c
if err := pa.decodeLargeCommunities(buf); err != nil {
return nil, consumed, fmt.Errorf("failed to decode large communities: %w", err)
}
case ExtendedCommunitiesAttr:
if err := pa.decodeExtendedCommunities(buf); err != nil {
return nil, consumed, fmt.Errorf("failed to decode extended communities: %s", err)
}
default:
if err := pa.decodeUnknown(buf); err != nil {
return nil, consumed, fmt.Errorf("failed to decode unknown attribute: %w", err)
Expand Down Expand Up @@ -408,6 +412,43 @@ func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error {
return nil
}

func (pa *PathAttribute) decodeExtendedCommunities(buf *bytes.Buffer) error {
if pa.Length%ExtendedCommunityLen != 0 {
return fmt.Errorf("unable to read extended community path attribute. Length %d is not divisible by %d", pa.Length, ExtendedCommunityLen)
}

count := pa.Length / ExtendedCommunityLen
eComs := make(types.ExtendedCommunities, count)

for i := uint16(0); i < count; i++ {
eCom := types.ExtendedCommunity{}

if err := decode.DecodeUint8(buf, &eCom.Type); err != nil {
return err
}

// TODO: Fix me! The sub type is optional and may not be available if the
// ExtComm is transitive. Check if the Type is transitive through a static-defined list
// and set it then. Also, the value length becomes variable if the sub type is not present.
// This fix requires the types to be defined somewhere.
if err := decode.DecodeUint8(buf, &eCom.SubType); err != nil {
return err
}

// TODO: Fix me! Make value size variable depending on if sub type is present or not
value := make([]byte, 6)
if err := decode.Decode(buf, []interface{}{&value}); err != nil {
return fmt.Errorf("unable to decode: %w", err)
}
eCom.Value = value

eComs[i] = eCom
}

pa.Value = &eComs
return nil
}

func (pa *PathAttribute) decodeAS4Aggregator(buf *bytes.Buffer) error {
return pa.decodeUint32(buf, "AS4Aggregator")
}
Expand Down Expand Up @@ -528,6 +569,8 @@ func (pa *PathAttribute) Serialize(buf *bytes.Buffer, opt *EncodeOptions) uint16
pathAttrLen = uint16(pa.serializeCommunities(buf))
case LargeCommunitiesAttr:
pathAttrLen = uint16(pa.serializeLargeCommunities(buf))
case ExtendedCommunitiesAttr:
pathAttrLen = uint16(pa.serializeExtendedCommunities(buf))
case MultiProtocolReachNLRIAttr:
pathAttrLen = pa.serializeMultiProtocolReachNLRI(buf, opt)
case MultiProtocolUnreachNLRIAttr:
Expand Down Expand Up @@ -730,6 +773,11 @@ func (pa *PathAttribute) serializeLargeCommunities(buf *bytes.Buffer) uint16 {
return length + 3
}

func (pa *PathAttribute) serializeExtendedCommunities(buf *bytes.Buffer) uint16 {
// TODO: implement
return 0
}

func (pa *PathAttribute) serializeOriginatorID(buf *bytes.Buffer) uint8 {
attrFlags := uint8(0)
attrFlags = setOptional(attrFlags)
Expand Down Expand Up @@ -981,4 +1029,4 @@ func PathAttributes(p *route.Path, iBGP bool, rrClient bool) (*PathAttribute, er
}

return asPath, nil
}
}
106 changes: 105 additions & 1 deletion protocols/bgp/packet/path_attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,110 @@ func TestDecodeCommunity(t *testing.T) {
}
}

func TestDecodeExtendedCommunity(t *testing.T) {
tests := []struct {
name string
input []byte
wantFail bool
explicitLength uint16
expected *PathAttribute
}{
{
name: "Empty list",
input: []byte{},
wantFail: false,
expected: &PathAttribute{
Length: 0,
Value: &types.ExtendedCommunities{},
},
},
{
name: "One extended community",
input: []byte{
0x80, 0x06, // Type 128 Sub-Type 6 (Flow-spec)
0x00, 0x00, // 2-Octet AS = 0
0x00, 0x00, 0x00, 0x00, // Rate shaper = 0
},
wantFail: false,
expected: &PathAttribute{
Length: 8,
Value: &types.ExtendedCommunities{
{
Type: 128,
SubType: 6,
Value: []byte{
0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
},
},
},
},
},
{
name: "Two extended communities",
wantFail: false,
input: []byte{
0x00, 0x02, // Type 0 Sub-Type 2 (Route Target)
0x00, 0x64, // 2-Octet AS = 100
0x00, // Local Admin Type = VID
0x00, 0x00, 0x64, // Service ID = 100

0x06, 0x01, // Type 6 Sub-Type 1 (EVPN ESI MPLS Lbl)
0x00, // All-Active redundancy
0x00, 0x00, 0x04, 0x93, 0xd0, // MPLS label 299984
},
expected: &PathAttribute{
Length: 16,
Value: &types.ExtendedCommunities{
{
Type: 0,
SubType: 2,
Value: []byte{
0x00, 0x64,
0x00,
0x00, 0x00, 0x64,
},
},
{
Type: 6,
SubType: 1,
Value: []byte{
0x00,
0x00, 0x00, 0x04, 0x93, 0xd0,
},
},
},
},
},
}

for _, test := range tests {
l := uint16(len(test.input))
if test.explicitLength != 0 {
l = test.explicitLength
}
pa := &PathAttribute{
Length: l,
}
err := pa.decodeExtendedCommunities(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 TestDecodeClusterList(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -2424,4 +2528,4 @@ func TestPathAttributeCopy(t *testing.T) {
copy := attr.Copy()
assert.Equal(t, *expected, *copy)
assert.False(t, attr == copy)
}
}
4 changes: 3 additions & 1 deletion protocols/bgp/server/fsm_address_family.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ func (f *fsmAddressFamily) processAttributes(attrs *packet.PathAttribute, path *
path.BGPPath.BGPPathA.OriginatorID = pa.Value.(uint32)
case packet.ClusterListAttr:
path.BGPPath.ClusterList = pa.Value.(*types.ClusterList)
case packet.ExtendedCommunitiesAttr:
path.BGPPath.ExtendedCommunities = pa.Value.(*types.ExtendedCommunities)
case packet.MultiProtocolReachNLRIAttr:
case packet.MultiProtocolUnreachNLRIAttr:
default:
Expand All @@ -362,4 +364,4 @@ func (f *fsmAddressFamily) processUnknownAttribute(attr *packet.PathAttribute) *
}

return u
}
}
54 changes: 54 additions & 0 deletions protocols/bgp/types/extended_community.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package types

import (
"fmt"

"github.com/bio-routing/bio-rd/route/api"
)

// TODO: Add EC type mappings

type ExtendedCommunities []ExtendedCommunity

// ExtendedCommunity represents an extended community as
// described in RFC 4360.
type ExtendedCommunity struct {
Type uint8
SubType uint8
Value []byte
}

// ToProto converts ExtendedCommunity to proto ExtendedCommunity
func (ec *ExtendedCommunity) ToProto() *api.ExtendedCommunity {
return &api.ExtendedCommunity{
Type: uint32(ec.Type),
Subtype: uint32(ec.SubType),
Value: ec.Value,
}
}

// ExtendedCommunityFromProtoExtendedCommunity converts a proto ExtendedCommunity to ExtendedCommunity
func ExtendedCommunityFromProtoExtendedCommunity(aec *api.ExtendedCommunity) ExtendedCommunity {
return ExtendedCommunity{
Type: uint8(aec.Type),
SubType: uint8(aec.Subtype),
Value: aec.Value,
}
}

// String transitions an extended community its (almost) human-readable representation
func (ec *ExtendedCommunity) String() string {
if ec == nil {
return ""
}

var subType uint8
if ec.SubType == 0 {
subType = 0xff
} else {
subType = ec.SubType
}

// TODO: translate types and value
return fmt.Sprintf("Type: %d Subtype: %d Value: %s", ec.Type, subType, ec.Value)
}
54 changes: 54 additions & 0 deletions protocols/bgp/types/extended_community_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package types

import (
"testing"

"github.com/bio-routing/bio-rd/route/api"
"github.com/stretchr/testify/assert"
)

func TestExtendedCommunityFromProtoExtendedCommunity(t *testing.T) {
input := &api.ExtendedCommunity{
Type: 128,
Subtype: 6, // Flow spec traffic-rate
Value: []byte{
0x00, 0x00, // 2-Octet AS = 0
0x00, 0x00, 0x00, 0x00, // Rate shaper = 0
},
}

expected := ExtendedCommunity{
Type: 128,
SubType: 6,
Value: []byte{
0x00, 0x00, // 2-Octet AS = 0
0x00, 0x00, 0x00, 0x00, // Rate shaper = 0
},
}

result := ExtendedCommunityFromProtoExtendedCommunity(input)
assert.Equal(t, expected, result)
}

func TestExtendedCommunityToProto(t *testing.T) {
input := ExtendedCommunity{
Type: 128,
SubType: 6,
Value: []byte{
0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
},
}

expected := &api.ExtendedCommunity{
Type: 128,
Subtype: 6,
Value: []byte{
0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
},
}

result := input.ToProto()
assert.Equal(t, expected, result)
}
4 changes: 2 additions & 2 deletions protocols/bgp/types/large_community.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func LargeCommunityFromProtoCommunity(alc *api.LargeCommunity) LargeCommunity {
}
}

// String transitions a large community to it's human readable representation
// String transitions a large community to its human-readable representation
func (c *LargeCommunity) String() string {
if c == nil {
return ""
Expand Down Expand Up @@ -85,4 +85,4 @@ func ParseLargeCommunityString(s string) (com LargeCommunity, err error) {
com.DataPart2 = uint32(v)

return com, err
}
}
Loading

0 comments on commit 8305ad6

Please sign in to comment.