diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index 8bc144ad..2d00a9a6 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -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 @@ -78,6 +79,7 @@ const ( ClusterListAttr = 10 MultiProtocolReachNLRIAttr = 14 MultiProtocolUnreachNLRIAttr = 15 + ExtendedCommunitiesAttr = 16 AS4PathAttr = 17 AS4AggregatorAttr = 18 LargeCommunitiesAttr = 32 @@ -207,4 +209,4 @@ func PeerRoleName(pr uint8) string { default: return "Unknown" } -} +} \ No newline at end of file diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index 577f01d7..a771ff64 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -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) @@ -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") } @@ -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: @@ -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) @@ -981,4 +1029,4 @@ func PathAttributes(p *route.Path, iBGP bool, rrClient bool) (*PathAttribute, er } return asPath, nil -} +} \ No newline at end of file diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index 75fd7e15..e1f3f456 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -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 @@ -2424,4 +2528,4 @@ func TestPathAttributeCopy(t *testing.T) { copy := attr.Copy() assert.Equal(t, *expected, *copy) assert.False(t, attr == copy) -} +} \ No newline at end of file diff --git a/protocols/bgp/server/fsm_address_family.go b/protocols/bgp/server/fsm_address_family.go index b7771b84..565239d0 100644 --- a/protocols/bgp/server/fsm_address_family.go +++ b/protocols/bgp/server/fsm_address_family.go @@ -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: @@ -362,4 +364,4 @@ func (f *fsmAddressFamily) processUnknownAttribute(attr *packet.PathAttribute) * } return u -} +} \ No newline at end of file diff --git a/protocols/bgp/types/extended_community.go b/protocols/bgp/types/extended_community.go new file mode 100644 index 00000000..c8e0830d --- /dev/null +++ b/protocols/bgp/types/extended_community.go @@ -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) +} \ No newline at end of file diff --git a/protocols/bgp/types/extended_community_test.go b/protocols/bgp/types/extended_community_test.go new file mode 100644 index 00000000..48cd4572 --- /dev/null +++ b/protocols/bgp/types/extended_community_test.go @@ -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) +} \ No newline at end of file diff --git a/protocols/bgp/types/large_community.go b/protocols/bgp/types/large_community.go index 891ffe93..4fc1d4dc 100644 --- a/protocols/bgp/types/large_community.go +++ b/protocols/bgp/types/large_community.go @@ -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 "" @@ -85,4 +85,4 @@ func ParseLargeCommunityString(s string) (com LargeCommunity, err error) { com.DataPart2 = uint32(v) return com, err -} +} \ No newline at end of file diff --git a/route/api/route.pb.go b/route/api/route.pb.go index 578d2972..2cddf395 100644 --- a/route/api/route.pb.go +++ b/route/api/route.pb.go @@ -377,22 +377,23 @@ type BGPPath struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - PathIdentifier uint32 `protobuf:"varint,1,opt,name=path_identifier,json=pathIdentifier,proto3" json:"path_identifier,omitempty"` - NextHop *api.IP `protobuf:"bytes,2,opt,name=next_hop,json=nextHop,proto3" json:"next_hop,omitempty"` - LocalPref uint32 `protobuf:"varint,3,opt,name=local_pref,json=localPref,proto3" json:"local_pref,omitempty"` - AsPath []*ASPathSegment `protobuf:"bytes,4,rep,name=as_path,json=asPath,proto3" json:"as_path,omitempty"` - Origin uint32 `protobuf:"varint,5,opt,name=origin,proto3" json:"origin,omitempty"` - Med uint32 `protobuf:"varint,6,opt,name=med,proto3" json:"med,omitempty"` - Ebgp bool `protobuf:"varint,7,opt,name=ebgp,proto3" json:"ebgp,omitempty"` - BgpIdentifier uint32 `protobuf:"varint,8,opt,name=bgp_identifier,json=bgpIdentifier,proto3" json:"bgp_identifier,omitempty"` - Source *api.IP `protobuf:"bytes,9,opt,name=source,proto3" json:"source,omitempty"` - Communities []uint32 `protobuf:"varint,10,rep,packed,name=communities,proto3" json:"communities,omitempty"` - LargeCommunities []*LargeCommunity `protobuf:"bytes,11,rep,name=large_communities,json=largeCommunities,proto3" json:"large_communities,omitempty"` - OriginatorId uint32 `protobuf:"varint,12,opt,name=originator_id,json=originatorId,proto3" json:"originator_id,omitempty"` - ClusterList []uint32 `protobuf:"varint,13,rep,packed,name=cluster_list,json=clusterList,proto3" json:"cluster_list,omitempty"` - UnknownAttributes []*UnknownPathAttribute `protobuf:"bytes,14,rep,name=unknown_attributes,json=unknownAttributes,proto3" json:"unknown_attributes,omitempty"` - BmpPostPolicy bool `protobuf:"varint,15,opt,name=bmp_post_policy,json=bmpPostPolicy,proto3" json:"bmp_post_policy,omitempty"` - OnlyToCustomer uint32 `protobuf:"varint,16,opt,name=only_to_customer,json=onlyToCustomer,proto3" json:"only_to_customer,omitempty"` + PathIdentifier uint32 `protobuf:"varint,1,opt,name=path_identifier,json=pathIdentifier,proto3" json:"path_identifier,omitempty"` + NextHop *api.IP `protobuf:"bytes,2,opt,name=next_hop,json=nextHop,proto3" json:"next_hop,omitempty"` + LocalPref uint32 `protobuf:"varint,3,opt,name=local_pref,json=localPref,proto3" json:"local_pref,omitempty"` + AsPath []*ASPathSegment `protobuf:"bytes,4,rep,name=as_path,json=asPath,proto3" json:"as_path,omitempty"` + Origin uint32 `protobuf:"varint,5,opt,name=origin,proto3" json:"origin,omitempty"` + Med uint32 `protobuf:"varint,6,opt,name=med,proto3" json:"med,omitempty"` + Ebgp bool `protobuf:"varint,7,opt,name=ebgp,proto3" json:"ebgp,omitempty"` + BgpIdentifier uint32 `protobuf:"varint,8,opt,name=bgp_identifier,json=bgpIdentifier,proto3" json:"bgp_identifier,omitempty"` + Source *api.IP `protobuf:"bytes,9,opt,name=source,proto3" json:"source,omitempty"` + Communities []uint32 `protobuf:"varint,10,rep,packed,name=communities,proto3" json:"communities,omitempty"` + LargeCommunities []*LargeCommunity `protobuf:"bytes,11,rep,name=large_communities,json=largeCommunities,proto3" json:"large_communities,omitempty"` + OriginatorId uint32 `protobuf:"varint,12,opt,name=originator_id,json=originatorId,proto3" json:"originator_id,omitempty"` + ClusterList []uint32 `protobuf:"varint,13,rep,packed,name=cluster_list,json=clusterList,proto3" json:"cluster_list,omitempty"` + UnknownAttributes []*UnknownPathAttribute `protobuf:"bytes,14,rep,name=unknown_attributes,json=unknownAttributes,proto3" json:"unknown_attributes,omitempty"` + BmpPostPolicy bool `protobuf:"varint,15,opt,name=bmp_post_policy,json=bmpPostPolicy,proto3" json:"bmp_post_policy,omitempty"` + OnlyToCustomer uint32 `protobuf:"varint,16,opt,name=only_to_customer,json=onlyToCustomer,proto3" json:"only_to_customer,omitempty"` + ExtendedCommunities []*ExtendedCommunity `protobuf:"bytes,17,rep,name=extended_communities,json=extendedCommunities,proto3" json:"extended_communities,omitempty"` } func (x *BGPPath) Reset() { @@ -539,6 +540,13 @@ func (x *BGPPath) GetOnlyToCustomer() uint32 { return 0 } +func (x *BGPPath) GetExtendedCommunities() []*ExtendedCommunity { + if x != nil { + return x.ExtendedCommunities + } + return nil +} + type ASPathSegment struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -736,6 +744,69 @@ func (x *UnknownPathAttribute) GetValue() []byte { return nil } +type ExtendedCommunity struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type uint32 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` + Subtype uint32 `protobuf:"varint,2,opt,name=subtype,proto3" json:"subtype,omitempty"` + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *ExtendedCommunity) Reset() { + *x = ExtendedCommunity{} + if protoimpl.UnsafeEnabled { + mi := &file_route_api_route_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExtendedCommunity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExtendedCommunity) ProtoMessage() {} + +func (x *ExtendedCommunity) ProtoReflect() protoreflect.Message { + mi := &file_route_api_route_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExtendedCommunity.ProtoReflect.Descriptor instead. +func (*ExtendedCommunity) Descriptor() ([]byte, []int) { + return file_route_api_route_proto_rawDescGZIP(), []int{8} +} + +func (x *ExtendedCommunity) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *ExtendedCommunity) GetSubtype() uint32 { + if x != nil { + return x.Subtype + } + return 0 +} + +func (x *ExtendedCommunity) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + var File_route_api_route_proto protoreflect.FileDescriptor var file_route_api_route_proto_rawDesc = []byte{ @@ -796,7 +867,7 @@ var file_route_api_route_proto_rawDesc = []byte{ 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8a, 0x05, 0x0a, 0x07, 0x42, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xdb, 0x05, 0x0a, 0x07, 0x42, 0x47, 0x50, 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x70, 0x61, 0x74, 0x68, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, @@ -837,33 +908,43 @@ var file_route_api_route_proto_rawDesc = []byte{ 0x62, 0x6d, 0x70, 0x50, 0x6f, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6f, 0x6e, 0x6c, 0x79, 0x54, 0x6f, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x22, 0x44, 0x0a, 0x0d, 0x41, 0x53, 0x50, 0x61, 0x74, - 0x68, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x73, 0x5f, 0x73, - 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, - 0x73, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x73, 0x6e, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x04, 0x61, 0x73, 0x6e, 0x73, 0x22, 0x81, 0x01, - 0x0a, 0x0e, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, - 0x12, 0x31, 0x0a, 0x14, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, - 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x61, 0x72, 0x74, - 0x31, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x72, - 0x74, 0x31, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x32, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x72, 0x74, - 0x32, 0x22, 0x9f, 0x01, 0x0a, 0x14, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x61, 0x74, - 0x68, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, - 0x74, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, - 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, - 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x62, 0x69, 0x6f, 0x2d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x62, 0x69, - 0x6f, 0x2d, 0x72, 0x64, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x12, 0x4f, 0x0a, 0x14, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x64, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, + 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x69, 0x6f, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, + 0x69, 0x74, 0x79, 0x52, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6d, + 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x44, 0x0a, 0x0d, 0x41, 0x53, 0x50, 0x61, + 0x74, 0x68, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x73, 0x5f, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x61, 0x73, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x73, + 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x04, 0x61, 0x73, 0x6e, 0x73, 0x22, 0x81, + 0x01, 0x0a, 0x0e, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, + 0x79, 0x12, 0x31, 0x0a, 0x14, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x13, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x61, 0x72, + 0x74, 0x31, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, + 0x72, 0x74, 0x31, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x61, 0x72, 0x74, + 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x72, + 0x74, 0x32, 0x22, 0x9f, 0x01, 0x0a, 0x14, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x61, + 0x74, 0x68, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, + 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x22, 0x57, 0x0a, 0x11, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, + 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x62, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x73, 0x75, 0x62, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x29, 0x5a, + 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x6f, 0x2d, + 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x62, 0x69, 0x6f, 0x2d, 0x72, 0x64, 0x2f, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -879,7 +960,7 @@ func file_route_api_route_proto_rawDescGZIP() []byte { } var file_route_api_route_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_route_api_route_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_route_api_route_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_route_api_route_proto_goTypes = []interface{}{ (Path_Type)(0), // 0: bio.route.Path.Type (Path_HiddenReason)(0), // 1: bio.route.Path.HiddenReason @@ -891,31 +972,33 @@ var file_route_api_route_proto_goTypes = []interface{}{ (*ASPathSegment)(nil), // 7: bio.route.ASPathSegment (*LargeCommunity)(nil), // 8: bio.route.LargeCommunity (*UnknownPathAttribute)(nil), // 9: bio.route.UnknownPathAttribute - nil, // 10: bio.route.GRPPath.MetaDataEntry - (*api.Prefix)(nil), // 11: bio.net.Prefix - (*api.IP)(nil), // 12: bio.net.IP + (*ExtendedCommunity)(nil), // 10: bio.route.ExtendedCommunity + nil, // 11: bio.route.GRPPath.MetaDataEntry + (*api.Prefix)(nil), // 12: bio.net.Prefix + (*api.IP)(nil), // 13: bio.net.IP } var file_route_api_route_proto_depIdxs = []int32{ - 11, // 0: bio.route.Route.pfx:type_name -> bio.net.Prefix + 12, // 0: bio.route.Route.pfx:type_name -> bio.net.Prefix 3, // 1: bio.route.Route.paths:type_name -> bio.route.Path 0, // 2: bio.route.Path.type:type_name -> bio.route.Path.Type 4, // 3: bio.route.Path.static_path:type_name -> bio.route.StaticPath 6, // 4: bio.route.Path.bgp_path:type_name -> bio.route.BGPPath 1, // 5: bio.route.Path.hidden_reason:type_name -> bio.route.Path.HiddenReason 5, // 6: bio.route.Path.grp_path:type_name -> bio.route.GRPPath - 12, // 7: bio.route.StaticPath.next_hop:type_name -> bio.net.IP - 12, // 8: bio.route.GRPPath.next_hop:type_name -> bio.net.IP - 10, // 9: bio.route.GRPPath.meta_data:type_name -> bio.route.GRPPath.MetaDataEntry - 12, // 10: bio.route.BGPPath.next_hop:type_name -> bio.net.IP + 13, // 7: bio.route.StaticPath.next_hop:type_name -> bio.net.IP + 13, // 8: bio.route.GRPPath.next_hop:type_name -> bio.net.IP + 11, // 9: bio.route.GRPPath.meta_data:type_name -> bio.route.GRPPath.MetaDataEntry + 13, // 10: bio.route.BGPPath.next_hop:type_name -> bio.net.IP 7, // 11: bio.route.BGPPath.as_path:type_name -> bio.route.ASPathSegment - 12, // 12: bio.route.BGPPath.source:type_name -> bio.net.IP + 13, // 12: bio.route.BGPPath.source:type_name -> bio.net.IP 8, // 13: bio.route.BGPPath.large_communities:type_name -> bio.route.LargeCommunity 9, // 14: bio.route.BGPPath.unknown_attributes:type_name -> bio.route.UnknownPathAttribute - 15, // [15:15] is the sub-list for method output_type - 15, // [15:15] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 10, // 15: bio.route.BGPPath.extended_communities:type_name -> bio.route.ExtendedCommunity + 16, // [16:16] is the sub-list for method output_type + 16, // [16:16] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name } func init() { file_route_api_route_proto_init() } @@ -1020,6 +1103,18 @@ func file_route_api_route_proto_init() { return nil } } + file_route_api_route_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExtendedCommunity); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1027,7 +1122,7 @@ func file_route_api_route_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_route_api_route_proto_rawDesc, NumEnums: 2, - NumMessages: 9, + NumMessages: 10, NumExtensions: 0, NumServices: 0, }, diff --git a/route/api/route.proto b/route/api/route.proto index 8749e6b4..11a0dc91 100644 --- a/route/api/route.proto +++ b/route/api/route.proto @@ -58,6 +58,7 @@ message BGPPath { repeated UnknownPathAttribute unknown_attributes = 14; bool bmp_post_policy = 15; uint32 only_to_customer = 16; + repeated ExtendedCommunity extended_communities = 17; } message ASPathSegment { @@ -78,3 +79,9 @@ message UnknownPathAttribute { uint32 type_code = 4; bytes value = 5; } + +message ExtendedCommunity { + uint32 type = 1; + uint32 subtype = 2; + bytes value = 3; +} \ No newline at end of file diff --git a/route/bgp_path.go b/route/bgp_path.go index 598f63f9..4b6e9ede 100644 --- a/route/bgp_path.go +++ b/route/bgp_path.go @@ -14,15 +14,16 @@ import ( // BGPPath represents a set of BGP path attributes type BGPPath struct { - BGPPathA *BGPPathA - ASPath *types.ASPath - ClusterList *types.ClusterList - Communities *types.Communities - LargeCommunities *types.LargeCommunities - UnknownAttributes []types.UnknownPathAttribute - PathIdentifier uint32 - ASPathLen uint16 - BMPPostPolicy bool // BMPPostPolicy fields is a hack used in BMP to differentiate between pre/post policy routes (L flag of the per peer header) + BGPPathA *BGPPathA + ASPath *types.ASPath + ClusterList *types.ClusterList + Communities *types.Communities + LargeCommunities *types.LargeCommunities + ExtendedCommunities *types.ExtendedCommunities + UnknownAttributes []types.UnknownPathAttribute + PathIdentifier uint32 + ASPathLen uint16 + BMPPostPolicy bool // BMPPostPolicy fields is a hack used in BMP to differentiate between pre/post policy routes (L flag of the per peer header) } // BGPPathA represents cachable BGP path attributes @@ -116,6 +117,13 @@ func (b *BGPPath) ToProto() *api.BGPPath { } } + if b.ExtendedCommunities != nil { + a.ExtendedCommunities = make([]*api.ExtendedCommunity, len(*b.ExtendedCommunities)) + for i := range *b.ExtendedCommunities { + a.ExtendedCommunities[i] = (*b.ExtendedCommunities)[i].ToProto() + } + } + for i := range b.UnknownAttributes { a.UnknownAttributes[i] = b.UnknownAttributes[i].ToProto() } @@ -161,6 +169,15 @@ func BGPPathFromProtoBGPPath(pb *api.BGPPath, dedup bool) *BGPPath { } } + if len(pb.ExtendedCommunities) > 0 { + extendedCommunities := make(types.ExtendedCommunities, len(pb.ExtendedCommunities)) + p.ExtendedCommunities = &extendedCommunities + + for i := range pb.ExtendedCommunities { + (*p.ExtendedCommunities)[i] = types.ExtendedCommunityFromProtoExtendedCommunity(pb.ExtendedCommunities[i]) + } + } + if len(pb.UnknownAttributes) > 0 { unknownAttr := make([]types.UnknownPathAttribute, len(pb.UnknownAttributes)) p.UnknownAttributes = unknownAttr @@ -179,7 +196,7 @@ func BGPPathFromProtoBGPPath(pb *api.BGPPath, dedup bool) *BGPPath { return p } -// Length get's the length of serialized path +// Length gets the length of serialized path func (b *BGPPath) Length() uint16 { asPathLen := uint16(3) for _, segment := range *b.ASPath { @@ -197,6 +214,13 @@ func (b *BGPPath) Length() uint16 { largeCommunitiesLen += 3 + uint16(len(*b.LargeCommunities)*12) } + extendedCommunitiesLen := uint16(0) + if b.ExtendedCommunities != nil && len(*b.ExtendedCommunities) != 0 { + // length of extended communities in BGPPath multiplied by the size of an EC + // defined in [packet.ExtendedCommunityLen] (cannot be imported because of a possible cycle dependency) + extendedCommunitiesLen += 3 + uint16(len(*b.ExtendedCommunities)*8) + } + clusterListLen := uint16(0) if b.ClusterList != nil && len(*b.ClusterList) != 0 { clusterListLen += 3 + uint16(len(*b.ClusterList)*4) @@ -219,10 +243,12 @@ func (b *BGPPath) Length() uint16 { } } - return 4*7 + 4 + asPathLen + communitiesLen + largeCommunitiesLen + clusterListLen + originatorID + onlyToCustomer + unknownAttributesLen + return 4*7 + 4 + asPathLen + + communitiesLen + largeCommunitiesLen + extendedCommunitiesLen + + clusterListLen + originatorID + onlyToCustomer + unknownAttributesLen } -// ECMP determines if routes b and c are euqal in terms of ECMP +// ECMP determines if routes b and c are equal in terms of ECMP defined in RFC 2992 func (b *BGPPath) ECMP(c *BGPPath) bool { return b.BGPPathA.LocalPref == c.BGPPathA.LocalPref && b.ASPathLen == c.ASPathLen && @@ -584,6 +610,9 @@ func (b *BGPPath) String() string { if b.LargeCommunities != nil { fmt.Fprintf(buf, "LargeCommunities: %v", *b.LargeCommunities) } + if b.ExtendedCommunities != nil { + fmt.Fprintf(buf, "ExtendedCommunities: %s", b.ExtendedCommunities) + } if b.BGPPathA.OriginatorID != 0 { oid := convert.Uint32Byte(b.BGPPathA.OriginatorID) @@ -632,6 +661,9 @@ func (b *BGPPath) Print() string { if b.LargeCommunities != nil { fmt.Fprintf(buf, "\t\tLargeCommunities: %v\n", *b.LargeCommunities) } + if b.ExtendedCommunities != nil { + fmt.Fprintf(buf, "\t\tExtendedCommunities: %s\n", b.ExtendedCommunitiesString()) + } if b.BGPPathA.OriginatorID != 0 { oid := convert.Uint32Byte(b.BGPPathA.OriginatorID) @@ -814,10 +846,26 @@ func (b *BGPPath) LargeCommunitiesString() string { return str.String() } +// ExtendedCommunitiesString returns human-readable, formatted extended communities +func (b *BGPPath) ExtendedCommunitiesString() string { + str := &strings.Builder{} + + for i, ec := range *b.ExtendedCommunities { + if i > 0 { + str.WriteByte(' ') + } + // TODO: Make EC human-readable + + fmt.Fprintf(str, "type: %d transitive: %d value: %s", ec.Type, ec.SubType, ec.Value) + } + + return str.String() +} + func (b *BGPPath) GetNextHop() *bnet.IP { if b == nil || b.BGPPathA == nil { return nil } return b.BGPPathA.NextHop -} +} \ No newline at end of file