diff --git a/Documentation/user/protocols/VAMP/README.md b/Documentation/user/protocols/VAMP/README.md new file mode 100644 index 00000000..4aac85ba --- /dev/null +++ b/Documentation/user/protocols/VAMP/README.md @@ -0,0 +1,16 @@ +# VRF-Aware Monitoring Protocol (VAMP) + +This protocol is designed to allow leveraging Bio-Routing to observe routing information present inside the network, across multiple VRFs and protocols. + +Now you may wonder, that this sound similar to the BGP Monitoring Protocol (BMP), and it is, but it's also different in some aspects: + + * BMP has support to expose routing information from different VRFs, which are identified by their respective Route Distinguisher (RD). To be able to leverage this feature there needs to be a unique RD per VRF, which introduces the requirement to managed RDs per VRF or device. When dealing with VRF-lite setups managing RDs would otherwise not be required and therefore it may not be desireable to add this additional requirement into the overall architecture. + * BMP only exposes routes learned via BGP - which is was designed to do - in some circumstances however it might be desireable to also expose routing information from other protocols like IS-IS, OSPF, or even static routes, which may have been added manually or programatically. + +To be most flexible the VRF-Aware Monitoring Protocol allows exporting routes for multiple VRFs over one gRPC connection. + +Bio-Routing, at least for now, only implements a VRF-Aware Monitoring Protocol client componenent, meaning that the user has to run a VAMP server by themselves. An example can be found in the examples/vamp/ folder inside this repository. + +The following diagram shows the architectural overview: + + \ No newline at end of file diff --git a/Documentation/user/protocols/VAMP/VAMP Architecture.drawio b/Documentation/user/protocols/VAMP/VAMP Architecture.drawio new file mode 100644 index 00000000..495257f2 --- /dev/null +++ b/Documentation/user/protocols/VAMP/VAMP Architecture.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/Documentation/user/protocols/VAMP/VAMP Architecture.drawio.png b/Documentation/user/protocols/VAMP/VAMP Architecture.drawio.png new file mode 100644 index 00000000..56594153 Binary files /dev/null and b/Documentation/user/protocols/VAMP/VAMP Architecture.drawio.png differ diff --git a/Documentation/user/protocols/VAMP/VAMP Architecture.drawio.svg b/Documentation/user/protocols/VAMP/VAMP Architecture.drawio.svg new file mode 100644 index 00000000..3c1d5aaa --- /dev/null +++ b/Documentation/user/protocols/VAMP/VAMP Architecture.drawio.svg @@ -0,0 +1,4 @@ + + + +VAMP PeerVAMP PeerVAMP ServerVAMP...VAMP ClientVAMP Client message VAMPMessage { enum MessageType{ VRF_INFO = 0; ROUTE_INFO = 1; END_OF_RIB = 2; } MessageType message_type = 1; uint32 vrf_id = 2; VRFInfo vrf_info = 3; RouteInfo route_info = 4; }message VAMPMessage {...Per associated VRFsPer associated VRFsLocRIBLocRIB message Route { bio.net.Prefix pfx = 1; repeated Path paths = 2; } message Path { enum Type { ... } Type type = 1; StaticPath static_path = 2; BGPPath bgp_path = 3; HiddenReason hidden_reason = 4; uint32 time_learned = 5;}message Route {... message VRFInfo { string name = 1; bool announement = 2; } message RouteInfo{ bio.route.Route route = 1; bool announcement = 2; }message VRFInfo {...Text is not SVG - cannot display \ No newline at end of file diff --git a/protocols/vamp/api/vamp.pb.go b/protocols/vamp/api/vamp.pb.go new file mode 100644 index 00000000..7ce9696c --- /dev/null +++ b/protocols/vamp/api/vamp.pb.go @@ -0,0 +1,451 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: protocols/vamp/api/vamp.proto + +package api + +import ( + _ "github.com/bio-routing/bio-rd/net/api" + api "github.com/bio-routing/bio-rd/route/api" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type VAMPMessage_MessageType int32 + +const ( + VAMPMessage_VRF_INFO VAMPMessage_MessageType = 0 + VAMPMessage_ROUTE_INFO VAMPMessage_MessageType = 1 + VAMPMessage_END_OF_RIB VAMPMessage_MessageType = 2 +) + +// Enum value maps for VAMPMessage_MessageType. +var ( + VAMPMessage_MessageType_name = map[int32]string{ + 0: "VRF_INFO", + 1: "ROUTE_INFO", + 2: "END_OF_RIB", + } + VAMPMessage_MessageType_value = map[string]int32{ + "VRF_INFO": 0, + "ROUTE_INFO": 1, + "END_OF_RIB": 2, + } +) + +func (x VAMPMessage_MessageType) Enum() *VAMPMessage_MessageType { + p := new(VAMPMessage_MessageType) + *p = x + return p +} + +func (x VAMPMessage_MessageType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (VAMPMessage_MessageType) Descriptor() protoreflect.EnumDescriptor { + return file_protocols_vamp_api_vamp_proto_enumTypes[0].Descriptor() +} + +func (VAMPMessage_MessageType) Type() protoreflect.EnumType { + return &file_protocols_vamp_api_vamp_proto_enumTypes[0] +} + +func (x VAMPMessage_MessageType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use VAMPMessage_MessageType.Descriptor instead. +func (VAMPMessage_MessageType) EnumDescriptor() ([]byte, []int) { + return file_protocols_vamp_api_vamp_proto_rawDescGZIP(), []int{1, 0} +} + +type VAMPSessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *VAMPSessionRequest) Reset() { + *x = VAMPSessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protocols_vamp_api_vamp_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VAMPSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VAMPSessionRequest) ProtoMessage() {} + +func (x *VAMPSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_protocols_vamp_api_vamp_proto_msgTypes[0] + 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 VAMPSessionRequest.ProtoReflect.Descriptor instead. +func (*VAMPSessionRequest) Descriptor() ([]byte, []int) { + return file_protocols_vamp_api_vamp_proto_rawDescGZIP(), []int{0} +} + +type VAMPMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MessageType VAMPMessage_MessageType `protobuf:"varint,1,opt,name=message_type,json=messageType,proto3,enum=bio.vamp.VAMPMessage_MessageType" json:"message_type,omitempty"` + VrfId uint32 `protobuf:"varint,2,opt,name=vrf_id,json=vrfId,proto3" json:"vrf_id,omitempty"` + VrfInfo *VRFInfo `protobuf:"bytes,3,opt,name=vrf_info,json=vrfInfo,proto3" json:"vrf_info,omitempty"` + RouteInfo *RouteInfo `protobuf:"bytes,4,opt,name=route_info,json=routeInfo,proto3" json:"route_info,omitempty"` +} + +func (x *VAMPMessage) Reset() { + *x = VAMPMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_protocols_vamp_api_vamp_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VAMPMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VAMPMessage) ProtoMessage() {} + +func (x *VAMPMessage) ProtoReflect() protoreflect.Message { + mi := &file_protocols_vamp_api_vamp_proto_msgTypes[1] + 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 VAMPMessage.ProtoReflect.Descriptor instead. +func (*VAMPMessage) Descriptor() ([]byte, []int) { + return file_protocols_vamp_api_vamp_proto_rawDescGZIP(), []int{1} +} + +func (x *VAMPMessage) GetMessageType() VAMPMessage_MessageType { + if x != nil { + return x.MessageType + } + return VAMPMessage_VRF_INFO +} + +func (x *VAMPMessage) GetVrfId() uint32 { + if x != nil { + return x.VrfId + } + return 0 +} + +func (x *VAMPMessage) GetVrfInfo() *VRFInfo { + if x != nil { + return x.VrfInfo + } + return nil +} + +func (x *VAMPMessage) GetRouteInfo() *RouteInfo { + if x != nil { + return x.RouteInfo + } + return nil +} + +type VRFInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Announement bool `protobuf:"varint,2,opt,name=announement,proto3" json:"announement,omitempty"` +} + +func (x *VRFInfo) Reset() { + *x = VRFInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_protocols_vamp_api_vamp_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VRFInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VRFInfo) ProtoMessage() {} + +func (x *VRFInfo) ProtoReflect() protoreflect.Message { + mi := &file_protocols_vamp_api_vamp_proto_msgTypes[2] + 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 VRFInfo.ProtoReflect.Descriptor instead. +func (*VRFInfo) Descriptor() ([]byte, []int) { + return file_protocols_vamp_api_vamp_proto_rawDescGZIP(), []int{2} +} + +func (x *VRFInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VRFInfo) GetAnnounement() bool { + if x != nil { + return x.Announement + } + return false +} + +type RouteInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Route *api.Route `protobuf:"bytes,1,opt,name=route,proto3" json:"route,omitempty"` + Announcement bool `protobuf:"varint,2,opt,name=announcement,proto3" json:"announcement,omitempty"` +} + +func (x *RouteInfo) Reset() { + *x = RouteInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_protocols_vamp_api_vamp_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RouteInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RouteInfo) ProtoMessage() {} + +func (x *RouteInfo) ProtoReflect() protoreflect.Message { + mi := &file_protocols_vamp_api_vamp_proto_msgTypes[3] + 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 RouteInfo.ProtoReflect.Descriptor instead. +func (*RouteInfo) Descriptor() ([]byte, []int) { + return file_protocols_vamp_api_vamp_proto_rawDescGZIP(), []int{3} +} + +func (x *RouteInfo) GetRoute() *api.Route { + if x != nil { + return x.Route + } + return nil +} + +func (x *RouteInfo) GetAnnouncement() bool { + if x != nil { + return x.Announcement + } + return false +} + +var File_protocols_vamp_api_vamp_proto protoreflect.FileDescriptor + +var file_protocols_vamp_api_vamp_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x2f, 0x76, 0x61, 0x6d, 0x70, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x08, 0x62, 0x69, 0x6f, 0x2e, 0x76, 0x61, 0x6d, 0x70, 0x1a, 0x11, 0x6e, 0x65, 0x74, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x14, 0x0a, 0x12, 0x56, 0x41, 0x4d, 0x50, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x89, 0x02, 0x0a, 0x0b, 0x56, 0x41, + 0x4d, 0x50, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x21, 0x2e, 0x62, 0x69, 0x6f, 0x2e, 0x76, 0x61, 0x6d, 0x70, 0x2e, 0x56, 0x41, 0x4d, 0x50, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x15, 0x0a, 0x06, 0x76, 0x72, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x05, 0x76, 0x72, 0x66, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x08, 0x76, 0x72, 0x66, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x69, 0x6f, 0x2e, 0x76, + 0x61, 0x6d, 0x70, 0x2e, 0x56, 0x52, 0x46, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x76, 0x72, 0x66, + 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x32, 0x0a, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x69, 0x6f, 0x2e, 0x76, + 0x61, 0x6d, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x3b, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x56, 0x52, 0x46, 0x5f, 0x49, + 0x4e, 0x46, 0x4f, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x49, + 0x4e, 0x46, 0x4f, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x4e, 0x44, 0x5f, 0x4f, 0x46, 0x5f, + 0x52, 0x49, 0x42, 0x10, 0x02, 0x22, 0x3f, 0x0a, 0x07, 0x56, 0x52, 0x46, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x75, + 0x6e, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x57, 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x26, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x69, 0x6f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x61, + 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0c, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x32, + 0x54, 0x0a, 0x0b, 0x56, 0x41, 0x4d, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, + 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x62, + 0x69, 0x6f, 0x2e, 0x76, 0x61, 0x6d, 0x70, 0x2e, 0x56, 0x41, 0x4d, 0x50, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x62, 0x69, 0x6f, + 0x2e, 0x76, 0x61, 0x6d, 0x70, 0x2e, 0x56, 0x41, 0x4d, 0x50, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, 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, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, + 0x2f, 0x76, 0x61, 0x6d, 0x70, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_protocols_vamp_api_vamp_proto_rawDescOnce sync.Once + file_protocols_vamp_api_vamp_proto_rawDescData = file_protocols_vamp_api_vamp_proto_rawDesc +) + +func file_protocols_vamp_api_vamp_proto_rawDescGZIP() []byte { + file_protocols_vamp_api_vamp_proto_rawDescOnce.Do(func() { + file_protocols_vamp_api_vamp_proto_rawDescData = protoimpl.X.CompressGZIP(file_protocols_vamp_api_vamp_proto_rawDescData) + }) + return file_protocols_vamp_api_vamp_proto_rawDescData +} + +var file_protocols_vamp_api_vamp_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_protocols_vamp_api_vamp_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_protocols_vamp_api_vamp_proto_goTypes = []interface{}{ + (VAMPMessage_MessageType)(0), // 0: bio.vamp.VAMPMessage.MessageType + (*VAMPSessionRequest)(nil), // 1: bio.vamp.VAMPSessionRequest + (*VAMPMessage)(nil), // 2: bio.vamp.VAMPMessage + (*VRFInfo)(nil), // 3: bio.vamp.VRFInfo + (*RouteInfo)(nil), // 4: bio.vamp.RouteInfo + (*api.Route)(nil), // 5: bio.route.Route +} +var file_protocols_vamp_api_vamp_proto_depIdxs = []int32{ + 0, // 0: bio.vamp.VAMPMessage.message_type:type_name -> bio.vamp.VAMPMessage.MessageType + 3, // 1: bio.vamp.VAMPMessage.vrf_info:type_name -> bio.vamp.VRFInfo + 4, // 2: bio.vamp.VAMPMessage.route_info:type_name -> bio.vamp.RouteInfo + 5, // 3: bio.vamp.RouteInfo.route:type_name -> bio.route.Route + 1, // 4: bio.vamp.VAMPService.RunSession:input_type -> bio.vamp.VAMPSessionRequest + 2, // 5: bio.vamp.VAMPService.RunSession:output_type -> bio.vamp.VAMPMessage + 5, // [5:6] is the sub-list for method output_type + 4, // [4:5] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_protocols_vamp_api_vamp_proto_init() } +func file_protocols_vamp_api_vamp_proto_init() { + if File_protocols_vamp_api_vamp_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_protocols_vamp_api_vamp_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VAMPSessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocols_vamp_api_vamp_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VAMPMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocols_vamp_api_vamp_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VRFInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocols_vamp_api_vamp_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RouteInfo); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_protocols_vamp_api_vamp_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_protocols_vamp_api_vamp_proto_goTypes, + DependencyIndexes: file_protocols_vamp_api_vamp_proto_depIdxs, + EnumInfos: file_protocols_vamp_api_vamp_proto_enumTypes, + MessageInfos: file_protocols_vamp_api_vamp_proto_msgTypes, + }.Build() + File_protocols_vamp_api_vamp_proto = out.File + file_protocols_vamp_api_vamp_proto_rawDesc = nil + file_protocols_vamp_api_vamp_proto_goTypes = nil + file_protocols_vamp_api_vamp_proto_depIdxs = nil +} diff --git a/protocols/vamp/api/vamp.proto b/protocols/vamp/api/vamp.proto new file mode 100644 index 00000000..1ab302d0 --- /dev/null +++ b/protocols/vamp/api/vamp.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package bio.vamp; + +import "net/api/net.proto"; +import "route/api/route.proto"; + +option go_package = "github.com/bio-routing/bio-rd/protocols/vamp/api"; + +message VAMPSessionRequest {} + +message VAMPMessage { + enum MessageType{ + VRF_INFO = 0; + ROUTE_INFO = 1; + END_OF_RIB = 2; + } + MessageType message_type = 1; + uint32 vrf_id = 2; + VRFInfo vrf_info = 3; + RouteInfo route_info = 4; +} + +message VRFInfo { + string name = 1; + bool announement = 2; +} + +message RouteInfo{ + bio.route.Route route = 1; + bool announcement = 2; +} + +service VAMPService { + rpc RunSession(VAMPSessionRequest) returns (stream VAMPMessage) {} +} diff --git a/protocols/vamp/api/vamp_grpc.pb.go b/protocols/vamp/api/vamp_grpc.pb.go new file mode 100644 index 00000000..2c11ce64 --- /dev/null +++ b/protocols/vamp/api/vamp_grpc.pb.go @@ -0,0 +1,128 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package api + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// VAMPServiceClient is the client API for VAMPService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type VAMPServiceClient interface { + RunSession(ctx context.Context, in *VAMPSessionRequest, opts ...grpc.CallOption) (VAMPService_RunSessionClient, error) +} + +type vAMPServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewVAMPServiceClient(cc grpc.ClientConnInterface) VAMPServiceClient { + return &vAMPServiceClient{cc} +} + +func (c *vAMPServiceClient) RunSession(ctx context.Context, in *VAMPSessionRequest, opts ...grpc.CallOption) (VAMPService_RunSessionClient, error) { + stream, err := c.cc.NewStream(ctx, &VAMPService_ServiceDesc.Streams[0], "/bio.vamp.VAMPService/RunSession", opts...) + if err != nil { + return nil, err + } + x := &vAMPServiceRunSessionClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type VAMPService_RunSessionClient interface { + Recv() (*VAMPMessage, error) + grpc.ClientStream +} + +type vAMPServiceRunSessionClient struct { + grpc.ClientStream +} + +func (x *vAMPServiceRunSessionClient) Recv() (*VAMPMessage, error) { + m := new(VAMPMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// VAMPServiceServer is the server API for VAMPService service. +// All implementations must embed UnimplementedVAMPServiceServer +// for forward compatibility +type VAMPServiceServer interface { + RunSession(*VAMPSessionRequest, VAMPService_RunSessionServer) error + mustEmbedUnimplementedVAMPServiceServer() +} + +// UnimplementedVAMPServiceServer must be embedded to have forward compatible implementations. +type UnimplementedVAMPServiceServer struct { +} + +func (UnimplementedVAMPServiceServer) RunSession(*VAMPSessionRequest, VAMPService_RunSessionServer) error { + return status.Errorf(codes.Unimplemented, "method RunSession not implemented") +} +func (UnimplementedVAMPServiceServer) mustEmbedUnimplementedVAMPServiceServer() {} + +// UnsafeVAMPServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to VAMPServiceServer will +// result in compilation errors. +type UnsafeVAMPServiceServer interface { + mustEmbedUnimplementedVAMPServiceServer() +} + +func RegisterVAMPServiceServer(s grpc.ServiceRegistrar, srv VAMPServiceServer) { + s.RegisterService(&VAMPService_ServiceDesc, srv) +} + +func _VAMPService_RunSession_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(VAMPSessionRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(VAMPServiceServer).RunSession(m, &vAMPServiceRunSessionServer{stream}) +} + +type VAMPService_RunSessionServer interface { + Send(*VAMPMessage) error + grpc.ServerStream +} + +type vAMPServiceRunSessionServer struct { + grpc.ServerStream +} + +func (x *vAMPServiceRunSessionServer) Send(m *VAMPMessage) error { + return x.ServerStream.SendMsg(m) +} + +// VAMPService_ServiceDesc is the grpc.ServiceDesc for VAMPService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var VAMPService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "bio.vamp.VAMPService", + HandlerType: (*VAMPServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "RunSession", + Handler: _VAMPService_RunSession_Handler, + ServerStreams: true, + }, + }, + Metadata: "protocols/vamp/api/vamp.proto", +} diff --git a/protocols/vamp/client.go b/protocols/vamp/client.go new file mode 100644 index 00000000..2e19903f --- /dev/null +++ b/protocols/vamp/client.go @@ -0,0 +1,135 @@ +package vamp + +import ( + "fmt" + "sync" + "time" + + vapi "github.com/bio-routing/bio-rd/protocols/vamp/api" + "github.com/bio-routing/bio-rd/routingtable/vrf" +) + +const ( + reconnectInterval = time.Second * 10 +) + +type ClientConfig struct { + ServerAddr string + VRFRegistry *vrf.VRFRegistry + VRFs []string +} + +type VAMPClient struct { + serverAddr string + vrfRegistry *vrf.VRFRegistry + vrfs []*vrf.VRF + vrfTracker *vrfTracker + + clientMu sync.Mutex // Guards ribOberservs + updateCh + ribOberservs []*RIBObserver + updateCh chan *vapi.VAMPMessage +} + +func NewClient(cfg ClientConfig) (*VAMPClient, error) { + vc := &VAMPClient{ + serverAddr: cfg.ServerAddr, + vrfRegistry: cfg.VRFRegistry, + vrfs: make([]*vrf.VRF, len(cfg.VRFs)), + vrfTracker: newVRFTracker(), + updateCh: make(chan *vapi.VAMPMessage), + } + + if cfg.VRFRegistry == nil { + return nil, fmt.Errorf("VRFRegistry config attribute must be set") + } + + if len(cfg.VRFs) == 0 { + return nil, fmt.Errorf("no VRFs specified in configuration") + } + + for i, vrfName := range cfg.VRFs { + vrf := vc.vrfRegistry.GetVRFByName(vrfName) + if vrf == nil { + return nil, fmt.Errorf("unknown VRF %q", vrfName) + } + + _, err := vc.vrfTracker.registerVRF(vrfName) + if err != nil { + return nil, err + } + + vc.vrfs[i] = vrf + } + + return vc, nil +} + +func (vc *VAMPClient) Init() error { + return vc.setupPlumbing() +} + +func (vc *VAMPClient) Start() { + vc.clientMu.Lock() + defer vc.clientMu.Unlock() + + for _, ribObserver := range vc.ribOberservs { + go ribObserver.Start() + } +} + +func (vc *VAMPClient) Stop() { + vc.clientMu.Lock() + defer vc.clientMu.Unlock() + + for _, ribObserver := range vc.ribOberservs { + go ribObserver.Stop() + } +} + +func (vc *VAMPClient) setupPlumbing() error { + err := vc.connect() + if err != nil { + return err + } + + return vc.setupRIBObservers() +} + +func (vc *VAMPClient) connect() error { + // TODO: Set up gRPC connection to vc.serverAddr + return nil +} + +func (vc *VAMPClient) setupRIBObservers() error { + vc.clientMu.Lock() + defer vc.clientMu.Unlock() + + if len(vc.ribOberservs) != 0 { + return fmt.Errorf("RIBObservers seem to have already been set up") + } + + // (Re)create update channel (HERE?) + vc.updateCh = make(chan *vapi.VAMPMessage) + + ribOberservs := make([]*RIBObserver, 0) + for _, vrf := range vc.vrfs { + vrfID, err := vc.vrfTracker.getVRFID(vrf.Name()) + if err != nil { + return fmt.Errorf("failed to get VRF ID for VRF %q: %v", vrf.Name(), err) + } + + ipv4RIB := vrf.IPv4UnicastRIB() + if ipv4RIB != nil { + ribOberservs = append(ribOberservs, newRIBOserver(ipv4RIB, vrfID, vc.updateCh)) + } + + ip64RIB := vrf.IPv6UnicastRIB() + if ipv4RIB != nil { + ribOberservs = append(ribOberservs, newRIBOserver(ip64RIB, vrfID, vc.updateCh)) + } + } + + vc.ribOberservs = ribOberservs + + return nil +} diff --git a/protocols/vamp/rib_observer.go b/protocols/vamp/rib_observer.go new file mode 100644 index 00000000..6fd31da1 --- /dev/null +++ b/protocols/vamp/rib_observer.go @@ -0,0 +1,90 @@ +package vamp + +import ( + "github.com/bio-routing/bio-rd/net" + vapi "github.com/bio-routing/bio-rd/protocols/vamp/api" + "github.com/bio-routing/bio-rd/route" + rapi "github.com/bio-routing/bio-rd/route/api" + "github.com/bio-routing/bio-rd/routingtable/locRIB" +) + +type RIBObserver struct { + vrfID uint32 + locRIB *locRIB.LocRIB + updateCh chan *vapi.VAMPMessage +} + +func newRIBOserver(locRIB *locRIB.LocRIB, vrfID uint32, updateCh chan *vapi.VAMPMessage) *RIBObserver { + return &RIBObserver{ + vrfID: vrfID, + locRIB: locRIB, + updateCh: updateCh, + } +} + +func (ro *RIBObserver) Start() { + ro.locRIB.Register(ro) +} + +func (ro *RIBObserver) Stop() { + ro.locRIB.Unregister(ro) +} + +func (ro *RIBObserver) sendMsg(msg *vapi.VAMPMessage) { + ro.updateCh <- msg +} + +func (ro *RIBObserver) sendRouteInfoMsg(pfx *net.Prefix, path *route.Path, announcement bool) { + msg := &vapi.VAMPMessage{ + MessageType: vapi.VAMPMessage_ROUTE_INFO, + VrfId: ro.vrfID, + RouteInfo: &vapi.RouteInfo{ + Route: &rapi.Route{ + Pfx: pfx.ToProto(), + Paths: []*rapi.Path{ + path.ToProto(), + }, + }, + Announcement: announcement, + }, + } + + ro.sendMsg(msg) +} + +/* + * RoutingTableClient interface + */ +func (ro *RIBObserver) AddPath(pfx *net.Prefix, path *route.Path) error { + ro.sendRouteInfoMsg(pfx, path, true) + return nil +} + +func (ro *RIBObserver) AddPathInitialDump(pfx *net.Prefix, path *route.Path) error { + ro.sendRouteInfoMsg(pfx, path, true) + return nil +} + +func (ro *RIBObserver) EndOfRIB() { + ro.sendMsg(&vapi.VAMPMessage{ + MessageType: vapi.VAMPMessage_END_OF_RIB, + VrfId: ro.vrfID, + }) +} + +func (ro *RIBObserver) RemovePath(pfx *net.Prefix, path *route.Path) bool { + ro.sendRouteInfoMsg(pfx, path, false) + return true +} + +func (ro *RIBObserver) ReplacePath(pfx *net.Prefix, oldPath *route.Path, newPath *route.Path) { + ro.sendRouteInfoMsg(pfx, oldPath, true) + ro.sendRouteInfoMsg(pfx, newPath, false) +} + +func (ro *RIBObserver) RefreshRoute(*net.Prefix, []*route.Path) { + // TODO +} + +// A call to Dispose() signals that no more updates are to be expected from the RIB the client is registered to. +func (ro *RIBObserver) Dispose() {} diff --git a/protocols/vamp/vrf_tracker.go b/protocols/vamp/vrf_tracker.go new file mode 100644 index 00000000..3d875898 --- /dev/null +++ b/protocols/vamp/vrf_tracker.go @@ -0,0 +1,59 @@ +package vamp + +import ( + "fmt" + "sync" +) + +type vrfTracker struct { + trackerMu sync.RWMutex + vrfNameToIDMap map[string]uint32 + nextID uint32 +} + +func newVRFTracker() *vrfTracker { + return &vrfTracker{ + vrfNameToIDMap: make(map[string]uint32), + } +} + +func (vt *vrfTracker) registerVRF(vrf string) (uint32, error) { + vt.trackerMu.Lock() + defer vt.trackerMu.Unlock() + + if _, exists := vt.vrfNameToIDMap[vrf]; exists { + return 0, fmt.Errorf("VRF %q already registed", vrf) + } + + vrfID := vt.nextID + + vt.vrfNameToIDMap[vrf] = vrfID + vt.nextID += 1 + + return vrfID, nil +} + +func (vt *vrfTracker) unregisterVRF(vrf string) error { + vt.trackerMu.Lock() + defer vt.trackerMu.Unlock() + + if _, exists := vt.vrfNameToIDMap[vrf]; !exists { + return fmt.Errorf("VRF %q not registered", vrf) + } + + delete(vt.vrfNameToIDMap, vrf) + + return nil +} + +func (vt *vrfTracker) getVRFID(vrf string) (uint32, error) { + vt.trackerMu.RLock() + defer vt.trackerMu.RUnlock() + + vrfID, exists := vt.vrfNameToIDMap[vrf] + if !exists { + return 0, fmt.Errorf("vrf %q has not been registered yet", vrf) + } + + return vrfID, nil +} diff --git a/protocols/vamp/vrf_tracker_test.go b/protocols/vamp/vrf_tracker_test.go new file mode 100644 index 00000000..aa9d167e --- /dev/null +++ b/protocols/vamp/vrf_tracker_test.go @@ -0,0 +1,47 @@ +package vamp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVRFTracker(t *testing.T) { + vt := newVRFTracker() + + _, err := vt.getVRFID("main") + if err == nil { + t.Fatalf("Error expected when querying non-existing VRF, but didn't get one") + } + + mainID, err := vt.registerVRF("main") + assert.NoError(t, err, "Got error while registering VRF main") + assert.Equal(t, uint32(0), mainID, "VRF main") + + id, err := vt.getVRFID("main") + if err != nil { + t.Fatalf("Got unexpected error while querying existing VRF main") + } + assert.Equal(t, uint32(0), id, "VRF main") + + _, err = vt.registerVRF("main") + assert.Error(t, err, "Got no error while re-registering VRF main") + + fourtytwoId, err := vt.registerVRF("fourtytwo") + assert.NoError(t, err, "Got error while registering VRF fourtytwo") + assert.Equal(t, uint32(1), fourtytwoId, "VRF fourtytwo") + + id, err = vt.getVRFID("fourtytwo") + if err != nil { + t.Fatalf("Got unexpected error while querying existing VRF fourtytwo") + } + assert.Equal(t, uint32(1), id, "VRF fourtytwo") + + vt.unregisterVRF("main") + assert.NoError(t, err, "Got error while unregistering VRF main") + + _, err = vt.getVRFID("main") + if err == nil { + t.Fatalf("Error expected when querying unregisted VRF main, but didn't get one") + } +}