Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft: bgp: add extended community support #456

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading