diff --git a/.gitignore b/.gitignore
index 0d49122196db33067d384bb07213f789dad44360..ec68f775685343b65163907dd927a868f14d2c47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,7 @@ examples/bgp
 examples/bmp
 examples/fib/fib
 examples/device/device
+cmd/ris/ris
 
 # bazel directories
 /bazel-*
diff --git a/benchmarks/bgp/learning/main.go b/benchmarks/bgp/learning/main.go
index f3c02c43d4e85da9838e9da2951a5fb76e58afdd..9ab4a69a68907fc038affcac68ed2193ba3dddf6 100644
--- a/benchmarks/bgp/learning/main.go
+++ b/benchmarks/bgp/learning/main.go
@@ -31,7 +31,7 @@ func main() {
 	go http.ListenAndServe("localhost:1337", nil)
 
 	b := server.NewBgpServer()
-	v, err := vrf.New("master")
+	v, err := vrf.New("master", 0)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/cmd/ris/api/ris.pb.go b/cmd/ris/api/ris.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..2cdee22e36699cdddddecdb4c7f898652faba5c3
--- /dev/null
+++ b/cmd/ris/api/ris.pb.go
@@ -0,0 +1,869 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: github.com/bio-routing/bio-rd/cmd/ris/api/ris.proto
+
+package api
+
+import (
+	context "context"
+	fmt "fmt"
+	api "github.com/bio-routing/bio-rd/net/api"
+	api1 "github.com/bio-routing/bio-rd/route/api"
+	proto "github.com/golang/protobuf/proto"
+	grpc "google.golang.org/grpc"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type ObserveRIBRequest_AFISAFI int32
+
+const (
+	ObserveRIBRequest_IPv4Unicast ObserveRIBRequest_AFISAFI = 0
+	ObserveRIBRequest_IPv6Unicast ObserveRIBRequest_AFISAFI = 1
+)
+
+var ObserveRIBRequest_AFISAFI_name = map[int32]string{
+	0: "IPv4Unicast",
+	1: "IPv6Unicast",
+}
+
+var ObserveRIBRequest_AFISAFI_value = map[string]int32{
+	"IPv4Unicast": 0,
+	"IPv6Unicast": 1,
+}
+
+func (x ObserveRIBRequest_AFISAFI) String() string {
+	return proto.EnumName(ObserveRIBRequest_AFISAFI_name, int32(x))
+}
+
+func (ObserveRIBRequest_AFISAFI) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{6, 0}
+}
+
+type DumpRIBRequest_AFISAFI int32
+
+const (
+	DumpRIBRequest_IPv4Unicast DumpRIBRequest_AFISAFI = 0
+	DumpRIBRequest_IPv6Unicast DumpRIBRequest_AFISAFI = 1
+)
+
+var DumpRIBRequest_AFISAFI_name = map[int32]string{
+	0: "IPv4Unicast",
+	1: "IPv6Unicast",
+}
+
+var DumpRIBRequest_AFISAFI_value = map[string]int32{
+	"IPv4Unicast": 0,
+	"IPv6Unicast": 1,
+}
+
+func (x DumpRIBRequest_AFISAFI) String() string {
+	return proto.EnumName(DumpRIBRequest_AFISAFI_name, int32(x))
+}
+
+func (DumpRIBRequest_AFISAFI) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{8, 0}
+}
+
+type LPMRequest struct {
+	Router               string      `protobuf:"bytes,1,opt,name=router,proto3" json:"router,omitempty"`
+	VrfId                uint64      `protobuf:"varint,2,opt,name=vrf_id,json=vrfId,proto3" json:"vrf_id,omitempty"`
+	Pfx                  *api.Prefix `protobuf:"bytes,3,opt,name=pfx,proto3" json:"pfx,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *LPMRequest) Reset()         { *m = LPMRequest{} }
+func (m *LPMRequest) String() string { return proto.CompactTextString(m) }
+func (*LPMRequest) ProtoMessage()    {}
+func (*LPMRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{0}
+}
+
+func (m *LPMRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_LPMRequest.Unmarshal(m, b)
+}
+func (m *LPMRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_LPMRequest.Marshal(b, m, deterministic)
+}
+func (m *LPMRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LPMRequest.Merge(m, src)
+}
+func (m *LPMRequest) XXX_Size() int {
+	return xxx_messageInfo_LPMRequest.Size(m)
+}
+func (m *LPMRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_LPMRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LPMRequest proto.InternalMessageInfo
+
+func (m *LPMRequest) GetRouter() string {
+	if m != nil {
+		return m.Router
+	}
+	return ""
+}
+
+func (m *LPMRequest) GetVrfId() uint64 {
+	if m != nil {
+		return m.VrfId
+	}
+	return 0
+}
+
+func (m *LPMRequest) GetPfx() *api.Prefix {
+	if m != nil {
+		return m.Pfx
+	}
+	return nil
+}
+
+type LPMResponse struct {
+	Routes               []*api1.Route `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
+}
+
+func (m *LPMResponse) Reset()         { *m = LPMResponse{} }
+func (m *LPMResponse) String() string { return proto.CompactTextString(m) }
+func (*LPMResponse) ProtoMessage()    {}
+func (*LPMResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{1}
+}
+
+func (m *LPMResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_LPMResponse.Unmarshal(m, b)
+}
+func (m *LPMResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_LPMResponse.Marshal(b, m, deterministic)
+}
+func (m *LPMResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LPMResponse.Merge(m, src)
+}
+func (m *LPMResponse) XXX_Size() int {
+	return xxx_messageInfo_LPMResponse.Size(m)
+}
+func (m *LPMResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_LPMResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LPMResponse proto.InternalMessageInfo
+
+func (m *LPMResponse) GetRoutes() []*api1.Route {
+	if m != nil {
+		return m.Routes
+	}
+	return nil
+}
+
+type GetRequest struct {
+	Router               string      `protobuf:"bytes,1,opt,name=router,proto3" json:"router,omitempty"`
+	VrfId                uint64      `protobuf:"varint,2,opt,name=vrf_id,json=vrfId,proto3" json:"vrf_id,omitempty"`
+	Pfx                  *api.Prefix `protobuf:"bytes,3,opt,name=pfx,proto3" json:"pfx,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *GetRequest) Reset()         { *m = GetRequest{} }
+func (m *GetRequest) String() string { return proto.CompactTextString(m) }
+func (*GetRequest) ProtoMessage()    {}
+func (*GetRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{2}
+}
+
+func (m *GetRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetRequest.Unmarshal(m, b)
+}
+func (m *GetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetRequest.Marshal(b, m, deterministic)
+}
+func (m *GetRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetRequest.Merge(m, src)
+}
+func (m *GetRequest) XXX_Size() int {
+	return xxx_messageInfo_GetRequest.Size(m)
+}
+func (m *GetRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetRequest proto.InternalMessageInfo
+
+func (m *GetRequest) GetRouter() string {
+	if m != nil {
+		return m.Router
+	}
+	return ""
+}
+
+func (m *GetRequest) GetVrfId() uint64 {
+	if m != nil {
+		return m.VrfId
+	}
+	return 0
+}
+
+func (m *GetRequest) GetPfx() *api.Prefix {
+	if m != nil {
+		return m.Pfx
+	}
+	return nil
+}
+
+type GetResponse struct {
+	Routes               []*api1.Route `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
+}
+
+func (m *GetResponse) Reset()         { *m = GetResponse{} }
+func (m *GetResponse) String() string { return proto.CompactTextString(m) }
+func (*GetResponse) ProtoMessage()    {}
+func (*GetResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{3}
+}
+
+func (m *GetResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetResponse.Unmarshal(m, b)
+}
+func (m *GetResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetResponse.Marshal(b, m, deterministic)
+}
+func (m *GetResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetResponse.Merge(m, src)
+}
+func (m *GetResponse) XXX_Size() int {
+	return xxx_messageInfo_GetResponse.Size(m)
+}
+func (m *GetResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetResponse proto.InternalMessageInfo
+
+func (m *GetResponse) GetRoutes() []*api1.Route {
+	if m != nil {
+		return m.Routes
+	}
+	return nil
+}
+
+type GetLongerRequest struct {
+	Router               string      `protobuf:"bytes,1,opt,name=router,proto3" json:"router,omitempty"`
+	VrfId                uint64      `protobuf:"varint,2,opt,name=vrf_id,json=vrfId,proto3" json:"vrf_id,omitempty"`
+	Pfx                  *api.Prefix `protobuf:"bytes,3,opt,name=pfx,proto3" json:"pfx,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *GetLongerRequest) Reset()         { *m = GetLongerRequest{} }
+func (m *GetLongerRequest) String() string { return proto.CompactTextString(m) }
+func (*GetLongerRequest) ProtoMessage()    {}
+func (*GetLongerRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{4}
+}
+
+func (m *GetLongerRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetLongerRequest.Unmarshal(m, b)
+}
+func (m *GetLongerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetLongerRequest.Marshal(b, m, deterministic)
+}
+func (m *GetLongerRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetLongerRequest.Merge(m, src)
+}
+func (m *GetLongerRequest) XXX_Size() int {
+	return xxx_messageInfo_GetLongerRequest.Size(m)
+}
+func (m *GetLongerRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetLongerRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetLongerRequest proto.InternalMessageInfo
+
+func (m *GetLongerRequest) GetRouter() string {
+	if m != nil {
+		return m.Router
+	}
+	return ""
+}
+
+func (m *GetLongerRequest) GetVrfId() uint64 {
+	if m != nil {
+		return m.VrfId
+	}
+	return 0
+}
+
+func (m *GetLongerRequest) GetPfx() *api.Prefix {
+	if m != nil {
+		return m.Pfx
+	}
+	return nil
+}
+
+type GetLongerResponse struct {
+	Routes               []*api1.Route `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
+}
+
+func (m *GetLongerResponse) Reset()         { *m = GetLongerResponse{} }
+func (m *GetLongerResponse) String() string { return proto.CompactTextString(m) }
+func (*GetLongerResponse) ProtoMessage()    {}
+func (*GetLongerResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{5}
+}
+
+func (m *GetLongerResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetLongerResponse.Unmarshal(m, b)
+}
+func (m *GetLongerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetLongerResponse.Marshal(b, m, deterministic)
+}
+func (m *GetLongerResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetLongerResponse.Merge(m, src)
+}
+func (m *GetLongerResponse) XXX_Size() int {
+	return xxx_messageInfo_GetLongerResponse.Size(m)
+}
+func (m *GetLongerResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetLongerResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetLongerResponse proto.InternalMessageInfo
+
+func (m *GetLongerResponse) GetRoutes() []*api1.Route {
+	if m != nil {
+		return m.Routes
+	}
+	return nil
+}
+
+type ObserveRIBRequest struct {
+	Router               string                    `protobuf:"bytes,1,opt,name=router,proto3" json:"router,omitempty"`
+	VrfId                uint64                    `protobuf:"varint,2,opt,name=vrf_id,json=vrfId,proto3" json:"vrf_id,omitempty"`
+	Afisafi              ObserveRIBRequest_AFISAFI `protobuf:"varint,3,opt,name=afisafi,proto3,enum=bio.ris.ObserveRIBRequest_AFISAFI" json:"afisafi,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                  `json:"-"`
+	XXX_unrecognized     []byte                    `json:"-"`
+	XXX_sizecache        int32                     `json:"-"`
+}
+
+func (m *ObserveRIBRequest) Reset()         { *m = ObserveRIBRequest{} }
+func (m *ObserveRIBRequest) String() string { return proto.CompactTextString(m) }
+func (*ObserveRIBRequest) ProtoMessage()    {}
+func (*ObserveRIBRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{6}
+}
+
+func (m *ObserveRIBRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ObserveRIBRequest.Unmarshal(m, b)
+}
+func (m *ObserveRIBRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ObserveRIBRequest.Marshal(b, m, deterministic)
+}
+func (m *ObserveRIBRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ObserveRIBRequest.Merge(m, src)
+}
+func (m *ObserveRIBRequest) XXX_Size() int {
+	return xxx_messageInfo_ObserveRIBRequest.Size(m)
+}
+func (m *ObserveRIBRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_ObserveRIBRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ObserveRIBRequest proto.InternalMessageInfo
+
+func (m *ObserveRIBRequest) GetRouter() string {
+	if m != nil {
+		return m.Router
+	}
+	return ""
+}
+
+func (m *ObserveRIBRequest) GetVrfId() uint64 {
+	if m != nil {
+		return m.VrfId
+	}
+	return 0
+}
+
+func (m *ObserveRIBRequest) GetAfisafi() ObserveRIBRequest_AFISAFI {
+	if m != nil {
+		return m.Afisafi
+	}
+	return ObserveRIBRequest_IPv4Unicast
+}
+
+type RIBUpdate struct {
+	Advertisement        bool        `protobuf:"varint,1,opt,name=advertisement,proto3" json:"advertisement,omitempty"`
+	Route                *api1.Route `protobuf:"bytes,2,opt,name=route,proto3" json:"route,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *RIBUpdate) Reset()         { *m = RIBUpdate{} }
+func (m *RIBUpdate) String() string { return proto.CompactTextString(m) }
+func (*RIBUpdate) ProtoMessage()    {}
+func (*RIBUpdate) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{7}
+}
+
+func (m *RIBUpdate) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_RIBUpdate.Unmarshal(m, b)
+}
+func (m *RIBUpdate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_RIBUpdate.Marshal(b, m, deterministic)
+}
+func (m *RIBUpdate) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RIBUpdate.Merge(m, src)
+}
+func (m *RIBUpdate) XXX_Size() int {
+	return xxx_messageInfo_RIBUpdate.Size(m)
+}
+func (m *RIBUpdate) XXX_DiscardUnknown() {
+	xxx_messageInfo_RIBUpdate.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RIBUpdate proto.InternalMessageInfo
+
+func (m *RIBUpdate) GetAdvertisement() bool {
+	if m != nil {
+		return m.Advertisement
+	}
+	return false
+}
+
+func (m *RIBUpdate) GetRoute() *api1.Route {
+	if m != nil {
+		return m.Route
+	}
+	return nil
+}
+
+type DumpRIBRequest struct {
+	Router               string                 `protobuf:"bytes,1,opt,name=router,proto3" json:"router,omitempty"`
+	VrfId                uint64                 `protobuf:"varint,2,opt,name=vrf_id,json=vrfId,proto3" json:"vrf_id,omitempty"`
+	Afisafi              DumpRIBRequest_AFISAFI `protobuf:"varint,3,opt,name=afisafi,proto3,enum=bio.ris.DumpRIBRequest_AFISAFI" json:"afisafi,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}               `json:"-"`
+	XXX_unrecognized     []byte                 `json:"-"`
+	XXX_sizecache        int32                  `json:"-"`
+}
+
+func (m *DumpRIBRequest) Reset()         { *m = DumpRIBRequest{} }
+func (m *DumpRIBRequest) String() string { return proto.CompactTextString(m) }
+func (*DumpRIBRequest) ProtoMessage()    {}
+func (*DumpRIBRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{8}
+}
+
+func (m *DumpRIBRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_DumpRIBRequest.Unmarshal(m, b)
+}
+func (m *DumpRIBRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_DumpRIBRequest.Marshal(b, m, deterministic)
+}
+func (m *DumpRIBRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DumpRIBRequest.Merge(m, src)
+}
+func (m *DumpRIBRequest) XXX_Size() int {
+	return xxx_messageInfo_DumpRIBRequest.Size(m)
+}
+func (m *DumpRIBRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_DumpRIBRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DumpRIBRequest proto.InternalMessageInfo
+
+func (m *DumpRIBRequest) GetRouter() string {
+	if m != nil {
+		return m.Router
+	}
+	return ""
+}
+
+func (m *DumpRIBRequest) GetVrfId() uint64 {
+	if m != nil {
+		return m.VrfId
+	}
+	return 0
+}
+
+func (m *DumpRIBRequest) GetAfisafi() DumpRIBRequest_AFISAFI {
+	if m != nil {
+		return m.Afisafi
+	}
+	return DumpRIBRequest_IPv4Unicast
+}
+
+type DumpRIBReply struct {
+	Route                *api1.Route `protobuf:"bytes,1,opt,name=route,proto3" json:"route,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *DumpRIBReply) Reset()         { *m = DumpRIBReply{} }
+func (m *DumpRIBReply) String() string { return proto.CompactTextString(m) }
+func (*DumpRIBReply) ProtoMessage()    {}
+func (*DumpRIBReply) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ffe1202aa518913f, []int{9}
+}
+
+func (m *DumpRIBReply) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_DumpRIBReply.Unmarshal(m, b)
+}
+func (m *DumpRIBReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_DumpRIBReply.Marshal(b, m, deterministic)
+}
+func (m *DumpRIBReply) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DumpRIBReply.Merge(m, src)
+}
+func (m *DumpRIBReply) XXX_Size() int {
+	return xxx_messageInfo_DumpRIBReply.Size(m)
+}
+func (m *DumpRIBReply) XXX_DiscardUnknown() {
+	xxx_messageInfo_DumpRIBReply.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DumpRIBReply proto.InternalMessageInfo
+
+func (m *DumpRIBReply) GetRoute() *api1.Route {
+	if m != nil {
+		return m.Route
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterEnum("bio.ris.ObserveRIBRequest_AFISAFI", ObserveRIBRequest_AFISAFI_name, ObserveRIBRequest_AFISAFI_value)
+	proto.RegisterEnum("bio.ris.DumpRIBRequest_AFISAFI", DumpRIBRequest_AFISAFI_name, DumpRIBRequest_AFISAFI_value)
+	proto.RegisterType((*LPMRequest)(nil), "bio.ris.LPMRequest")
+	proto.RegisterType((*LPMResponse)(nil), "bio.ris.LPMResponse")
+	proto.RegisterType((*GetRequest)(nil), "bio.ris.GetRequest")
+	proto.RegisterType((*GetResponse)(nil), "bio.ris.GetResponse")
+	proto.RegisterType((*GetLongerRequest)(nil), "bio.ris.GetLongerRequest")
+	proto.RegisterType((*GetLongerResponse)(nil), "bio.ris.GetLongerResponse")
+	proto.RegisterType((*ObserveRIBRequest)(nil), "bio.ris.ObserveRIBRequest")
+	proto.RegisterType((*RIBUpdate)(nil), "bio.ris.RIBUpdate")
+	proto.RegisterType((*DumpRIBRequest)(nil), "bio.ris.DumpRIBRequest")
+	proto.RegisterType((*DumpRIBReply)(nil), "bio.ris.DumpRIBReply")
+}
+
+func init() {
+	proto.RegisterFile("github.com/bio-routing/bio-rd/cmd/ris/api/ris.proto", fileDescriptor_ffe1202aa518913f)
+}
+
+var fileDescriptor_ffe1202aa518913f = []byte{
+	// 534 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x94, 0xdf, 0x6f, 0xd3, 0x30,
+	0x10, 0xc7, 0xeb, 0x95, 0xb5, 0xf4, 0x0a, 0x5b, 0x67, 0x18, 0x74, 0x79, 0xa1, 0x44, 0x08, 0x15,
+	0x4d, 0x24, 0x53, 0x86, 0x86, 0x10, 0x3f, 0xa4, 0x55, 0xd3, 0xaa, 0x48, 0x9d, 0xa8, 0x3c, 0xed,
+	0x01, 0x1e, 0x40, 0x49, 0xe3, 0x14, 0x4b, 0x4b, 0x1c, 0x6c, 0x37, 0xda, 0xfe, 0x22, 0x9e, 0xf8,
+	0x37, 0xf8, 0xbb, 0x50, 0x9c, 0x2c, 0x4d, 0xd9, 0x0f, 0xd8, 0xd0, 0x5e, 0x12, 0xfb, 0x2e, 0xdf,
+	0xbb, 0x8f, 0x2f, 0x77, 0x86, 0xed, 0x29, 0x53, 0xdf, 0x66, 0xbe, 0x35, 0xe1, 0x91, 0xed, 0x33,
+	0xfe, 0x52, 0xf0, 0x99, 0x62, 0xf1, 0x34, 0x5f, 0x07, 0xf6, 0x24, 0x0a, 0x6c, 0xc1, 0xa4, 0xed,
+	0x25, 0x2c, 0x7b, 0x5b, 0x89, 0xe0, 0x8a, 0xe3, 0xa6, 0xcf, 0xb8, 0x25, 0x98, 0x34, 0xec, 0xab,
+	0xd5, 0x31, 0x55, 0x5a, 0x19, 0x53, 0x95, 0x2b, 0x8d, 0xbf, 0xa4, 0xcb, 0xb6, 0x34, 0x4f, 0x96,
+	0xad, 0x72, 0x91, 0xf9, 0x05, 0x60, 0x34, 0x3e, 0x20, 0xf4, 0xfb, 0x8c, 0x4a, 0x85, 0x1f, 0x41,
+	0x43, 0x3b, 0x45, 0x17, 0xf5, 0x50, 0xbf, 0x45, 0x8a, 0x1d, 0x5e, 0x87, 0x46, 0x2a, 0xc2, 0xaf,
+	0x2c, 0xe8, 0x2e, 0xf5, 0x50, 0xff, 0x0e, 0x59, 0x4e, 0x45, 0xe8, 0x06, 0xf8, 0x29, 0xd4, 0x93,
+	0xf0, 0xa4, 0x5b, 0xef, 0xa1, 0x7e, 0xdb, 0x59, 0xb5, 0x32, 0xf2, 0x0c, 0x67, 0x2c, 0x68, 0xc8,
+	0x4e, 0x48, 0xe6, 0x33, 0x5f, 0x43, 0x5b, 0xc7, 0x97, 0x09, 0x8f, 0x25, 0xc5, 0xfd, 0x22, 0x81,
+	0xec, 0xa2, 0x5e, 0xbd, 0xdf, 0x76, 0x3a, 0x5a, 0x94, 0x03, 0x91, 0xec, 0x59, 0xa4, 0x94, 0x19,
+	0xd8, 0x90, 0xaa, 0x5b, 0x05, 0xd3, 0xf1, 0xaf, 0x0d, 0x16, 0x40, 0x67, 0x48, 0xd5, 0x88, 0xc7,
+	0x53, 0x2a, 0x6e, 0x0f, 0xef, 0x3d, 0xac, 0x55, 0xb2, 0x5c, 0x1b, 0xf2, 0x27, 0x82, 0xb5, 0x8f,
+	0xbe, 0xa4, 0x22, 0xa5, 0xc4, 0x1d, 0xdc, 0x10, 0xf3, 0x1d, 0x34, 0xbd, 0x90, 0x49, 0x2f, 0x64,
+	0x1a, 0x75, 0xc5, 0x31, 0xad, 0xa2, 0x39, 0xad, 0x73, 0xb1, 0xad, 0xdd, 0x7d, 0xf7, 0x70, 0x77,
+	0xdf, 0x25, 0x67, 0x12, 0x73, 0x13, 0x9a, 0x85, 0x0d, 0xaf, 0x42, 0xdb, 0x1d, 0xa7, 0xaf, 0x8e,
+	0x62, 0x36, 0xf1, 0xa4, 0xea, 0xd4, 0x0a, 0xc3, 0xce, 0x99, 0x01, 0x99, 0x9f, 0xa0, 0x45, 0xdc,
+	0xc1, 0x51, 0x12, 0x78, 0x8a, 0xe2, 0x67, 0x70, 0xdf, 0x0b, 0x52, 0x2a, 0x14, 0x93, 0x34, 0xa2,
+	0xb1, 0xd2, 0xb4, 0x77, 0xc9, 0xa2, 0x11, 0x3f, 0x87, 0x65, 0x8d, 0xaf, 0x99, 0x2f, 0xaa, 0x45,
+	0xee, 0x36, 0x7f, 0x20, 0x58, 0xd9, 0x9b, 0x45, 0xc9, 0xcd, 0xeb, 0xf0, 0xe6, 0xcf, 0x3a, 0x3c,
+	0x29, 0xeb, 0xb0, 0x18, 0xf8, 0x3f, 0x8b, 0xb0, 0x03, 0xf7, 0xca, 0x78, 0xc9, 0xf1, 0xe9, 0xfc,
+	0x84, 0xe8, 0xca, 0x13, 0x3a, 0xbf, 0x96, 0x60, 0x83, 0xe4, 0xc3, 0xee, 0xc6, 0x21, 0x17, 0x91,
+	0xa7, 0x18, 0x8f, 0x0f, 0xa9, 0x48, 0xd9, 0x84, 0x62, 0x07, 0xea, 0xa3, 0xf1, 0x01, 0x7e, 0x50,
+	0x32, 0xcf, 0xe7, 0xdd, 0x78, 0xb8, 0x68, 0xcc, 0xdb, 0xcc, 0xac, 0x65, 0x9a, 0x21, 0x55, 0x15,
+	0xcd, 0x7c, 0x14, 0x2b, 0x9a, 0xca, 0xfc, 0x98, 0x35, 0xbc, 0x07, 0xad, 0xb2, 0x63, 0xf1, 0x46,
+	0xf5, 0xa3, 0x85, 0x59, 0x31, 0x8c, 0x8b, 0x5c, 0x65, 0x94, 0x0f, 0x00, 0xf3, 0xde, 0xc2, 0xc6,
+	0xe5, 0x0d, 0x67, 0xe0, 0xd2, 0x57, 0x76, 0xce, 0x16, 0xc2, 0x6f, 0xa1, 0x59, 0xd4, 0x10, 0x3f,
+	0xbe, 0xe4, 0x2f, 0x19, 0xeb, 0xe7, 0x1d, 0xc9, 0xf1, 0xe9, 0x16, 0x1a, 0x6c, 0x7e, 0x7e, 0xf1,
+	0xcf, 0x57, 0xb6, 0xdf, 0xd0, 0x17, 0xe8, 0xf6, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x59, 0x4d,
+	0xc3, 0x86, 0xe6, 0x05, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// RoutingInformationServiceClient is the client API for RoutingInformationService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type RoutingInformationServiceClient interface {
+	LPM(ctx context.Context, in *LPMRequest, opts ...grpc.CallOption) (*LPMResponse, error)
+	Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error)
+	GetLonger(ctx context.Context, in *GetLongerRequest, opts ...grpc.CallOption) (*GetLongerResponse, error)
+	ObserveRIB(ctx context.Context, in *ObserveRIBRequest, opts ...grpc.CallOption) (RoutingInformationService_ObserveRIBClient, error)
+	DumpRIB(ctx context.Context, in *DumpRIBRequest, opts ...grpc.CallOption) (RoutingInformationService_DumpRIBClient, error)
+}
+
+type routingInformationServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewRoutingInformationServiceClient(cc *grpc.ClientConn) RoutingInformationServiceClient {
+	return &routingInformationServiceClient{cc}
+}
+
+func (c *routingInformationServiceClient) LPM(ctx context.Context, in *LPMRequest, opts ...grpc.CallOption) (*LPMResponse, error) {
+	out := new(LPMResponse)
+	err := c.cc.Invoke(ctx, "/bio.ris.RoutingInformationService/LPM", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *routingInformationServiceClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) {
+	out := new(GetResponse)
+	err := c.cc.Invoke(ctx, "/bio.ris.RoutingInformationService/Get", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *routingInformationServiceClient) GetLonger(ctx context.Context, in *GetLongerRequest, opts ...grpc.CallOption) (*GetLongerResponse, error) {
+	out := new(GetLongerResponse)
+	err := c.cc.Invoke(ctx, "/bio.ris.RoutingInformationService/GetLonger", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *routingInformationServiceClient) ObserveRIB(ctx context.Context, in *ObserveRIBRequest, opts ...grpc.CallOption) (RoutingInformationService_ObserveRIBClient, error) {
+	stream, err := c.cc.NewStream(ctx, &_RoutingInformationService_serviceDesc.Streams[0], "/bio.ris.RoutingInformationService/ObserveRIB", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &routingInformationServiceObserveRIBClient{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 RoutingInformationService_ObserveRIBClient interface {
+	Recv() (*RIBUpdate, error)
+	grpc.ClientStream
+}
+
+type routingInformationServiceObserveRIBClient struct {
+	grpc.ClientStream
+}
+
+func (x *routingInformationServiceObserveRIBClient) Recv() (*RIBUpdate, error) {
+	m := new(RIBUpdate)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *routingInformationServiceClient) DumpRIB(ctx context.Context, in *DumpRIBRequest, opts ...grpc.CallOption) (RoutingInformationService_DumpRIBClient, error) {
+	stream, err := c.cc.NewStream(ctx, &_RoutingInformationService_serviceDesc.Streams[1], "/bio.ris.RoutingInformationService/DumpRIB", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &routingInformationServiceDumpRIBClient{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 RoutingInformationService_DumpRIBClient interface {
+	Recv() (*DumpRIBReply, error)
+	grpc.ClientStream
+}
+
+type routingInformationServiceDumpRIBClient struct {
+	grpc.ClientStream
+}
+
+func (x *routingInformationServiceDumpRIBClient) Recv() (*DumpRIBReply, error) {
+	m := new(DumpRIBReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// RoutingInformationServiceServer is the server API for RoutingInformationService service.
+type RoutingInformationServiceServer interface {
+	LPM(context.Context, *LPMRequest) (*LPMResponse, error)
+	Get(context.Context, *GetRequest) (*GetResponse, error)
+	GetLonger(context.Context, *GetLongerRequest) (*GetLongerResponse, error)
+	ObserveRIB(*ObserveRIBRequest, RoutingInformationService_ObserveRIBServer) error
+	DumpRIB(*DumpRIBRequest, RoutingInformationService_DumpRIBServer) error
+}
+
+func RegisterRoutingInformationServiceServer(s *grpc.Server, srv RoutingInformationServiceServer) {
+	s.RegisterService(&_RoutingInformationService_serviceDesc, srv)
+}
+
+func _RoutingInformationService_LPM_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(LPMRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(RoutingInformationServiceServer).LPM(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/bio.ris.RoutingInformationService/LPM",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(RoutingInformationServiceServer).LPM(ctx, req.(*LPMRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _RoutingInformationService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(RoutingInformationServiceServer).Get(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/bio.ris.RoutingInformationService/Get",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(RoutingInformationServiceServer).Get(ctx, req.(*GetRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _RoutingInformationService_GetLonger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetLongerRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(RoutingInformationServiceServer).GetLonger(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/bio.ris.RoutingInformationService/GetLonger",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(RoutingInformationServiceServer).GetLonger(ctx, req.(*GetLongerRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _RoutingInformationService_ObserveRIB_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(ObserveRIBRequest)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(RoutingInformationServiceServer).ObserveRIB(m, &routingInformationServiceObserveRIBServer{stream})
+}
+
+type RoutingInformationService_ObserveRIBServer interface {
+	Send(*RIBUpdate) error
+	grpc.ServerStream
+}
+
+type routingInformationServiceObserveRIBServer struct {
+	grpc.ServerStream
+}
+
+func (x *routingInformationServiceObserveRIBServer) Send(m *RIBUpdate) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func _RoutingInformationService_DumpRIB_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(DumpRIBRequest)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(RoutingInformationServiceServer).DumpRIB(m, &routingInformationServiceDumpRIBServer{stream})
+}
+
+type RoutingInformationService_DumpRIBServer interface {
+	Send(*DumpRIBReply) error
+	grpc.ServerStream
+}
+
+type routingInformationServiceDumpRIBServer struct {
+	grpc.ServerStream
+}
+
+func (x *routingInformationServiceDumpRIBServer) Send(m *DumpRIBReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+var _RoutingInformationService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "bio.ris.RoutingInformationService",
+	HandlerType: (*RoutingInformationServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "LPM",
+			Handler:    _RoutingInformationService_LPM_Handler,
+		},
+		{
+			MethodName: "Get",
+			Handler:    _RoutingInformationService_Get_Handler,
+		},
+		{
+			MethodName: "GetLonger",
+			Handler:    _RoutingInformationService_GetLonger_Handler,
+		},
+	},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "ObserveRIB",
+			Handler:       _RoutingInformationService_ObserveRIB_Handler,
+			ServerStreams: true,
+		},
+		{
+			StreamName:    "DumpRIB",
+			Handler:       _RoutingInformationService_DumpRIB_Handler,
+			ServerStreams: true,
+		},
+	},
+	Metadata: "github.com/bio-routing/bio-rd/cmd/ris/api/ris.proto",
+}
diff --git a/cmd/ris/api/ris.proto b/cmd/ris/api/ris.proto
new file mode 100644
index 0000000000000000000000000000000000000000..cdc7555e4bb60fe1da32704ebf2c0ba56f05e193
--- /dev/null
+++ b/cmd/ris/api/ris.proto
@@ -0,0 +1,75 @@
+syntax = "proto3";
+
+package bio.ris;
+
+import "github.com/bio-routing/bio-rd/net/api/net.proto";
+import "github.com/bio-routing/bio-rd/route/api/route.proto";
+option go_package = "github.com/bio-routing/bio-rd/cmd/ris/api";
+
+service RoutingInformationService {
+    rpc LPM(LPMRequest) returns (LPMResponse) {}
+    rpc Get(GetRequest) returns (GetResponse) {}
+    rpc GetLonger(GetLongerRequest) returns (GetLongerResponse) {}
+    rpc ObserveRIB(ObserveRIBRequest) returns (stream RIBUpdate);
+    rpc DumpRIB(DumpRIBRequest) returns (stream DumpRIBReply);
+}
+
+message LPMRequest {
+    string router = 1;
+    uint64 vrf_id = 2;
+    bio.net.Prefix pfx = 3;
+}
+
+message LPMResponse {
+    repeated bio.route.Route routes = 1;
+}
+
+message GetRequest {
+    string router = 1;
+    uint64 vrf_id = 2;
+    bio.net.Prefix pfx = 3;
+}
+
+message GetResponse {
+    repeated bio.route.Route routes = 1;
+}
+
+message GetLongerRequest {
+    string router = 1;
+    uint64 vrf_id = 2;
+    bio.net.Prefix pfx = 3;
+}
+
+message GetLongerResponse {
+    repeated bio.route.Route routes = 1;
+}
+
+message ObserveRIBRequest {
+    string router = 1;
+    uint64 vrf_id = 2;
+    enum AFISAFI {
+        IPv4Unicast = 0;
+        IPv6Unicast = 1;
+    }
+    AFISAFI afisafi = 3;
+}
+
+message RIBUpdate {
+    bool advertisement = 1;
+    bio.route.Route route = 2;
+}
+
+
+message DumpRIBRequest {
+    string router = 1;
+    uint64 vrf_id = 2;
+    enum AFISAFI {
+        IPv4Unicast = 0;
+        IPv6Unicast = 1;
+    }
+    AFISAFI afisafi = 3;
+}
+
+message DumpRIBReply {
+    bio.route.Route route = 1;
+}
\ No newline at end of file
diff --git a/cmd/ris/config/config.go b/cmd/ris/config/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a75bdaeef33ae0fe8d80d9100bb5ff56f572066
--- /dev/null
+++ b/cmd/ris/config/config.go
@@ -0,0 +1,35 @@
+package config
+
+import (
+	"io/ioutil"
+
+	"github.com/pkg/errors"
+	"gopkg.in/yaml.v2"
+)
+
+// RISConfig is the config of RIS instance
+type RISConfig struct {
+	BMPServers []BMPServer `yaml:"bmp_servers"`
+}
+
+// BMPServer represent a BMP enable Router
+type BMPServer struct {
+	Address string `yaml:"address"`
+	Port    uint16 `yaml:"port"`
+}
+
+// LoadConfig loads a RIS config
+func LoadConfig(filepath string) (*RISConfig, error) {
+	f, err := ioutil.ReadFile(filepath)
+	if err != nil {
+		return nil, errors.Wrap(err, "Unable to read config file")
+	}
+
+	cfg := &RISConfig{}
+	err = yaml.Unmarshal(f, cfg)
+	if err != nil {
+		return nil, errors.Wrap(err, "Unmarshal failed")
+	}
+
+	return cfg, nil
+}
diff --git a/cmd/ris/main.go b/cmd/ris/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..69d9d9a29fc8491cd9422b8d7508dbdfb2d6b272
--- /dev/null
+++ b/cmd/ris/main.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+	"flag"
+	"net"
+	"os"
+
+	"google.golang.org/grpc"
+
+	"github.com/bio-routing/bio-rd/cmd/ris/config"
+	"github.com/bio-routing/bio-rd/cmd/ris/risserver"
+	"github.com/bio-routing/bio-rd/protocols/bgp/server"
+	"github.com/bio-routing/bio-rd/util/servicewrapper"
+	"github.com/prometheus/client_golang/prometheus"
+
+	pb "github.com/bio-routing/bio-rd/cmd/ris/api"
+	prom_bmp "github.com/bio-routing/bio-rd/metrics/bmp/adapter/prom"
+	log "github.com/sirupsen/logrus"
+)
+
+var (
+	grpcPort       = flag.Uint("grpc_port", 4321, "gRPC server port")
+	httpPort       = flag.Uint("http_port", 4320, "HTTP server port")
+	configFilePath = flag.String("config.file", "ris_config.yml", "Configuration file")
+)
+
+func main() {
+	flag.Parse()
+
+	cfg, err := config.LoadConfig(*configFilePath)
+	if err != nil {
+		log.Errorf("Failed to load config: %v", err)
+		os.Exit(1)
+	}
+
+	b := server.NewServer()
+	prometheus.MustRegister(prom_bmp.NewCollector(b))
+
+	for _, r := range cfg.BMPServers {
+		ip := net.ParseIP(r.Address)
+		if ip == nil {
+			log.Errorf("Unable to convert %q to net.IP", r.Address)
+			os.Exit(1)
+		}
+		b.AddRouter(ip, r.Port)
+	}
+
+	s := risserver.NewServer(b)
+	unaryInterceptors := []grpc.UnaryServerInterceptor{}
+	streamInterceptors := []grpc.StreamServerInterceptor{}
+	srv, err := servicewrapper.New(
+		uint16(*grpcPort),
+		servicewrapper.HTTP(uint16(*httpPort)),
+		unaryInterceptors,
+		streamInterceptors,
+	)
+	if err != nil {
+		log.Errorf("failed to listen: %v", err)
+		os.Exit(1)
+	}
+
+	pb.RegisterRoutingInformationServiceServer(srv.GRPC(), s)
+	if err := srv.Serve(); err != nil {
+		log.Fatalf("failed to start server: %v", err)
+	}
+}
diff --git a/cmd/ris/ris_config.yml b/cmd/ris/ris_config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c6ff3384dfbdfb23776258e3619169507afb544a
--- /dev/null
+++ b/cmd/ris/ris_config.yml
@@ -0,0 +1,3 @@
+bmp_servers:
+  - address: 10.0.255.1
+    port: 30119
\ No newline at end of file
diff --git a/cmd/ris/risserver/server.go b/cmd/ris/risserver/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..a309db5f74c987db45e4841619976db172b0fcff
--- /dev/null
+++ b/cmd/ris/risserver/server.go
@@ -0,0 +1,269 @@
+package risserver
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/protocols/bgp/server"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/bio-routing/bio-rd/routingtable"
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+
+	pb "github.com/bio-routing/bio-rd/cmd/ris/api"
+	bnet "github.com/bio-routing/bio-rd/net"
+	netapi "github.com/bio-routing/bio-rd/net/api"
+	routeapi "github.com/bio-routing/bio-rd/route/api"
+)
+
+// Server represents an RoutingInformationService server
+type Server struct {
+	bmp *server.BMPServer
+}
+
+// NewServer creates a new server
+func NewServer(b *server.BMPServer) *Server {
+	return &Server{
+		bmp: b,
+	}
+}
+
+func (s Server) getRIB(rtr string, vrfID uint64, ipVersion netapi.IP_Version) (*locRIB.LocRIB, error) {
+	r := s.bmp.GetRouter(rtr)
+	if r == nil {
+		return nil, fmt.Errorf("Unable to get router %q", rtr)
+	}
+
+	v := r.GetVRF(vrfID)
+	if v == nil {
+		return nil, fmt.Errorf("Unable to get VRF %d", vrfID)
+	}
+
+	var rib *locRIB.LocRIB
+	switch ipVersion {
+	case netapi.IP_IPv4:
+		rib = v.IPv4UnicastRIB()
+	case netapi.IP_IPv6:
+		rib = v.IPv6UnicastRIB()
+	default:
+		return nil, fmt.Errorf("Unknown afi")
+	}
+
+	if rib == nil {
+		return nil, fmt.Errorf("Unable to get RIB")
+	}
+
+	return rib, nil
+}
+
+// LPM provides a longest prefix match service
+func (s *Server) LPM(ctx context.Context, req *pb.LPMRequest) (*pb.LPMResponse, error) {
+	if req.Pfx == nil {
+		panic("XXX")
+	}
+	rib, err := s.getRIB(req.Router, req.VrfId, req.Pfx.Address.Version)
+	if err != nil {
+		return nil, err
+	}
+
+	routes := rib.LPM(bnet.NewPrefixFromProtoPrefix(*req.Pfx))
+	res := &pb.LPMResponse{
+		Routes: make([]*routeapi.Route, 0, len(routes)),
+	}
+	for _, route := range routes {
+		res.Routes = append(res.Routes, route.ToProto())
+	}
+
+	return res, nil
+}
+
+// Get gets a prefix (exact match)
+func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
+	rib, err := s.getRIB(req.Router, req.VrfId, req.Pfx.Address.Version)
+	if err != nil {
+		return nil, err
+	}
+
+	route := rib.Get(bnet.NewPrefixFromProtoPrefix(*req.Pfx))
+	if route == nil {
+		return &pb.GetResponse{
+			Routes: make([]*routeapi.Route, 0, 0),
+		}, nil
+	}
+
+	return &pb.GetResponse{
+		Routes: []*routeapi.Route{
+			route.ToProto(),
+		},
+	}, nil
+}
+
+// GetLonger gets all more specifics of a prefix
+func (s *Server) GetLonger(ctx context.Context, req *pb.GetLongerRequest) (*pb.GetLongerResponse, error) {
+	rib, err := s.getRIB(req.Router, req.VrfId, req.Pfx.Address.Version)
+	if err != nil {
+		return nil, err
+	}
+
+	routes := rib.GetLonger(bnet.NewPrefixFromProtoPrefix(*req.Pfx))
+	res := &pb.GetLongerResponse{
+		Routes: make([]*routeapi.Route, 0, len(routes)),
+	}
+	for _, route := range routes {
+		res.Routes = append(res.Routes, route.ToProto())
+	}
+
+	return res, nil
+}
+
+func (s *Server) ObserveRIB(req *pb.ObserveRIBRequest, stream pb.RoutingInformationService_ObserveRIBServer) error {
+	ipVersion := netapi.IP_IPv4
+	switch req.Afisafi {
+	case pb.ObserveRIBRequest_IPv4Unicast:
+		ipVersion = netapi.IP_IPv4
+	case pb.ObserveRIBRequest_IPv6Unicast:
+		ipVersion = netapi.IP_IPv6
+	default:
+		return fmt.Errorf("Unknown AFI/SAFI")
+	}
+
+	rib, err := s.getRIB(req.Router, req.VrfId, ipVersion)
+	if err != nil {
+		return err
+	}
+
+	fifo := newUpdateFIFO()
+	rc := newRIBClient(fifo)
+	ret := make(chan error)
+
+	go func(fifo *updateFIFO) {
+		var err error
+
+		for {
+			for _, toSend := range fifo.dequeue() {
+				err = stream.Send(toSend)
+				if err != nil {
+					ret <- err
+					return
+				}
+			}
+
+		}
+	}(fifo)
+
+	rib.RegisterWithOptions(rc, routingtable.ClientOptions{
+		MaxPaths: 100,
+	})
+	defer rib.Unregister(rc)
+
+	err = <-ret
+	if err != nil {
+		return fmt.Errorf("Stream ended: %v", err)
+	}
+
+	return nil
+}
+
+func (s *Server) DumpRIB(req *pb.DumpRIBRequest, stream pb.RoutingInformationService_DumpRIBServer) error {
+	ipVersion := netapi.IP_IPv4
+	switch req.Afisafi {
+	case pb.DumpRIBRequest_IPv4Unicast:
+		ipVersion = netapi.IP_IPv4
+	case pb.DumpRIBRequest_IPv6Unicast:
+		ipVersion = netapi.IP_IPv6
+	default:
+		return fmt.Errorf("Unknown AFI/SAFI")
+	}
+
+	rib, err := s.getRIB(req.Router, req.VrfId, ipVersion)
+	if err != nil {
+		return err
+	}
+
+	toSend := &pb.DumpRIBReply{
+		Route: &routeapi.Route{
+			Paths: make([]*routeapi.Path, 1),
+		},
+	}
+
+	routes := rib.Dump()
+	for i := range routes {
+		toSend.Route = routes[i].ToProto()
+
+		err = stream.Send(toSend)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+type update struct {
+	advertisement bool
+	prefix        net.Prefix
+	path          *route.Path
+}
+
+type ribClient struct {
+	fifo *updateFIFO
+}
+
+func newRIBClient(fifo *updateFIFO) *ribClient {
+	return &ribClient{
+		fifo: fifo,
+	}
+}
+
+func (r *ribClient) AddPath(pfx net.Prefix, path *route.Path) error {
+	r.fifo.queue(&pb.RIBUpdate{
+		Advertisement: true,
+		Route: &routeapi.Route{
+			Pfx: pfx.ToProto(),
+			Paths: []*routeapi.Path{
+				path.ToProto(),
+			},
+		},
+	})
+
+	return nil
+}
+
+func (r *ribClient) RemovePath(pfx net.Prefix, path *route.Path) bool {
+	r.fifo.queue(&pb.RIBUpdate{
+		Advertisement: false,
+		Route: &routeapi.Route{
+			Pfx: pfx.ToProto(),
+			Paths: []*routeapi.Path{
+				path.ToProto(),
+			},
+		},
+	})
+
+	return false
+}
+
+func (r *ribClient) UpdateNewClient(routingtable.RouteTableClient) error {
+	return nil
+}
+
+func (r *ribClient) Register(routingtable.RouteTableClient) {
+}
+
+func (r *ribClient) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) {
+}
+
+func (r *ribClient) Unregister(routingtable.RouteTableClient) {
+}
+
+func (r *ribClient) RouteCount() int64 {
+	return -1
+}
+
+func (r *ribClient) ClientCount() uint64 {
+	return 0
+}
+
+func (r *ribClient) Dump() []*route.Route {
+	return nil
+}
diff --git a/cmd/ris/risserver/update_queue.go b/cmd/ris/risserver/update_queue.go
new file mode 100644
index 0000000000000000000000000000000000000000..719901163a9246e8736689292d00da532e5f46b5
--- /dev/null
+++ b/cmd/ris/risserver/update_queue.go
@@ -0,0 +1,47 @@
+package risserver
+
+import (
+	"sync"
+
+	pb "github.com/bio-routing/bio-rd/cmd/ris/api"
+)
+
+type updateFIFO struct {
+	dataArrived chan struct{}
+	data        []*pb.RIBUpdate
+	mu          sync.Mutex
+}
+
+func newUpdateFIFO() *updateFIFO {
+	return &updateFIFO{
+		dataArrived: make(chan struct{}, 1),
+		data:        make([]*pb.RIBUpdate, 0),
+	}
+}
+
+func (uf *updateFIFO) queue(r *pb.RIBUpdate) {
+	uf.mu.Lock()
+	uf.data = append(uf.data, r)
+	select {
+	case uf.dataArrived <- struct{}{}:
+	default:
+	}
+
+	uf.mu.Unlock()
+}
+
+func (uf *updateFIFO) empty() bool {
+	return len(uf.data) == 0
+}
+
+// dequeue get's all elements in the queue unless the queue is empty. Then it blocks until it is not empty.
+func (uf *updateFIFO) dequeue() []*pb.RIBUpdate {
+	<-uf.dataArrived
+
+	uf.mu.Lock()
+	data := uf.data
+	uf.data = make([]*pb.RIBUpdate, 0, 128)
+
+	uf.mu.Unlock()
+	return data
+}
diff --git a/examples/bgp/main.go b/examples/bgp/main.go
index 54047b24a09818631e50c7d5f51657bb8e9e5670..94aa8c8a29061ef49c0ecb634afbce8e8d415646 100644
--- a/examples/bgp/main.go
+++ b/examples/bgp/main.go
@@ -24,7 +24,7 @@ func main() {
 	logrus.Printf("This is a BGP speaker\n")
 
 	b := server.NewBgpServer()
-	v, err := vrf.New("master")
+	v, err := vrf.New("master", 0)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -38,7 +38,7 @@ func main() {
 
 func startMetricsEndpoint(server server.BGPServer) {
 	prometheus.MustRegister(prom_bgp.NewCollector(server))
-	prometheus.MustRegister(prom_vrf.NewCollector())
+	prometheus.MustRegister(prom_vrf.NewCollector(vrf.GetGlobalRegistry()))
 
 	http.Handle("/metrics", promhttp.Handler())
 
diff --git a/examples/bmp/main_bmp.go b/examples/bmp/main_bmp.go
index c973e6f51049a691a64ee3fde321524f2f6badcc..d16e4a6f38780c859de019d299c811db419f6187 100644
--- a/examples/bmp/main_bmp.go
+++ b/examples/bmp/main_bmp.go
@@ -6,21 +6,61 @@ import (
 	"time"
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/server"
-	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 	"github.com/sirupsen/logrus"
+
+	bnet "github.com/bio-routing/bio-rd/net"
 )
 
 func main() {
 	logrus.Printf("This is a BMP speaker\n")
 
-	rib4 := locRIB.New("inet.0")
-	rib6 := locRIB.New("inet6.0")
 	b := server.NewServer()
-	b.AddRouter(net.IP{10, 0, 255, 0}, 30119, rib4, rib6)
+	b.AddRouter(net.IP{10, 0, 255, 1}, 30119)
 
 	go func() {
 		for {
-			fmt.Printf("LocRIB4 count: %d\n", rib4.Count())
+			for _, r := range b.GetRouters() {
+				for _, v := range r.GetVRFs() {
+
+					rib4 := v.IPv4UnicastRIB()
+					c := rib4.Count()
+					fmt.Printf("Router: %s VRF: %s IPv4 route count: %d\n", r.Name(), v.Name(), c)
+
+					if v.RD() == 220434901565105 {
+						for _, route := range rib4.Dump() {
+							fmt.Printf("Pfx: %s\n", route.Prefix().String())
+							for _, p := range route.Paths() {
+								fmt.Printf("   %s\n", p.String())
+							}
+						}
+
+						fmt.Printf("looking up 185.65.240.100\n")
+						for _, r := range rib4.LPM(bnet.NewPfx(bnet.IPv4FromOctets(185, 65, 240, 100), 32)) {
+							fmt.Printf("Pfx: %s\n", r.Prefix().String())
+							for _, p := range r.Paths() {
+								fmt.Printf("   %s\n", p.String())
+							}
+						}
+
+						fmt.Printf("is 8.8.8.8 in closednet?\n")
+						x := rib4.LPM(bnet.NewPfx(bnet.IPv4FromOctets(8, 8, 8, 8), 32))
+						if len(x) == 0 {
+							fmt.Printf("Nope\n")
+						} else {
+							fmt.Printf("Yep\n")
+						}
+
+						fmt.Printf("is 185.65.240.100 in closednet?\n")
+						x = rib4.LPM(bnet.NewPfx(bnet.IPv4FromOctets(185, 65, 240, 0), 32))
+						if len(x) == 0 {
+							fmt.Printf("Nope\n")
+						} else {
+							fmt.Printf("Yep\n")
+						}
+					}
+				}
+			}
+
 			time.Sleep(time.Second * 10)
 		}
 	}()
diff --git a/examples/kernel/main.go b/examples/kernel/main.go
index 714e4b9f264c76ba684f878782d46a67ca518f5e..7d198d00912ed65d55cc56db125725c9cfee43b8 100644
--- a/examples/kernel/main.go
+++ b/examples/kernel/main.go
@@ -12,7 +12,7 @@ import (
 )
 
 func main() {
-	vrf, err := vrf.New("inet.0")
+	vrf, err := vrf.New("inet.0", 0)
 	if err != nil {
 		log.Errorf("Unable to create VRF: %v", err)
 		os.Exit(1)
diff --git a/metrics/bgp/adapter/prom/bgp_prom_adapter.go b/metrics/bgp/adapter/prom/bgp_prom_adapter.go
index 2ba446e7557005bf1bb4d9f0c6f13666d9be8205..891673c005f32cf5fe9a512d537ea0354c68e603 100644
--- a/metrics/bgp/adapter/prom/bgp_prom_adapter.go
+++ b/metrics/bgp/adapter/prom/bgp_prom_adapter.go
@@ -16,15 +16,24 @@ const (
 )
 
 var (
-	upDesc              *prometheus.Desc
-	stateDesc           *prometheus.Desc
-	uptimeDesc          *prometheus.Desc
-	updatesReceivedDesc *prometheus.Desc
-	updatesSentDesc     *prometheus.Desc
-	routesReceivedDesc  *prometheus.Desc
-	routesSentDesc      *prometheus.Desc
-	routesRejectedDesc  *prometheus.Desc
-	routesAcceptedDesc  *prometheus.Desc
+	upDesc                    *prometheus.Desc
+	stateDesc                 *prometheus.Desc
+	uptimeDesc                *prometheus.Desc
+	updatesReceivedDesc       *prometheus.Desc
+	updatesSentDesc           *prometheus.Desc
+	upDescRouter              *prometheus.Desc
+	stateDescRouter           *prometheus.Desc
+	uptimeDescRouter          *prometheus.Desc
+	updatesReceivedDescRouter *prometheus.Desc
+	updatesSentDescRouter     *prometheus.Desc
+	routesReceivedDesc        *prometheus.Desc
+	routesSentDesc            *prometheus.Desc
+	routesRejectedDesc        *prometheus.Desc
+	routesAcceptedDesc        *prometheus.Desc
+	routesReceivedDescRouter  *prometheus.Desc
+	routesSentDescRouter      *prometheus.Desc
+	routesRejectedDescRouter  *prometheus.Desc
+	routesAcceptedDescRouter  *prometheus.Desc
 )
 
 func init() {
@@ -35,11 +44,24 @@ func init() {
 	updatesReceivedDesc = prometheus.NewDesc(prefix+"update_received_count", "Number of updates received", labels, nil)
 	updatesSentDesc = prometheus.NewDesc(prefix+"update_sent_count", "Number of updates sent", labels, nil)
 
+	labelsRouter := append(labels, "router")
+	upDescRouter = prometheus.NewDesc(prefix+"up", "Returns if the session is up", labelsRouter, nil)
+	stateDescRouter = prometheus.NewDesc(prefix+"state", "State of the BGP session (Down = 0, Idle = 1, Connect = 2, Active = 3, OpenSent = 4, OpenConfirm = 5, Established = 6)", labelsRouter, nil)
+	uptimeDescRouter = prometheus.NewDesc(prefix+"uptime_second", "Time since the session was established in seconds", labelsRouter, nil)
+	updatesReceivedDescRouter = prometheus.NewDesc(prefix+"update_received_count", "Number of updates received", labelsRouter, nil)
+	updatesSentDescRouter = prometheus.NewDesc(prefix+"update_sent_count", "Number of updates sent", labelsRouter, nil)
+
 	labels = append(labels, "afi", "safi")
 	routesReceivedDesc = prometheus.NewDesc(prefix+"route_received_count", "Number of routes received", labels, nil)
 	routesSentDesc = prometheus.NewDesc(prefix+"route_sent_count", "Number of routes sent", labels, nil)
 	routesRejectedDesc = prometheus.NewDesc(prefix+"route_rejected_count", "Number of routes rejected", labels, nil)
 	routesAcceptedDesc = prometheus.NewDesc(prefix+"route_accepted_count", "Number of routes accepted", labels, nil)
+
+	labelsRouter = append(labelsRouter, "afi", "safi")
+	routesReceivedDescRouter = prometheus.NewDesc(prefix+"route_received_count", "Number of routes received", labelsRouter, nil)
+	routesSentDescRouter = prometheus.NewDesc(prefix+"route_sent_count", "Number of routes sent", labelsRouter, nil)
+	routesRejectedDescRouter = prometheus.NewDesc(prefix+"route_rejected_count", "Number of routes rejected", labelsRouter, nil)
+	routesAcceptedDescRouter = prometheus.NewDesc(prefix+"route_accepted_count", "Number of routes accepted", labelsRouter, nil)
 }
 
 // NewCollector creates a new collector instance for the given BGP server
@@ -65,6 +87,18 @@ func (c *bgpCollector) Describe(ch chan<- *prometheus.Desc) {
 	ch <- routesAcceptedDesc
 }
 
+func DescribeRouter(ch chan<- *prometheus.Desc) {
+	ch <- upDescRouter
+	ch <- stateDescRouter
+	ch <- uptimeDescRouter
+	ch <- updatesReceivedDescRouter
+	ch <- updatesSentDescRouter
+	ch <- routesReceivedDescRouter
+	ch <- routesSentDescRouter
+	ch <- routesRejectedDescRouter
+	ch <- routesAcceptedDescRouter
+}
+
 // Collect conforms to the prometheus collector interface
 func (c *bgpCollector) Collect(ch chan<- prometheus.Metric) {
 	m, err := c.server.Metrics()
@@ -74,16 +108,17 @@ func (c *bgpCollector) Collect(ch chan<- prometheus.Metric) {
 	}
 
 	for _, peer := range m.Peers {
-		c.collectForPeer(ch, peer)
+		collectForPeer(ch, peer)
 	}
 }
 
-func (c *bgpCollector) collectForPeer(ch chan<- prometheus.Metric, peer *metrics.BGPPeerMetrics) {
+func collectForPeer(ch chan<- prometheus.Metric, peer *metrics.BGPPeerMetrics) {
 	l := []string{
 		peer.IP.String(),
 		strconv.Itoa(int(peer.LocalASN)),
 		strconv.Itoa(int(peer.ASN)),
-		peer.VRF}
+		peer.VRF,
+	}
 
 	var up float64
 	var uptime float64
@@ -99,13 +134,47 @@ func (c *bgpCollector) collectForPeer(ch chan<- prometheus.Metric, peer *metrics
 	ch <- prometheus.MustNewConstMetric(updatesSentDesc, prometheus.CounterValue, float64(peer.UpdatesSent), l...)
 
 	for _, family := range peer.AddressFamilies {
-		c.collectForFamily(ch, family, l)
+		collectForFamily(ch, family, l)
 	}
 }
 
-func (c *bgpCollector) collectForFamily(ch chan<- prometheus.Metric, family *metrics.BGPAddressFamilyMetrics, l []string) {
+func CollectForPeerRouter(ch chan<- prometheus.Metric, rtr string, peer *metrics.BGPPeerMetrics) {
+	l := []string{
+		peer.IP.String(),
+		strconv.Itoa(int(peer.LocalASN)),
+		strconv.Itoa(int(peer.ASN)),
+		peer.VRF,
+		rtr,
+	}
+
+	var up float64
+	var uptime float64
+	if peer.Up {
+		up = 1
+		uptime = float64(time.Since(peer.Since) * time.Second)
+	}
+	ch <- prometheus.MustNewConstMetric(upDescRouter, prometheus.GaugeValue, up, l...)
+	ch <- prometheus.MustNewConstMetric(uptimeDescRouter, prometheus.GaugeValue, uptime, l...)
+	ch <- prometheus.MustNewConstMetric(stateDescRouter, prometheus.GaugeValue, float64(peer.State), l...)
+
+	ch <- prometheus.MustNewConstMetric(updatesReceivedDescRouter, prometheus.CounterValue, float64(peer.UpdatesReceived), l...)
+	ch <- prometheus.MustNewConstMetric(updatesSentDescRouter, prometheus.CounterValue, float64(peer.UpdatesSent), l...)
+
+	for _, family := range peer.AddressFamilies {
+		collectForFamilyRouter(ch, family, l)
+	}
+}
+
+func collectForFamily(ch chan<- prometheus.Metric, family *metrics.BGPAddressFamilyMetrics, l []string) {
 	l = append(l, strconv.Itoa(int(family.AFI)), strconv.Itoa(int(family.SAFI)))
 
 	ch <- prometheus.MustNewConstMetric(routesReceivedDesc, prometheus.CounterValue, float64(family.RoutesReceived), l...)
 	ch <- prometheus.MustNewConstMetric(routesSentDesc, prometheus.CounterValue, float64(family.RoutesSent), l...)
 }
+
+func collectForFamilyRouter(ch chan<- prometheus.Metric, family *metrics.BGPAddressFamilyMetrics, l []string) {
+	l = append(l, strconv.Itoa(int(family.AFI)), strconv.Itoa(int(family.SAFI)))
+
+	ch <- prometheus.MustNewConstMetric(routesReceivedDescRouter, prometheus.CounterValue, float64(family.RoutesReceived), l...)
+	ch <- prometheus.MustNewConstMetric(routesSentDescRouter, prometheus.CounterValue, float64(family.RoutesSent), l...)
+}
diff --git a/metrics/bmp/adapter/prom/bmp_prom_adapter.go b/metrics/bmp/adapter/prom/bmp_prom_adapter.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b31429ce98391300b614199113fc206e5ace84c
--- /dev/null
+++ b/metrics/bmp/adapter/prom/bmp_prom_adapter.go
@@ -0,0 +1,95 @@
+package prom
+
+import (
+	"github.com/bio-routing/bio-rd/protocols/bgp/metrics"
+	"github.com/bio-routing/bio-rd/protocols/bgp/server"
+	"github.com/pkg/errors"
+	"github.com/prometheus/client_golang/prometheus"
+
+	bgp_prom "github.com/bio-routing/bio-rd/metrics/bgp/adapter/prom"
+	vrf_prom "github.com/bio-routing/bio-rd/metrics/vrf/adapter/prom"
+	log "github.com/sirupsen/logrus"
+)
+
+const (
+	prefix = "bio_bmp_"
+)
+
+var (
+	routeMonitoringMessagesDesc  *prometheus.Desc
+	statisticsReportMessages     *prometheus.Desc
+	peerDownNotificationMessages *prometheus.Desc
+	peerUpNotificationMessages   *prometheus.Desc
+	initiationMessages           *prometheus.Desc
+	terminationMessages          *prometheus.Desc
+	routeMirroringMessages       *prometheus.Desc
+)
+
+func init() {
+	labels := []string{"router"}
+
+	routeMonitoringMessagesDesc = prometheus.NewDesc(prefix+"route_monitoring_messages", "Returns number of received route monitoring messages", labels, nil)
+	statisticsReportMessages = prometheus.NewDesc(prefix+"statistics_report_messages", "Returns number of received statistics report messages", labels, nil)
+	peerDownNotificationMessages = prometheus.NewDesc(prefix+"peer_down_messages", "Returns number of received peer down notification messages", labels, nil)
+	peerUpNotificationMessages = prometheus.NewDesc(prefix+"peer_up_messages", "Returns number of received peer up notification messages", labels, nil)
+	initiationMessages = prometheus.NewDesc(prefix+"initiation_messages", "Returns number of received initiation messages", labels, nil)
+	terminationMessages = prometheus.NewDesc(prefix+"termination_messages", "Returns number of received termination messages", labels, nil)
+	routeMirroringMessages = prometheus.NewDesc(prefix+"route_mirroring_messages", "Returns number of received route mirroring messages", labels, nil)
+}
+
+// NewCollector creates a new collector instance for the given BMP server
+func NewCollector(server *server.BMPServer) prometheus.Collector {
+	return &bmpCollector{server}
+}
+
+// bmpCollector provides a collector for BGP metrics of BIO to use with Prometheus
+type bmpCollector struct {
+	server *server.BMPServer
+}
+
+// Describe conforms to the prometheus collector interface
+func (c *bmpCollector) Describe(ch chan<- *prometheus.Desc) {
+	ch <- routeMonitoringMessagesDesc
+	ch <- statisticsReportMessages
+	ch <- peerDownNotificationMessages
+	ch <- peerUpNotificationMessages
+	ch <- initiationMessages
+	ch <- terminationMessages
+	ch <- routeMirroringMessages
+
+	vrf_prom.DescribeRouter(ch)
+	bgp_prom.DescribeRouter(ch)
+}
+
+// Collect conforms to the prometheus collector interface
+func (c *bmpCollector) Collect(ch chan<- prometheus.Metric) {
+	m, err := c.server.Metrics()
+	if err != nil {
+		log.Error(errors.Wrap(err, "Could not retrieve metrics from BMP server"))
+		return
+	}
+
+	for _, rtr := range m.Routers {
+		c.collectForRouter(ch, rtr)
+	}
+}
+
+func (c *bmpCollector) collectForRouter(ch chan<- prometheus.Metric, rtr *metrics.BMPRouterMetrics) {
+	l := []string{rtr.Name}
+
+	ch <- prometheus.MustNewConstMetric(routeMonitoringMessagesDesc, prometheus.CounterValue, float64(rtr.RouteMonitoringMessages), l...)
+	ch <- prometheus.MustNewConstMetric(statisticsReportMessages, prometheus.CounterValue, float64(rtr.StatisticsReportMessages), l...)
+	ch <- prometheus.MustNewConstMetric(peerDownNotificationMessages, prometheus.CounterValue, float64(rtr.PeerDownNotificationMessages), l...)
+	ch <- prometheus.MustNewConstMetric(peerUpNotificationMessages, prometheus.CounterValue, float64(rtr.PeerUpNotificationMessages), l...)
+	ch <- prometheus.MustNewConstMetric(initiationMessages, prometheus.CounterValue, float64(rtr.InitiationMessages), l...)
+	ch <- prometheus.MustNewConstMetric(terminationMessages, prometheus.CounterValue, float64(rtr.TerminationMessages), l...)
+	ch <- prometheus.MustNewConstMetric(routeMirroringMessages, prometheus.CounterValue, float64(rtr.RouteMirroringMessages), l...)
+
+	for _, vrfMetric := range rtr.VRFMetrics {
+		vrf_prom.CollectForVRFRouter(ch, rtr.Name, vrfMetric)
+	}
+
+	for _, peerMetric := range rtr.PeerMetrics {
+		bgp_prom.CollectForPeerRouter(ch, rtr.Name, peerMetric)
+	}
+}
diff --git a/metrics/vrf/adapter/prom/vrf_prom_adapter.go b/metrics/vrf/adapter/prom/vrf_prom_adapter.go
index f2a4fa0c3392a9b2848454710310a3b995606666..1c76154b96e0962e99773556d1a73d1dee982251 100644
--- a/metrics/vrf/adapter/prom/vrf_prom_adapter.go
+++ b/metrics/vrf/adapter/prom/vrf_prom_adapter.go
@@ -3,7 +3,6 @@ package prom
 import (
 	"strconv"
 
-	"github.com/bio-routing/bio-rd/protocols/bgp/server"
 	"github.com/bio-routing/bio-rd/routingtable/vrf"
 	"github.com/bio-routing/bio-rd/routingtable/vrf/metrics"
 	"github.com/prometheus/client_golang/prometheus"
@@ -14,22 +13,26 @@ const (
 )
 
 var (
-	routeCountDesc *prometheus.Desc
+	routeCountDesc       *prometheus.Desc
+	routeCountDescRouter *prometheus.Desc
 )
 
 func init() {
 	labels := []string{"vrf", "rib", "afi", "safi"}
 	routeCountDesc = prometheus.NewDesc(prefix+"route_count", "Number of routes in the RIB", labels, nil)
+	routeCountDescRouter = prometheus.NewDesc(prefix+"route_count", "Number of routes in the RIB", append([]string{"router"}, labels...), nil)
 }
 
 // NewCollector creates a new collector instance for the given BGP server
-func NewCollector() prometheus.Collector {
-	return &vrfCollector{}
+func NewCollector(r *vrf.VRFRegistry) prometheus.Collector {
+	return &vrfCollector{
+		registry: r,
+	}
 }
 
-// BGPCollector provides a collector for BGP metrics of BIO to use with Prometheus
+// vrfCollector provides a collector for VRF metrics of BIO to use with Prometheus
 type vrfCollector struct {
-	server server.BGPServer
+	registry *vrf.VRFRegistry
 }
 
 // Describe conforms to the prometheus collector interface
@@ -37,9 +40,14 @@ func (c *vrfCollector) Describe(ch chan<- *prometheus.Desc) {
 	ch <- routeCountDesc
 }
 
+// DescribeRouter conforms to the prometheus collector interface (used by BMP Server)
+func DescribeRouter(ch chan<- *prometheus.Desc) {
+	ch <- routeCountDescRouter
+}
+
 // Collect conforms to the prometheus collector interface
 func (c *vrfCollector) Collect(ch chan<- prometheus.Metric) {
-	for _, v := range vrf.Metrics() {
+	for _, v := range vrf.Metrics(c.registry) {
 		c.collectForVRF(ch, v)
 	}
 }
@@ -50,3 +58,11 @@ func (c *vrfCollector) collectForVRF(ch chan<- prometheus.Metric, v *metrics.VRF
 			v.Name, rib.Name, strconv.Itoa(int(rib.AFI)), strconv.Itoa(int(rib.SAFI)))
 	}
 }
+
+// CollectForVRFRouter collects metrics for a certain router (used by BMP Server)
+func CollectForVRFRouter(ch chan<- prometheus.Metric, rtr string, v *metrics.VRFMetrics) {
+	for _, rib := range v.RIBs {
+		ch <- prometheus.MustNewConstMetric(routeCountDescRouter, prometheus.GaugeValue, float64(rib.RouteCount),
+			rtr, v.Name, rib.Name, strconv.Itoa(int(rib.AFI)), strconv.Itoa(int(rib.SAFI)))
+	}
+}
diff --git a/net/api/net.pb.go b/net/api/net.pb.go
index b9ac5c96cbaad402c15c15aa1707154006735dbc..8a8230a69f4615d5a555ff6ec9eaa29ee4a2186e 100644
--- a/net/api/net.pb.go
+++ b/net/api/net.pb.go
@@ -1,21 +1,13 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // source: github.com/bio-routing/bio-rd/net/api/net.proto
 
-/*
-Package api is a generated protocol buffer package.
-
-It is generated from these files:
-	github.com/bio-routing/bio-rd/net/api/net.proto
-
-It has these top-level messages:
-	Prefix
-	IP
-*/
 package api
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
@@ -26,7 +18,7 @@ var _ = math.Inf
 // is compatible with the proto package it is being compiled against.
 // A compilation error at this line likely means your copy of the
 // proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 
 type IP_Version int32
 
@@ -39,6 +31,7 @@ var IP_Version_name = map[int32]string{
 	0: "IPv4",
 	1: "IPv6",
 }
+
 var IP_Version_value = map[string]int32{
 	"IPv4": 0,
 	"IPv6": 1,
@@ -47,17 +40,43 @@ var IP_Version_value = map[string]int32{
 func (x IP_Version) String() string {
 	return proto.EnumName(IP_Version_name, int32(x))
 }
-func (IP_Version) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
+
+func (IP_Version) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_e879b68d7a71dcc0, []int{1, 0}
+}
 
 type Prefix struct {
-	Address *IP    `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"`
-	Pfxlen  uint32 `protobuf:"varint,2,opt,name=pfxlen" json:"pfxlen,omitempty"`
+	Address              *IP      `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+	Pfxlen               uint32   `protobuf:"varint,2,opt,name=pfxlen,proto3" json:"pfxlen,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Prefix) Reset()         { *m = Prefix{} }
+func (m *Prefix) String() string { return proto.CompactTextString(m) }
+func (*Prefix) ProtoMessage()    {}
+func (*Prefix) Descriptor() ([]byte, []int) {
+	return fileDescriptor_e879b68d7a71dcc0, []int{0}
+}
+
+func (m *Prefix) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Prefix.Unmarshal(m, b)
+}
+func (m *Prefix) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Prefix.Marshal(b, m, deterministic)
+}
+func (m *Prefix) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Prefix.Merge(m, src)
+}
+func (m *Prefix) XXX_Size() int {
+	return xxx_messageInfo_Prefix.Size(m)
+}
+func (m *Prefix) XXX_DiscardUnknown() {
+	xxx_messageInfo_Prefix.DiscardUnknown(m)
 }
 
-func (m *Prefix) Reset()                    { *m = Prefix{} }
-func (m *Prefix) String() string            { return proto.CompactTextString(m) }
-func (*Prefix) ProtoMessage()               {}
-func (*Prefix) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+var xxx_messageInfo_Prefix proto.InternalMessageInfo
 
 func (m *Prefix) GetAddress() *IP {
 	if m != nil {
@@ -74,15 +93,38 @@ func (m *Prefix) GetPfxlen() uint32 {
 }
 
 type IP struct {
-	Higher  uint64     `protobuf:"varint,1,opt,name=higher" json:"higher,omitempty"`
-	Lower   uint64     `protobuf:"varint,2,opt,name=lower" json:"lower,omitempty"`
-	Version IP_Version `protobuf:"varint,3,opt,name=version,enum=bio.net.IP_Version" json:"version,omitempty"`
+	Higher               uint64     `protobuf:"varint,1,opt,name=higher,proto3" json:"higher,omitempty"`
+	Lower                uint64     `protobuf:"varint,2,opt,name=lower,proto3" json:"lower,omitempty"`
+	Version              IP_Version `protobuf:"varint,3,opt,name=version,proto3,enum=bio.net.IP_Version" json:"version,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
 }
 
-func (m *IP) Reset()                    { *m = IP{} }
-func (m *IP) String() string            { return proto.CompactTextString(m) }
-func (*IP) ProtoMessage()               {}
-func (*IP) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+func (m *IP) Reset()         { *m = IP{} }
+func (m *IP) String() string { return proto.CompactTextString(m) }
+func (*IP) ProtoMessage()    {}
+func (*IP) Descriptor() ([]byte, []int) {
+	return fileDescriptor_e879b68d7a71dcc0, []int{1}
+}
+
+func (m *IP) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_IP.Unmarshal(m, b)
+}
+func (m *IP) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_IP.Marshal(b, m, deterministic)
+}
+func (m *IP) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_IP.Merge(m, src)
+}
+func (m *IP) XXX_Size() int {
+	return xxx_messageInfo_IP.Size(m)
+}
+func (m *IP) XXX_DiscardUnknown() {
+	xxx_messageInfo_IP.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_IP proto.InternalMessageInfo
 
 func (m *IP) GetHigher() uint64 {
 	if m != nil {
@@ -106,14 +148,16 @@ func (m *IP) GetVersion() IP_Version {
 }
 
 func init() {
+	proto.RegisterEnum("bio.net.IP_Version", IP_Version_name, IP_Version_value)
 	proto.RegisterType((*Prefix)(nil), "bio.net.Prefix")
 	proto.RegisterType((*IP)(nil), "bio.net.IP")
-	proto.RegisterEnum("bio.net.IP_Version", IP_Version_name, IP_Version_value)
 }
 
-func init() { proto.RegisterFile("github.com/bio-routing/bio-rd/net/api/net.proto", fileDescriptor0) }
+func init() {
+	proto.RegisterFile("github.com/bio-routing/bio-rd/net/api/net.proto", fileDescriptor_e879b68d7a71dcc0)
+}
 
-var fileDescriptor0 = []byte{
+var fileDescriptor_e879b68d7a71dcc0 = []byte{
 	// 228 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4f, 0xcf, 0x2c, 0xc9,
 	0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xca, 0xcc, 0xd7, 0x2d, 0xca, 0x2f, 0x2d, 0xc9,
diff --git a/protocols/bgp/api/bgp.pb.go b/protocols/bgp/api/bgp.pb.go
index 40fd44f1f9cdae75d2615e6f6f8d5701871a7d83..65de520e03c74ebf22d3a7e129710db1d4d5134e 100644
--- a/protocols/bgp/api/bgp.pb.go
+++ b/protocols/bgp/api/bgp.pb.go
@@ -1,32 +1,16 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // source: github.com/bio-routing/bio-rd/protocols/bgp/api/bgp.proto
 
-/*
-Package api is a generated protocol buffer package.
-
-It is generated from these files:
-	github.com/bio-routing/bio-rd/protocols/bgp/api/bgp.proto
-	github.com/bio-routing/bio-rd/protocols/bgp/api/session.proto
-
-It has these top-level messages:
-	ListSessionsRequest
-	SessionFilter
-	ListSessionsResponse
-	DumpRIBRequest
-	Session
-	SessionStats
-*/
 package api
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
-import bio_net "github.com/bio-routing/bio-rd/net/api"
-import bio_route "github.com/bio-routing/bio-rd/route/api"
-
 import (
-	context "golang.org/x/net/context"
+	context "context"
+	fmt "fmt"
+	api "github.com/bio-routing/bio-rd/net/api"
+	api1 "github.com/bio-routing/bio-rd/route/api"
+	proto "github.com/golang/protobuf/proto"
 	grpc "google.golang.org/grpc"
+	math "math"
 )
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -38,16 +22,39 @@ var _ = math.Inf
 // is compatible with the proto package it is being compiled against.
 // A compilation error at this line likely means your copy of the
 // proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 
 type ListSessionsRequest struct {
-	Filter *SessionFilter `protobuf:"bytes,1,opt,name=filter" json:"filter,omitempty"`
+	Filter               *SessionFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *ListSessionsRequest) Reset()         { *m = ListSessionsRequest{} }
+func (m *ListSessionsRequest) String() string { return proto.CompactTextString(m) }
+func (*ListSessionsRequest) ProtoMessage()    {}
+func (*ListSessionsRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d4ce551e16bb738, []int{0}
+}
+
+func (m *ListSessionsRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ListSessionsRequest.Unmarshal(m, b)
+}
+func (m *ListSessionsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ListSessionsRequest.Marshal(b, m, deterministic)
+}
+func (m *ListSessionsRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListSessionsRequest.Merge(m, src)
+}
+func (m *ListSessionsRequest) XXX_Size() int {
+	return xxx_messageInfo_ListSessionsRequest.Size(m)
+}
+func (m *ListSessionsRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListSessionsRequest.DiscardUnknown(m)
 }
 
-func (m *ListSessionsRequest) Reset()                    { *m = ListSessionsRequest{} }
-func (m *ListSessionsRequest) String() string            { return proto.CompactTextString(m) }
-func (*ListSessionsRequest) ProtoMessage()               {}
-func (*ListSessionsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+var xxx_messageInfo_ListSessionsRequest proto.InternalMessageInfo
 
 func (m *ListSessionsRequest) GetFilter() *SessionFilter {
 	if m != nil {
@@ -57,16 +64,39 @@ func (m *ListSessionsRequest) GetFilter() *SessionFilter {
 }
 
 type SessionFilter struct {
-	NeighborIp *bio_net.IP `protobuf:"bytes,1,opt,name=neighbor_ip,json=neighborIp" json:"neighbor_ip,omitempty"`
-	VrfName    string      `protobuf:"bytes,2,opt,name=vrf_name,json=vrfName" json:"vrf_name,omitempty"`
+	NeighborIp           *api.IP  `protobuf:"bytes,1,opt,name=neighbor_ip,json=neighborIp,proto3" json:"neighbor_ip,omitempty"`
+	VrfName              string   `protobuf:"bytes,2,opt,name=vrf_name,json=vrfName,proto3" json:"vrf_name,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *SessionFilter) Reset()         { *m = SessionFilter{} }
+func (m *SessionFilter) String() string { return proto.CompactTextString(m) }
+func (*SessionFilter) ProtoMessage()    {}
+func (*SessionFilter) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d4ce551e16bb738, []int{1}
+}
+
+func (m *SessionFilter) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_SessionFilter.Unmarshal(m, b)
+}
+func (m *SessionFilter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_SessionFilter.Marshal(b, m, deterministic)
+}
+func (m *SessionFilter) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SessionFilter.Merge(m, src)
+}
+func (m *SessionFilter) XXX_Size() int {
+	return xxx_messageInfo_SessionFilter.Size(m)
+}
+func (m *SessionFilter) XXX_DiscardUnknown() {
+	xxx_messageInfo_SessionFilter.DiscardUnknown(m)
 }
 
-func (m *SessionFilter) Reset()                    { *m = SessionFilter{} }
-func (m *SessionFilter) String() string            { return proto.CompactTextString(m) }
-func (*SessionFilter) ProtoMessage()               {}
-func (*SessionFilter) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+var xxx_messageInfo_SessionFilter proto.InternalMessageInfo
 
-func (m *SessionFilter) GetNeighborIp() *bio_net.IP {
+func (m *SessionFilter) GetNeighborIp() *api.IP {
 	if m != nil {
 		return m.NeighborIp
 	}
@@ -81,13 +111,36 @@ func (m *SessionFilter) GetVrfName() string {
 }
 
 type ListSessionsResponse struct {
-	Sessions []*Session `protobuf:"bytes,1,rep,name=sessions" json:"sessions,omitempty"`
+	Sessions             []*Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
+}
+
+func (m *ListSessionsResponse) Reset()         { *m = ListSessionsResponse{} }
+func (m *ListSessionsResponse) String() string { return proto.CompactTextString(m) }
+func (*ListSessionsResponse) ProtoMessage()    {}
+func (*ListSessionsResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d4ce551e16bb738, []int{2}
+}
+
+func (m *ListSessionsResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ListSessionsResponse.Unmarshal(m, b)
+}
+func (m *ListSessionsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ListSessionsResponse.Marshal(b, m, deterministic)
+}
+func (m *ListSessionsResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListSessionsResponse.Merge(m, src)
+}
+func (m *ListSessionsResponse) XXX_Size() int {
+	return xxx_messageInfo_ListSessionsResponse.Size(m)
+}
+func (m *ListSessionsResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListSessionsResponse.DiscardUnknown(m)
 }
 
-func (m *ListSessionsResponse) Reset()                    { *m = ListSessionsResponse{} }
-func (m *ListSessionsResponse) String() string            { return proto.CompactTextString(m) }
-func (*ListSessionsResponse) ProtoMessage()               {}
-func (*ListSessionsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+var xxx_messageInfo_ListSessionsResponse proto.InternalMessageInfo
 
 func (m *ListSessionsResponse) GetSessions() []*Session {
 	if m != nil {
@@ -97,17 +150,40 @@ func (m *ListSessionsResponse) GetSessions() []*Session {
 }
 
 type DumpRIBRequest struct {
-	Peer *bio_net.IP `protobuf:"bytes,1,opt,name=peer" json:"peer,omitempty"`
-	Afi  uint32      `protobuf:"varint,2,opt,name=afi" json:"afi,omitempty"`
-	Safi uint32      `protobuf:"varint,3,opt,name=safi" json:"safi,omitempty"`
+	Peer                 *api.IP  `protobuf:"bytes,1,opt,name=peer,proto3" json:"peer,omitempty"`
+	Afi                  uint32   `protobuf:"varint,2,opt,name=afi,proto3" json:"afi,omitempty"`
+	Safi                 uint32   `protobuf:"varint,3,opt,name=safi,proto3" json:"safi,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *DumpRIBRequest) Reset()         { *m = DumpRIBRequest{} }
+func (m *DumpRIBRequest) String() string { return proto.CompactTextString(m) }
+func (*DumpRIBRequest) ProtoMessage()    {}
+func (*DumpRIBRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d4ce551e16bb738, []int{3}
+}
+
+func (m *DumpRIBRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_DumpRIBRequest.Unmarshal(m, b)
+}
+func (m *DumpRIBRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_DumpRIBRequest.Marshal(b, m, deterministic)
+}
+func (m *DumpRIBRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DumpRIBRequest.Merge(m, src)
+}
+func (m *DumpRIBRequest) XXX_Size() int {
+	return xxx_messageInfo_DumpRIBRequest.Size(m)
+}
+func (m *DumpRIBRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_DumpRIBRequest.DiscardUnknown(m)
 }
 
-func (m *DumpRIBRequest) Reset()                    { *m = DumpRIBRequest{} }
-func (m *DumpRIBRequest) String() string            { return proto.CompactTextString(m) }
-func (*DumpRIBRequest) ProtoMessage()               {}
-func (*DumpRIBRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
+var xxx_messageInfo_DumpRIBRequest proto.InternalMessageInfo
 
-func (m *DumpRIBRequest) GetPeer() *bio_net.IP {
+func (m *DumpRIBRequest) GetPeer() *api.IP {
 	if m != nil {
 		return m.Peer
 	}
@@ -135,6 +211,38 @@ func init() {
 	proto.RegisterType((*DumpRIBRequest)(nil), "bio.bgp.DumpRIBRequest")
 }
 
+func init() {
+	proto.RegisterFile("github.com/bio-routing/bio-rd/protocols/bgp/api/bgp.proto", fileDescriptor_2d4ce551e16bb738)
+}
+
+var fileDescriptor_2d4ce551e16bb738 = []byte{
+	// 382 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0xcb, 0x6a, 0xdb, 0x40,
+	0x14, 0xb5, 0x6a, 0xe3, 0xc7, 0x55, 0x5d, 0xcc, 0xb4, 0xb4, 0xae, 0x68, 0xa9, 0xd1, 0xca, 0x0b,
+	0x57, 0x6a, 0xed, 0x55, 0x1b, 0xb2, 0x31, 0x4e, 0x40, 0x90, 0x17, 0xe3, 0x45, 0x42, 0x36, 0x46,
+	0x72, 0x46, 0xf2, 0x80, 0x35, 0x33, 0xd1, 0x8c, 0xfc, 0xa5, 0xf9, 0xa0, 0xa0, 0xd1, 0x58, 0x44,
+	0x21, 0x31, 0x78, 0x23, 0x5d, 0x9d, 0xc7, 0xe5, 0x1c, 0x71, 0xe1, 0x5f, 0x42, 0xd5, 0x26, 0x8f,
+	0xbc, 0x35, 0x4f, 0xfd, 0x88, 0xf2, 0xdf, 0x19, 0xcf, 0x15, 0x65, 0x49, 0x39, 0x3f, 0xf8, 0x22,
+	0xe3, 0x8a, 0xaf, 0xf9, 0x56, 0xfa, 0x51, 0x22, 0xfc, 0x50, 0xd0, 0xe2, 0xed, 0x69, 0x14, 0x75,
+	0x22, 0xca, 0xbd, 0x28, 0x11, 0x8e, 0x7f, 0x78, 0x07, 0x23, 0x4a, 0x3b, 0x19, 0x51, 0xa5, 0xd3,
+	0x99, 0x1d, 0x36, 0x14, 0x9f, 0x44, 0x5b, 0xf4, 0x64, 0x4c, 0xa7, 0xc7, 0x26, 0x95, 0x44, 0x4a,
+	0xca, 0x59, 0x69, 0x77, 0xcf, 0xe0, 0xf3, 0x05, 0x95, 0x6a, 0x59, 0x82, 0x12, 0x93, 0xc7, 0x9c,
+	0x48, 0x85, 0x3c, 0x68, 0xc7, 0x74, 0xab, 0x48, 0x36, 0xb4, 0x46, 0xd6, 0xd8, 0x9e, 0x7e, 0xf5,
+	0x4c, 0x2b, 0xcf, 0x28, 0xcf, 0x35, 0x8b, 0x8d, 0xca, 0xbd, 0x83, 0x7e, 0x8d, 0x40, 0x13, 0xb0,
+	0x19, 0xa1, 0xc9, 0x26, 0xe2, 0xd9, 0x8a, 0x0a, 0xb3, 0xc5, 0xd6, 0x5b, 0x8a, 0xc2, 0xc1, 0x0d,
+	0x86, 0x3d, 0x1f, 0x08, 0xf4, 0x1d, 0xba, 0xbb, 0x2c, 0x5e, 0xb1, 0x30, 0x25, 0xc3, 0x0f, 0x23,
+	0x6b, 0xdc, 0xc3, 0x9d, 0x5d, 0x16, 0x5f, 0x85, 0x29, 0x71, 0x17, 0xf0, 0xa5, 0x1e, 0x50, 0x0a,
+	0xce, 0x24, 0x41, 0x13, 0xe8, 0x9a, 0x26, 0x72, 0x68, 0x8d, 0x9a, 0x63, 0x7b, 0x3a, 0x78, 0x9d,
+	0x11, 0x57, 0x0a, 0xf7, 0x16, 0x3e, 0x2d, 0xf2, 0x54, 0xe0, 0x60, 0xbe, 0x6f, 0xf8, 0x0b, 0x5a,
+	0x82, 0x54, 0xfd, 0x6a, 0xc9, 0x34, 0x81, 0x06, 0xd0, 0x0c, 0x63, 0xaa, 0xe3, 0xf4, 0x71, 0x31,
+	0x22, 0x04, 0x2d, 0x59, 0x40, 0x4d, 0x0d, 0xe9, 0x79, 0xfa, 0x64, 0x01, 0xcc, 0x13, 0xb1, 0x24,
+	0xd9, 0x8e, 0xae, 0x09, 0xba, 0x84, 0x8f, 0x2f, 0xd3, 0xa2, 0x1f, 0x55, 0xa6, 0x37, 0xfe, 0xb2,
+	0xf3, 0xf3, 0x1d, 0xb6, 0xac, 0xe8, 0x36, 0xd0, 0x7f, 0xe8, 0x99, 0xd8, 0x01, 0x43, 0xdf, 0x2a,
+	0x75, 0xbd, 0x8a, 0x53, 0x16, 0x2f, 0x8f, 0x02, 0x17, 0x4f, 0xb7, 0xf1, 0xc7, 0x42, 0x27, 0x00,
+	0x46, 0x77, 0x9d, 0xab, 0x23, 0xcd, 0xf3, 0xbf, 0xf7, 0xfe, 0x91, 0x77, 0x15, 0xb5, 0x35, 0x34,
+	0x7b, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x47, 0x78, 0x29, 0x1d, 0x3b, 0x03, 0x00, 0x00,
+}
+
 // Reference imports to suppress errors if they are not otherwise used.
 var _ context.Context
 var _ grpc.ClientConn
@@ -143,8 +251,9 @@ var _ grpc.ClientConn
 // is compatible with the grpc package it is being compiled against.
 const _ = grpc.SupportPackageIsVersion4
 
-// Client API for BgpService service
-
+// BgpServiceClient is the client API for BgpService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
 type BgpServiceClient interface {
 	ListSessions(ctx context.Context, in *ListSessionsRequest, opts ...grpc.CallOption) (*ListSessionsResponse, error)
 	DumpRIBIn(ctx context.Context, in *DumpRIBRequest, opts ...grpc.CallOption) (BgpService_DumpRIBInClient, error)
@@ -161,7 +270,7 @@ func NewBgpServiceClient(cc *grpc.ClientConn) BgpServiceClient {
 
 func (c *bgpServiceClient) ListSessions(ctx context.Context, in *ListSessionsRequest, opts ...grpc.CallOption) (*ListSessionsResponse, error) {
 	out := new(ListSessionsResponse)
-	err := grpc.Invoke(ctx, "/bio.bgp.BgpService/ListSessions", in, out, c.cc, opts...)
+	err := c.cc.Invoke(ctx, "/bio.bgp.BgpService/ListSessions", in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -169,7 +278,7 @@ func (c *bgpServiceClient) ListSessions(ctx context.Context, in *ListSessionsReq
 }
 
 func (c *bgpServiceClient) DumpRIBIn(ctx context.Context, in *DumpRIBRequest, opts ...grpc.CallOption) (BgpService_DumpRIBInClient, error) {
-	stream, err := grpc.NewClientStream(ctx, &_BgpService_serviceDesc.Streams[0], c.cc, "/bio.bgp.BgpService/DumpRIBIn", opts...)
+	stream, err := c.cc.NewStream(ctx, &_BgpService_serviceDesc.Streams[0], "/bio.bgp.BgpService/DumpRIBIn", opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -184,7 +293,7 @@ func (c *bgpServiceClient) DumpRIBIn(ctx context.Context, in *DumpRIBRequest, op
 }
 
 type BgpService_DumpRIBInClient interface {
-	Recv() (*bio_route.Route, error)
+	Recv() (*api1.Route, error)
 	grpc.ClientStream
 }
 
@@ -192,8 +301,8 @@ type bgpServiceDumpRIBInClient struct {
 	grpc.ClientStream
 }
 
-func (x *bgpServiceDumpRIBInClient) Recv() (*bio_route.Route, error) {
-	m := new(bio_route.Route)
+func (x *bgpServiceDumpRIBInClient) Recv() (*api1.Route, error) {
+	m := new(api1.Route)
 	if err := x.ClientStream.RecvMsg(m); err != nil {
 		return nil, err
 	}
@@ -201,7 +310,7 @@ func (x *bgpServiceDumpRIBInClient) Recv() (*bio_route.Route, error) {
 }
 
 func (c *bgpServiceClient) DumpRIBOut(ctx context.Context, in *DumpRIBRequest, opts ...grpc.CallOption) (BgpService_DumpRIBOutClient, error) {
-	stream, err := grpc.NewClientStream(ctx, &_BgpService_serviceDesc.Streams[1], c.cc, "/bio.bgp.BgpService/DumpRIBOut", opts...)
+	stream, err := c.cc.NewStream(ctx, &_BgpService_serviceDesc.Streams[1], "/bio.bgp.BgpService/DumpRIBOut", opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -216,7 +325,7 @@ func (c *bgpServiceClient) DumpRIBOut(ctx context.Context, in *DumpRIBRequest, o
 }
 
 type BgpService_DumpRIBOutClient interface {
-	Recv() (*bio_route.Route, error)
+	Recv() (*api1.Route, error)
 	grpc.ClientStream
 }
 
@@ -224,16 +333,15 @@ type bgpServiceDumpRIBOutClient struct {
 	grpc.ClientStream
 }
 
-func (x *bgpServiceDumpRIBOutClient) Recv() (*bio_route.Route, error) {
-	m := new(bio_route.Route)
+func (x *bgpServiceDumpRIBOutClient) Recv() (*api1.Route, error) {
+	m := new(api1.Route)
 	if err := x.ClientStream.RecvMsg(m); err != nil {
 		return nil, err
 	}
 	return m, nil
 }
 
-// Server API for BgpService service
-
+// BgpServiceServer is the server API for BgpService service.
 type BgpServiceServer interface {
 	ListSessions(context.Context, *ListSessionsRequest) (*ListSessionsResponse, error)
 	DumpRIBIn(*DumpRIBRequest, BgpService_DumpRIBInServer) error
@@ -271,7 +379,7 @@ func _BgpService_DumpRIBIn_Handler(srv interface{}, stream grpc.ServerStream) er
 }
 
 type BgpService_DumpRIBInServer interface {
-	Send(*bio_route.Route) error
+	Send(*api1.Route) error
 	grpc.ServerStream
 }
 
@@ -279,7 +387,7 @@ type bgpServiceDumpRIBInServer struct {
 	grpc.ServerStream
 }
 
-func (x *bgpServiceDumpRIBInServer) Send(m *bio_route.Route) error {
+func (x *bgpServiceDumpRIBInServer) Send(m *api1.Route) error {
 	return x.ServerStream.SendMsg(m)
 }
 
@@ -292,7 +400,7 @@ func _BgpService_DumpRIBOut_Handler(srv interface{}, stream grpc.ServerStream) e
 }
 
 type BgpService_DumpRIBOutServer interface {
-	Send(*bio_route.Route) error
+	Send(*api1.Route) error
 	grpc.ServerStream
 }
 
@@ -300,7 +408,7 @@ type bgpServiceDumpRIBOutServer struct {
 	grpc.ServerStream
 }
 
-func (x *bgpServiceDumpRIBOutServer) Send(m *bio_route.Route) error {
+func (x *bgpServiceDumpRIBOutServer) Send(m *api1.Route) error {
 	return x.ServerStream.SendMsg(m)
 }
 
@@ -327,35 +435,3 @@ var _BgpService_serviceDesc = grpc.ServiceDesc{
 	},
 	Metadata: "github.com/bio-routing/bio-rd/protocols/bgp/api/bgp.proto",
 }
-
-func init() {
-	proto.RegisterFile("github.com/bio-routing/bio-rd/protocols/bgp/api/bgp.proto", fileDescriptor0)
-}
-
-var fileDescriptor0 = []byte{
-	// 382 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0xcb, 0x6a, 0xdb, 0x40,
-	0x14, 0xb5, 0x6a, 0xe3, 0xc7, 0x55, 0x5d, 0xcc, 0xb4, 0xb4, 0xae, 0x68, 0xa9, 0xd1, 0xca, 0x0b,
-	0x57, 0x6a, 0xed, 0x55, 0x1b, 0xb2, 0x31, 0x4e, 0x40, 0x90, 0x17, 0xe3, 0x45, 0x42, 0x36, 0x46,
-	0x72, 0x46, 0xf2, 0x80, 0x35, 0x33, 0xd1, 0x8c, 0xfc, 0xa5, 0xf9, 0xa0, 0xa0, 0xd1, 0x58, 0x44,
-	0x21, 0x31, 0x78, 0x23, 0x5d, 0x9d, 0xc7, 0xe5, 0x1c, 0x71, 0xe1, 0x5f, 0x42, 0xd5, 0x26, 0x8f,
-	0xbc, 0x35, 0x4f, 0xfd, 0x88, 0xf2, 0xdf, 0x19, 0xcf, 0x15, 0x65, 0x49, 0x39, 0x3f, 0xf8, 0x22,
-	0xe3, 0x8a, 0xaf, 0xf9, 0x56, 0xfa, 0x51, 0x22, 0xfc, 0x50, 0xd0, 0xe2, 0xed, 0x69, 0x14, 0x75,
-	0x22, 0xca, 0xbd, 0x28, 0x11, 0x8e, 0x7f, 0x78, 0x07, 0x23, 0x4a, 0x3b, 0x19, 0x51, 0xa5, 0xd3,
-	0x99, 0x1d, 0x36, 0x14, 0x9f, 0x44, 0x5b, 0xf4, 0x64, 0x4c, 0xa7, 0xc7, 0x26, 0x95, 0x44, 0x4a,
-	0xca, 0x59, 0x69, 0x77, 0xcf, 0xe0, 0xf3, 0x05, 0x95, 0x6a, 0x59, 0x82, 0x12, 0x93, 0xc7, 0x9c,
-	0x48, 0x85, 0x3c, 0x68, 0xc7, 0x74, 0xab, 0x48, 0x36, 0xb4, 0x46, 0xd6, 0xd8, 0x9e, 0x7e, 0xf5,
-	0x4c, 0x2b, 0xcf, 0x28, 0xcf, 0x35, 0x8b, 0x8d, 0xca, 0xbd, 0x83, 0x7e, 0x8d, 0x40, 0x13, 0xb0,
-	0x19, 0xa1, 0xc9, 0x26, 0xe2, 0xd9, 0x8a, 0x0a, 0xb3, 0xc5, 0xd6, 0x5b, 0x8a, 0xc2, 0xc1, 0x0d,
-	0x86, 0x3d, 0x1f, 0x08, 0xf4, 0x1d, 0xba, 0xbb, 0x2c, 0x5e, 0xb1, 0x30, 0x25, 0xc3, 0x0f, 0x23,
-	0x6b, 0xdc, 0xc3, 0x9d, 0x5d, 0x16, 0x5f, 0x85, 0x29, 0x71, 0x17, 0xf0, 0xa5, 0x1e, 0x50, 0x0a,
-	0xce, 0x24, 0x41, 0x13, 0xe8, 0x9a, 0x26, 0x72, 0x68, 0x8d, 0x9a, 0x63, 0x7b, 0x3a, 0x78, 0x9d,
-	0x11, 0x57, 0x0a, 0xf7, 0x16, 0x3e, 0x2d, 0xf2, 0x54, 0xe0, 0x60, 0xbe, 0x6f, 0xf8, 0x0b, 0x5a,
-	0x82, 0x54, 0xfd, 0x6a, 0xc9, 0x34, 0x81, 0x06, 0xd0, 0x0c, 0x63, 0xaa, 0xe3, 0xf4, 0x71, 0x31,
-	0x22, 0x04, 0x2d, 0x59, 0x40, 0x4d, 0x0d, 0xe9, 0x79, 0xfa, 0x64, 0x01, 0xcc, 0x13, 0xb1, 0x24,
-	0xd9, 0x8e, 0xae, 0x09, 0xba, 0x84, 0x8f, 0x2f, 0xd3, 0xa2, 0x1f, 0x55, 0xa6, 0x37, 0xfe, 0xb2,
-	0xf3, 0xf3, 0x1d, 0xb6, 0xac, 0xe8, 0x36, 0xd0, 0x7f, 0xe8, 0x99, 0xd8, 0x01, 0x43, 0xdf, 0x2a,
-	0x75, 0xbd, 0x8a, 0x53, 0x16, 0x2f, 0x8f, 0x02, 0x17, 0x4f, 0xb7, 0xf1, 0xc7, 0x42, 0x27, 0x00,
-	0x46, 0x77, 0x9d, 0xab, 0x23, 0xcd, 0xf3, 0xbf, 0xf7, 0xfe, 0x91, 0x77, 0x15, 0xb5, 0x35, 0x34,
-	0x7b, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x47, 0x78, 0x29, 0x1d, 0x3b, 0x03, 0x00, 0x00,
-}
diff --git a/protocols/bgp/api/session.pb.go b/protocols/bgp/api/session.pb.go
index 1a9abc364299904fbe26da453da198aedd8adc4c..c15509510e383f4c39e5ce2d5ea3ba9ad7fd2e45 100644
--- a/protocols/bgp/api/session.pb.go
+++ b/protocols/bgp/api/session.pb.go
@@ -3,16 +3,24 @@
 
 package api
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
-import bio_net "github.com/bio-routing/bio-rd/net/api"
+import (
+	fmt "fmt"
+	api "github.com/bio-routing/bio-rd/net/api"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
 var _ = fmt.Errorf
 var _ = math.Inf
 
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
 type Session_State int32
 
 const (
@@ -34,6 +42,7 @@ var Session_State_name = map[int32]string{
 	5: "OpenConfirmed",
 	6: "Established",
 }
+
 var Session_State_value = map[string]int32{
 	"Disabled":      0,
 	"Idle":          1,
@@ -47,31 +56,57 @@ var Session_State_value = map[string]int32{
 func (x Session_State) String() string {
 	return proto.EnumName(Session_State_name, int32(x))
 }
-func (Session_State) EnumDescriptor() ([]byte, []int) { return fileDescriptor1, []int{0, 0} }
+
+func (Session_State) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_5b53032c0bb76d75, []int{0, 0}
+}
 
 type Session struct {
-	LocalAddress     *bio_net.IP   `protobuf:"bytes,1,opt,name=local_address,json=localAddress" json:"local_address,omitempty"`
-	NeighborAddress  *bio_net.IP   `protobuf:"bytes,2,opt,name=neighbor_address,json=neighborAddress" json:"neighbor_address,omitempty"`
-	LocalAsn         uint32        `protobuf:"varint,3,opt,name=local_asn,json=localAsn" json:"local_asn,omitempty"`
-	PeerAsn          uint32        `protobuf:"varint,4,opt,name=peer_asn,json=peerAsn" json:"peer_asn,omitempty"`
-	Status           Session_State `protobuf:"varint,5,opt,name=status,enum=bio.bgp.Session_State" json:"status,omitempty"`
-	Stats            *SessionStats `protobuf:"bytes,6,opt,name=stats" json:"stats,omitempty"`
-	EstablishedSince uint64        `protobuf:"varint,7,opt,name=established_since,json=establishedSince" json:"established_since,omitempty"`
+	LocalAddress         *api.IP       `protobuf:"bytes,1,opt,name=local_address,json=localAddress,proto3" json:"local_address,omitempty"`
+	NeighborAddress      *api.IP       `protobuf:"bytes,2,opt,name=neighbor_address,json=neighborAddress,proto3" json:"neighbor_address,omitempty"`
+	LocalAsn             uint32        `protobuf:"varint,3,opt,name=local_asn,json=localAsn,proto3" json:"local_asn,omitempty"`
+	PeerAsn              uint32        `protobuf:"varint,4,opt,name=peer_asn,json=peerAsn,proto3" json:"peer_asn,omitempty"`
+	Status               Session_State `protobuf:"varint,5,opt,name=status,proto3,enum=bio.bgp.Session_State" json:"status,omitempty"`
+	Stats                *SessionStats `protobuf:"bytes,6,opt,name=stats,proto3" json:"stats,omitempty"`
+	EstablishedSince     uint64        `protobuf:"varint,7,opt,name=established_since,json=establishedSince,proto3" json:"established_since,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
+}
+
+func (m *Session) Reset()         { *m = Session{} }
+func (m *Session) String() string { return proto.CompactTextString(m) }
+func (*Session) ProtoMessage()    {}
+func (*Session) Descriptor() ([]byte, []int) {
+	return fileDescriptor_5b53032c0bb76d75, []int{0}
 }
 
-func (m *Session) Reset()                    { *m = Session{} }
-func (m *Session) String() string            { return proto.CompactTextString(m) }
-func (*Session) ProtoMessage()               {}
-func (*Session) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
+func (m *Session) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Session.Unmarshal(m, b)
+}
+func (m *Session) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Session.Marshal(b, m, deterministic)
+}
+func (m *Session) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Session.Merge(m, src)
+}
+func (m *Session) XXX_Size() int {
+	return xxx_messageInfo_Session.Size(m)
+}
+func (m *Session) XXX_DiscardUnknown() {
+	xxx_messageInfo_Session.DiscardUnknown(m)
+}
 
-func (m *Session) GetLocalAddress() *bio_net.IP {
+var xxx_messageInfo_Session proto.InternalMessageInfo
+
+func (m *Session) GetLocalAddress() *api.IP {
 	if m != nil {
 		return m.LocalAddress
 	}
 	return nil
 }
 
-func (m *Session) GetNeighborAddress() *bio_net.IP {
+func (m *Session) GetNeighborAddress() *api.IP {
 	if m != nil {
 		return m.NeighborAddress
 	}
@@ -114,18 +149,41 @@ func (m *Session) GetEstablishedSince() uint64 {
 }
 
 type SessionStats struct {
-	MessagesIn     uint64 `protobuf:"varint,1,opt,name=messages_in,json=messagesIn" json:"messages_in,omitempty"`
-	MessagesOut    uint64 `protobuf:"varint,2,opt,name=messages_out,json=messagesOut" json:"messages_out,omitempty"`
-	Flaps          uint64 `protobuf:"varint,3,opt,name=flaps" json:"flaps,omitempty"`
-	RoutesReceived uint64 `protobuf:"varint,4,opt,name=routes_received,json=routesReceived" json:"routes_received,omitempty"`
-	RoutesImported uint64 `protobuf:"varint,5,opt,name=routes_imported,json=routesImported" json:"routes_imported,omitempty"`
-	RoutesExported uint64 `protobuf:"varint,6,opt,name=routes_exported,json=routesExported" json:"routes_exported,omitempty"`
+	MessagesIn           uint64   `protobuf:"varint,1,opt,name=messages_in,json=messagesIn,proto3" json:"messages_in,omitempty"`
+	MessagesOut          uint64   `protobuf:"varint,2,opt,name=messages_out,json=messagesOut,proto3" json:"messages_out,omitempty"`
+	Flaps                uint64   `protobuf:"varint,3,opt,name=flaps,proto3" json:"flaps,omitempty"`
+	RoutesReceived       uint64   `protobuf:"varint,4,opt,name=routes_received,json=routesReceived,proto3" json:"routes_received,omitempty"`
+	RoutesImported       uint64   `protobuf:"varint,5,opt,name=routes_imported,json=routesImported,proto3" json:"routes_imported,omitempty"`
+	RoutesExported       uint64   `protobuf:"varint,6,opt,name=routes_exported,json=routesExported,proto3" json:"routes_exported,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 
-func (m *SessionStats) Reset()                    { *m = SessionStats{} }
-func (m *SessionStats) String() string            { return proto.CompactTextString(m) }
-func (*SessionStats) ProtoMessage()               {}
-func (*SessionStats) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} }
+func (m *SessionStats) Reset()         { *m = SessionStats{} }
+func (m *SessionStats) String() string { return proto.CompactTextString(m) }
+func (*SessionStats) ProtoMessage()    {}
+func (*SessionStats) Descriptor() ([]byte, []int) {
+	return fileDescriptor_5b53032c0bb76d75, []int{1}
+}
+
+func (m *SessionStats) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_SessionStats.Unmarshal(m, b)
+}
+func (m *SessionStats) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_SessionStats.Marshal(b, m, deterministic)
+}
+func (m *SessionStats) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SessionStats.Merge(m, src)
+}
+func (m *SessionStats) XXX_Size() int {
+	return xxx_messageInfo_SessionStats.Size(m)
+}
+func (m *SessionStats) XXX_DiscardUnknown() {
+	xxx_messageInfo_SessionStats.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SessionStats proto.InternalMessageInfo
 
 func (m *SessionStats) GetMessagesIn() uint64 {
 	if m != nil {
@@ -170,16 +228,16 @@ func (m *SessionStats) GetRoutesExported() uint64 {
 }
 
 func init() {
+	proto.RegisterEnum("bio.bgp.Session_State", Session_State_name, Session_State_value)
 	proto.RegisterType((*Session)(nil), "bio.bgp.Session")
 	proto.RegisterType((*SessionStats)(nil), "bio.bgp.SessionStats")
-	proto.RegisterEnum("bio.bgp.Session_State", Session_State_name, Session_State_value)
 }
 
 func init() {
-	proto.RegisterFile("github.com/bio-routing/bio-rd/protocols/bgp/api/session.proto", fileDescriptor1)
+	proto.RegisterFile("github.com/bio-routing/bio-rd/protocols/bgp/api/session.proto", fileDescriptor_5b53032c0bb76d75)
 }
 
-var fileDescriptor1 = []byte{
+var fileDescriptor_5b53032c0bb76d75 = []byte{
 	// 469 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x4d, 0x6f, 0xd3, 0x4e,
 	0x10, 0xc6, 0xff, 0x6e, 0xfc, 0x92, 0xff, 0x38, 0x69, 0xdc, 0x15, 0x20, 0x03, 0x07, 0x42, 0x2e,
diff --git a/protocols/bgp/metrics/bmp_metrics.go b/protocols/bgp/metrics/bmp_metrics.go
new file mode 100644
index 0000000000000000000000000000000000000000..c0541203fc9b360b651b3adac99bfa46e8382f3c
--- /dev/null
+++ b/protocols/bgp/metrics/bmp_metrics.go
@@ -0,0 +1,43 @@
+package metrics
+
+import (
+	vrf_metrics "github.com/bio-routing/bio-rd/routingtable/vrf/metrics"
+)
+
+// BMPMetrics contains per router BMP metrics
+type BMPMetrics struct {
+	Routers []*BMPRouterMetrics
+}
+
+// BMPRouterMetrics contains a routers BMP metrics
+type BMPRouterMetrics struct {
+	// Name of the monitored routers
+	Name string
+
+	// Count of received RouteMonitoringMessages
+	RouteMonitoringMessages uint64
+
+	// Count of received StatisticsReportMessages
+	StatisticsReportMessages uint64
+
+	// Count of received PeerDownNotificationMessages
+	PeerDownNotificationMessages uint64
+
+	// Count of received PeerUpNotificationMessages
+	PeerUpNotificationMessages uint64
+
+	// Count of received InitiationMessages
+	InitiationMessages uint64
+
+	// Count of received TerminationMessages
+	TerminationMessages uint64
+
+	// Count of received RouteMirroringMessages
+	RouteMirroringMessages uint64
+
+	// VRFMetrics represent per VRF metrics
+	VRFMetrics []*vrf_metrics.VRFMetrics
+
+	// PeerMetrics contains BGP per peer metrics
+	PeerMetrics []*BGPPeerMetrics
+}
diff --git a/protocols/bgp/server/bgp_api_test.go b/protocols/bgp/server/bgp_api_test.go
index 0fa97120d7d1653f5dd123281335f04416a92929..9793094eb66c6527d07ac9256283824d64623a93 100644
--- a/protocols/bgp/server/bgp_api_test.go
+++ b/protocols/bgp/server/bgp_api_test.go
@@ -120,11 +120,11 @@ func TestDumpRIBInOut(t *testing.T) {
 					Paths: []*routeapi.Path{
 						{
 							Type: routeapi.Path_BGP,
-							BGPPath: &routeapi.BGPPath{
+							BgpPath: &routeapi.BGPPath{
 								OriginatorId:      1,
 								NextHop:           bnet.IPv4FromOctets(100, 100, 100, 100).ToProto(),
 								Source:            bnet.IPv4FromOctets(100, 100, 100, 100).ToProto(),
-								ASPath:            nil,
+								AsPath:            nil,
 								Communities:       nil,
 								LargeCommunities:  nil,
 								UnknownAttributes: nil,
@@ -203,16 +203,16 @@ func TestDumpRIBInOut(t *testing.T) {
 					Paths: []*routeapi.Path{
 						{
 							Type: routeapi.Path_BGP,
-							BGPPath: &routeapi.BGPPath{
+							BgpPath: &routeapi.BGPPath{
 								OriginatorId: 1,
 								LocalPref:    1000,
-								MED:          2000,
+								Med:          2000,
 								NextHop:      bnet.IPv4FromOctets(100, 100, 100, 100).ToProto(),
 								Source:       bnet.IPv4FromOctets(100, 100, 100, 100).ToProto(),
-								ASPath: []*routeapi.ASPathSegment{
+								AsPath: []*routeapi.ASPathSegment{
 									{
-										ASSequence: true,
-										ASNs:       []uint32{15169, 3320},
+										AsSequence: true,
+										Asns:       []uint32{15169, 3320},
 									},
 								},
 								Communities: []uint32{100, 200, 300},
diff --git a/protocols/bgp/server/bmp_metrics_service.go b/protocols/bgp/server/bmp_metrics_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..d678bd12d76a3ac3626ad97b1049e09dd7e0d832
--- /dev/null
+++ b/protocols/bgp/server/bmp_metrics_service.go
@@ -0,0 +1,57 @@
+package server
+
+import (
+	"sync/atomic"
+
+	"github.com/bio-routing/bio-rd/protocols/bgp/metrics"
+	bgp_metrics "github.com/bio-routing/bio-rd/protocols/bgp/metrics"
+	"github.com/bio-routing/bio-rd/routingtable/vrf"
+	vrf_metrics "github.com/bio-routing/bio-rd/routingtable/vrf/metrics"
+)
+
+type bmpMetricsService struct {
+	server *BMPServer
+}
+
+func (b *bmpMetricsService) metrics() *metrics.BMPMetrics {
+	return &metrics.BMPMetrics{
+		Routers: b.routerMetrics(),
+	}
+}
+
+func (b *bmpMetricsService) routerMetrics() []*metrics.BMPRouterMetrics {
+	routers := make([]*metrics.BMPRouterMetrics, 0)
+
+	for _, rtr := range b.server.getRouters() {
+		routers = append(routers, b.metricsForRouter(rtr))
+	}
+
+	return routers
+}
+
+func (b *bmpMetricsService) metricsForRouter(rtr *Router) *metrics.BMPRouterMetrics {
+	rm := &metrics.BMPRouterMetrics{
+		Name:                         rtr.name,
+		RouteMonitoringMessages:      atomic.LoadUint64(&rtr.counters.routeMonitoringMessages),
+		StatisticsReportMessages:     atomic.LoadUint64(&rtr.counters.statisticsReportMessages),
+		PeerDownNotificationMessages: atomic.LoadUint64(&rtr.counters.peerDownNotificationMessages),
+		PeerUpNotificationMessages:   atomic.LoadUint64(&rtr.counters.peerUpNotificationMessages),
+		InitiationMessages:           atomic.LoadUint64(&rtr.counters.initiationMessages),
+		TerminationMessages:          atomic.LoadUint64(&rtr.counters.terminationMessages),
+		RouteMirroringMessages:       atomic.LoadUint64(&rtr.counters.routeMirroringMessages),
+	}
+
+	vrfs := rtr.vrfRegistry.List()
+	rm.VRFMetrics = make([]*vrf_metrics.VRFMetrics, 0, len(vrfs))
+	for _, v := range vrfs {
+		rm.VRFMetrics = append(rm.VRFMetrics, vrf.MetricsForVRF(v))
+	}
+
+	peers := rtr.neighborManager.list()
+	rm.PeerMetrics = make([]*bgp_metrics.BGPPeerMetrics, len(peers))
+	for i := range peers {
+		rm.PeerMetrics[i] = metricsForPeer(peers[i].fsm.peer)
+	}
+
+	return rm
+}
diff --git a/protocols/bgp/server/bmp_neighbor_manager.go b/protocols/bgp/server/bmp_neighbor_manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..86c2898f78acb271f48f434757127fc3b198b03d
--- /dev/null
+++ b/protocols/bgp/server/bmp_neighbor_manager.go
@@ -0,0 +1,93 @@
+package server
+
+import (
+	"fmt"
+	"sync"
+)
+
+type neighborManager struct {
+	neighbors   []*neighbor
+	neighborsMu sync.Mutex
+}
+
+func newNeighborManager() *neighborManager {
+	return &neighborManager{
+		neighbors: make([]*neighbor, 0),
+	}
+}
+
+func (nm *neighborManager) addNeighbor(n *neighbor) error {
+	nm.neighborsMu.Lock()
+	defer nm.neighborsMu.Unlock()
+
+	for i := range nm.neighbors {
+		if nm.neighbors[i].vrfID == n.vrfID && nm.neighbors[i].peerAddress == n.peerAddress {
+			return fmt.Errorf("Unable to add neighbor %s on VRF %d: exists", n.peerAddress, n.vrfID)
+		}
+	}
+
+	nm.neighbors = append(nm.neighbors, n)
+	return nil
+}
+
+func (nm *neighborManager) getNeighbor(vrfID uint64, addr [16]byte) *neighbor {
+	nm.neighborsMu.Lock()
+	defer nm.neighborsMu.Unlock()
+
+	for i := range nm.neighbors {
+		if nm.neighbors[i].vrfID == vrfID && nm.neighbors[i].peerAddress == addr {
+			return nm.neighbors[i]
+		}
+	}
+
+	return nil
+}
+
+func (nm *neighborManager) neighborDown(vrfID uint64, addr [16]byte) error {
+	nm.neighborsMu.Lock()
+	defer nm.neighborsMu.Unlock()
+
+	return nm._neighborDown(vrfID, addr)
+}
+
+func (nm *neighborManager) _neighborDown(vrfID uint64, addr [16]byte) error {
+	for i := range nm.neighbors {
+		if nm.neighbors[i].vrfID != vrfID || nm.neighbors[i].peerAddress != addr {
+			continue
+		}
+
+		if nm.neighbors[i].fsm.ipv4Unicast != nil {
+			nm.neighbors[i].fsm.ipv4Unicast.bmpDispose()
+		}
+
+		if nm.neighbors[i].fsm.ipv6Unicast != nil {
+			nm.neighbors[i].fsm.ipv6Unicast.bmpDispose()
+		}
+
+		nm.neighbors = append(nm.neighbors[:i], nm.neighbors[i+1:]...)
+		return nil
+	}
+
+	return fmt.Errorf("Neighbor %d/%v not found", vrfID, addr)
+}
+
+func (nm *neighborManager) disposeAll() {
+	nm.neighborsMu.Lock()
+	defer nm.neighborsMu.Unlock()
+
+	for i := range nm.neighbors {
+		nm._neighborDown(nm.neighbors[i].vrfID, nm.neighbors[i].peerAddress)
+	}
+}
+
+func (nm *neighborManager) list() []*neighbor {
+	nm.neighborsMu.Lock()
+	defer nm.neighborsMu.Unlock()
+
+	ret := make([]*neighbor, len(nm.neighbors))
+	for i := range nm.neighbors {
+		ret[i] = nm.neighbors[i]
+	}
+
+	return ret
+}
diff --git a/protocols/bgp/server/bmp_router.go b/protocols/bgp/server/bmp_router.go
index 748b6a7afc947e3e0053c209666593a282fef80b..2f84948846d3e1cf676c6c8f63a5aeb5aff7afe0 100644
--- a/protocols/bgp/server/bmp_router.go
+++ b/protocols/bgp/server/bmp_router.go
@@ -5,21 +5,24 @@ import (
 	"fmt"
 	"net"
 	"sync"
+	"sync/atomic"
 	"time"
 
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+
 	bnet "github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	bmppkt "github.com/bio-routing/bio-rd/protocols/bmp/packet"
 	"github.com/bio-routing/bio-rd/routingtable"
 	"github.com/bio-routing/bio-rd/routingtable/filter"
-	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+	"github.com/bio-routing/bio-rd/routingtable/vrf"
 	"github.com/bio-routing/tflow2/convert"
-	"github.com/pkg/errors"
-	log "github.com/sirupsen/logrus"
 )
 
-type router struct {
+type Router struct {
 	name             string
+	nameMu           sync.RWMutex
 	address          net.IP
 	port             uint16
 	con              net.Conn
@@ -27,97 +30,68 @@ type router struct {
 	reconnectTimeMax int
 	reconnectTime    int
 	reconnectTimer   *time.Timer
-	rib4             *locRIB.LocRIB
-	rib6             *locRIB.LocRIB
-	neighbors        map[[16]byte]*neighbor
-	neighborsMu      sync.Mutex
+	vrfRegistry      *vrf.VRFRegistry
+	neighborManager  *neighborManager
 	logger           *log.Logger
 	runMu            sync.Mutex
 	stop             chan struct{}
 
 	ribClients   map[afiClient]struct{}
 	ribClientsMu sync.Mutex
+
+	counters routerCounters
+}
+
+type routerCounters struct {
+	routeMonitoringMessages      uint64
+	statisticsReportMessages     uint64
+	peerDownNotificationMessages uint64
+	peerUpNotificationMessages   uint64
+	initiationMessages           uint64
+	terminationMessages          uint64
+	routeMirroringMessages       uint64
 }
 
 type neighbor struct {
+	vrfID       uint64
+	peerAddress [16]byte
 	localAS     uint32
 	peerAS      uint32
-	peerAddress [16]byte
 	routerID    uint32
 	fsm         *FSM
 	opt         *packet.DecodeOptions
 }
 
-func newRouter(addr net.IP, port uint16, rib4 *locRIB.LocRIB, rib6 *locRIB.LocRIB) *router {
-	return &router{
+func newRouter(addr net.IP, port uint16) *Router {
+	return &Router{
 		address:          addr,
 		port:             port,
 		reconnectTimeMin: 30,  // Suggested by RFC 7854
 		reconnectTimeMax: 720, // Suggested by RFC 7854
 		reconnectTimer:   time.NewTimer(time.Duration(0)),
-		rib4:             rib4,
-		rib6:             rib6,
-		neighbors:        make(map[[16]byte]*neighbor),
+		vrfRegistry:      vrf.NewVRFRegistry(),
+		neighborManager:  newNeighborManager(),
 		logger:           log.New(),
 		stop:             make(chan struct{}),
 		ribClients:       make(map[afiClient]struct{}),
 	}
 }
 
-func (r *router) subscribeRIBs(client routingtable.RouteTableClient, afi uint8) {
-	ac := afiClient{
-		afi:    afi,
-		client: client,
-	}
-
-	r.ribClientsMu.Lock()
-	defer r.ribClientsMu.Unlock()
-	if _, ok := r.ribClients[ac]; ok {
-		return
-	}
-	r.ribClients[ac] = struct{}{}
-
-	r.neighborsMu.Lock()
-	defer r.neighborsMu.Unlock()
-	for _, n := range r.neighbors {
-		if afi == packet.IPv4AFI {
-			n.fsm.ipv4Unicast.adjRIBIn.Register(client)
-		}
-		if afi == packet.IPv6AFI {
-			n.fsm.ipv6Unicast.adjRIBIn.Register(client)
-		}
-	}
+func (r *Router) GetVRF(rd uint64) *vrf.VRF {
+	return r.vrfRegistry.GetVRFByRD(rd)
 }
 
-func (r *router) unsubscribeRIBs(client routingtable.RouteTableClient, afi uint8) {
-	ac := afiClient{
-		afi:    afi,
-		client: client,
-	}
-
-	r.ribClientsMu.Lock()
-	defer r.ribClientsMu.Unlock()
-	if _, ok := r.ribClients[ac]; !ok {
-		return
-	}
-	delete(r.ribClients, ac)
+func (r *Router) GetVRFs() []*vrf.VRF {
+	return r.vrfRegistry.List()
+}
 
-	r.neighborsMu.Lock()
-	defer r.neighborsMu.Unlock()
-	for _, n := range r.neighbors {
-		if !n.fsm.ribsInitialized {
-			continue
-		}
-		if afi == packet.IPv4AFI {
-			n.fsm.ipv4Unicast.adjRIBIn.Unregister(client)
-		}
-		if afi == packet.IPv6AFI {
-			n.fsm.ipv6Unicast.adjRIBIn.Unregister(client)
-		}
-	}
+func (r *Router) Name() string {
+	r.nameMu.RLock()
+	defer r.nameMu.RUnlock()
+	return r.name
 }
 
-func (r *router) serve(con net.Conn) {
+func (r *Router) serve(con net.Conn) {
 	r.con = con
 	r.runMu.Lock()
 	defer r.con.Close()
@@ -136,46 +110,56 @@ func (r *router) serve(con net.Conn) {
 			return
 		}
 
-		bmpMsg, err := bmppkt.Decode(msg)
-		if err != nil {
-			r.logger.Errorf("Unable to decode BMP message: %v", err)
-			return
-		}
+		r.processMsg(msg)
+	}
+}
 
-		switch bmpMsg.MsgType() {
-		case bmppkt.PeerUpNotificationType:
-			err = r.processPeerUpNotification(bmpMsg.(*bmppkt.PeerUpNotification))
-			if err != nil {
-				r.logger.Errorf("Unable to process peer up notification: %v", err)
-			}
-		case bmppkt.PeerDownNotificationType:
-			r.processPeerDownNotification(bmpMsg.(*bmppkt.PeerDownNotification))
-		case bmppkt.InitiationMessageType:
-			r.processInitiationMsg(bmpMsg.(*bmppkt.InitiationMessage))
-		case bmppkt.TerminationMessageType:
-			r.processTerminationMsg(bmpMsg.(*bmppkt.TerminationMessage))
-			return
-		case bmppkt.RouteMonitoringType:
-			r.processRouteMonitoringMsg(bmpMsg.(*bmppkt.RouteMonitoringMsg))
+func (r *Router) processMsg(msg []byte) {
+	bmpMsg, err := bmppkt.Decode(msg)
+	if err != nil {
+		r.logger.Errorf("Unable to decode BMP message: %v", err)
+		return
+	}
+
+	switch bmpMsg.MsgType() {
+	case bmppkt.PeerUpNotificationType:
+		err = r.processPeerUpNotification(bmpMsg.(*bmppkt.PeerUpNotification))
+		if err != nil {
+			r.logger.Errorf("Unable to process peer up notification: %v", err)
 		}
+	case bmppkt.PeerDownNotificationType:
+		r.processPeerDownNotification(bmpMsg.(*bmppkt.PeerDownNotification))
+	case bmppkt.InitiationMessageType:
+		r.processInitiationMsg(bmpMsg.(*bmppkt.InitiationMessage))
+	case bmppkt.TerminationMessageType:
+		r.processTerminationMsg(bmpMsg.(*bmppkt.TerminationMessage))
+		return
+	case bmppkt.RouteMonitoringType:
+		r.processRouteMonitoringMsg(bmpMsg.(*bmppkt.RouteMonitoringMsg))
+	case bmppkt.RouteMirroringMessageType:
+		atomic.AddUint64(&r.counters.routeMirroringMessages, 1)
 	}
 }
 
-func (r *router) processRouteMonitoringMsg(msg *bmppkt.RouteMonitoringMsg) {
-	r.neighborsMu.Lock()
-	defer r.neighborsMu.Unlock()
+func (r *Router) processRouteMonitoringMsg(msg *bmppkt.RouteMonitoringMsg) {
+	atomic.AddUint64(&r.counters.routeMonitoringMessages, 1)
 
-	if _, ok := r.neighbors[msg.PerPeerHeader.PeerAddress]; !ok {
-		r.logger.Errorf("Received route monitoring message for non-existent neighbor %v on %s", msg.PerPeerHeader.PeerAddress, r.address.String())
+	n := r.neighborManager.getNeighbor(msg.PerPeerHeader.PeerDistinguisher, msg.PerPeerHeader.PeerAddress)
+	if n == nil {
+		r.logger.Errorf("Received route monitoring message for non-existent neighbor %d/%v on %s", msg.PerPeerHeader.PeerDistinguisher, msg.PerPeerHeader.PeerAddress, r.address.String())
 		return
 	}
 
-	n := r.neighbors[msg.PerPeerHeader.PeerAddress]
 	s := n.fsm.state.(*establishedState)
 	s.msgReceived(msg.BGPUpdate, s.fsm.decodeOptions())
 }
 
-func (r *router) processInitiationMsg(msg *bmppkt.InitiationMessage) {
+func (r *Router) processInitiationMsg(msg *bmppkt.InitiationMessage) {
+	atomic.AddUint64(&r.counters.initiationMessages, 1)
+
+	r.nameMu.Lock()
+	defer r.nameMu.Unlock()
+
 	const (
 		stringType   = 0
 		sysDescrType = 1
@@ -199,7 +183,7 @@ func (r *router) processInitiationMsg(msg *bmppkt.InitiationMessage) {
 	r.logger.Info(logMsg)
 }
 
-func (r *router) processTerminationMsg(msg *bmppkt.TerminationMessage) {
+func (r *Router) processTerminationMsg(msg *bmppkt.TerminationMessage) {
 	const (
 		stringType = 0
 		reasonType = 1
@@ -211,18 +195,19 @@ func (r *router) processTerminationMsg(msg *bmppkt.TerminationMessage) {
 		permAdminDown = 4
 	)
 
+	atomic.AddUint64(&r.counters.terminationMessages, 1)
 	logMsg := fmt.Sprintf("Received termination message from %s: ", r.address.String())
 	for _, tlv := range msg.TLVs {
 		switch tlv.InformationType {
 		case stringType:
 			logMsg += fmt.Sprintf("Message: %q", string(tlv.Information))
 		case reasonType:
-			reason := convert.Uint16b(tlv.Information[:2])
+			reason := convert.Uint16b(tlv.Information[:1])
 			switch reason {
 			case adminDown:
 				logMsg += "Session administratively down"
 			case unspecReason:
-				logMsg += "Unespcified reason"
+				logMsg += "Unspecified reason"
 			case outOfRes:
 				logMsg += "Out of resources"
 			case redundantCon:
@@ -236,44 +221,32 @@ func (r *router) processTerminationMsg(msg *bmppkt.TerminationMessage) {
 	r.logger.Warning(logMsg)
 
 	r.con.Close()
-	for n := range r.neighbors {
-		r.peerDown(n)
-	}
-}
-
-func (r *router) processPeerDownNotification(msg *bmppkt.PeerDownNotification) {
-	r.neighborsMu.Lock()
-	defer r.neighborsMu.Unlock()
-
-	if _, ok := r.neighbors[msg.PerPeerHeader.PeerAddress]; !ok {
-		r.logger.Warningf("Received peer down notification for %v: Peer doesn't exist.", msg.PerPeerHeader.PeerAddress)
-		return
-	}
-
-	r.peerDown(msg.PerPeerHeader.PeerAddress)
+	r.neighborManager.disposeAll()
 }
 
-func (r *router) peerDown(addr [16]byte) {
-	if r.neighbors[addr].fsm != nil {
-		if r.neighbors[addr].fsm.ipv4Unicast != nil {
-			r.neighbors[addr].fsm.ipv4Unicast.bmpDispose()
-		}
+func (r *Router) processPeerDownNotification(msg *bmppkt.PeerDownNotification) {
+	r.logger.WithFields(log.Fields{
+		"address":            r.address.String(),
+		"router":             r.name,
+		"peer_distinguisher": msg.PerPeerHeader.PeerDistinguisher,
+		"peer_address":       addrToNetIP(msg.PerPeerHeader.PeerAddress).String(),
+	}).Infof("peer down notification received")
+	atomic.AddUint64(&r.counters.peerDownNotificationMessages, 1)
 
-		if r.neighbors[addr].fsm.ipv6Unicast != nil {
-			r.neighbors[addr].fsm.ipv6Unicast.bmpDispose()
-		}
+	err := r.neighborManager.neighborDown(msg.PerPeerHeader.PeerDistinguisher, msg.PerPeerHeader.PeerAddress)
+	if err != nil {
+		r.logger.Errorf("Failed to process peer down notification: %v", err)
 	}
-
-	delete(r.neighbors, addr)
 }
 
-func (r *router) processPeerUpNotification(msg *bmppkt.PeerUpNotification) error {
-	r.neighborsMu.Lock()
-	defer r.neighborsMu.Unlock()
-
-	if _, ok := r.neighbors[msg.PerPeerHeader.PeerAddress]; ok {
-		return fmt.Errorf("Received peer up notification for %v: Peer exists already", msg.PerPeerHeader.PeerAddress)
-	}
+func (r *Router) processPeerUpNotification(msg *bmppkt.PeerUpNotification) error {
+	atomic.AddUint64(&r.counters.peerUpNotificationMessages, 1)
+	r.logger.WithFields(log.Fields{
+		"address":            r.address.String(),
+		"router":             r.name,
+		"peer_distinguisher": msg.PerPeerHeader.PeerDistinguisher,
+		"peer_address":       addrToNetIP(msg.PerPeerHeader.PeerAddress).String(),
+	}).Infof("peer up notification received")
 
 	if len(msg.SentOpenMsg) < packet.MinOpenLen {
 		return fmt.Errorf("Received peer up notification for %v: Invalid sent open message: %v", msg.PerPeerHeader.PeerAddress, msg.SentOpenMsg)
@@ -312,19 +285,33 @@ func (r *router) processPeerUpNotification(msg *bmppkt.PeerUpNotification) error
 			localASN:  uint32(sentOpen.ASN),
 			ipv4:      &peerAddressFamily{},
 			ipv6:      &peerAddressFamily{},
+			vrf:       r.vrfRegistry.CreateVRFIfNotExists(fmt.Sprintf("%d", msg.PerPeerHeader.PeerDistinguisher), msg.PerPeerHeader.PeerDistinguisher),
 		},
 	}
 
+	fsm.peer.fsms = []*FSM{
+		fsm,
+	}
+
 	fsm.peer.configureBySentOpen(sentOpen)
 
+	rib4, found := fsm.peer.vrf.RIBByName("inet.0")
+	if !found {
+		return fmt.Errorf("Unable to get inet RIB")
+	}
 	fsm.ipv4Unicast = newFSMAddressFamily(packet.IPv4AFI, packet.UnicastSAFI, &peerAddressFamily{
-		rib:          r.rib4,
+		rib:          rib4,
 		importFilter: filter.NewAcceptAllFilter(),
 	}, fsm)
 	fsm.ipv4Unicast.bmpInit()
 
+	rib6, found := fsm.peer.vrf.RIBByName("inet6.0")
+	if !found {
+		return fmt.Errorf("Unable to get inet6 RIB")
+	}
+
 	fsm.ipv6Unicast = newFSMAddressFamily(packet.IPv6AFI, packet.UnicastSAFI, &peerAddressFamily{
-		rib:          r.rib6,
+		rib:          rib6,
 		importFilter: filter.NewAcceptAllFilter(),
 	}, fsm)
 	fsm.ipv6Unicast.bmpInit()
@@ -335,6 +322,7 @@ func (r *router) processPeerUpNotification(msg *bmppkt.PeerUpNotification) error
 
 	fsm.state = newEstablishedState(fsm)
 	n := &neighbor{
+		vrfID:       msg.PerPeerHeader.PeerDistinguisher,
 		localAS:     fsm.peer.localASN,
 		peerAS:      msg.PerPeerHeader.PeerAS,
 		peerAddress: msg.PerPeerHeader.PeerAddress,
@@ -343,7 +331,10 @@ func (r *router) processPeerUpNotification(msg *bmppkt.PeerUpNotification) error
 		opt:         fsm.decodeOptions(),
 	}
 
-	r.neighbors[msg.PerPeerHeader.PeerAddress] = n
+	err = r.neighborManager.addNeighbor(n)
+	if err != nil {
+		return errors.Wrap(err, "Unable to add neighbor")
+	}
 
 	r.ribClientsMu.Lock()
 	defer r.ribClientsMu.Unlock()
@@ -403,3 +394,13 @@ func getCaps(optParams []packet.OptParam) packet.Capabilities {
 	}
 	return nil
 }
+
+func addrToNetIP(a [16]byte) net.IP {
+	for i := 0; i < 12; i++ {
+		if a[i] != 0 {
+			return net.IP(a[:])
+		}
+	}
+
+	return net.IP(a[12:])
+}
diff --git a/protocols/bgp/server/bmp_router_test.go b/protocols/bgp/server/bmp_router_test.go
index 3ced25bfcff69e299b7bc56d43d9a18315937eee..ff0e90c1574a166786caddfff985bb5ffee13e88 100644
--- a/protocols/bgp/server/bmp_router_test.go
+++ b/protocols/bgp/server/bmp_router_test.go
@@ -1,25 +1,6 @@
 package server
 
-import (
-	"bytes"
-	"net"
-	"testing"
-	"time"
-
-	bnet "github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-	bmppkt "github.com/bio-routing/bio-rd/protocols/bmp/packet"
-	"github.com/bio-routing/bio-rd/route"
-	"github.com/bio-routing/bio-rd/routingtable"
-	"github.com/bio-routing/bio-rd/routingtable/adjRIBIn"
-	"github.com/bio-routing/bio-rd/routingtable/filter"
-	"github.com/bio-routing/bio-rd/routingtable/locRIB"
-	biotesting "github.com/bio-routing/bio-rd/testing"
-	log "github.com/sirupsen/logrus"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestBMPRouterServe(t *testing.T) {
+/*func TestBMPRouterServe(t *testing.T) {
 	tests := []struct {
 		name     string
 		msg      []byte
@@ -2746,3 +2727,4 @@ func TestIntegrationPeerUpRouteMonitorIPv6WithClientBeforeBMPPeer(t *testing.T)
 		conA.Close()
 	}
 }
+*/
diff --git a/protocols/bgp/server/bmp_server.go b/protocols/bgp/server/bmp_server.go
index 6aa751bf51819d416d5204e110cf5cfad37bf223..cc387a2c6e18399d62d3087f3cb882f06e312e46 100644
--- a/protocols/bgp/server/bmp_server.go
+++ b/protocols/bgp/server/bmp_server.go
@@ -7,11 +7,12 @@ import (
 	"sync"
 	"time"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/metrics"
 	bmppkt "github.com/bio-routing/bio-rd/protocols/bmp/packet"
 	"github.com/bio-routing/bio-rd/routingtable"
-	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 	"github.com/bio-routing/tflow2/convert"
 	"github.com/pkg/errors"
+
 	log "github.com/sirupsen/logrus"
 )
 
@@ -21,9 +22,11 @@ const (
 
 // BMPServer represents a BMP server
 type BMPServer struct {
-	routers    map[string]*router
+	routers    map[string]*Router
+	routersMu  sync.RWMutex
 	ribClients map[string]map[afiClient]struct{}
 	gloablMu   sync.RWMutex
+	metrics    *bmpMetricsService
 }
 
 type afiClient struct {
@@ -33,70 +36,24 @@ type afiClient struct {
 
 // NewServer creates a new BMP server
 func NewServer() *BMPServer {
-	return &BMPServer{
-		routers:    make(map[string]*router),
+	b := &BMPServer{
+		routers:    make(map[string]*Router),
 		ribClients: make(map[string]map[afiClient]struct{}),
 	}
-}
-
-// SubscribeRIBs subscribes c for all RIB updates of router rtr
-func (b *BMPServer) SubscribeRIBs(client routingtable.RouteTableClient, rtr net.IP, afi uint8) {
-	b.gloablMu.Lock()
-	defer b.gloablMu.Unlock()
-
-	rtrStr := rtr.String()
-	if _, ok := b.ribClients[rtrStr]; !ok {
-		b.ribClients[rtrStr] = make(map[afiClient]struct{})
-	}
-
-	ac := afiClient{
-		afi:    afi,
-		client: client,
-	}
-	if _, ok := b.ribClients[rtrStr][ac]; ok {
-		return
-	}
-
-	b.ribClients[rtrStr][ac] = struct{}{}
-
-	if _, ok := b.routers[rtrStr]; !ok {
-		return
-	}
-
-	b.routers[rtrStr].subscribeRIBs(client, afi)
-}
-
-// UnsubscribeRIBs unsubscribes client from RIBs of address family afi
-func (b *BMPServer) UnsubscribeRIBs(client routingtable.RouteTableClient, rtr net.IP, afi uint8) {
-	b.gloablMu.Lock()
-	defer b.gloablMu.Unlock()
-
-	rtrStr := rtr.String()
-	if _, ok := b.ribClients[rtrStr]; !ok {
-		return
-	}
-
-	ac := afiClient{
-		afi:    afi,
-		client: client,
-	}
-	if _, ok := b.ribClients[rtrStr][ac]; !ok {
-		return
-	}
 
-	delete(b.ribClients[rtrStr], ac)
-	b.routers[rtrStr].unsubscribeRIBs(client, afi)
+	b.metrics = &bmpMetricsService{b}
+	return b
 }
 
 // AddRouter adds a router to which we connect with BMP
-func (b *BMPServer) AddRouter(addr net.IP, port uint16, rib4 *locRIB.LocRIB, rib6 *locRIB.LocRIB) {
+func (b *BMPServer) AddRouter(addr net.IP, port uint16) {
 	b.gloablMu.Lock()
 	defer b.gloablMu.Unlock()
 
-	r := newRouter(addr, port, rib4, rib6)
-	b.routers[fmt.Sprintf("%s", r.address.String())] = r
+	r := newRouter(addr, port)
+	b.addRouter(r)
 
-	go func(r *router) {
+	go func(r *Router) {
 		for {
 			<-r.reconnectTimer.C
 			c, err := net.Dial("tcp", fmt.Sprintf("%s:%d", r.address.String(), r.port))
@@ -118,6 +75,10 @@ func (b *BMPServer) AddRouter(addr net.IP, port uint16, rib4 *locRIB.LocRIB, rib
 	}(r)
 }
 
+func (b *BMPServer) addRouter(r *Router) {
+	b.routers[fmt.Sprintf("%s", r.address.String())] = r
+}
+
 // RemoveRouter removes a BMP monitored router
 func (b *BMPServer) RemoveRouter(addr net.IP, port uint16) {
 	b.gloablMu.Lock()
@@ -129,6 +90,18 @@ func (b *BMPServer) RemoveRouter(addr net.IP, port uint16) {
 	delete(b.routers, id)
 }
 
+func (b *BMPServer) getRouters() []*Router {
+	b.routersMu.RLock()
+	defer b.routersMu.RUnlock()
+
+	ret := make([]*Router, 0, len(b.routers))
+	for r := range b.routers {
+		ret = append(ret, b.routers[r])
+	}
+
+	return ret
+}
+
 func recvBMPMsg(c net.Conn) (msg []byte, err error) {
 	buffer := make([]byte, defaultBufferLen)
 	_, err = io.ReadFull(c, buffer[0:bmppkt.MinLen])
@@ -151,3 +124,38 @@ func recvBMPMsg(c net.Conn) (msg []byte, err error) {
 
 	return buffer[0:toRead], nil
 }
+
+func (b *BMPServer) GetRouters() []*Router {
+	b.routersMu.RLock()
+	defer b.routersMu.RUnlock()
+
+	r := make([]*Router, 0, len(b.routers))
+	for name := range b.routers {
+		r = append(r, b.routers[name])
+	}
+
+	return r
+}
+
+func (b *BMPServer) GetRouter(name string) *Router {
+	b.routersMu.RLock()
+	defer b.routersMu.RUnlock()
+
+	for x := range b.routers {
+		if x != name {
+			continue
+		}
+
+		return b.routers[x]
+	}
+
+	return nil
+}
+
+func (b *BMPServer) Metrics() (*metrics.BMPMetrics, error) {
+	if b.metrics == nil {
+		return nil, fmt.Errorf("Server not started yet")
+	}
+
+	return b.metrics.metrics(), nil
+}
diff --git a/protocols/bgp/server/bmp_server_test.go b/protocols/bgp/server/bmp_server_test.go
index 8b32438627057ddeec26536f05d6f2331e3fa3f3..62c10ab7565e68d93472402da44ee62d6ef13f7e 100644
--- a/protocols/bgp/server/bmp_server_test.go
+++ b/protocols/bgp/server/bmp_server_test.go
@@ -4,291 +4,366 @@ import (
 	"net"
 	"testing"
 
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-	"github.com/stretchr/testify/assert"
+	bnet "github.com/bio-routing/bio-rd/net"
 )
 
-func TestNewServer(t *testing.T) {
-	s := NewServer()
-	assert.Equal(t, &BMPServer{
-		routers:    map[string]*router{},
-		ribClients: map[string]map[afiClient]struct{}{},
-	}, s)
-}
+func TestBMPServer(t *testing.T) {
+	srv := NewServer()
+
+	rtr := newRouter(net.IP{10, 0, 255, 1}, 30119)
+	_, pipe := net.Pipe()
+	rtr.con = pipe
+	srv.addRouter(rtr)
+
+	init := []byte{
+		3,           // Version
+		0, 0, 0, 22, // Length
+		4, // Msg Type (init)
 
-func TestSubscribeRIBs(t *testing.T) {
-	tests := []struct {
-		name     string
-		srv      *BMPServer
-		expected *BMPServer
-	}{
-		{
-			name: "Test without routers with clients",
-			srv: &BMPServer{
-				routers: make(map[string]*router),
-				ribClients: map[string]map[afiClient]struct{}{
-					"20.30.40.50": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-				},
-			},
-			expected: &BMPServer{
-				routers: make(map[string]*router),
-				ribClients: map[string]map[afiClient]struct{}{
-					"20.30.40.50": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-					"10.20.30.40": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-				},
-			},
-		},
-		{
-			name: "Test with routers no clients",
-			srv: &BMPServer{
-				routers: map[string]*router{
-					"10.20.30.40": {
-						ribClients: make(map[afiClient]struct{}),
-					},
-				},
-				ribClients: map[string]map[afiClient]struct{}{},
-			},
-			expected: &BMPServer{
-				routers: map[string]*router{
-					"10.20.30.40": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-				},
-				ribClients: map[string]map[afiClient]struct{}{
-					"10.20.30.40": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-				},
-			},
-		},
+		0, 1, // SysDescr TLV
+		0, 4, // Length
+		0x42, 0x42, 0x42, 0x42,
+
+		0, 2, // SysName TLV
+		0, 4, // Length
+		0x41, 0x41, 0x41, 0x41,
 	}
+	rtr.processMsg(init)
+
+	peerUpA := []byte{
+		3,            // Version
+		0, 0, 0, 126, // Length
+		3, // Msg Type (peer up)
 
-	for _, test := range tests {
-		test.srv.SubscribeRIBs(nil, net.IP{10, 20, 30, 40}, packet.IPv4AFI)
+		0,                        // Peer Type (global instance peer)
+		0,                        // Peer Flags
+		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 1, // Peer Address (10.1.1.1)
+		0, 0, 0, 200, // Peer AS = 200
+		0, 0, 0, 200, // Peer BGP ID = 200
+		0, 0, 0, 0, // Timestamp seconds
+		0, 0, 0, 0, // Timestamp microseconds
 
-		assert.Equalf(t, test.expected, test.srv, "Test %q", test.name)
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 2, // Local Address (10.1.1.2)
+		0, 222, // Local Port
+		0, 179, // Remote Port
 
+		// Sent OPEN
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+		0, 29, // Length
+		1,      // Type (OPEN)
+		4,      // BGP Version
+		0, 100, // ASN
+		0, 180, // Hold Time
+		1, 0, 0, 100, // BGP ID
+		0, // Ops Param Len
+
+		// Received OPEN
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+		0, 29, // Length
+		1,      // Type (OPEN)
+		4,      // BGP Version
+		0, 200, // ASN
+		0, 180, // Hold Time
+		1, 0, 0, 200, // BGP ID
+		0, // Ops Param Len
 	}
-}
+	rtr.processMsg(peerUpA)
+
+	if srv.GetRouter("NotExistent") != nil {
+		t.Errorf("GetRouter() returned a non-existent router")
+		return
+	}
+
+	aaaa := srv.GetRouter("10.0.255.1")
+	if aaaa == nil {
+		t.Errorf("Router AAAA not found")
+		return
+	}
+
+	aaaaVRFs := aaaa.GetVRFs()
+	if len(aaaaVRFs) != 1 {
+		t.Errorf("Unexpected VRF count for router AAAA: %d", len(aaaaVRFs))
+		return
+	}
+
+	peerUpB := []byte{
+		3,            // Version
+		0, 0, 0, 126, // Length
+		3, // Msg Type (peer up)
+
+		0,                        // Peer Type (global instance peer)
+		0,                        // Peer Flags
+		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1, // Peer Address (10.1.2.1)
+		0, 0, 0, 222, // Peer AS = 222
+		0, 0, 0, 222, // Peer BGP ID = 222
+		0, 0, 0, 0, // Timestamp seconds
+		0, 0, 0, 0, // Timestamp microseconds
+
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 2, // Local Address (10.1.2.2)
+		0, 222, // Local Port
+		0, 179, // Remote Port
+
+		// Sent OPEN
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+		0, 29, // Length
+		1,      // Type (OPEN)
+		4,      // BGP Version
+		0, 100, // ASN
+		0, 180, // Hold Time
+		1, 0, 0, 100, // BGP ID
+		0, // Ops Param Len
+
+		// Received OPEN
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+		0, 29, // Length
+		1,      // Type (OPEN)
+		4,      // BGP Version
+		0, 222, // ASN
+		0, 180, // Hold Time
+		1, 0, 0, 222, // BGP ID
+		0, // Ops Param Len
+	}
+	rtr.processMsg(peerUpB)
+
+	aaaaVRFs = aaaa.GetVRFs()
+	if len(aaaaVRFs) != 1 {
+		t.Errorf("Unexpected VRF count for router AAAA: %d", len(aaaaVRFs))
+		return
+	}
+
+	peerUpC := []byte{
+		3,            // Version
+		0, 0, 0, 126, // Length
+		3, // Msg Type (peer up)
+
+		0,                      // Peer Type (global instance peer)
+		0,                      // Peer Flags
+		0, 0, 0, 0, 0, 0, 0, 0, // Peer Distinguisher
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1, // Peer Address (10.1.3.1)
+		0, 0, 0, 233, // Peer AS = 233
+		0, 0, 0, 233, // Peer BGP ID = 233
+		0, 0, 0, 0, // Timestamp seconds
+		0, 0, 0, 0, // Timestamp microseconds
+
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 2, // Local Address (10.1.3.2)
+		0, 222, // Local Port
+		0, 179, // Remote Port
+
+		// Sent OPEN
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+		0, 29, // Length
+		1,      // Type (OPEN)
+		4,      // BGP Version
+		0, 100, // ASN
+		0, 180, // Hold Time
+		1, 0, 0, 100, // BGP ID
+		0, // Ops Param Len
+
+		// Received OPEN
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+		0, 29, // Length
+		1,      // Type (OPEN)
+		4,      // BGP Version
+		0, 233, // ASN
+		0, 180, // Hold Time
+		1, 0, 0, 222, // BGP ID
+		0, // Ops Param Len
+	}
+	rtr.processMsg(peerUpC)
+
+	aaaaVRFs = aaaa.GetVRFs()
+	if len(aaaaVRFs) != 2 {
+		t.Errorf("Unexpected VRF count for router AAAA: %d", len(aaaaVRFs))
+		return
+	}
+
+	peerDownC := []byte{
+		3,           // Version
+		0, 0, 0, 69, // Length
+		2, // Msg Type (peer down)
+
+		0,                      // Peer Type (global instance peer)
+		0,                      // Peer Flags
+		0, 0, 0, 0, 0, 0, 0, 0, // Peer Distinguisher
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1, // Peer Address (10.1.3.1)
+		0, 0, 0, 233, // Peer AS = 233
+		0, 0, 0, 233, // Peer BGP ID = 233
+		0, 0, 0, 0, // Timestamp seconds
+		0, 0, 0, 0, // Timestamp microseconds
+
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 2, // Local Address (10.1.3.2)
+		0, 222, // Local Port
+		0, 179, // Remote Port
+
+		4, // Reason = unexpected termination of transport session
+	}
+	rtr.processMsg(peerDownC)
+
+	if aaaa.neighborManager.getNeighbor(0, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1}) != nil {
+		t.Errorf("Unexpected neighbor")
+		return
+	}
+
+	if aaaa.neighborManager.getNeighbor(123, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1}) == nil {
+		t.Errorf("Expected neighbor not found")
+		return
+	}
+
+	v := aaaa.GetVRF(123)
+	lr := v.IPv4UnicastRIB()
+	if lr.Count() != 0 {
+		t.Errorf("Unexpected route count")
+		return
+	}
+
+	updateA1 := []byte{
+		3,           // Version
+		0, 0, 0, 93, // Length
+		0, // Msg Type (route monitoring)
+
+		0,                        // Peer Type (global instance peer)
+		0,                        // Peer Flags
+		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 1, // Peer Address (10.1.1.1)
+		0, 0, 0, 200, // Peer AS = 200
+		0, 0, 0, 200, // Peer BGP ID = 200
+		0, 0, 0, 0, // Timestamp seconds
+		0, 0, 0, 0, // Timestamp microseconds
+
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+		0, 45, // Length
+		2, // Type (UPDATE)
+
+		0, 0, // Withdraw length
+		0, 20, // Total Path Attribute Length
 
-func TestUnsubscribeRIBs(t *testing.T) {
-	tests := []struct {
-		name     string
-		srv      *BMPServer
-		expected *BMPServer
-	}{
-		{
-			name: "Unsubscribe existing from router",
-			srv: &BMPServer{
-				routers: map[string]*router{
-					"10.20.30.40": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-					"20.30.40.50": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-				},
-				ribClients: map[string]map[afiClient]struct{}{
-					"20.30.40.50": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-					"10.20.30.40": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-				},
-			},
-			expected: &BMPServer{
-				routers: map[string]*router{
-					"10.20.30.40": {
-						ribClients: map[afiClient]struct{}{},
-					},
-					"20.30.40.50": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-				},
-				ribClients: map[string]map[afiClient]struct{}{
-					"20.30.40.50": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-					"10.20.30.40": {},
-				},
-			},
-		},
-		{
-			name: "Unsubscribe existing from non-router",
-			srv: &BMPServer{
-				routers: map[string]*router{
-					"10.20.30.60": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-					"20.30.40.50": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-				},
-				ribClients: map[string]map[afiClient]struct{}{
-					"20.30.40.50": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-					"10.20.30.60": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-				},
-			},
-			expected: &BMPServer{
-				routers: map[string]*router{
-					"10.20.30.60": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-					"20.30.40.50": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-				},
-				ribClients: map[string]map[afiClient]struct{}{
-					"20.30.40.50": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-					"10.20.30.60": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-				},
-			},
-		},
-		{
-			name: "Unsubscribe existing from non-existing client",
-			srv: &BMPServer{
-				routers: map[string]*router{
-					"10.20.30.40": {
-						ribClients: map[afiClient]struct{}{},
-					},
-					"20.30.40.50": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-				},
-				ribClients: map[string]map[afiClient]struct{}{
-					"20.30.40.40": {},
-					"10.20.30.60": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-				},
-			},
-			expected: &BMPServer{
-				routers: map[string]*router{
-					"10.20.30.40": {
-						ribClients: map[afiClient]struct{}{},
-					},
-					"20.30.40.50": {
-						ribClients: map[afiClient]struct{}{
-							{
-								afi:    packet.IPv4AFI,
-								client: nil,
-							}: {},
-						},
-					},
-				},
-				ribClients: map[string]map[afiClient]struct{}{
-					"20.30.40.40": {},
-					"10.20.30.60": {
-						{
-							afi:    packet.IPv4AFI,
-							client: nil,
-						}: {},
-					},
-				},
-			},
-		},
+		255,  // Attribute flags
+		1,    // Attribute Type code (ORIGIN)
+		0, 1, // Length
+		2, // INCOMPLETE
+
+		0,      // Attribute flags
+		2,      // Attribute Type code (AS Path)
+		12,     // Length
+		2,      // Type = AS_SEQUENCE
+		2,      // Path Segment Length
+		59, 65, // AS15169
+		12, 248, // AS3320
+		1,      // Type = AS_SET
+		2,      // Path Segment Length
+		59, 65, // AS15169
+		12, 248, // AS3320
+
+		8, 10, // 10.0.0.0/8
 	}
+	rtr.processMsg(updateA1)
 
-	for _, test := range tests {
-		test.srv.UnsubscribeRIBs(nil, net.IP{10, 20, 30, 40}, packet.IPv4AFI)
+	if lr.Count() != 1 {
+		t.Errorf("Unexpected route count")
+		return
+	}
+
+	route := lr.Get(bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8))
+	if route == nil {
+		t.Errorf("Expected route not found")
+		return
+	}
 
-		assert.Equalf(t, test.expected, test.srv, "Test %q", test.name)
+	peerDownA := []byte{
+		3,           // Version
+		0, 0, 0, 69, // Length
+		2, // Msg Type (peer down)
+
+		0,                        // Peer Type (global instance peer)
+		0,                        // Peer Flags
+		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 1, // Peer Address (10.1.1.1)
+		0, 0, 0, 200, // Peer AS = 200
+		0, 0, 0, 200, // Peer BGP ID = 200
+		0, 0, 0, 0, // Timestamp seconds
+		0, 0, 0, 0, // Timestamp microseconds
+
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 2, // Local Address (10.1.1.2)
+		0, 222, // Local Port
+		0, 179, // Remote Port
+
+		4, // Reason = unexpected termination of transport session
+	}
+	rtr.processMsg(peerDownA)
+
+	if lr.Count() != 0 {
+		t.Errorf("Unexpected route count")
+		return
+	}
+
+	route = lr.Get(bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8))
+	if route != nil {
+		t.Errorf("Unexpected route found")
+		return
+	}
+
+	updateB1 := []byte{
+		3,           // Version
+		0, 0, 0, 93, // Length
+		0, // Msg Type (route monitoring)
+
+		0,                        // Peer Type (global instance peer)
+		0,                        // Peer Flags
+		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1, // Peer Address (10.1.2.1)
+		0, 0, 0, 222, // Peer AS = 222
+		0, 0, 0, 222, // Peer BGP ID = 222
+		0, 0, 0, 0, // Timestamp seconds
+		0, 0, 0, 0, // Timestamp microseconds
+
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+		0, 45, // Length
+		2, // Type (UPDATE)
+
+		0, 0, // Withdraw length
+		0, 20, // Total Path Attribute Length
+
+		255,  // Attribute flags
+		1,    // Attribute Type code (ORIGIN)
+		0, 1, // Length
+		2, // INCOMPLETE
+
+		0,      // Attribute flags
+		2,      // Attribute Type code (AS Path)
+		12,     // Length
+		2,      // Type = AS_SEQUENCE
+		2,      // Path Segment Length
+		59, 65, // AS15169
+		12, 248, // AS3320
+		1,      // Type = AS_SET
+		2,      // Path Segment Length
+		59, 65, // AS15169
+		12, 248, // AS3320
+
+		8, 10, // 10.0.0.0/8
+	}
+	rtr.processMsg(updateB1)
+
+	if lr.Count() != 1 {
+		t.Errorf("Unexpected route count")
+		return
+	}
+
+	termination := []byte{
+		3,           // Version
+		0, 0, 0, 11, // Length
+		5, // Msg Type (termination)
+
+		0, 1, // Type = Reason
+		0, 1, // Length
+		0, // Reason = Admin Down
+	}
+	rtr.processMsg(termination)
 
+	if lr.Count() != 0 {
+		t.Errorf("Unexpected route count")
+		return
 	}
 }
diff --git a/protocols/bgp/server/metrics_service.go b/protocols/bgp/server/metrics_service.go
index b2b457e2f6c16d600d374ccf08f9031b920edbc6..3e5e7270574ee585313e96ef85b348088ee59139 100644
--- a/protocols/bgp/server/metrics_service.go
+++ b/protocols/bgp/server/metrics_service.go
@@ -18,14 +18,14 @@ func (b *metricsService) peerMetrics() []*metrics.BGPPeerMetrics {
 	peers := make([]*metrics.BGPPeerMetrics, 0)
 
 	for _, peer := range b.server.peers.list() {
-		m := b.metricsForPeer(peer)
+		m := metricsForPeer(peer)
 		peers = append(peers, m)
 	}
 
 	return peers
 }
 
-func (b *metricsService) metricsForPeer(peer *peer) *metrics.BGPPeerMetrics {
+func metricsForPeer(peer *peer) *metrics.BGPPeerMetrics {
 	m := &metrics.BGPPeerMetrics{
 		ASN:             peer.peerASN,
 		LocalASN:        peer.localASN,
@@ -40,7 +40,7 @@ func (b *metricsService) metricsForPeer(peer *peer) *metrics.BGPPeerMetrics {
 	}
 
 	fsm := fsms[0]
-	m.State = b.statusFromFSM(fsm)
+	m.State = statusFromFSM(fsm)
 	m.Up = m.State == metrics.StateEstablished
 
 	if m.Up {
@@ -51,26 +51,31 @@ func (b *metricsService) metricsForPeer(peer *peer) *metrics.BGPPeerMetrics {
 	m.UpdatesSent = fsm.counters.updatesSent
 
 	if peer.ipv4 != nil {
-		m.AddressFamilies = append(m.AddressFamilies, b.metricsForFamily(fsm.ipv4Unicast))
+		m.AddressFamilies = append(m.AddressFamilies, metricsForFamily(fsm.ipv4Unicast))
 	}
 
 	if peer.ipv6 != nil {
-		m.AddressFamilies = append(m.AddressFamilies, b.metricsForFamily(fsm.ipv6Unicast))
+		m.AddressFamilies = append(m.AddressFamilies, metricsForFamily(fsm.ipv6Unicast))
 	}
 
 	return m
 }
 
-func (b *metricsService) metricsForFamily(family *fsmAddressFamily) *metrics.BGPAddressFamilyMetrics {
-	return &metrics.BGPAddressFamilyMetrics{
+func metricsForFamily(family *fsmAddressFamily) *metrics.BGPAddressFamilyMetrics {
+	m := &metrics.BGPAddressFamilyMetrics{
 		AFI:            family.afi,
 		SAFI:           family.safi,
 		RoutesReceived: uint64(family.adjRIBIn.RouteCount()),
-		RoutesSent:     uint64(family.adjRIBOut.RouteCount()),
 	}
+
+	if family.adjRIBOut != nil {
+		m.RoutesSent = uint64(family.adjRIBOut.RouteCount())
+	}
+
+	return m
 }
 
-func (b *metricsService) statusFromFSM(fsm *FSM) uint8 {
+func statusFromFSM(fsm *FSM) uint8 {
 	switch fsm.state.(type) {
 	case *idleState:
 		return metrics.StateIdle
diff --git a/protocols/bgp/server/metrics_service_test.go b/protocols/bgp/server/metrics_service_test.go
index 486f7bff27bf5766390ce67d764c232ad9830b8c..65af6847bce45aac6729861578a88cc813001e16 100644
--- a/protocols/bgp/server/metrics_service_test.go
+++ b/protocols/bgp/server/metrics_service_test.go
@@ -14,7 +14,7 @@ import (
 )
 
 func TestMetrics(t *testing.T) {
-	vrf, _ := vrf.New("inet.0")
+	vrf, _ := vrf.New("inet.0", 0)
 	establishedTime := time.Now()
 
 	tests := []struct {
diff --git a/protocols/bgp/types/as_path.go b/protocols/bgp/types/as_path.go
index 7d77b4de9bbe0cb7056292ae4574164fd19b64f7..460d36421bda31b463c38503511829a0812928e4 100644
--- a/protocols/bgp/types/as_path.go
+++ b/protocols/bgp/types/as_path.go
@@ -32,19 +32,43 @@ func (pa ASPath) ToProto() []*api.ASPathSegment {
 	ret := make([]*api.ASPathSegment, len(pa))
 	for i := range pa {
 		ret[i] = &api.ASPathSegment{
-			ASNs: make([]uint32, len(pa[i].ASNs)),
+			Asns: make([]uint32, len(pa[i].ASNs)),
 		}
 
 		if pa[i].Type == ASSequence {
-			ret[i].ASSequence = true
+			ret[i].AsSequence = true
 		}
 
-		copy(ret[i].ASNs, pa[i].ASNs)
+		copy(ret[i].Asns, pa[i].ASNs)
 	}
 
 	return ret
 }
 
+// ASPathFromProtoASPath converts an proto ASPath to ASPath
+func ASPathFromProtoASPath(segments []*api.ASPathSegment) ASPath {
+	asPath := make(ASPath, len(segments))
+
+	for i := range segments {
+		s := ASPathSegment{
+			Type: ASSet,
+			ASNs: make([]uint32, len(segments[i].Asns)),
+		}
+
+		if segments[i].AsSequence {
+			s.Type = ASSequence
+		}
+
+		for j := range segments[i].Asns {
+			s.ASNs[j] = segments[i].Asns[j]
+		}
+
+		asPath[i] = s
+	}
+
+	return asPath
+}
+
 // String converts an ASPath to it's human redable representation
 func (pa ASPath) String() (ret string) {
 	for _, p := range pa {
diff --git a/protocols/bgp/types/large_community.go b/protocols/bgp/types/large_community.go
index eaba4aea77807705ad04c71a74a81e6174ba2b9a..a2031b820658b6990bb409d1a7ab4a0d06a71387 100644
--- a/protocols/bgp/types/large_community.go
+++ b/protocols/bgp/types/large_community.go
@@ -24,6 +24,15 @@ func (c *LargeCommunity) ToProto() *api.LargeCommunity {
 	}
 }
 
+// LargeCommunityFromProtoCommunity converts a proto LargeCommunity to LargeCommunity
+func LargeCommunityFromProtoCommunity(alc *api.LargeCommunity) LargeCommunity {
+	return LargeCommunity{
+		GlobalAdministrator: alc.GlobalAdministrator,
+		DataPart1:           alc.DataPart1,
+		DataPart2:           alc.DataPart2,
+	}
+}
+
 // String transitions a large community to it's human readable representation
 func (c *LargeCommunity) String() string {
 	return fmt.Sprintf("(%d,%d,%d)", c.GlobalAdministrator, c.DataPart1, c.DataPart2)
diff --git a/protocols/bgp/types/large_community_test.go b/protocols/bgp/types/large_community_test.go
index f3ff70222bf794e6edbc31fce9b811f02e40c582..d1f7bc2b16d9d4d00559318a4c4ae1428ffb6668 100644
--- a/protocols/bgp/types/large_community_test.go
+++ b/protocols/bgp/types/large_community_test.go
@@ -9,9 +9,27 @@ import (
 
 	"strconv"
 
+	"github.com/bio-routing/bio-rd/route/api"
 	"github.com/stretchr/testify/assert"
 )
 
+func TestLargeCommunityFromProtoCommunity(t *testing.T) {
+	input := &api.LargeCommunity{
+		GlobalAdministrator: 1,
+		DataPart1:           100,
+		DataPart2:           200,
+	}
+
+	expected := LargeCommunity{
+		GlobalAdministrator: 1,
+		DataPart1:           100,
+		DataPart2:           200,
+	}
+
+	result := LargeCommunityFromProtoCommunity(input)
+	assert.Equal(t, expected, result)
+}
+
 func TestParseLargeCommunityString(t *testing.T) {
 	tests := []struct {
 		name     string
diff --git a/protocols/bgp/types/unknown_attribute.go b/protocols/bgp/types/unknown_attribute.go
index dcb52354461b11591372b9ad1144dc913a533188..69e95accd766a13cc051c70869178967c12985a2 100644
--- a/protocols/bgp/types/unknown_attribute.go
+++ b/protocols/bgp/types/unknown_attribute.go
@@ -35,3 +35,14 @@ func (u *UnknownPathAttribute) ToProto() *api.UnknownPathAttribute {
 	copy(a.Value, u.Value)
 	return a
 }
+
+// UnknownPathAttributeFromProtoUnknownPathAttribute convers an proto UnknownPathAttribute to UnknownPathAttribute
+func UnknownPathAttributeFromProtoUnknownPathAttribute(x *api.UnknownPathAttribute) UnknownPathAttribute {
+	return UnknownPathAttribute{
+		Optional:   x.Optional,
+		Transitive: x.Transitive,
+		Partial:    x.Partial,
+		TypeCode:   uint8(x.TypeCode),
+		Value:      x.Value,
+	}
+}
diff --git a/protocols/bmp/server/router.go b/protocols/bmp/server/router.go
index 3e54e25c6c750933a07e2a1899c4e57e2405104b..27906afa8312bedef61f741819dc0440ad945eba 100644
--- a/protocols/bmp/server/router.go
+++ b/protocols/bmp/server/router.go
@@ -1,12 +1,12 @@
 package server
 
 import (
-	"fmt"
 	"net"
 	"time"
 
 	"github.com/bio-routing/bio-rd/protocols/bmp/packet"
 	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+	"github.com/bio-routing/bio-rd/routingtable/vrf"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -20,6 +20,8 @@ type router struct {
 	reconnectTimer   *time.Timer
 	rib4             *locRIB.LocRIB
 	rib6             *locRIB.LocRIB
+	vrfs             []*vrf.VRF
+	msgCounter       uint64
 }
 
 func (r *router) serve() {
@@ -29,6 +31,7 @@ func (r *router) serve() {
 			log.Errorf("Unable to get message: %v", err)
 			return
 		}
+		r.msgCounter++
 
 		bmpMsg, err := packet.Decode(msg)
 		if err != nil {
@@ -36,8 +39,24 @@ func (r *router) serve() {
 			return
 		}
 
-		// TODO: Finish implementation
-		fmt.Printf("%v\n", bmpMsg)
+		if r.msgCounter == 1 {
+			if bmpMsg.MsgType() != packet.InitiationMessageType {
+				log.Errorf("Invalid message type of first message (%d) for neighbor %s", bmpMsg.MsgType(), r.address.String())
+				return
+			}
+		}
+
+		switch bmpMsg.MsgType() {
+		case packet.TerminationMessageType:
+			log.Infof("BMP neighbor %s has sent a termination message", r.address.String())
+			return
+		case packet.RouteMonitoringType:
+			r.processRouteMonitoringMsg(bmpMsg.(*packet.RouteMonitoringMsg))
+			return
+		}
 	}
+}
+
+func (r *router) processRouteMonitoringMsg(m *packet.RouteMonitoringMsg) {
 
 }
diff --git a/protocols/bmp/server/server.go b/protocols/bmp/server/server.go
index 0a7a5bf9883ecd7ab9906c028f6cbaf47c26b88d..413598b706d6e7abfe0f70877a4757256fb407bd 100644
--- a/protocols/bmp/server/server.go
+++ b/protocols/bmp/server/server.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+	"github.com/bio-routing/bio-rd/routingtable/vrf"
 	"github.com/bio-routing/tflow2/convert"
 	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
@@ -18,18 +19,21 @@ const (
 	defaultBufferLen = 4096
 )
 
+// BMPServer represents a BMP speaker
 type BMPServer struct {
 	routers       map[string]*router
 	routersMu     sync.RWMutex
 	reconnectTime uint
 }
 
+// NewServer creates a new BMP server
 func NewServer() *BMPServer {
 	return &BMPServer{
 		routers: make(map[string]*router),
 	}
 }
 
+// AddRouter adds a BMP session to a router to the BMP server
 func (b *BMPServer) AddRouter(addr net.IP, port uint16, rib4 *locRIB.LocRIB, rib6 *locRIB.LocRIB) {
 	r := &router{
 		address:          addr,
@@ -39,6 +43,7 @@ func (b *BMPServer) AddRouter(addr net.IP, port uint16, rib4 *locRIB.LocRIB, rib
 		reconnectTimer:   time.NewTimer(time.Duration(0)),
 		rib4:             rib4,
 		rib6:             rib6,
+		vrfs:             make([]*vrf.VRF, 0),
 	}
 
 	b.routersMu.Lock()
@@ -63,6 +68,7 @@ func (b *BMPServer) AddRouter(addr net.IP, port uint16, rib4 *locRIB.LocRIB, rib
 			r.reconnectTime = 0
 			r.con = c
 			r.serve()
+			r.con.Close()
 		}
 	}(r)
 }
diff --git a/regenerate_proto.sh b/regenerate_proto.sh
index bb4e875f1f16b29b2a0c868ac927a80f944be102..451c45e40c013ed13885a0b4358616aad41bc638 100755
--- a/regenerate_proto.sh
+++ b/regenerate_proto.sh
@@ -6,5 +6,6 @@ echo "Generating protobuf code"
 protoc --go_out=plugins=grpc:. github.com/bio-routing/bio-rd/net/api/*.proto
 protoc --go_out=plugins=grpc:. github.com/bio-routing/bio-rd/route/api/*.proto
 protoc --go_out=plugins=grpc:. github.com/bio-routing/bio-rd/protocols/bgp/api/*.proto
+protoc --go_out=plugins=grpc:. github.com/bio-routing/bio-rd/cmd/ris/api/*.proto
 echo "Switching back to working directory"
 cd $dir
diff --git a/route/api/route.pb.go b/route/api/route.pb.go
index 60041f35a80635b1169424329d31446966c07536..6d49bf74efdbfd5eee4d3b766a4d26bab5f4a2c2 100644
--- a/route/api/route.pb.go
+++ b/route/api/route.pb.go
@@ -1,27 +1,14 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // source: github.com/bio-routing/bio-rd/route/api/route.proto
 
-/*
-Package api is a generated protocol buffer package.
-
-It is generated from these files:
-	github.com/bio-routing/bio-rd/route/api/route.proto
-
-It has these top-level messages:
-	Route
-	Path
-	StaticPath
-	BGPPath
-	ASPathSegment
-	LargeCommunity
-	UnknownPathAttribute
-*/
 package api
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
-import bio_net "github.com/bio-routing/bio-rd/net/api"
+import (
+	fmt "fmt"
+	api "github.com/bio-routing/bio-rd/net/api"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
@@ -32,7 +19,7 @@ var _ = math.Inf
 // is compatible with the proto package it is being compiled against.
 // A compilation error at this line likely means your copy of the
 // proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 
 type Path_Type int32
 
@@ -45,6 +32,7 @@ var Path_Type_name = map[int32]string{
 	0: "Static",
 	1: "BGP",
 }
+
 var Path_Type_value = map[string]int32{
 	"Static": 0,
 	"BGP":    1,
@@ -53,19 +41,45 @@ var Path_Type_value = map[string]int32{
 func (x Path_Type) String() string {
 	return proto.EnumName(Path_Type_name, int32(x))
 }
-func (Path_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
+
+func (Path_Type) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_00363871266b6b0e, []int{1, 0}
+}
 
 type Route struct {
-	Pfx   *bio_net.Prefix `protobuf:"bytes,1,opt,name=pfx" json:"pfx,omitempty"`
-	Paths []*Path         `protobuf:"bytes,2,rep,name=paths" json:"paths,omitempty"`
+	Pfx                  *api.Prefix `protobuf:"bytes,1,opt,name=pfx,proto3" json:"pfx,omitempty"`
+	Paths                []*Path     `protobuf:"bytes,2,rep,name=paths,proto3" json:"paths,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *Route) Reset()         { *m = Route{} }
+func (m *Route) String() string { return proto.CompactTextString(m) }
+func (*Route) ProtoMessage()    {}
+func (*Route) Descriptor() ([]byte, []int) {
+	return fileDescriptor_00363871266b6b0e, []int{0}
+}
+
+func (m *Route) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Route.Unmarshal(m, b)
+}
+func (m *Route) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Route.Marshal(b, m, deterministic)
+}
+func (m *Route) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Route.Merge(m, src)
+}
+func (m *Route) XXX_Size() int {
+	return xxx_messageInfo_Route.Size(m)
+}
+func (m *Route) XXX_DiscardUnknown() {
+	xxx_messageInfo_Route.DiscardUnknown(m)
 }
 
-func (m *Route) Reset()                    { *m = Route{} }
-func (m *Route) String() string            { return proto.CompactTextString(m) }
-func (*Route) ProtoMessage()               {}
-func (*Route) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+var xxx_messageInfo_Route proto.InternalMessageInfo
 
-func (m *Route) GetPfx() *bio_net.Prefix {
+func (m *Route) GetPfx() *api.Prefix {
 	if m != nil {
 		return m.Pfx
 	}
@@ -80,15 +94,38 @@ func (m *Route) GetPaths() []*Path {
 }
 
 type Path struct {
-	Type       Path_Type   `protobuf:"varint,1,opt,name=type,enum=bio.route.Path_Type" json:"type,omitempty"`
-	StaticPath *StaticPath `protobuf:"bytes,2,opt,name=static_path,json=staticPath" json:"static_path,omitempty"`
-	BGPPath    *BGPPath    `protobuf:"bytes,3,opt,name=BGP_path,json=BGPPath" json:"BGP_path,omitempty"`
+	Type                 Path_Type   `protobuf:"varint,1,opt,name=type,proto3,enum=bio.route.Path_Type" json:"type,omitempty"`
+	StaticPath           *StaticPath `protobuf:"bytes,2,opt,name=static_path,json=staticPath,proto3" json:"static_path,omitempty"`
+	BgpPath              *BGPPath    `protobuf:"bytes,3,opt,name=bgp_path,json=bgpPath,proto3" json:"bgp_path,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *Path) Reset()         { *m = Path{} }
+func (m *Path) String() string { return proto.CompactTextString(m) }
+func (*Path) ProtoMessage()    {}
+func (*Path) Descriptor() ([]byte, []int) {
+	return fileDescriptor_00363871266b6b0e, []int{1}
+}
+
+func (m *Path) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Path.Unmarshal(m, b)
+}
+func (m *Path) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Path.Marshal(b, m, deterministic)
+}
+func (m *Path) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Path.Merge(m, src)
+}
+func (m *Path) XXX_Size() int {
+	return xxx_messageInfo_Path.Size(m)
+}
+func (m *Path) XXX_DiscardUnknown() {
+	xxx_messageInfo_Path.DiscardUnknown(m)
 }
 
-func (m *Path) Reset()                    { *m = Path{} }
-func (m *Path) String() string            { return proto.CompactTextString(m) }
-func (*Path) ProtoMessage()               {}
-func (*Path) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+var xxx_messageInfo_Path proto.InternalMessageInfo
 
 func (m *Path) GetType() Path_Type {
 	if m != nil {
@@ -104,23 +141,46 @@ func (m *Path) GetStaticPath() *StaticPath {
 	return nil
 }
 
-func (m *Path) GetBGPPath() *BGPPath {
+func (m *Path) GetBgpPath() *BGPPath {
 	if m != nil {
-		return m.BGPPath
+		return m.BgpPath
 	}
 	return nil
 }
 
 type StaticPath struct {
-	NextHop *bio_net.IP `protobuf:"bytes,1,opt,name=next_hop,json=nextHop" json:"next_hop,omitempty"`
+	NextHop              *api.IP  `protobuf:"bytes,1,opt,name=next_hop,json=nextHop,proto3" json:"next_hop,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *StaticPath) Reset()         { *m = StaticPath{} }
+func (m *StaticPath) String() string { return proto.CompactTextString(m) }
+func (*StaticPath) ProtoMessage()    {}
+func (*StaticPath) Descriptor() ([]byte, []int) {
+	return fileDescriptor_00363871266b6b0e, []int{2}
+}
+
+func (m *StaticPath) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_StaticPath.Unmarshal(m, b)
+}
+func (m *StaticPath) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_StaticPath.Marshal(b, m, deterministic)
+}
+func (m *StaticPath) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_StaticPath.Merge(m, src)
+}
+func (m *StaticPath) XXX_Size() int {
+	return xxx_messageInfo_StaticPath.Size(m)
+}
+func (m *StaticPath) XXX_DiscardUnknown() {
+	xxx_messageInfo_StaticPath.DiscardUnknown(m)
 }
 
-func (m *StaticPath) Reset()                    { *m = StaticPath{} }
-func (m *StaticPath) String() string            { return proto.CompactTextString(m) }
-func (*StaticPath) ProtoMessage()               {}
-func (*StaticPath) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+var xxx_messageInfo_StaticPath proto.InternalMessageInfo
 
-func (m *StaticPath) GetNextHop() *bio_net.IP {
+func (m *StaticPath) GetNextHop() *api.IP {
 	if m != nil {
 		return m.NextHop
 	}
@@ -128,26 +188,49 @@ func (m *StaticPath) GetNextHop() *bio_net.IP {
 }
 
 type BGPPath struct {
-	PathIdentifier    uint32                  `protobuf:"varint,1,opt,name=path_identifier,json=pathIdentifier" json:"path_identifier,omitempty"`
-	NextHop           *bio_net.IP             `protobuf:"bytes,2,opt,name=next_hop,json=nextHop" json:"next_hop,omitempty"`
-	LocalPref         uint32                  `protobuf:"varint,3,opt,name=local_pref,json=localPref" json:"local_pref,omitempty"`
-	ASPath            []*ASPathSegment        `protobuf:"bytes,4,rep,name=AS_path,json=ASPath" json:"AS_path,omitempty"`
-	Origin            uint32                  `protobuf:"varint,5,opt,name=origin" json:"origin,omitempty"`
-	MED               uint32                  `protobuf:"varint,6,opt,name=MED" json:"MED,omitempty"`
-	EBGP              bool                    `protobuf:"varint,7,opt,name=EBGP" json:"EBGP,omitempty"`
-	BGPIdentifier     uint32                  `protobuf:"varint,8,opt,name=BGP_identifier,json=BGPIdentifier" json:"BGP_identifier,omitempty"`
-	Source            *bio_net.IP             `protobuf:"bytes,9,opt,name=source" json:"source,omitempty"`
-	Communities       []uint32                `protobuf:"varint,10,rep,packed,name=communities" json:"communities,omitempty"`
-	LargeCommunities  []*LargeCommunity       `protobuf:"bytes,11,rep,name=large_communities,json=largeCommunities" json:"large_communities,omitempty"`
-	OriginatorId      uint32                  `protobuf:"varint,12,opt,name=originator_id,json=originatorId" json:"originator_id,omitempty"`
-	ClusterList       []uint32                `protobuf:"varint,13,rep,packed,name=cluster_list,json=clusterList" json:"cluster_list,omitempty"`
-	UnknownAttributes []*UnknownPathAttribute `protobuf:"bytes,14,rep,name=unknown_attributes,json=unknownAttributes" json:"unknown_attributes,omitempty"`
-}
-
-func (m *BGPPath) Reset()                    { *m = BGPPath{} }
-func (m *BGPPath) String() string            { return proto.CompactTextString(m) }
-func (*BGPPath) ProtoMessage()               {}
-func (*BGPPath) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
+	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"`
+	XXX_NoUnkeyedLiteral struct{}                `json:"-"`
+	XXX_unrecognized     []byte                  `json:"-"`
+	XXX_sizecache        int32                   `json:"-"`
+}
+
+func (m *BGPPath) Reset()         { *m = BGPPath{} }
+func (m *BGPPath) String() string { return proto.CompactTextString(m) }
+func (*BGPPath) ProtoMessage()    {}
+func (*BGPPath) Descriptor() ([]byte, []int) {
+	return fileDescriptor_00363871266b6b0e, []int{3}
+}
+
+func (m *BGPPath) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_BGPPath.Unmarshal(m, b)
+}
+func (m *BGPPath) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_BGPPath.Marshal(b, m, deterministic)
+}
+func (m *BGPPath) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_BGPPath.Merge(m, src)
+}
+func (m *BGPPath) XXX_Size() int {
+	return xxx_messageInfo_BGPPath.Size(m)
+}
+func (m *BGPPath) XXX_DiscardUnknown() {
+	xxx_messageInfo_BGPPath.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BGPPath proto.InternalMessageInfo
 
 func (m *BGPPath) GetPathIdentifier() uint32 {
 	if m != nil {
@@ -156,7 +239,7 @@ func (m *BGPPath) GetPathIdentifier() uint32 {
 	return 0
 }
 
-func (m *BGPPath) GetNextHop() *bio_net.IP {
+func (m *BGPPath) GetNextHop() *api.IP {
 	if m != nil {
 		return m.NextHop
 	}
@@ -170,9 +253,9 @@ func (m *BGPPath) GetLocalPref() uint32 {
 	return 0
 }
 
-func (m *BGPPath) GetASPath() []*ASPathSegment {
+func (m *BGPPath) GetAsPath() []*ASPathSegment {
 	if m != nil {
-		return m.ASPath
+		return m.AsPath
 	}
 	return nil
 }
@@ -184,28 +267,28 @@ func (m *BGPPath) GetOrigin() uint32 {
 	return 0
 }
 
-func (m *BGPPath) GetMED() uint32 {
+func (m *BGPPath) GetMed() uint32 {
 	if m != nil {
-		return m.MED
+		return m.Med
 	}
 	return 0
 }
 
-func (m *BGPPath) GetEBGP() bool {
+func (m *BGPPath) GetEbgp() bool {
 	if m != nil {
-		return m.EBGP
+		return m.Ebgp
 	}
 	return false
 }
 
-func (m *BGPPath) GetBGPIdentifier() uint32 {
+func (m *BGPPath) GetBgpIdentifier() uint32 {
 	if m != nil {
-		return m.BGPIdentifier
+		return m.BgpIdentifier
 	}
 	return 0
 }
 
-func (m *BGPPath) GetSource() *bio_net.IP {
+func (m *BGPPath) GetSource() *api.IP {
 	if m != nil {
 		return m.Source
 	}
@@ -248,39 +331,85 @@ func (m *BGPPath) GetUnknownAttributes() []*UnknownPathAttribute {
 }
 
 type ASPathSegment struct {
-	ASSequence bool     `protobuf:"varint,1,opt,name=AS_sequence,json=ASSequence" json:"AS_sequence,omitempty"`
-	ASNs       []uint32 `protobuf:"varint,2,rep,packed,name=ASNs" json:"ASNs,omitempty"`
+	AsSequence           bool     `protobuf:"varint,1,opt,name=as_sequence,json=asSequence,proto3" json:"as_sequence,omitempty"`
+	Asns                 []uint32 `protobuf:"varint,2,rep,packed,name=asns,proto3" json:"asns,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ASPathSegment) Reset()         { *m = ASPathSegment{} }
+func (m *ASPathSegment) String() string { return proto.CompactTextString(m) }
+func (*ASPathSegment) ProtoMessage()    {}
+func (*ASPathSegment) Descriptor() ([]byte, []int) {
+	return fileDescriptor_00363871266b6b0e, []int{4}
 }
 
-func (m *ASPathSegment) Reset()                    { *m = ASPathSegment{} }
-func (m *ASPathSegment) String() string            { return proto.CompactTextString(m) }
-func (*ASPathSegment) ProtoMessage()               {}
-func (*ASPathSegment) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
+func (m *ASPathSegment) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ASPathSegment.Unmarshal(m, b)
+}
+func (m *ASPathSegment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ASPathSegment.Marshal(b, m, deterministic)
+}
+func (m *ASPathSegment) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ASPathSegment.Merge(m, src)
+}
+func (m *ASPathSegment) XXX_Size() int {
+	return xxx_messageInfo_ASPathSegment.Size(m)
+}
+func (m *ASPathSegment) XXX_DiscardUnknown() {
+	xxx_messageInfo_ASPathSegment.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ASPathSegment proto.InternalMessageInfo
 
-func (m *ASPathSegment) GetASSequence() bool {
+func (m *ASPathSegment) GetAsSequence() bool {
 	if m != nil {
-		return m.ASSequence
+		return m.AsSequence
 	}
 	return false
 }
 
-func (m *ASPathSegment) GetASNs() []uint32 {
+func (m *ASPathSegment) GetAsns() []uint32 {
 	if m != nil {
-		return m.ASNs
+		return m.Asns
 	}
 	return nil
 }
 
 type LargeCommunity struct {
-	GlobalAdministrator uint32 `protobuf:"varint,1,opt,name=global_administrator,json=globalAdministrator" json:"global_administrator,omitempty"`
-	DataPart1           uint32 `protobuf:"varint,2,opt,name=data_part1,json=dataPart1" json:"data_part1,omitempty"`
-	DataPart2           uint32 `protobuf:"varint,3,opt,name=data_part2,json=dataPart2" json:"data_part2,omitempty"`
+	GlobalAdministrator  uint32   `protobuf:"varint,1,opt,name=global_administrator,json=globalAdministrator,proto3" json:"global_administrator,omitempty"`
+	DataPart1            uint32   `protobuf:"varint,2,opt,name=data_part1,json=dataPart1,proto3" json:"data_part1,omitempty"`
+	DataPart2            uint32   `protobuf:"varint,3,opt,name=data_part2,json=dataPart2,proto3" json:"data_part2,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *LargeCommunity) Reset()         { *m = LargeCommunity{} }
+func (m *LargeCommunity) String() string { return proto.CompactTextString(m) }
+func (*LargeCommunity) ProtoMessage()    {}
+func (*LargeCommunity) Descriptor() ([]byte, []int) {
+	return fileDescriptor_00363871266b6b0e, []int{5}
 }
 
-func (m *LargeCommunity) Reset()                    { *m = LargeCommunity{} }
-func (m *LargeCommunity) String() string            { return proto.CompactTextString(m) }
-func (*LargeCommunity) ProtoMessage()               {}
-func (*LargeCommunity) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
+func (m *LargeCommunity) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_LargeCommunity.Unmarshal(m, b)
+}
+func (m *LargeCommunity) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_LargeCommunity.Marshal(b, m, deterministic)
+}
+func (m *LargeCommunity) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LargeCommunity.Merge(m, src)
+}
+func (m *LargeCommunity) XXX_Size() int {
+	return xxx_messageInfo_LargeCommunity.Size(m)
+}
+func (m *LargeCommunity) XXX_DiscardUnknown() {
+	xxx_messageInfo_LargeCommunity.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LargeCommunity proto.InternalMessageInfo
 
 func (m *LargeCommunity) GetGlobalAdministrator() uint32 {
 	if m != nil {
@@ -304,17 +433,40 @@ func (m *LargeCommunity) GetDataPart2() uint32 {
 }
 
 type UnknownPathAttribute struct {
-	Optional   bool   `protobuf:"varint,1,opt,name=optional" json:"optional,omitempty"`
-	Transitive bool   `protobuf:"varint,2,opt,name=transitive" json:"transitive,omitempty"`
-	Partial    bool   `protobuf:"varint,3,opt,name=partial" json:"partial,omitempty"`
-	TypeCode   uint32 `protobuf:"varint,4,opt,name=type_code,json=typeCode" json:"type_code,omitempty"`
-	Value      []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
+	Optional             bool     `protobuf:"varint,1,opt,name=optional,proto3" json:"optional,omitempty"`
+	Transitive           bool     `protobuf:"varint,2,opt,name=transitive,proto3" json:"transitive,omitempty"`
+	Partial              bool     `protobuf:"varint,3,opt,name=partial,proto3" json:"partial,omitempty"`
+	TypeCode             uint32   `protobuf:"varint,4,opt,name=type_code,json=typeCode,proto3" json:"type_code,omitempty"`
+	Value                []byte   `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *UnknownPathAttribute) Reset()         { *m = UnknownPathAttribute{} }
+func (m *UnknownPathAttribute) String() string { return proto.CompactTextString(m) }
+func (*UnknownPathAttribute) ProtoMessage()    {}
+func (*UnknownPathAttribute) Descriptor() ([]byte, []int) {
+	return fileDescriptor_00363871266b6b0e, []int{6}
 }
 
-func (m *UnknownPathAttribute) Reset()                    { *m = UnknownPathAttribute{} }
-func (m *UnknownPathAttribute) String() string            { return proto.CompactTextString(m) }
-func (*UnknownPathAttribute) ProtoMessage()               {}
-func (*UnknownPathAttribute) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
+func (m *UnknownPathAttribute) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_UnknownPathAttribute.Unmarshal(m, b)
+}
+func (m *UnknownPathAttribute) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_UnknownPathAttribute.Marshal(b, m, deterministic)
+}
+func (m *UnknownPathAttribute) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_UnknownPathAttribute.Merge(m, src)
+}
+func (m *UnknownPathAttribute) XXX_Size() int {
+	return xxx_messageInfo_UnknownPathAttribute.Size(m)
+}
+func (m *UnknownPathAttribute) XXX_DiscardUnknown() {
+	xxx_messageInfo_UnknownPathAttribute.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UnknownPathAttribute proto.InternalMessageInfo
 
 func (m *UnknownPathAttribute) GetOptional() bool {
 	if m != nil {
@@ -352,6 +504,7 @@ func (m *UnknownPathAttribute) GetValue() []byte {
 }
 
 func init() {
+	proto.RegisterEnum("bio.route.Path_Type", Path_Type_name, Path_Type_value)
 	proto.RegisterType((*Route)(nil), "bio.route.Route")
 	proto.RegisterType((*Path)(nil), "bio.route.Path")
 	proto.RegisterType((*StaticPath)(nil), "bio.route.StaticPath")
@@ -359,59 +512,58 @@ func init() {
 	proto.RegisterType((*ASPathSegment)(nil), "bio.route.ASPathSegment")
 	proto.RegisterType((*LargeCommunity)(nil), "bio.route.LargeCommunity")
 	proto.RegisterType((*UnknownPathAttribute)(nil), "bio.route.UnknownPathAttribute")
-	proto.RegisterEnum("bio.route.Path_Type", Path_Type_name, Path_Type_value)
 }
 
 func init() {
-	proto.RegisterFile("github.com/bio-routing/bio-rd/route/api/route.proto", fileDescriptor0)
-}
-
-var fileDescriptor0 = []byte{
-	// 732 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0xcd, 0x72, 0xda, 0x48,
-	0x10, 0x5e, 0xcc, 0x9f, 0x68, 0x10, 0xc6, 0xb3, 0xec, 0x96, 0xd6, 0xae, 0x5d, 0x63, 0xb9, 0xbc,
-	0x66, 0x0f, 0x86, 0x32, 0xde, 0xda, 0x3b, 0xd8, 0x5e, 0xe2, 0x2a, 0xc7, 0xa5, 0x8c, 0x92, 0x4b,
-	0x2e, 0xaa, 0x41, 0x1a, 0x60, 0x2a, 0x42, 0xa3, 0x48, 0x23, 0xc7, 0x3e, 0xe6, 0x49, 0xf2, 0x12,
-	0x39, 0xe4, 0xf1, 0x52, 0x33, 0x23, 0xb0, 0xc8, 0x5f, 0xe5, 0xd6, 0xfd, 0x75, 0x7f, 0xdd, 0xfd,
-	0xf5, 0xb4, 0x04, 0x17, 0x0b, 0x26, 0x96, 0xd9, 0x6c, 0xe0, 0xf3, 0xd5, 0x70, 0xc6, 0xf8, 0x59,
-	0xc2, 0x33, 0xc1, 0xa2, 0x85, 0xb6, 0x83, 0xa1, 0x74, 0xe9, 0x90, 0xc4, 0x4c, 0x5b, 0x83, 0x38,
-	0xe1, 0x82, 0xa3, 0xc6, 0x8c, 0xf1, 0x81, 0x02, 0xf6, 0x87, 0x3f, 0xe6, 0x47, 0x54, 0x28, 0x76,
-	0x44, 0x85, 0xe6, 0xda, 0x2f, 0xa0, 0x8a, 0x25, 0x13, 0x1d, 0x41, 0x39, 0x9e, 0x3f, 0x58, 0xa5,
-	0x5e, 0xa9, 0xdf, 0x1c, 0xed, 0x0e, 0x64, 0x49, 0x99, 0xe5, 0x24, 0x74, 0xce, 0x1e, 0xb0, 0x8c,
-	0xa1, 0x13, 0xa8, 0xc6, 0x44, 0x2c, 0x53, 0x6b, 0xa7, 0x57, 0xde, 0x24, 0xe9, 0x41, 0x1c, 0x22,
-	0x96, 0x58, 0x47, 0xed, 0x8f, 0x25, 0xa8, 0x48, 0x1f, 0xf5, 0xa1, 0x22, 0x1e, 0x63, 0xaa, 0x6a,
-	0xb6, 0x47, 0xdd, 0x2f, 0xd2, 0x07, 0x2f, 0x1f, 0x63, 0x8a, 0x55, 0x06, 0xfa, 0x0f, 0x9a, 0xa9,
-	0x20, 0x82, 0xf9, 0x9e, 0x2c, 0x61, 0xed, 0xa8, 0x21, 0x7e, 0x2b, 0x10, 0x5c, 0x15, 0x55, 0x5d,
-	0x20, 0xdd, 0xd8, 0xe8, 0x0c, 0x8c, 0xc9, 0xd4, 0xd1, 0xa4, 0xb2, 0x22, 0xa1, 0x02, 0x69, 0x32,
-	0x75, 0x14, 0xa3, 0x9e, 0x1b, 0xf6, 0x01, 0x54, 0x64, 0x53, 0x04, 0x50, 0xd3, 0x05, 0x3b, 0xbf,
-	0xa0, 0x3a, 0x94, 0x27, 0x53, 0xa7, 0x53, 0xb2, 0xff, 0x05, 0x78, 0xea, 0x82, 0xfe, 0x06, 0x23,
-	0xa2, 0x0f, 0xc2, 0x5b, 0xf2, 0x38, 0xdf, 0x49, 0x73, 0xb3, 0x93, 0x1b, 0x07, 0xd7, 0x65, 0xf0,
-	0x19, 0x8f, 0xed, 0x4f, 0x15, 0x58, 0x97, 0x47, 0xa7, 0xb0, 0x2b, 0x27, 0xf1, 0x58, 0x40, 0x23,
-	0xc1, 0xe6, 0x8c, 0x26, 0x8a, 0x6a, 0xe2, 0xb6, 0x84, 0x6f, 0x36, 0xe8, 0x56, 0xf1, 0x9d, 0xef,
-	0x17, 0x47, 0x7f, 0x02, 0x84, 0xdc, 0x27, 0xa1, 0x17, 0x27, 0x74, 0xae, 0x04, 0x9a, 0xb8, 0xa1,
-	0x10, 0xf9, 0x2c, 0xe8, 0x1c, 0xea, 0x63, 0x57, 0x8b, 0xaf, 0xa8, 0x17, 0xb1, 0x0a, 0xe2, 0xc7,
-	0xae, 0x9c, 0xc9, 0xa5, 0x8b, 0x15, 0x8d, 0x04, 0xae, 0x69, 0x17, 0xfd, 0x0e, 0x35, 0x9e, 0xb0,
-	0x05, 0x8b, 0xac, 0xaa, 0xaa, 0x96, 0x7b, 0xa8, 0x03, 0xe5, 0xe7, 0xd7, 0x57, 0x56, 0x4d, 0x81,
-	0xd2, 0x44, 0x08, 0x2a, 0xd7, 0x93, 0xa9, 0x63, 0xd5, 0x7b, 0xa5, 0xbe, 0x81, 0x95, 0x8d, 0x4e,
-	0xa0, 0x2d, 0xd7, 0x5d, 0xd0, 0x67, 0x28, 0x82, 0x39, 0x99, 0x3a, 0x05, 0x79, 0xc7, 0x50, 0x4b,
-	0x79, 0x96, 0xf8, 0xd4, 0x6a, 0x7c, 0x2d, 0x2e, 0x0f, 0xa1, 0x1e, 0x34, 0x7d, 0xbe, 0x5a, 0x65,
-	0x11, 0x13, 0x8c, 0xa6, 0x16, 0xf4, 0xca, 0x7d, 0x13, 0x17, 0x21, 0xf4, 0x3f, 0xec, 0x85, 0x24,
-	0x59, 0x50, 0xaf, 0x98, 0xd7, 0x54, 0x42, 0xff, 0x28, 0x08, 0xbd, 0x95, 0x39, 0x97, 0x79, 0xca,
-	0x23, 0xee, 0x84, 0x45, 0x5f, 0xd6, 0x39, 0x06, 0x53, 0xab, 0x24, 0x82, 0x27, 0x1e, 0x0b, 0xac,
-	0x96, 0x1a, 0xba, 0xf5, 0x04, 0xde, 0x04, 0xe8, 0x08, 0x5a, 0x7e, 0x98, 0xa5, 0x82, 0x26, 0x5e,
-	0xc8, 0x52, 0x61, 0x99, 0xf9, 0x3c, 0x1a, 0xbb, 0x65, 0xa9, 0x40, 0x77, 0x80, 0xb2, 0xe8, 0x4d,
-	0xc4, 0xdf, 0x45, 0x1e, 0x11, 0x22, 0x61, 0xb3, 0x4c, 0xd0, 0xd4, 0x6a, 0xab, 0x81, 0x0e, 0x0b,
-	0x03, 0xbd, 0xd2, 0x49, 0x72, 0xdf, 0xe3, 0x75, 0x1e, 0xde, 0xcb, 0xa9, 0x1b, 0x24, 0xb5, 0xaf,
-	0xc0, 0xdc, 0x7a, 0x24, 0x74, 0x08, 0xcd, 0xb1, 0xeb, 0xa5, 0xf4, 0x6d, 0x46, 0x23, 0x5f, 0x7f,
-	0x36, 0x06, 0x86, 0xb1, 0xeb, 0xe6, 0x88, 0x7c, 0x93, 0xb1, 0x7b, 0xa7, 0xbf, 0x3f, 0x13, 0x2b,
-	0xdb, 0x7e, 0x5f, 0x82, 0xf6, 0xf6, 0x0a, 0xd0, 0x39, 0x74, 0x17, 0x21, 0x9f, 0x91, 0xd0, 0x23,
-	0xc1, 0x8a, 0x45, 0x2c, 0x15, 0x89, 0x54, 0x99, 0x1f, 0xe3, 0xaf, 0x3a, 0x36, 0x2e, 0x86, 0xe4,
-	0xa5, 0x05, 0x44, 0x10, 0x2f, 0x26, 0x89, 0x38, 0x57, 0x37, 0x69, 0xe2, 0x86, 0x44, 0x1c, 0x09,
-	0x6c, 0x85, 0x47, 0xeb, 0x43, 0x5c, 0x87, 0x47, 0xf6, 0x87, 0x12, 0x74, 0xbf, 0xa5, 0x1a, 0xed,
-	0x83, 0xc1, 0x63, 0xc1, 0x78, 0x44, 0xc2, 0x5c, 0xce, 0xc6, 0x47, 0x7f, 0x01, 0x88, 0x84, 0x44,
-	0x29, 0x13, 0xec, 0x9e, 0xaa, 0x96, 0x06, 0x2e, 0x20, 0xc8, 0x82, 0xba, 0x6c, 0xc7, 0x48, 0xa8,
-	0x1a, 0x1a, 0x78, 0xed, 0xa2, 0x03, 0x68, 0xc8, 0xbf, 0x86, 0xe7, 0xf3, 0x80, 0x5a, 0x15, 0x35,
-	0x8c, 0x21, 0x81, 0x4b, 0x1e, 0x50, 0xd4, 0x85, 0xea, 0x3d, 0x09, 0x33, 0xaa, 0x0e, 0xbc, 0x85,
-	0xb5, 0x33, 0xf9, 0xe7, 0xf5, 0xe9, 0x4f, 0xfe, 0x59, 0x67, 0x35, 0xf5, 0x63, 0xbc, 0xf8, 0x1c,
-	0x00, 0x00, 0xff, 0xff, 0x00, 0xf1, 0xa7, 0x12, 0x8b, 0x05, 0x00, 0x00,
+	proto.RegisterFile("github.com/bio-routing/bio-rd/route/api/route.proto", fileDescriptor_00363871266b6b0e)
+}
+
+var fileDescriptor_00363871266b6b0e = []byte{
+	// 730 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x4d, 0x6f, 0xdb, 0x38,
+	0x10, 0x5d, 0xc7, 0x5f, 0xf2, 0xd8, 0x72, 0x1c, 0xae, 0x77, 0xa1, 0x4d, 0xb0, 0x8d, 0xa3, 0x20,
+	0x8d, 0x7b, 0x88, 0x8d, 0x38, 0x45, 0xef, 0x49, 0x8a, 0xa6, 0x01, 0x82, 0xc2, 0xa5, 0xdb, 0x4b,
+	0x2f, 0x02, 0x25, 0xd1, 0x32, 0x51, 0x99, 0x54, 0x45, 0x2a, 0x4d, 0x8e, 0xfd, 0x25, 0xfd, 0x13,
+	0x3d, 0xf4, 0xe7, 0x15, 0xa4, 0x64, 0x47, 0xee, 0x17, 0x7a, 0x9b, 0x79, 0x33, 0x6f, 0xe6, 0x3d,
+	0x92, 0x12, 0x9c, 0x45, 0x4c, 0x2d, 0x32, 0x7f, 0x14, 0x88, 0xe5, 0xd8, 0x67, 0xe2, 0x24, 0x15,
+	0x99, 0x62, 0x3c, 0xca, 0xe3, 0x70, 0xac, 0x53, 0x3a, 0x26, 0x09, 0xcb, 0xa3, 0x51, 0x92, 0x0a,
+	0x25, 0x50, 0xcb, 0x67, 0x62, 0x64, 0x80, 0xdd, 0xf1, 0xef, 0xf9, 0x9c, 0x2a, 0xc3, 0xe6, 0x54,
+	0xe5, 0x5c, 0xf7, 0x35, 0xd4, 0xb1, 0x66, 0xa2, 0x03, 0xa8, 0x26, 0xf3, 0x3b, 0xa7, 0x32, 0xa8,
+	0x0c, 0xdb, 0x93, 0xed, 0x91, 0x1e, 0xa9, 0xbb, 0xa6, 0x29, 0x9d, 0xb3, 0x3b, 0xac, 0x6b, 0xe8,
+	0x08, 0xea, 0x09, 0x51, 0x0b, 0xe9, 0x6c, 0x0d, 0xaa, 0xeb, 0xa6, 0x5c, 0xc8, 0x94, 0xa8, 0x05,
+	0xce, 0xab, 0xee, 0x97, 0x0a, 0xd4, 0x74, 0x8e, 0x86, 0x50, 0x53, 0xf7, 0x09, 0x35, 0x33, 0xbb,
+	0x93, 0xfe, 0x77, 0xed, 0xa3, 0x37, 0xf7, 0x09, 0xc5, 0xa6, 0x03, 0x3d, 0x83, 0xb6, 0x54, 0x44,
+	0xb1, 0xc0, 0xd3, 0x23, 0x9c, 0x2d, 0x23, 0xe2, 0x9f, 0x12, 0x61, 0x66, 0xaa, 0x66, 0x0b, 0xc8,
+	0x75, 0x8c, 0x4e, 0xc0, 0xf2, 0xa3, 0x24, 0x27, 0x55, 0x0d, 0x09, 0x95, 0x48, 0x17, 0x57, 0x53,
+	0xc3, 0x68, 0xfa, 0x51, 0xa2, 0x03, 0x77, 0x0f, 0x6a, 0x7a, 0x29, 0x02, 0x68, 0xe4, 0x03, 0x7b,
+	0x7f, 0xa1, 0x26, 0x54, 0x2f, 0xae, 0xa6, 0xbd, 0x8a, 0xfb, 0x14, 0xe0, 0x61, 0x0b, 0x7a, 0x0c,
+	0x16, 0xa7, 0x77, 0xca, 0x5b, 0x88, 0xa4, 0x38, 0x93, 0xf6, 0xfa, 0x4c, 0xae, 0xa7, 0xb8, 0xa9,
+	0x8b, 0x2f, 0x45, 0xe2, 0x7e, 0xad, 0x41, 0xb3, 0xd8, 0x83, 0x8e, 0x61, 0x5b, 0x2b, 0xf1, 0x58,
+	0x48, 0xb9, 0x62, 0x73, 0x46, 0x53, 0x43, 0xb5, 0x71, 0x57, 0xc3, 0xd7, 0x6b, 0x74, 0x63, 0xf8,
+	0xd6, 0xaf, 0x87, 0xa3, 0xff, 0x01, 0x62, 0x11, 0x90, 0xd8, 0x4b, 0x52, 0x3a, 0x37, 0x06, 0x6d,
+	0xdc, 0x32, 0x88, 0xbe, 0x16, 0x74, 0x0a, 0x4d, 0x22, 0x73, 0xf3, 0x35, 0x73, 0x23, 0x4e, 0xc9,
+	0xfc, 0xf9, 0x4c, 0x6b, 0x9a, 0xd1, 0x68, 0x49, 0xb9, 0xc2, 0x0d, 0x22, 0x8d, 0xc4, 0x7f, 0xa1,
+	0x21, 0x52, 0x16, 0x31, 0xee, 0xd4, 0xcd, 0xb4, 0x22, 0x43, 0x3d, 0xa8, 0x2e, 0x69, 0xe8, 0x34,
+	0x0c, 0xa8, 0x43, 0x84, 0xa0, 0x46, 0xfd, 0x28, 0x71, 0x9a, 0x83, 0xca, 0xd0, 0xc2, 0x26, 0x46,
+	0x47, 0xd0, 0xd5, 0xc7, 0x5d, 0xf2, 0x67, 0x19, 0x82, 0xed, 0x47, 0x49, 0xc9, 0xde, 0x21, 0x34,
+	0xa4, 0xc8, 0xd2, 0x80, 0x3a, 0xad, 0x1f, 0xcd, 0x15, 0x25, 0x34, 0x80, 0x76, 0x20, 0x96, 0xcb,
+	0x8c, 0x33, 0xc5, 0xa8, 0x74, 0x60, 0x50, 0x1d, 0xda, 0xb8, 0x0c, 0xa1, 0x17, 0xb0, 0x13, 0x93,
+	0x34, 0xa2, 0x5e, 0xb9, 0xaf, 0x6d, 0x8c, 0xfe, 0x57, 0x32, 0x7a, 0xa3, 0x7b, 0x2e, 0x8b, 0x96,
+	0x7b, 0xdc, 0x8b, 0xcb, 0xb9, 0x9e, 0x73, 0x08, 0x76, 0xee, 0x92, 0x28, 0x91, 0x7a, 0x2c, 0x74,
+	0x3a, 0x46, 0x74, 0xe7, 0x01, 0xbc, 0x0e, 0xd1, 0x01, 0x74, 0x82, 0x38, 0x93, 0x8a, 0xa6, 0x5e,
+	0xcc, 0xa4, 0x72, 0xec, 0x42, 0x4f, 0x8e, 0xdd, 0x30, 0xa9, 0xd0, 0x2b, 0x40, 0x19, 0x7f, 0xcf,
+	0xc5, 0x47, 0xee, 0x11, 0xa5, 0x52, 0xe6, 0x67, 0x8a, 0x4a, 0xa7, 0x6b, 0x04, 0xed, 0x97, 0x04,
+	0xbd, 0xcd, 0x9b, 0xf4, 0x79, 0x9f, 0xaf, 0xfa, 0xf0, 0x4e, 0x41, 0x5d, 0x23, 0xd2, 0x7d, 0x0e,
+	0xf6, 0xc6, 0x25, 0xa1, 0x7d, 0x68, 0x13, 0xe9, 0x49, 0xfa, 0x21, 0xa3, 0x3c, 0xc8, 0x3f, 0x1b,
+	0x0b, 0x03, 0x91, 0xb3, 0x02, 0xd1, 0x77, 0x42, 0x24, 0xcf, 0xbf, 0x3f, 0x1b, 0x9b, 0xd8, 0xfd,
+	0x54, 0x81, 0xee, 0xe6, 0x11, 0xa0, 0x53, 0xe8, 0x47, 0xb1, 0xf0, 0x49, 0xec, 0x91, 0x70, 0xc9,
+	0x38, 0x93, 0x2a, 0xd5, 0x2e, 0x8b, 0xc7, 0xf8, 0x77, 0x5e, 0x3b, 0x2f, 0x97, 0xf4, 0x4b, 0x0b,
+	0x89, 0x22, 0x5e, 0x42, 0x52, 0x75, 0x6a, 0xde, 0xa4, 0x8d, 0x5b, 0x1a, 0x99, 0x6a, 0x60, 0xa3,
+	0x3c, 0x59, 0x3d, 0xc4, 0x55, 0x79, 0xe2, 0x7e, 0xae, 0x40, 0xff, 0x67, 0xae, 0xd1, 0x2e, 0x58,
+	0x22, 0x51, 0x4c, 0x70, 0x12, 0x17, 0x76, 0xd6, 0x39, 0x7a, 0x04, 0xa0, 0x52, 0xc2, 0x25, 0x53,
+	0xec, 0x96, 0x9a, 0x95, 0x16, 0x2e, 0x21, 0xc8, 0x81, 0xa6, 0x5e, 0xc7, 0x48, 0x6c, 0x16, 0x5a,
+	0x78, 0x95, 0xa2, 0x3d, 0x68, 0xe9, 0xbf, 0x86, 0x17, 0x88, 0x90, 0x3a, 0x35, 0x23, 0xc6, 0xd2,
+	0xc0, 0xa5, 0x08, 0x29, 0xea, 0x43, 0xfd, 0x96, 0xc4, 0x19, 0x35, 0x0f, 0xbc, 0x83, 0xf3, 0xe4,
+	0xe2, 0xc9, 0xbb, 0xe3, 0x3f, 0xfc, 0xb3, 0xfa, 0x0d, 0xf3, 0x63, 0x3c, 0xfb, 0x16, 0x00, 0x00,
+	0xff, 0xff, 0x9e, 0xbe, 0x17, 0xdc, 0x8b, 0x05, 0x00, 0x00,
 }
diff --git a/route/api/route.proto b/route/api/route.proto
index dd40bea80e1e279f0564e099ec51a19aaed88e8b..e856f22ece1fc56ed8ab080b3189143849718af7 100644
--- a/route/api/route.proto
+++ b/route/api/route.proto
@@ -17,7 +17,7 @@ message Path {
     }
     Type type = 1;
     StaticPath static_path = 2;
-    BGPPath BGP_path = 3;
+    BGPPath bgp_path = 3;
 }
  
 message StaticPath {
@@ -28,11 +28,11 @@ message BGPPath {
     uint32 path_identifier = 1;
     bio.net.IP next_hop = 2;
     uint32 local_pref = 3;
-    repeated ASPathSegment AS_path = 4;
+    repeated ASPathSegment as_path = 4;
     uint32 origin = 5;
-    uint32 MED = 6;
-    bool EBGP = 7;
-    uint32 BGP_identifier = 8;
+    uint32 med = 6;
+    bool ebgp = 7;
+    uint32 bgp_identifier = 8;
     bio.net.IP source = 9;
     repeated uint32 communities = 10;
     repeated LargeCommunity large_communities = 11;
@@ -42,8 +42,8 @@ message BGPPath {
 }
  
 message ASPathSegment {
-    bool AS_sequence = 1;
-    repeated uint32 ASNs = 2;
+    bool as_sequence = 1;
+    repeated uint32 asns = 2;
 }
  
 message LargeCommunity {
diff --git a/route/bgp_path.go b/route/bgp_path.go
index 3b4c56c21e274168c77280b874158d8a8528e645..308f2ce2223ef5a0bee7cb1064bfda267b927c0b 100644
--- a/route/bgp_path.go
+++ b/route/bgp_path.go
@@ -43,11 +43,11 @@ func (b *BGPPath) ToProto() *api.BGPPath {
 		PathIdentifier:    b.PathIdentifier,
 		NextHop:           b.NextHop.ToProto(),
 		LocalPref:         b.LocalPref,
-		ASPath:            b.ASPath.ToProto(),
+		AsPath:            b.ASPath.ToProto(),
 		Origin:            uint32(b.Origin),
-		MED:               b.MED,
-		EBGP:              b.EBGP,
-		BGPIdentifier:     b.BGPIdentifier,
+		Med:               b.MED,
+		Ebgp:              b.EBGP,
+		BgpIdentifier:     b.BGPIdentifier,
 		Source:            b.Source.ToProto(),
 		Communities:       make([]uint32, len(b.Communities)),
 		LargeCommunities:  make([]*api.LargeCommunity, len(b.LargeCommunities)),
@@ -70,6 +70,44 @@ func (b *BGPPath) ToProto() *api.BGPPath {
 	return a
 }
 
+// BGPPathFromProtoBGPPath converts a proto BGPPath to BGPPath
+func BGPPathFromProtoBGPPath(pb *api.BGPPath) *BGPPath {
+	p := &BGPPath{
+		PathIdentifier:    pb.PathIdentifier,
+		NextHop:           bnet.IPFromProtoIP(*pb.NextHop),
+		LocalPref:         pb.LocalPref,
+		ASPath:            types.ASPathFromProtoASPath(pb.AsPath),
+		Origin:            uint8(pb.Origin),
+		MED:               pb.Med,
+		EBGP:              pb.Ebgp,
+		BGPIdentifier:     pb.BgpIdentifier,
+		Source:            bnet.IPFromProtoIP(*pb.Source),
+		Communities:       make([]uint32, len(pb.Communities)),
+		LargeCommunities:  make([]types.LargeCommunity, len(pb.LargeCommunities)),
+		UnknownAttributes: make([]types.UnknownPathAttribute, len(pb.UnknownAttributes)),
+		OriginatorID:      pb.OriginatorId,
+		ClusterList:       make([]uint32, len(pb.ClusterList)),
+	}
+
+	for i := range pb.Communities {
+		p.Communities[i] = pb.Communities[i]
+	}
+
+	for i := range pb.LargeCommunities {
+		p.LargeCommunities[i] = types.LargeCommunityFromProtoCommunity(pb.LargeCommunities[i])
+	}
+
+	for i := range pb.UnknownAttributes {
+		p.UnknownAttributes[i] = types.UnknownPathAttributeFromProtoUnknownPathAttribute(pb.UnknownAttributes[i])
+	}
+
+	for i := range pb.ClusterList {
+		p.ClusterList[i] = pb.ClusterList[i]
+	}
+
+	return p
+}
+
 // Length get's the length of serialized path
 func (b *BGPPath) Length() uint16 {
 	asPathLen := uint16(3)
diff --git a/route/bgp_path_test.go b/route/bgp_path_test.go
index dff71dac720eb980952f6444db0be20df6324da2..9fa3318e15b2e2609a31ab3c2712945244d2d40d 100644
--- a/route/bgp_path_test.go
+++ b/route/bgp_path_test.go
@@ -3,10 +3,103 @@ package route
 import (
 	"testing"
 
+	bnet "github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/protocols/bgp/types"
+	"github.com/bio-routing/bio-rd/route/api"
 	"github.com/stretchr/testify/assert"
 )
 
+func TestBGPPathFromProtoBGPPath(t *testing.T) {
+	input := &api.BGPPath{
+		PathIdentifier: 100,
+		NextHop:        bnet.IPv4FromOctets(10, 0, 0, 1).ToProto(),
+		LocalPref:      1000,
+		AsPath: []*api.ASPathSegment{
+			{
+				AsSequence: true,
+				Asns: []uint32{
+					3320,
+					201701,
+				},
+			},
+		},
+		Origin:        1,
+		Ebgp:          true,
+		BgpIdentifier: 123,
+		Source:        bnet.IPv4FromOctets(10, 0, 0, 2).ToProto(),
+		Communities:   []uint32{100, 200, 300},
+		LargeCommunities: []*api.LargeCommunity{
+			{
+				GlobalAdministrator: 222,
+				DataPart1:           500,
+				DataPart2:           600,
+			},
+			{
+				GlobalAdministrator: 333,
+				DataPart1:           555,
+				DataPart2:           666,
+			},
+		},
+		UnknownAttributes: []*api.UnknownPathAttribute{
+			{
+				Optional:   true,
+				Transitive: true,
+				Partial:    true,
+				TypeCode:   233,
+				Value:      []byte{200, 222},
+			},
+		},
+		OriginatorId: 8888,
+		ClusterList:  []uint32{999, 199},
+	}
+
+	expected := &BGPPath{
+		PathIdentifier: 100,
+		NextHop:        bnet.IPv4FromOctets(10, 0, 0, 1),
+		LocalPref:      1000,
+		ASPath: types.ASPath{
+			{
+				Type: types.ASSequence,
+				ASNs: []uint32{
+					3320,
+					201701,
+				},
+			},
+		},
+		Origin:        1,
+		EBGP:          true,
+		BGPIdentifier: 123,
+		Source:        bnet.IPv4FromOctets(10, 0, 0, 2),
+		Communities:   []uint32{100, 200, 300},
+		LargeCommunities: []types.LargeCommunity{
+			{
+				GlobalAdministrator: 222,
+				DataPart1:           500,
+				DataPart2:           600,
+			},
+			{
+				GlobalAdministrator: 333,
+				DataPart1:           555,
+				DataPart2:           666,
+			},
+		},
+		UnknownAttributes: []types.UnknownPathAttribute{
+			{
+				Optional:   true,
+				Transitive: true,
+				Partial:    true,
+				TypeCode:   233,
+				Value:      []byte{200, 222},
+			},
+		},
+		OriginatorID: 8888,
+		ClusterList:  []uint32{999, 199},
+	}
+
+	result := BGPPathFromProtoBGPPath(input)
+	assert.Equal(t, expected, result)
+}
+
 func TestBGPSelect(t *testing.T) {
 	tests := []struct {
 		name     string
diff --git a/route/path.go b/route/path.go
index 46ede40ac62cdec4f674a66a692238a57d8ab933..c8de5c60828009d063df5632e2562a0ea839923f 100644
--- a/route/path.go
+++ b/route/path.go
@@ -65,7 +65,7 @@ func (p *Path) ECMP(q *Path) bool {
 func (p *Path) ToProto() *api.Path {
 	a := &api.Path{
 		StaticPath: p.StaticPath.ToProto(),
-		BGPPath:    p.BGPPath.ToProto(),
+		BgpPath:    p.BGPPath.ToProto(),
 	}
 
 	switch p.Type {
diff --git a/route/route.go b/route/route.go
index c1dc348686a9489c314da0e309ea7b3e7d8ff816..d96ef7618dd355ace8d948f4f1af10b58a16cecc 100644
--- a/route/route.go
+++ b/route/route.go
@@ -254,6 +254,27 @@ func (r *Route) ToProto() *api.Route {
 	return a
 }
 
+// RouteFromProtoRoute converts a proto Route to a Route
+func RouteFromProtoRoute(ar *api.Route) *Route {
+	r := &Route{
+		pfx:   net.NewPrefixFromProtoPrefix(*ar.Pfx),
+		paths: make([]*Path, 0, len(ar.Paths)),
+	}
+
+	for i := range ar.Paths {
+		p := &Path{}
+		switch ar.Paths[i].Type {
+		case api.Path_BGP:
+			p.Type = BGPPathType
+			p.BGPPath = BGPPathFromProtoBGPPath(ar.Paths[i].BgpPath)
+		}
+
+		r.paths = append(r.paths, p)
+	}
+
+	return r
+}
+
 func (r *Route) updateEqualPathCount() {
 	count := uint(1)
 	for i := 0; i < len(r.paths)-1; i++ {
diff --git a/routingtable/locRIB/loc_rib.go b/routingtable/locRIB/loc_rib.go
index 3503aacfce8e9204ce06b58303e2553221d618ee..f349c4b3ae9b97a7feae5cabd308e70b2d1c5fbe 100644
--- a/routingtable/locRIB/loc_rib.go
+++ b/routingtable/locRIB/loc_rib.go
@@ -38,6 +38,11 @@ func New(name string) *LocRIB {
 	return a
 }
 
+// Name gets the name of the LocRIB
+func (a *LocRIB) Name() string {
+	return a.name
+}
+
 // ClientCount gets the number of registered clients
 func (a *LocRIB) ClientCount() uint64 {
 	return a.clientManager.ClientCount()
@@ -53,6 +58,21 @@ func (a *LocRIB) Count() uint64 {
 	return uint64(a.rt.GetRouteCount())
 }
 
+// LPM performs a longest prefix match on the routing table
+func (a *LocRIB) LPM(pfx net.Prefix) (res []*route.Route) {
+	return a.rt.LPM(pfx)
+}
+
+// Get gets a route
+func (a *LocRIB) Get(pfx net.Prefix) *route.Route {
+	return a.rt.Get(pfx)
+}
+
+// GetLonger gets all more specifics
+func (a *LocRIB) GetLonger(pfx net.Prefix) (res []*route.Route) {
+	return a.rt.GetLonger(pfx)
+}
+
 // Dump dumps the RIB
 func (a *LocRIB) Dump() []*route.Route {
 	a.mu.RLock()
@@ -76,7 +96,9 @@ func (a *LocRIB) UpdateNewClient(client routingtable.RouteTableClient) error {
 
 	routes := a.rt.Dump()
 	for _, r := range routes {
-		a.propagateChanges(&route.Route{}, r)
+		for _, p := range r.Paths() {
+			client.AddPath(r.Prefix(), p)
+		}
 	}
 
 	return nil
diff --git a/routingtable/vrf/metrics.go b/routingtable/vrf/metrics.go
index b8d43ee42f7b39217d90a1333e301117e28a9a48..5e4dd53e867982c39deb9f80583524b4b9314eb9 100644
--- a/routingtable/vrf/metrics.go
+++ b/routingtable/vrf/metrics.go
@@ -5,20 +5,20 @@ import (
 )
 
 // Metrics returns metrics for all VRFs
-func Metrics() []*metrics.VRFMetrics {
-	vrfs := globalRegistry.list()
+func Metrics(r *VRFRegistry) []*metrics.VRFMetrics {
+	vrfs := r.List()
 
 	m := make([]*metrics.VRFMetrics, len(vrfs))
 	i := 0
 	for _, v := range vrfs {
-		m[i] = metricsForVRF(v)
+		m[i] = MetricsForVRF(v)
 		i++
 	}
 
 	return m
 }
 
-func metricsForVRF(v *VRF) *metrics.VRFMetrics {
+func MetricsForVRF(v *VRF) *metrics.VRFMetrics {
 	m := &metrics.VRFMetrics{
 		Name: v.Name(),
 		RIBs: make([]*metrics.RIBMetrics, 0),
diff --git a/routingtable/vrf/metrics_test.go b/routingtable/vrf/metrics_test.go
index b946699e1cea38d53116062f9eceb486bbcf888e..1cb2af75ca610784b0543ac680ea16a37f85486d 100644
--- a/routingtable/vrf/metrics_test.go
+++ b/routingtable/vrf/metrics_test.go
@@ -6,24 +6,20 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	bnet "github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/bio-routing/bio-rd/routingtable/vrf/metrics"
+
+	bnet "github.com/bio-routing/bio-rd/net"
 )
 
 func TestMetrics(t *testing.T) {
-	green, err := New("green")
-	if err != nil {
-		t.Fatal(err)
-	}
+	r := NewVRFRegistry()
+	green := r.CreateVRFIfNotExists("green", 0)
 	green.IPv4UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv4FromOctets(8, 0, 0, 0), 8), &route.Path{})
 	green.IPv4UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv4FromOctets(8, 0, 0, 0), 16), &route.Path{})
 	green.IPv6UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0), 48), &route.Path{})
 
-	red, err := New("red")
-	if err != nil {
-		t.Fatal(err)
-	}
+	red := r.CreateVRFIfNotExists("red", 1)
 	red.IPv6UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0), 64), &route.Path{})
 	red.IPv6UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x200, 0, 0, 0, 0), 64), &route.Path{})
 
@@ -64,20 +60,21 @@ func TestMetrics(t *testing.T) {
 		},
 	}
 
-	actual := Metrics()
+	actual := Metrics(r)
 	sortResult(actual)
 
 	assert.Equal(t, expected, actual)
+	_ = green
 }
 
-func sortResult(m []*metrics.VRFMetrics) {
-	sort.Slice(m, func(i, j int) bool {
-		return m[i].Name < m[j].Name
+func sortResult(vrfMetrics []*metrics.VRFMetrics) {
+	sort.Slice(vrfMetrics, func(i, j int) bool {
+		return vrfMetrics[i].Name < vrfMetrics[j].Name
 	})
 
-	for _, v := range m {
-		sort.Slice(v.RIBs, func(i, j int) bool {
-			return m[i].Name < m[j].Name
+	for _, vrfMetric := range vrfMetrics {
+		sort.Slice(vrfMetric.RIBs, func(i, j int) bool {
+			return vrfMetric.RIBs[i].Name < vrfMetric.RIBs[j].Name
 		})
 	}
 }
diff --git a/routingtable/vrf/vrf.go b/routingtable/vrf/vrf.go
index bdc290588ab88830fa99f4a9a3c74e911781828f..127df8e6b715fa25c14d1ab3743d563ae72c4696 100644
--- a/routingtable/vrf/vrf.go
+++ b/routingtable/vrf/vrf.go
@@ -20,15 +20,16 @@ type addressFamily struct {
 
 // VRF a list of RIBs for different address families building a routing instance
 type VRF struct {
-	name     string
-	ribs     map[addressFamily]*locRIB.LocRIB
-	mu       sync.Mutex
-	ribNames map[string]*locRIB.LocRIB
+	name               string
+	routeDistinguisher uint64
+	ribs               map[addressFamily]*locRIB.LocRIB
+	mu                 sync.Mutex
+	ribNames           map[string]*locRIB.LocRIB
 }
 
 // New creates a new VRF. The VRF is registered automatically to the global VRF registry.
-func New(name string) (*VRF, error) {
-	v := newUntrackedVRF(name)
+func New(name string, rd uint64) (*VRF, error) {
+	v := newUntrackedVRF(name, rd)
 	v.CreateIPv4UnicastLocRIB("inet.0")
 	v.CreateIPv6UnicastLocRIB("inet6.0")
 
@@ -40,11 +41,12 @@ func New(name string) (*VRF, error) {
 	return v, nil
 }
 
-func newUntrackedVRF(name string) *VRF {
+func newUntrackedVRF(name string, rd uint64) *VRF {
 	return &VRF{
-		name:     name,
-		ribs:     make(map[addressFamily]*locRIB.LocRIB),
-		ribNames: make(map[string]*locRIB.LocRIB),
+		name:               name,
+		routeDistinguisher: rd,
+		ribs:               make(map[addressFamily]*locRIB.LocRIB),
+		ribNames:           make(map[string]*locRIB.LocRIB),
 	}
 }
 
@@ -90,6 +92,11 @@ func (v *VRF) Name() string {
 	return v.name
 }
 
+// RD returns the route distinguisher of the VRF
+func (v *VRF) RD() uint64 {
+	return v.routeDistinguisher
+}
+
 // Unregister removes this VRF from the global registry.
 func (v *VRF) Unregister() {
 	globalRegistry.unregisterVRF(v)
diff --git a/routingtable/vrf/vrf_registry.go b/routingtable/vrf/vrf_registry.go
index 0fae0720ba623be30f3d06e2349ef7340d0c715a..242565160e7304e1e91ee850091790745254161f 100644
--- a/routingtable/vrf/vrf_registry.go
+++ b/routingtable/vrf/vrf_registry.go
@@ -5,44 +5,61 @@ import (
 	"sync"
 )
 
-var globalRegistry *vrfRegistry
+var globalRegistry *VRFRegistry
 
 func init() {
-	globalRegistry = &vrfRegistry{
-		vrfs: make(map[string]*VRF),
-	}
+	globalRegistry = NewVRFRegistry()
 }
 
-// vrfRegistry holds a reference to all active VRFs. Every VRF have to have a different name.
-type vrfRegistry struct {
-	vrfs map[string]*VRF
+// VRFRegistry holds a reference to all active VRFs. Every VRF have to have a different name.
+type VRFRegistry struct {
+	vrfs map[uint64]*VRF
 	mu   sync.Mutex
 }
 
+func NewVRFRegistry() *VRFRegistry {
+	return &VRFRegistry{
+		vrfs: make(map[uint64]*VRF),
+	}
+}
+
+func (r *VRFRegistry) CreateVRFIfNotExists(name string, rd uint64) *VRF {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	if _, ok := r.vrfs[rd]; ok {
+		return r.vrfs[rd]
+	}
+
+	r.vrfs[rd] = newUntrackedVRF(name, rd)
+	r.vrfs[rd].CreateIPv4UnicastLocRIB("inet.0")
+	r.vrfs[rd].CreateIPv6UnicastLocRIB("inet6.0")
+	return r.vrfs[rd]
+}
+
 // registerVRF adds the given VRF from the global registry.
-// An error is returned if there is already a VRF registered with the same name.
-func (r *vrfRegistry) registerVRF(v *VRF) error {
+// An error is returned if there is already a VRF registered with the same route distinguisher.
+func (r *VRFRegistry) registerVRF(v *VRF) error {
 	r.mu.Lock()
 	defer r.mu.Unlock()
 
-	_, found := r.vrfs[v.name]
-	if found {
-		return fmt.Errorf("a VRF with the name '%s' already exists", v.name)
+	if _, ok := r.vrfs[v.routeDistinguisher]; ok {
+		return fmt.Errorf("a VRF with the rd '%d' already exists", v.routeDistinguisher)
 	}
 
-	r.vrfs[v.name] = v
+	r.vrfs[v.routeDistinguisher] = v
 	return nil
 }
 
 // unregisterVRF removes the given VRF from the global registry.
-func (r *vrfRegistry) unregisterVRF(v *VRF) {
+func (r *VRFRegistry) unregisterVRF(v *VRF) {
 	r.mu.Lock()
 	defer r.mu.Unlock()
 
-	delete(r.vrfs, v.name)
+	delete(r.vrfs, v.routeDistinguisher)
 }
 
-func (r *vrfRegistry) list() []*VRF {
+func (r *VRFRegistry) List() []*VRF {
 	r.mu.Lock()
 	defer r.mu.Unlock()
 
@@ -55,3 +72,25 @@ func (r *vrfRegistry) list() []*VRF {
 
 	return l
 }
+
+// GetVRFByRD gets a VRF by it's Route Distinguisher
+func GetVRFByRD(rd uint64) *VRF {
+	return globalRegistry.GetVRFByRD(rd)
+}
+
+// GetGlobalRegistry gets the global registry
+func GetGlobalRegistry() *VRFRegistry {
+	return globalRegistry
+}
+
+// GetVRFByRD gets a VRF by route distinguisher
+func (r *VRFRegistry) GetVRFByRD(rd uint64) *VRF {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	if _, ok := r.vrfs[rd]; ok {
+		return r.vrfs[rd]
+	}
+
+	return nil
+}
diff --git a/routingtable/vrf/vrf_test.go b/routingtable/vrf/vrf_test.go
index 9f154d88b1c795a8aa605f9f2686d7ae6c482573..4bd936530d61c7fa495ad7e9f7fef0fdacd7264c 100644
--- a/routingtable/vrf/vrf_test.go
+++ b/routingtable/vrf/vrf_test.go
@@ -7,15 +7,15 @@ import (
 )
 
 func TestNewWithDuplicate(t *testing.T) {
-	_, err := New("master")
+	_, err := New("master", 123)
 	assert.Nil(t, err, "no error on first invocation")
 
-	_, err = New("master")
+	_, err = New("master", 123)
 	assert.NotNil(t, err, "ambigious VRF name")
 }
 
 func TestIPv4UnicastRIBWith(t *testing.T) {
-	v := newUntrackedVRF("master")
+	v := newUntrackedVRF("master", 0)
 	rib, err := v.CreateIPv4UnicastLocRIB("inet.0")
 
 	assert.Equal(t, rib, v.IPv4UnicastRIB())
@@ -23,7 +23,7 @@ func TestIPv4UnicastRIBWith(t *testing.T) {
 }
 
 func TestIPv6UnicastRIB(t *testing.T) {
-	v := newUntrackedVRF("master")
+	v := newUntrackedVRF("master", 0)
 	rib, err := v.CreateIPv6UnicastLocRIB("inet6.0")
 
 	assert.Equal(t, rib, v.IPv6UnicastRIB())
@@ -31,7 +31,7 @@ func TestIPv6UnicastRIB(t *testing.T) {
 }
 
 func TestCreateLocRIBTwice(t *testing.T) {
-	v := newUntrackedVRF("master")
+	v := newUntrackedVRF("master", 0)
 	_, err := v.CreateIPv6UnicastLocRIB("inet6.0")
 	assert.Nil(t, err, "error must be nil on first invokation")
 
@@ -40,7 +40,7 @@ func TestCreateLocRIBTwice(t *testing.T) {
 }
 
 func TestRIBByName(t *testing.T) {
-	v := newUntrackedVRF("master")
+	v := newUntrackedVRF("master", 0)
 	rib, _ := v.CreateIPv6UnicastLocRIB("inet6.0")
 	assert.NotNil(t, rib, "rib must not be nil after creation")
 
@@ -50,24 +50,24 @@ func TestRIBByName(t *testing.T) {
 }
 
 func TestName(t *testing.T) {
-	v := newUntrackedVRF("foo")
+	v := newUntrackedVRF("foo", 0)
 	assert.Equal(t, "foo", v.Name())
 }
 
 func TestUnregister(t *testing.T) {
 	vrfName := "registeredVRF"
-	v, err := New(vrfName)
+	v, err := New(vrfName, 10)
 	assert.Nil(t, err, "error must be nil on first invokation")
 
-	_, err = New(vrfName)
+	_, err = New(vrfName, 10)
 	assert.NotNil(t, err, "error must not be nil on second invokation")
 
-	_, found := globalRegistry.vrfs[vrfName]
+	_, found := globalRegistry.vrfs[10]
 	assert.True(t, found, "vrf must be in global registry")
 
 	v.Unregister()
 
-	_, found = globalRegistry.vrfs[vrfName]
+	_, found = globalRegistry.vrfs[10]
 	assert.False(t, found, "vrf must not be in global registry")
 
 }
diff --git a/util/servicewrapper/grpc.go b/util/servicewrapper/grpc.go
new file mode 100644
index 0000000000000000000000000000000000000000..d435c0d3830b7c0af71bbfcd65f13cf100eb31f3
--- /dev/null
+++ b/util/servicewrapper/grpc.go
@@ -0,0 +1,142 @@
+package servicewrapper
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"os"
+	"sync"
+	"time"
+
+	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
+	grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
+	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
+	grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
+	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/reflection"
+
+	log "github.com/sirupsen/logrus"
+)
+
+// CodeToLogrusLevel is the default 0-stor implementation
+// of gRPC return codes to log(rus) levels for server side.
+func codeToLogrusLevel(code codes.Code) log.Level {
+	if level, ok := _GRPCCodeToLogrusLevelMapping[code]; ok {
+		return level
+	}
+	return log.ErrorLevel
+}
+
+var _GRPCCodeToLogrusLevelMapping = map[codes.Code]log.Level{
+	codes.OK:                 log.DebugLevel,
+	codes.Canceled:           log.DebugLevel,
+	codes.InvalidArgument:    log.DebugLevel,
+	codes.NotFound:           log.DebugLevel,
+	codes.AlreadyExists:      log.DebugLevel,
+	codes.Unauthenticated:    log.InfoLevel,
+	codes.PermissionDenied:   log.InfoLevel,
+	codes.DeadlineExceeded:   log.WarnLevel,
+	codes.ResourceExhausted:  log.WarnLevel,
+	codes.FailedPrecondition: log.WarnLevel,
+	codes.Aborted:            log.WarnLevel,
+}
+
+// Server represents an exarpc server wrapper
+type Server struct {
+	grpcSrv *grpcSrv
+	httpSrv *http.Server
+	opt     grpc.ServerOption
+}
+
+type grpcSrv struct {
+	port uint16
+	srv  *grpc.Server
+}
+
+// New creates a new exarpc server wrapper
+func New(grpcPort uint16, h *http.Server, unaryInterceptors []grpc.UnaryServerInterceptor, streamInterceptors []grpc.StreamServerInterceptor) (*Server, error) {
+	s := &Server{
+		grpcSrv: &grpcSrv{port: grpcPort},
+		httpSrv: h,
+	}
+
+	logrusEntry := log.NewEntry(log.StandardLogger())
+	levelOpt := grpc_logrus.WithLevels(codeToLogrusLevel)
+
+	unaryInterceptors = append(unaryInterceptors,
+		grpc_prometheus.UnaryServerInterceptor,
+		grpc_ctxtags.UnaryServerInterceptor(),
+		grpc_recovery.UnaryServerInterceptor(),
+		grpc_logrus.UnaryServerInterceptor(logrusEntry, levelOpt),
+	)
+
+	streamInterceptors = append(streamInterceptors,
+		grpc_prometheus.StreamServerInterceptor,
+		grpc_ctxtags.StreamServerInterceptor(),
+		grpc_recovery.StreamServerInterceptor(),
+		grpc_logrus.StreamServerInterceptor(logrusEntry, levelOpt),
+	)
+	unaryOpts := grpc_middleware.WithUnaryServerChain(unaryInterceptors...)
+	streamOpts := grpc_middleware.WithStreamServerChain(streamInterceptors...)
+
+	s.grpcSrv.srv = grpc.NewServer(unaryOpts, streamOpts)
+	reflection.Register(s.grpcSrv.srv)
+	grpc_prometheus.Register(s.grpcSrv.srv)
+	grpc_prometheus.EnableClientHandlingTimeHistogram()
+	grpc_prometheus.EnableHandlingTimeHistogram()
+	grpc_prometheus.Register(s.GRPC())
+
+	return s, nil
+}
+
+// HTTP creates an HTTP server instance
+func HTTP(port uint16) *http.Server {
+	h := &http.Server{
+		Addr:           fmt.Sprintf(":%d", port),
+		ReadTimeout:    10 * time.Second,
+		WriteTimeout:   10 * time.Second,
+		MaxHeaderBytes: 1 << 20,
+	}
+
+	return h
+}
+
+// Serve starts GRPC and HTTP serving
+func (s *Server) Serve() error {
+	var wg sync.WaitGroup
+
+	// GRPC
+	grpcLis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.grpcSrv.port))
+	if err != nil {
+		return fmt.Errorf("Unable to listen: %v", err)
+	}
+
+	wg.Add(1)
+	go func(wg *sync.WaitGroup) {
+		err := s.grpcSrv.srv.Serve(grpcLis)
+		log.Fatalf("GRPC serving failed: %v", err)
+		os.Exit(1)
+		wg.Done()
+	}(&wg)
+
+	// HTTP
+	http.Handle("/metrics", promhttp.Handler())
+	wg.Add(1)
+	go func(wg *sync.WaitGroup) {
+		err := s.httpSrv.ListenAndServe()
+		log.Fatalf("HTTP serving failed: %v", err)
+		os.Exit(1)
+		wg.Done()
+	}(&wg)
+
+	wg.Wait()
+	return nil
+}
+
+// GRPC get's the GRPC server object
+func (s *Server) GRPC() *grpc.Server {
+	return s.grpcSrv.srv
+}