diff --git a/examples/bmp/BUILD.bazel b/examples/bmp/BUILD.bazel
index 63c52adaba65907513eda97e260a79955d622d34..66cf5d6efb5b53bdb99944da2591fc4997b753b6 100644
--- a/examples/bmp/BUILD.bazel
+++ b/examples/bmp/BUILD.bazel
@@ -6,7 +6,7 @@ go_library(
     importpath = "github.com/bio-routing/bio-rd/examples/bmp",
     visibility = ["//visibility:private"],
     deps = [
-        "//protocols/bmp/server:go_default_library",
+        "//protocols/bgp/server:go_default_library",
         "//routingtable/locRIB:go_default_library",
         "//vendor/github.com/sirupsen/logrus:go_default_library",
     ],
diff --git a/examples/bmp/main_bmp.go b/examples/bmp/main_bmp.go
index 146fa4244f267d3fadfcf7fb1b5815524cfcc909..63de954fed0cbefea47ddd398d522b5d96e00b29 100644
--- a/examples/bmp/main_bmp.go
+++ b/examples/bmp/main_bmp.go
@@ -5,7 +5,7 @@ import (
 	"net"
 	"time"
 
-	"github.com/bio-routing/bio-rd/protocols/bmp/server"
+	"github.com/bio-routing/bio-rd/protocols/bgp/server"
 	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 	"github.com/sirupsen/logrus"
 )
@@ -13,13 +13,14 @@ import (
 func main() {
 	logrus.Printf("This is a BMP speaker\n")
 
-	rib := locRIB.New()
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
 	b := server.NewServer()
-	b.AddRouter(net.IP{127, 0, 0, 1}, 1234, rib, nil)
+	b.AddRouter(net.IP{10, 0, 255, 0}, 30119, rib4, rib6)
 
 	go func() {
 		for {
-			fmt.Printf("LocRIB count: %d\n", rib.Count())
+			fmt.Printf("LocRIB4 count: %d\n", rib4.Count())
 			time.Sleep(time.Second * 10)
 		}
 	}()
diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go
index 736137582bf1d73f5b474297cdaaf3c6cd8ba167..5ad26c878dfb728531f2a4bef87a3894c50640ed 100644
--- a/protocols/bgp/packet/decoder.go
+++ b/protocols/bgp/packet/decoder.go
@@ -30,7 +30,7 @@ func Decode(buf *bytes.Buffer, opt *DecodeOptions) (*BGPMessage, error) {
 func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16, opt *DecodeOptions) (interface{}, error) {
 	switch msgType {
 	case OpenMsg:
-		return decodeOpenMsg(buf)
+		return DecodeOpenMsg(buf)
 	case UpdateMsg:
 		return decodeUpdateMsg(buf, l, opt)
 	case KeepaliveMsg:
@@ -128,7 +128,8 @@ func invalidErrCode(n *BGPNotification) (*BGPNotification, error) {
 	return n, fmt.Errorf("Invalid error sub code: %d/%d", n.ErrorCode, n.ErrorSubcode)
 }
 
-func decodeOpenMsg(buf *bytes.Buffer) (*BGPOpen, error) {
+// DecodeOpenMsg decodes a BGP OPEN message
+func DecodeOpenMsg(buf *bytes.Buffer) (*BGPOpen, error) {
 	msg, err := _decodeOpenMsg(buf)
 	if err != nil {
 		return nil, fmt.Errorf("Unable to decode OPEN message: %v", err)
@@ -241,13 +242,13 @@ func decodeCapability(buf *bytes.Buffer) (Capability, error) {
 	case AddPathCapabilityCode:
 		addPathCap, err := decodeAddPathCapability(buf)
 		if err != nil {
-			return cap, fmt.Errorf("Unable to decode add path capability")
+			return cap, fmt.Errorf("Unable to decode add path capability: %v", err)
 		}
 		cap.Value = addPathCap
 	case ASN4CapabilityCode:
 		asn4Cap, err := decodeASN4Capability(buf)
 		if err != nil {
-			return cap, fmt.Errorf("Unable to decode 4 octet ASN capability")
+			return cap, fmt.Errorf("Unable to decode 4 octet ASN capability: %v", err)
 		}
 		cap.Value = asn4Cap
 	default:
diff --git a/protocols/bgp/packet/nlri_test.go b/protocols/bgp/packet/nlri_test.go
index 1efb81eaf44e758685923c2e553d3890a92c5592..e9e786bc77299bedbc5320fc3801ceb135c4a904 100644
--- a/protocols/bgp/packet/nlri_test.go
+++ b/protocols/bgp/packet/nlri_test.go
@@ -60,6 +60,42 @@ func TestDecodeNLRIs(t *testing.T) {
 	}
 }
 
+func TestDecodeNLRIv6(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		addPath  bool
+		wantFail bool
+		expected *NLRI
+	}{
+		{
+			name: "IPv6 default",
+			input: []byte{
+				0,
+			},
+			wantFail: false,
+			expected: &NLRI{
+				Prefix: bnet.NewPfx(bnet.IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0), 0),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		res, _, err := decodeNLRI(buf, IPv6AFI, test.addPath)
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
+
 func TestDecodeNLRI(t *testing.T) {
 	tests := []struct {
 		name     string
diff --git a/protocols/bgp/server/BUILD.bazel b/protocols/bgp/server/BUILD.bazel
index bd45a72d0abd1f419175febf884094109f6eb982..ea2b4ea6a2c5d575297404967aa5c6eee28eb900 100644
--- a/protocols/bgp/server/BUILD.bazel
+++ b/protocols/bgp/server/BUILD.bazel
@@ -3,6 +3,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 go_library(
     name = "go_default_library",
     srcs = [
+        "bmp_router.go",
+        "bmp_server.go",
         "fake_conn.go",
         "fsm.go",
         "fsm_active.go",
@@ -29,6 +31,7 @@ go_library(
         "//net:go_default_library",
         "//protocols/bgp/packet:go_default_library",
         "//protocols/bgp/types:go_default_library",
+        "//protocols/bmp/packet:go_default_library",
         "//route:go_default_library",
         "//routingtable:go_default_library",
         "//routingtable/adjRIBIn:go_default_library",
@@ -36,12 +39,15 @@ go_library(
         "//routingtable/filter:go_default_library",
         "//routingtable/locRIB:go_default_library",
         "//vendor/github.com/sirupsen/logrus:go_default_library",
+        "//vendor/github.com/taktv6/tflow2/convert:go_default_library",
     ],
 )
 
 go_test(
     name = "go_default_test",
     srcs = [
+        "bmp_router_test.go",
+        "bmp_server_test.go",
         "fsm_address_family_test.go",
         "fsm_open_sent_test.go",
         "fsm_test.go",
@@ -55,11 +61,14 @@ go_test(
         "//net:go_default_library",
         "//protocols/bgp/packet:go_default_library",
         "//protocols/bgp/types:go_default_library",
+        "//protocols/bmp/packet:go_default_library",
         "//route:go_default_library",
         "//routingtable:go_default_library",
+        "//routingtable/adjRIBIn:go_default_library",
         "//routingtable/filter:go_default_library",
         "//routingtable/locRIB:go_default_library",
         "//testing:go_default_library",
+        "//vendor/github.com/sirupsen/logrus:go_default_library",
         "//vendor/github.com/stretchr/testify/assert:go_default_library",
     ],
 )
diff --git a/protocols/bgp/server/bmp_router.go b/protocols/bgp/server/bmp_router.go
new file mode 100644
index 0000000000000000000000000000000000000000..84f5c668508137ea3ab13ed93e7051b2d282e0d7
--- /dev/null
+++ b/protocols/bgp/server/bmp_router.go
@@ -0,0 +1,404 @@
+package server
+
+import (
+	"bytes"
+	"fmt"
+	"net"
+	"sync"
+	"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/routingtable"
+	"github.com/bio-routing/bio-rd/routingtable/filter"
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+	log "github.com/sirupsen/logrus"
+	"github.com/taktv6/tflow2/convert"
+)
+
+type router struct {
+	name             string
+	address          net.IP
+	port             uint16
+	con              net.Conn
+	reconnectTimeMin int
+	reconnectTimeMax int
+	reconnectTime    int
+	reconnectTimer   *time.Timer
+	rib4             *locRIB.LocRIB
+	rib6             *locRIB.LocRIB
+	neighbors        map[[16]byte]*neighbor
+	neighborsMu      sync.Mutex
+	logger           *log.Logger
+	runMu            sync.Mutex
+	stop             chan struct{}
+
+	ribClients   map[afiClient]struct{}
+	ribClientsMu sync.Mutex
+}
+
+type neighbor struct {
+	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{
+		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),
+		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) 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)
+
+	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) serve(con net.Conn) {
+	r.con = con
+	r.runMu.Lock()
+	defer r.con.Close()
+	defer r.runMu.Unlock()
+
+	for {
+		select {
+		case <-r.stop:
+			return
+		default:
+		}
+
+		msg, err := recvBMPMsg(r.con)
+		if err != nil {
+			r.logger.Errorf("Unable to get message: %v", err)
+			return
+		}
+
+		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))
+		}
+	}
+}
+
+func (r *router) processRouteMonitoringMsg(msg *bmppkt.RouteMonitoringMsg) {
+	r.neighborsMu.Lock()
+	defer r.neighborsMu.Unlock()
+
+	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())
+		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) {
+	const (
+		stringType   = 0
+		sysDescrType = 1
+		sysNameType  = 2
+	)
+
+	logMsg := fmt.Sprintf("Received initiation 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 sysDescrType:
+			logMsg += fmt.Sprintf(" sysDescr.: %s", string(tlv.Information))
+		case sysNameType:
+			r.name = string(tlv.Information)
+			logMsg += fmt.Sprintf(" sysName.: %s", string(tlv.Information))
+		}
+	}
+
+	r.logger.Info(logMsg)
+}
+
+func (r *router) processTerminationMsg(msg *bmppkt.TerminationMessage) {
+	const (
+		stringType = 0
+		reasonType = 1
+
+		adminDown     = 0
+		unspecReason  = 1
+		outOfRes      = 2
+		redundantCon  = 3
+		permAdminDown = 4
+	)
+
+	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])
+			switch reason {
+			case adminDown:
+				logMsg += "Session administratively down"
+			case unspecReason:
+				logMsg += "Unespcified reason"
+			case outOfRes:
+				logMsg += "Out of resources"
+			case redundantCon:
+				logMsg += "Redundant connection"
+			case permAdminDown:
+				logMsg += "Session permanently administratively closed"
+			}
+		}
+	}
+
+	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)
+}
+
+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()
+		}
+
+		if r.neighbors[addr].fsm.ipv6Unicast != nil {
+			r.neighbors[addr].fsm.ipv6Unicast.bmpDispose()
+		}
+	}
+
+	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)
+	}
+
+	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)
+	}
+
+	sentOpen, err := packet.DecodeOpenMsg(bytes.NewBuffer(msg.SentOpenMsg[packet.HeaderLen:]))
+	if err != nil {
+		return fmt.Errorf("Unable to decode sent open message sent from %v to %v: %v", r.address.String(), msg.PerPeerHeader.PeerAddress, err)
+	}
+
+	if len(msg.ReceivedOpenMsg) < packet.MinOpenLen {
+		return fmt.Errorf("Received peer up notification for %v: Invalid received open message: %v", msg.PerPeerHeader.PeerAddress, msg.ReceivedOpenMsg)
+	}
+
+	recvOpen, err := packet.DecodeOpenMsg(bytes.NewBuffer(msg.ReceivedOpenMsg[packet.HeaderLen:]))
+	if err != nil {
+		return fmt.Errorf("Unable to decode received open message sent from %v to %v: %v", msg.PerPeerHeader.PeerAddress, r.address.String(), err)
+	}
+
+	addrLen := net.IPv4len
+	if msg.PerPeerHeader.GetIPVersion() == 6 {
+		addrLen = net.IPv6len
+	}
+
+	// bnet.IPFromBytes can only fail if length of argument is not 4 or 16. However, length is ensured here.
+	peerAddress, _ := bnet.IPFromBytes(msg.PerPeerHeader.PeerAddress[16-addrLen:])
+	localAddress, _ := bnet.IPFromBytes(msg.LocalAddress[16-addrLen:])
+
+	fsm := &FSM{
+		isBMP: true,
+		peer: &peer{
+			routerID:  sentOpen.BGPIdentifier,
+			addr:      peerAddress,
+			localAddr: localAddress,
+			peerASN:   msg.PerPeerHeader.PeerAS,
+			localASN:  uint32(sentOpen.ASN),
+			ipv4:      &peerAddressFamily{},
+			ipv6:      &peerAddressFamily{},
+		},
+	}
+
+	fsm.peer.configureBySentOpen(sentOpen)
+
+	fsm.ipv4Unicast = newFSMAddressFamily(packet.IPv4AFI, packet.UnicastSAFI, &peerAddressFamily{
+		rib:          r.rib4,
+		importFilter: filter.NewAcceptAllFilter(),
+	}, fsm)
+	fsm.ipv4Unicast.bmpInit()
+
+	fsm.ipv6Unicast = newFSMAddressFamily(packet.IPv6AFI, packet.UnicastSAFI, &peerAddressFamily{
+		rib:          r.rib6,
+		importFilter: filter.NewAcceptAllFilter(),
+	}, fsm)
+	fsm.ipv6Unicast.bmpInit()
+
+	fsm.state = newOpenSentState(fsm)
+	openSent := fsm.state.(*openSentState)
+	openSent.openMsgReceived(recvOpen)
+
+	fsm.state = newEstablishedState(fsm)
+	n := &neighbor{
+		localAS:     fsm.peer.localASN,
+		peerAS:      msg.PerPeerHeader.PeerAS,
+		peerAddress: msg.PerPeerHeader.PeerAddress,
+		routerID:    recvOpen.BGPIdentifier,
+		fsm:         fsm,
+		opt:         fsm.decodeOptions(),
+	}
+
+	r.neighbors[msg.PerPeerHeader.PeerAddress] = n
+
+	r.ribClientsMu.Lock()
+	defer r.ribClientsMu.Unlock()
+	n.registerClients(r.ribClients)
+
+	return nil
+}
+
+func (n *neighbor) registerClients(clients map[afiClient]struct{}) {
+	for ac := range clients {
+		if ac.afi == packet.IPv4AFI {
+			n.fsm.ipv4Unicast.adjRIBIn.Register(ac.client)
+		}
+		if ac.afi == packet.IPv6AFI {
+			n.fsm.ipv6Unicast.adjRIBIn.Register(ac.client)
+		}
+	}
+}
+
+func (p *peer) configureBySentOpen(msg *packet.BGPOpen) {
+	caps := getCaps(msg.OptParams)
+	for _, cap := range caps {
+		switch cap.Code {
+		case packet.AddPathCapabilityCode:
+			addPathCap := cap.Value.(packet.AddPathCapability)
+			peerFamily := p.addressFamily(addPathCap.AFI, addPathCap.SAFI)
+			if peerFamily == nil {
+				continue
+			}
+			switch addPathCap.SendReceive {
+			case packet.AddPathSend:
+				peerFamily.addPathSend = routingtable.ClientOptions{
+					MaxPaths: 10,
+				}
+			case packet.AddPathReceive:
+				peerFamily.addPathReceive = true
+			case packet.AddPathSendReceive:
+				peerFamily.addPathReceive = true
+				peerFamily.addPathSend = routingtable.ClientOptions{
+					MaxPaths: 10,
+				}
+			}
+		case packet.ASN4CapabilityCode:
+			asn4Cap := cap.Value.(packet.ASN4Capability)
+			p.localASN = asn4Cap.ASN4
+		}
+	}
+}
+
+func getCaps(optParams []packet.OptParam) packet.Capabilities {
+	for _, optParam := range optParams {
+		if optParam.Type != packet.CapabilitiesParamType {
+			continue
+		}
+
+		return optParam.Value.(packet.Capabilities)
+	}
+	return nil
+}
diff --git a/protocols/bgp/server/bmp_router_test.go b/protocols/bgp/server/bmp_router_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3c74e6b82b358c964b5ed58f6a2a8d3b28944de9
--- /dev/null
+++ b/protocols/bgp/server/bmp_router_test.go
@@ -0,0 +1,2746 @@
+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) {
+	tests := []struct {
+		name     string
+		msg      []byte
+		wantFail bool
+	}{
+		{
+			name:     "Test #1",
+			msg:      []byte{1, 2, 3},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		addr := net.IP{10, 20, 30, 40}
+		port := uint16(123)
+		rib4 := locRIB.New()
+		rib6 := locRIB.New()
+		conA, conB := net.Pipe()
+
+		r := newRouter(addr, port, rib4, rib6)
+		buf := bytes.NewBuffer(nil)
+		r.logger.Out = buf
+		go r.serve(conA)
+
+		conB.Write(test.msg)
+
+		assert.Equalf(t, 0, len(buf.Bytes()), "Test %q", test.name)
+	}
+}
+
+func TestStartStopBMP(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(123)
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	con := biotesting.NewMockConn()
+
+	r := newRouter(addr, port, rib4, rib6)
+	go r.serve(con)
+
+	r.stop <- struct{}{}
+
+	r.runMu.Lock()
+	assert.Equal(t, true, r.con.(*biotesting.MockConn).Closed)
+}
+
+func TestConfigureBySentOpen(t *testing.T) {
+	tests := []struct {
+		name     string
+		p        *peer
+		openMsg  *packet.BGPOpen
+		expected *peer
+	}{
+		{
+			name: "Test 32bit ASN",
+			p:    &peer{},
+			openMsg: &packet.BGPOpen{
+				OptParams: []packet.OptParam{
+					{
+						Type: 2,
+						Value: packet.Capabilities{
+							{
+								Code: packet.ASN4CapabilityCode,
+								Value: packet.ASN4Capability{
+									ASN4: 201701,
+								},
+							},
+						},
+					},
+				},
+			},
+			expected: &peer{
+				localASN: 201701,
+			},
+		},
+		{
+			name: "Test Add Path TX",
+			p: &peer{
+				ipv4: &peerAddressFamily{},
+			},
+			openMsg: &packet.BGPOpen{
+				OptParams: []packet.OptParam{
+					{
+						Type: 2,
+						Value: packet.Capabilities{
+							{
+								Code: packet.AddPathCapabilityCode,
+								Value: packet.AddPathCapability{
+									AFI:         1,
+									SAFI:        1,
+									SendReceive: 2,
+								},
+							},
+						},
+					},
+				},
+			},
+			expected: &peer{
+				ipv4: &peerAddressFamily{
+					addPathSend: routingtable.ClientOptions{
+						MaxPaths: 10,
+					},
+				},
+			},
+		},
+		{
+			name: "Test Add Path RX",
+			p: &peer{
+				ipv4: &peerAddressFamily{},
+			},
+			openMsg: &packet.BGPOpen{
+				OptParams: []packet.OptParam{
+					{
+						Type: 2,
+						Value: packet.Capabilities{
+							{
+								Code: packet.AddPathCapabilityCode,
+								Value: packet.AddPathCapability{
+									AFI:         1,
+									SAFI:        1,
+									SendReceive: 1,
+								},
+							},
+						},
+					},
+				},
+			},
+			expected: &peer{
+				ipv4: &peerAddressFamily{
+					addPathReceive: true,
+				},
+			},
+		},
+		{
+			name: "Test Add Path RX/TX",
+			p: &peer{
+				ipv4: &peerAddressFamily{},
+			},
+			openMsg: &packet.BGPOpen{
+				OptParams: []packet.OptParam{
+					{
+						Type: 2,
+						Value: packet.Capabilities{
+							{
+								Code: packet.AddPathCapabilityCode,
+								Value: packet.AddPathCapability{
+									AFI:         1,
+									SAFI:        1,
+									SendReceive: 3,
+								},
+							},
+						},
+					},
+				},
+			},
+			expected: &peer{
+				ipv4: &peerAddressFamily{
+					addPathSend: routingtable.ClientOptions{
+						MaxPaths: 10,
+					},
+					addPathReceive: true,
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		test.p.configureBySentOpen(test.openMsg)
+
+		assert.Equalf(t, test.expected, test.p, "Test %q", test.name)
+	}
+}
+
+func TestProcessPeerUpNotification(t *testing.T) {
+	tests := []struct {
+		name     string
+		router   *router
+		pkt      *bmppkt.PeerUpNotification
+		wantFail bool
+		expected *router
+	}{
+		{
+			name: "Invalid sent open message",
+			router: &router{
+				neighbors: make(map[[16]byte]*neighbor),
+			},
+			pkt: &bmppkt.PeerUpNotification{
+				PerPeerHeader: &bmppkt.PerPeerHeader{
+					PeerType:          0,
+					PeerFlags:         0,
+					PeerDistinguisher: 0,
+					PeerAddress:       [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 255, 1},
+					PeerAS:            51324,
+					PeerBGPID:         100,
+				},
+				LocalAddress:    [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 255, 0},
+				LocalPort:       179,
+				RemotePort:      34542,
+				SentOpenMsg:     []byte{},
+				ReceivedOpenMsg: []byte{},
+				Information:     []byte{},
+			},
+			wantFail: true,
+		},
+		{
+			name: "Invalid received open message",
+			router: &router{
+				neighbors: make(map[[16]byte]*neighbor),
+			},
+			pkt: &bmppkt.PeerUpNotification{
+				PerPeerHeader: &bmppkt.PerPeerHeader{
+					PeerType:          0,
+					PeerFlags:         0,
+					PeerDistinguisher: 0,
+					PeerAddress:       [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 255, 1},
+					PeerAS:            51324,
+					PeerBGPID:         100,
+				},
+				LocalAddress: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 255, 0},
+				LocalPort:    179,
+				RemotePort:   34542,
+				SentOpenMsg: []byte{
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 29,
+					1,
+					4,
+					100, 200,
+					20, 0,
+					10, 20, 30, 40,
+					0,
+				},
+				ReceivedOpenMsg: []byte{},
+				Information:     []byte{},
+			},
+			wantFail: true,
+		},
+		{
+			name: "Regular BGP by RFC4271",
+			router: &router{
+				rib4:      locRIB.New(),
+				rib6:      locRIB.New(),
+				neighbors: make(map[[16]byte]*neighbor),
+			},
+			pkt: &bmppkt.PeerUpNotification{
+				PerPeerHeader: &bmppkt.PerPeerHeader{
+					PeerType:          0,
+					PeerFlags:         0,
+					PeerDistinguisher: 0,
+					PeerAddress:       [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 255, 1},
+					PeerAS:            100,
+					PeerBGPID:         100,
+				},
+				LocalAddress: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 255, 0},
+				LocalPort:    179,
+				RemotePort:   34542,
+				SentOpenMsg: []byte{
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 29,
+					1,
+					4,
+					0, 200,
+					20, 0,
+					10, 20, 30, 40,
+					0,
+				},
+				ReceivedOpenMsg: []byte{
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 29,
+					1,
+					4,
+					0, 100,
+					20, 0,
+					10, 20, 30, 50,
+					0,
+				},
+				Information: []byte{},
+			},
+			wantFail: false,
+			expected: &router{
+				rib4: locRIB.New(),
+				rib6: locRIB.New(),
+				neighbors: map[[16]byte]*neighbor{
+					[16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 255, 1}: {
+						localAS:     200,
+						peerAS:      100,
+						peerAddress: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 255, 1},
+						routerID:    169090610,
+						opt: &packet.DecodeOptions{
+							AddPath:     false,
+							Use32BitASN: false,
+						},
+						fsm: &FSM{
+							isBMP:      true,
+							neighborID: 169090610,
+							state:      &establishedState{},
+							peer: &peer{
+								routerID:  169090600,
+								addr:      bnet.IPv4FromOctets(10, 0, 255, 1),
+								localAddr: bnet.IPv4FromOctets(10, 0, 255, 0),
+								peerASN:   100,
+								localASN:  200,
+								ipv4:      &peerAddressFamily{},
+								ipv6:      &peerAddressFamily{},
+							},
+							ipv4Unicast: &fsmAddressFamily{
+								afi:          1,
+								safi:         1,
+								adjRIBIn:     adjRIBIn.New(filter.NewAcceptAllFilter(), &routingtable.ContributingASNs{}, 169090600, 0, false),
+								importFilter: filter.NewAcceptAllFilter(),
+							},
+							ipv6Unicast: &fsmAddressFamily{
+								afi:          2,
+								safi:         1,
+								adjRIBIn:     adjRIBIn.New(filter.NewAcceptAllFilter(), &routingtable.ContributingASNs{}, 169090600, 0, false),
+								importFilter: filter.NewAcceptAllFilter(),
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		err := test.router.processPeerUpNotification(test.pkt)
+		if err != nil {
+			if test.wantFail {
+				continue
+			}
+
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		if test.wantFail {
+			t.Errorf("Unexpected success for test %q", test.name)
+			continue
+		}
+
+		test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.state = &establishedState{fsm: test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm}
+
+		if test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.ipv4Unicast != nil {
+			test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.ipv4Unicast.rib = test.router.rib4
+			test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.ipv4Unicast.fsm = test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm
+			test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.ipv4Unicast.adjRIBIn.Register(test.router.rib4)
+		}
+
+		if test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.ipv6Unicast != nil {
+			test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.ipv6Unicast.rib = test.router.rib6
+			test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.ipv6Unicast.fsm = test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm
+			test.expected.neighbors[test.pkt.PerPeerHeader.PeerAddress].fsm.ipv6Unicast.adjRIBIn.Register(test.router.rib6)
+		}
+
+		assert.Equalf(t, test.expected, test.router, "Test %q", test.name)
+	}
+
+}
+
+func TestProcessRouteMonitoringMsg(t *testing.T) {
+	tests := []struct {
+		name           string
+		r              *router
+		msg            *bmppkt.RouteMonitoringMsg
+		expectedLogBuf string
+		logOnly        bool
+		expected       *router
+	}{
+		{
+			name: "Unknown peer address",
+			r: &router{
+				address: net.IP{10, 20, 30, 40},
+				logger:  log.New(),
+			},
+			msg: &bmppkt.RouteMonitoringMsg{
+				PerPeerHeader: &bmppkt.PerPeerHeader{
+					PeerAddress: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+				},
+			},
+			expectedLogBuf: "level=error msg=\"Received route monitoring message for non-existent neighbor [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16] on 10.20.30.40\"",
+			logOnly:        true,
+			expected:       &router{},
+		},
+	}
+
+	for _, test := range tests {
+		logBuf := bytes.NewBuffer(nil)
+		test.r.logger.Out = logBuf
+		test.r.logger.Formatter = biotesting.NewLogFormatter()
+
+		test.expected.logger = test.r.logger
+
+		test.r.processRouteMonitoringMsg(test.msg)
+
+		assert.Equalf(t, test.expectedLogBuf, string(logBuf.Bytes()), "Test %q", test.name)
+		if test.logOnly {
+			continue
+		}
+
+		assert.Equalf(t, test.expected, test.r, "Test %q", test.name)
+	}
+}
+
+func TestProcessInitiationMsg(t *testing.T) {
+	tests := []struct {
+		name        string
+		r           *router
+		msg         *bmppkt.InitiationMessage
+		expectedLog string
+	}{
+		{
+			name: "Test #1",
+			r: &router{
+				address: net.IP{10, 20, 30, 40},
+				logger:  log.New(),
+			},
+			msg: &bmppkt.InitiationMessage{
+				TLVs: []*bmppkt.InformationTLV{
+					{
+						InformationType: 0,
+						Information:     []byte("Foo Bar"),
+					},
+					{
+						InformationType: 1,
+						Information:     []byte("SYS DESCR"),
+					},
+					{
+						InformationType: 2,
+						Information:     []byte("core01.fra01"),
+					},
+				},
+			},
+			expectedLog: "level=info msg=\"Received initiation message from 10.20.30.40: Message: \\\"Foo Bar\\\" sysDescr.: SYS DESCR sysName.: core01.fra01\"",
+		},
+	}
+
+	for _, test := range tests {
+		logBuf := bytes.NewBuffer(nil)
+		test.r.logger.Out = logBuf
+		test.r.logger.Formatter = biotesting.NewLogFormatter()
+
+		test.r.processInitiationMsg(test.msg)
+
+		assert.Equalf(t, test.expectedLog, string(logBuf.Bytes()), "Test %q", test.name)
+	}
+}
+
+func TestProcessTerminationMsg(t *testing.T) {
+	tests := []struct {
+		name        string
+		r           *router
+		msg         *bmppkt.TerminationMessage
+		expectedLog string
+		expected    *router
+	}{
+		{
+			name: "Test shutdown",
+			r: &router{
+				con:     &biotesting.MockConn{},
+				address: net.IP{10, 20, 30, 40},
+				logger:  log.New(),
+				neighbors: map[[16]byte]*neighbor{
+					[16]byte{1, 2, 3}: &neighbor{},
+				},
+			},
+			msg: &bmppkt.TerminationMessage{
+				TLVs: []*bmppkt.InformationTLV{
+					{
+						InformationType: 0, // string type
+						Information:     []byte("Foo Bar"),
+					},
+				},
+			},
+			expectedLog: "level=warning msg=\"Received termination message from 10.20.30.40: Message: \\\"Foo Bar\\\"\"",
+			expected: &router{
+				con: &biotesting.MockConn{
+					Closed: true,
+				},
+				address:   net.IP{10, 20, 30, 40},
+				neighbors: map[[16]byte]*neighbor{},
+			},
+		},
+		{
+			name: "Test logs",
+			r: &router{
+				con:     &biotesting.MockConn{},
+				address: net.IP{10, 20, 30, 40},
+				logger:  log.New(),
+				neighbors: map[[16]byte]*neighbor{
+					[16]byte{1, 2, 3}: &neighbor{},
+				},
+			},
+			msg: &bmppkt.TerminationMessage{
+				TLVs: []*bmppkt.InformationTLV{
+					{
+						InformationType: 1, // reason type
+						Information:     []byte{0, 0},
+					},
+					{
+						InformationType: 1, // reason type
+						Information:     []byte{0, 1},
+					},
+					{
+						InformationType: 1, // reason type
+						Information:     []byte{0, 2},
+					},
+					{
+						InformationType: 1, // reason type
+						Information:     []byte{0, 3},
+					},
+					{
+						InformationType: 1, // reason type
+						Information:     []byte{0, 4},
+					},
+				},
+			},
+			expectedLog: "level=warning msg=\"Received termination message from 10.20.30.40: Session administratively downUnespcified reasonOut of resourcesRedundant connectionSession permanently administratively closed\"",
+			expected: &router{
+				con: &biotesting.MockConn{
+					Closed: true,
+				},
+				address:   net.IP{10, 20, 30, 40},
+				neighbors: map[[16]byte]*neighbor{},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		logBuf := bytes.NewBuffer(nil)
+		test.r.logger.Out = logBuf
+		test.r.logger.Formatter = biotesting.NewLogFormatter()
+
+		test.expected.logger = test.r.logger
+
+		test.r.processTerminationMsg(test.msg)
+
+		assert.Equalf(t, test.expectedLog, string(logBuf.Bytes()), "Test %q", test.name)
+		assert.Equalf(t, test.expected, test.r, "Test %q", test.name)
+	}
+}
+
+func TestProcessPeerDownNotification(t *testing.T) {
+	tests := []struct {
+		name        string
+		r           *router
+		msg         *bmppkt.PeerDownNotification
+		expectedLog string
+		expected    *router
+	}{
+		{
+			name: "Peer down notification for existing peer",
+			r: &router{
+				address: net.IP{10, 20, 30, 40},
+				logger:  log.New(),
+				neighbors: map[[16]byte]*neighbor{
+					[16]byte{1, 2, 3}: &neighbor{},
+				},
+			},
+			msg: &bmppkt.PeerDownNotification{
+				PerPeerHeader: &bmppkt.PerPeerHeader{
+					PeerAddress: [16]byte{1, 2, 3},
+				},
+			},
+			expectedLog: "",
+			expected: &router{
+				address:   net.IP{10, 20, 30, 40},
+				neighbors: map[[16]byte]*neighbor{},
+			},
+		},
+		{
+			name: "Peer down notification for non-existing peer",
+			r: &router{
+				address: net.IP{10, 20, 30, 40},
+				logger:  log.New(),
+				neighbors: map[[16]byte]*neighbor{
+					[16]byte{1, 2, 3}: &neighbor{},
+				},
+			},
+			msg: &bmppkt.PeerDownNotification{
+				PerPeerHeader: &bmppkt.PerPeerHeader{
+					PeerAddress: [16]byte{10, 20, 30},
+				},
+			},
+			expectedLog: "level=warning msg=\"Received peer down notification for [10 20 30 0 0 0 0 0 0 0 0 0 0 0 0 0]: Peer doesn't exist.\"",
+			expected: &router{
+				address: net.IP{10, 20, 30, 40},
+				neighbors: map[[16]byte]*neighbor{
+					[16]byte{1, 2, 3}: &neighbor{},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		logBuf := bytes.NewBuffer(nil)
+		test.r.logger.Out = logBuf
+		test.r.logger.Formatter = biotesting.NewLogFormatter()
+
+		test.expected.logger = test.r.logger
+
+		test.r.processPeerDownNotification(test.msg)
+
+		assert.Equalf(t, test.expectedLog, string(logBuf.Bytes()), "Test %q", test.name)
+		assert.Equalf(t, test.expected, test.r, "Test %q", test.name)
+	}
+}
+
+func TestRegisterClients(t *testing.T) {
+	n := &neighbor{
+		fsm: &FSM{
+			ipv4Unicast: &fsmAddressFamily{
+				adjRIBIn: locRIB.New(),
+			},
+			ipv6Unicast: &fsmAddressFamily{
+				adjRIBIn: locRIB.New(),
+			},
+		},
+	}
+
+	client4 := locRIB.New()
+	client6 := locRIB.New()
+	ac4 := afiClient{
+		afi:    packet.IPv4AFI,
+		client: client4,
+	}
+	ac6 := afiClient{
+		afi:    packet.IPv6AFI,
+		client: client6,
+	}
+	clients4 := map[afiClient]struct{}{
+		ac4: struct{}{},
+	}
+	clients6 := map[afiClient]struct{}{
+		ac6: struct{}{},
+	}
+
+	n.registerClients(clients4)
+	n.registerClients(clients6)
+	n.fsm.ipv4Unicast.adjRIBIn.AddPath(bnet.NewPfx(bnet.IPv4(0), 0), &route.Path{
+		BGPPath: &route.BGPPath{
+			LocalPref: 100,
+		},
+	})
+	n.fsm.ipv6Unicast.adjRIBIn.AddPath(bnet.NewPfx(bnet.IPv6(0, 0), 0), &route.Path{
+		BGPPath: &route.BGPPath{
+			LocalPref: 200,
+		},
+	})
+
+	assert.Equal(t, int64(1), n.fsm.ipv4Unicast.adjRIBIn.RouteCount())
+	assert.Equal(t, int64(1), n.fsm.ipv6Unicast.adjRIBIn.RouteCount())
+}
+
+func TestIntegrationPeerUpRouteMonitor(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	conA, conB := net.Pipe()
+
+	go r.serve(conB)
+
+	// Peer Up Notification
+	_, err := conA.Write([]byte{
+		// Common Header
+		3,            // Version
+		0, 0, 0, 126, // Message Length
+		3, // Msg Type = Peer Up Notification
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		0, // Opt param length
+
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		0, // Opt param length
+
+		// SECOND MESSAGE:
+
+		// Common Header
+		3,            // Version
+		0, 0, 0, 116, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 68, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 41, // 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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+
+		// NLRI
+		24,
+		192, 168, 0,
+	})
+
+	if err != nil {
+		panic("write failed")
+	}
+
+	time.Sleep(time.Millisecond * 50)
+	assert.NotEmpty(t, r.neighbors)
+
+	time.Sleep(time.Millisecond * 50)
+
+	count := rib4.RouteCount()
+	if count != 1 {
+		t.Errorf("Unexpected route count. Expected: 1 Got: %d", count)
+	}
+
+	conA.Close()
+}
+
+func TestIntegrationPeerUpRouteMonitorIPv6IPv4(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	conA, conB := net.Pipe()
+
+	go r.serve(conB)
+
+	// Peer Up Notification
+	_, err := conA.Write([]byte{
+		// Common Header
+		3,            // Version
+		0, 0, 0, 142, // Message Length
+		3, // Msg Type = Peer Up Notification
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 37, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		8, // Opt param length
+		2, 6,
+		1, 4, // MP BGP
+		0, 2, // IPv6
+		0, 1, // Unicast
+
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 37, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		8, // Opt param length
+		2, 6,
+		1, 4, // MP BGP
+		0, 2, // IPv6
+		0, 1, // Unicast
+
+		// SECOND MESSAGE:
+
+		// Common Header
+		3,            // Version
+		0, 0, 0, 138, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 90, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 67, // Total Path Attribute Length
+
+		255,
+		14,    // MP REACH NLRI
+		0, 22, // Length
+		0, 2, // IPv6
+		1,  // Unicast
+		16, // IPv6 Next Hop
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0, // Reserved
+		0, // Pfxlen /0
+
+		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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+
+		// THIRD MESSAGE
+		// Common Header
+		3,            // Version
+		0, 0, 0, 113, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 65, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 41, // 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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+
+		0, // /0
+	})
+
+	if err != nil {
+		panic("write failed")
+	}
+
+	time.Sleep(time.Millisecond * 50)
+	assert.NotEmpty(t, r.neighbors)
+
+	time.Sleep(time.Millisecond * 50)
+
+	count := rib6.RouteCount()
+	if count != 1 {
+		t.Errorf("Unexpected IPv6 route count. Expected: 1 Got: %d", count)
+	}
+
+	count = rib4.RouteCount()
+	if count != 1 {
+		t.Errorf("Unexpected IPv4 route count. Expected: 1 Got: %d", count)
+	}
+
+	conA.Close()
+}
+
+func TestIntegrationPeerUpRouteMonitorIPv4IPv6(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	conA, conB := net.Pipe()
+
+	go r.serve(conB)
+
+	// Peer Up Notification
+	_, err := conA.Write([]byte{
+		// Common Header
+		3,            // Version
+		0, 0, 0, 142, // Message Length
+		3, // Msg Type = Peer Up Notification
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 37, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		8, // Opt param length
+		2, 6,
+		1, 4, // MP BGP
+		0, 2, // IPv6
+		0, 1, // Unicast
+
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 37, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		8, // Opt param length
+		2, 6,
+		1, 4, // MP BGP
+		0, 2, // IPv6
+		0, 1, // Unicast
+
+		// SECOND MESSAGE:
+
+		// Common Header
+		3,            // Version
+		0, 0, 0, 113, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 65, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 41, // 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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+
+		0, // /0
+
+		// THIRD MESSAGE
+
+		// Common Header
+		3,            // Version
+		0, 0, 0, 138, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 90, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 67, // Total Path Attribute Length
+
+		255,
+		14,    // MP REACH NLRI
+		0, 22, // Length
+		0, 2, // IPv6
+		1,  // Unicast
+		16, // IPv6 Next Hop
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0, // Reserved
+		0, // Pfxlen /0
+
+		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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+	})
+
+	if err != nil {
+		panic("write failed")
+	}
+
+	time.Sleep(time.Millisecond * 50)
+	assert.NotEmpty(t, r.neighbors)
+
+	time.Sleep(time.Millisecond * 50)
+
+	count := rib6.RouteCount()
+	if count != 1 {
+		t.Errorf("Unexpected IPv6 route count. Expected: 1 Got: %d", count)
+	}
+
+	count = rib4.RouteCount()
+	if count != 1 {
+		t.Errorf("Unexpected IPv4 route count. Expected: 1 Got: %d", count)
+	}
+
+	conA.Close()
+}
+
+func TestIntegrationPeerUpRouteMonitorIPv6(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	conA, conB := net.Pipe()
+
+	go r.serve(conB)
+
+	// Peer Up Notification
+	_, err := conA.Write([]byte{
+		// Common Header
+		3,            // Version
+		0, 0, 0, 142, // Message Length
+		3, // Msg Type = Peer Up Notification
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 37, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		8, // Opt param length
+		2, 6,
+		1, 4, // MP BGP
+		0, 2, // IPv6
+		0, 1, // Unicast
+
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 37, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		8, // Opt param length
+		2, 6,
+		1, 4, // MP BGP
+		0, 2, // IPv6
+		0, 1, // Unicast
+
+		// SECOND MESSAGE:
+
+		// Common Header
+		3,            // Version
+		0, 0, 0, 138, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 90, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 67, // Total Path Attribute Length
+
+		255,
+		14,    // MP REACH NLRI
+		0, 22, // Length
+		0, 2, // IPv6
+		1,  // Unicast
+		16, // IPv6 Next Hop
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0, // Reserved
+		0, // Pfxlen /0
+
+		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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+	})
+
+	if err != nil {
+		panic("write failed")
+	}
+
+	time.Sleep(time.Millisecond * 50)
+	assert.NotEmpty(t, r.neighbors)
+
+	time.Sleep(time.Millisecond * 50)
+
+	count := rib6.RouteCount()
+	if count != 1 {
+		t.Errorf("Unexpected IPv6 route count. Expected: 1 Got: %d", count)
+	}
+
+	count = rib4.RouteCount()
+	if count != 0 {
+		t.Errorf("Unexpected IPv4 route count. Expected: 0 Got: %d", count)
+	}
+
+	conA.Close()
+}
+
+func TestIntegrationIncompleteBMPMsg(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	con := biotesting.NewMockConn()
+
+	// Peer Up Notification with invalid version number (4)
+	con.Write([]byte{
+		// Common Header
+		4,            // Version
+		0, 0, 0, 126, // Message Length
+		3, // Msg Type = Peer Up Notification
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		0, // Opt param length
+
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		0, // Opt param length
+
+		// SECOND MESSAGE:
+
+		// Common Header
+		3,            // Version
+		0, 0, 0, 116, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+	})
+
+	r.serve(con)
+
+	if !con.Closed {
+		t.Errorf("Connection not closed although failure should have happened")
+	}
+}
+
+func TestBMPFullRunWithWithdraw(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	con := biotesting.NewMockConn()
+
+	go r.serve(con)
+
+	con.Write([]byte{
+		// #####################################################################
+		// Initiation Message
+		// #####################################################################
+		// Common Header
+		3,
+		0, 0, 0, 23,
+		4,
+		0, 1, // sysDescr
+		0, 4, // Length
+		42, 42, 42, 42, // AAAA
+		0, 2, //sysName
+		0, 5, // Length
+		43, 43, 43, 43, 43, // BBBBB
+
+		// #####################################################################
+		// Peer UP Notification
+		// #####################################################################
+		// Common Header
+		3,            // Version
+		0, 0, 0, 126, // Message Length
+		3, // Msg Type = Peer Up Notification
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		0, // Opt param length
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		0, // Opt param length
+
+		// #####################################################################
+		// Route Monitoring Message #1
+		// #####################################################################
+		// Common Header
+		3,            // Version
+		0, 0, 0, 116, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 68, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 41, // 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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+
+		// NLRI
+		24,
+		192, 168, 0,
+
+		// #####################################################################
+		// Route Monitoring Message #2 (withdraw of prefix from #1)
+		// #####################################################################
+		// Common Header
+		3,           // Version
+		0, 0, 0, 75, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 27, // Length
+		2, // Update
+
+		0, 4, // Withdrawn Routes Length
+		24,
+		192, 168, 0,
+		0, 0, // Total Path Attribute Length
+	})
+
+	time.Sleep(time.Millisecond * 50)
+	assert.NotEmpty(t, r.neighbors)
+
+	time.Sleep(time.Millisecond * 50)
+
+	count := rib4.RouteCount()
+	if count != 0 {
+		t.Errorf("Unexpected route count. Expected: 0 Got: %d", count)
+	}
+
+}
+
+func TestBMPFullRunWithPeerDownNotification(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	con := biotesting.NewMockConn()
+
+	go r.serve(con)
+
+	con.Write([]byte{
+		// #####################################################################
+		// Initiation Message
+		// #####################################################################
+		// Common Header
+		3,
+		0, 0, 0, 23,
+		4,
+		0, 1, // sysDescr
+		0, 4, // Length
+		42, 42, 42, 42, // AAAA
+		0, 2, //sysName
+		0, 5, // Length
+		43, 43, 43, 43, 43, // BBBBB
+
+		// #####################################################################
+		// Peer UP Notification
+		// #####################################################################
+		// Common Header
+		3,            // Version
+		0, 0, 0, 126, // Message Length
+		3, // Msg Type = Peer Up Notification
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		0, // Opt param length
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		0, // Opt param length
+
+		// #####################################################################
+		// Route Monitoring Message #1
+		// #####################################################################
+		// Common Header
+		3,            // Version
+		0, 0, 0, 116, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 68, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 41, // 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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+
+		// NLRI
+		24,
+		192, 168, 0,
+
+		// #####################################################################
+		// Peer Down Notification
+		// #####################################################################
+		// Common Header
+		3,           // Version
+		0, 0, 0, 49, // Message Length
+		2, // Msg Type = Peer Down Notification
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		4, // Reason
+	})
+
+	time.Sleep(time.Millisecond * 50)
+	assert.Empty(t, r.neighbors)
+
+	time.Sleep(time.Millisecond * 50)
+
+	count := rib4.RouteCount()
+	if count != 0 {
+		t.Errorf("Unexpected route count. Expected: 0 Got: %d", count)
+	}
+}
+
+func TestBMPFullRunWithTerminationMessage(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	con := biotesting.NewMockConn()
+
+	go r.serve(con)
+
+	con.Write([]byte{
+		// #####################################################################
+		// Initiation Message
+		// #####################################################################
+		// Common Header
+		3,
+		0, 0, 0, 23,
+		4,
+		0, 1, // sysDescr
+		0, 4, // Length
+		42, 42, 42, 42, // AAAA
+		0, 2, //sysName
+		0, 5, // Length
+		43, 43, 43, 43, 43, // BBBBB
+
+		// #####################################################################
+		// Peer UP Notification
+		// #####################################################################
+		// Common Header
+		3,            // Version
+		0, 0, 0, 126, // Message Length
+		3, // Msg Type = Peer Up Notification
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		0, // Opt param length
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 29, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		0, // Opt param length
+
+		// #####################################################################
+		// Route Monitoring Message #1
+		// #####################################################################
+		// Common Header
+		3,            // Version
+		0, 0, 0, 116, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 68, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 41, // 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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+
+		// NLRI
+		24,
+		192, 168, 0,
+
+		// #####################################################################
+		// Termination Message
+		// #####################################################################
+		// Common Header
+		3,           // Version
+		0, 0, 0, 12, // Message Length
+		5, // Msg Type = Termination Message
+
+		// TLV
+		0, 0, 0, 2,
+		42, 42,
+	})
+
+	time.Sleep(time.Millisecond * 50)
+	assert.Empty(t, r.neighbors)
+
+	time.Sleep(time.Millisecond * 50)
+
+	count := rib4.RouteCount()
+	if count != 0 {
+		t.Errorf("Unexpected route count. Expected: 0 Got: %d", count)
+	}
+}
+
+func TestIntegrationPeerUpRouteMonitorIPv6WithClientAtEnd(t *testing.T) {
+	addr := net.IP{10, 20, 30, 40}
+	port := uint16(12346)
+
+	rib4 := locRIB.New()
+	rib6 := locRIB.New()
+
+	r := newRouter(addr, port, rib4, rib6)
+	conA, conB := net.Pipe()
+
+	go r.serve(conB)
+
+	// Peer Up Notification
+	_, err := conA.Write([]byte{
+		// Common Header
+		3,            // Version
+		0, 0, 0, 142, // Message Length
+		3, // Msg Type = Peer Up Notification
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// Peer Up Notification
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+		0, 123, // Local Port
+		0, 234, // Remote Port
+
+		// Sent OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 37, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 200, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 1, // BGP Identifier
+		8, // Opt param length
+		2, 6,
+		1, 4, // MP BGP
+		0, 2, // IPv6
+		0, 1, // Unicast
+
+		// Received OPEN message
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 37, // Length
+		1,      // Open message type
+		4,      // BGP Version
+		0, 100, // AS
+		0, 180, // Hold Time
+		1, 0, 0, 255, // BGP Identifier
+		8, // Opt param length
+		2, 6,
+		1, 4, // MP BGP
+		0, 2, // IPv6
+		0, 1, // Unicast
+
+		// SECOND MESSAGE:
+
+		// Common Header
+		3,            // Version
+		0, 0, 0, 138, // Message Length
+		0, // Msg Type = Route Monitoring Message
+
+		// Per Peer Header
+		0,                      // Peer Type
+		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, 20, 30, 40, // 10.20.30.40 peer address
+		0, 0, 0, 100, // Peer AS
+		0, 0, 0, 255, // Peer BGP ID
+		0, 0, 0, 0, // Timestamp s
+		0, 0, 0, 0, // Timestamp µs
+
+		// BGP Update
+		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+		0, 90, // Length
+		2, // Update
+
+		0, 0, // Withdrawn Routes Length
+		0, 67, // Total Path Attribute Length
+
+		255,
+		14,    // MP REACH NLRI
+		0, 22, // Length
+		0, 2, // IPv6
+		1,  // Unicast
+		16, // IPv6 Next Hop
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0, // Reserved
+		0, // Pfxlen /0
+
+		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
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+	})
+
+	if err != nil {
+		panic("write failed")
+	}
+
+	time.Sleep(time.Millisecond * 50)
+	assert.NotEmpty(t, r.neighbors)
+
+	time.Sleep(time.Millisecond * 50)
+
+	count := rib6.RouteCount()
+	if count != 1 {
+		t.Errorf("Unexpected IPv6 route count. Expected: 1 Got: %d", count)
+	}
+
+	count = rib4.RouteCount()
+	if count != 0 {
+		t.Errorf("Unexpected IPv4 route count. Expected: 0 Got: %d", count)
+	}
+
+	client6 := locRIB.New()
+	r.subscribeRIBs(client6, packet.IPv6AFI)
+
+	count = client6.RouteCount()
+	if count != 1 {
+		t.Errorf("Unexpected IPv6 route count. Expected: 1 Got: %d", count)
+	}
+
+	conA.Close()
+}
+
+func TestIntegrationPeerUpRouteMonitorIPv6WithClientBeforeBMPPeer(t *testing.T) {
+	tests := []struct {
+		name               string
+		afi                uint8
+		unregister         bool
+		doubleSubscribe    bool
+		doubleUnsubscribe  bool
+		input              []byte
+		expectedRouteCount int
+	}{
+		{
+			name:       "IPv4 without unregister",
+			afi:        packet.IPv4AFI,
+			unregister: false,
+			input: []byte{
+				// Common Header
+				3,            // Version
+				0, 0, 0, 142, // Message Length
+				3, // Msg Type = Peer Up Notification
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// Peer Up Notification
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+				0, 123, // Local Port
+				0, 234, // Remote Port
+
+				// Sent OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 200, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 1, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// Received OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 100, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 255, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// SECOND MESSAGE:
+
+				// Common Header
+				3,            // Version
+				0, 0, 0, 116, // Message Length
+				0, // Msg Type = Route Monitoring Message
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// BGP Update
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 68, // Length
+				2, // Update
+
+				0, 0, // Withdrawn Routes Length
+				0, 41, // 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
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (MED)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+				0,          // Attribute flags
+				5,          // Attribute Type code (Local Pref)
+				4,          // Length
+				0, 0, 1, 0, // Local Pref 256
+
+				// NLRI
+				24,
+				192, 168, 0,
+			},
+			expectedRouteCount: 1,
+		},
+		{
+			name:       "IPv4 with unregister",
+			afi:        packet.IPv4AFI,
+			unregister: true,
+			input: []byte{
+				// Common Header
+				3,            // Version
+				0, 0, 0, 142, // Message Length
+				3, // Msg Type = Peer Up Notification
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// Peer Up Notification
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+				0, 123, // Local Port
+				0, 234, // Remote Port
+
+				// Sent OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 200, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 1, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// Received OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 100, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 255, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// SECOND MESSAGE:
+
+				// Common Header
+				3,            // Version
+				0, 0, 0, 116, // Message Length
+				0, // Msg Type = Route Monitoring Message
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// BGP Update
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 68, // Length
+				2, // Update
+
+				0, 0, // Withdrawn Routes Length
+				0, 41, // 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
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (MED)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+				0,          // Attribute flags
+				5,          // Attribute Type code (Local Pref)
+				4,          // Length
+				0, 0, 1, 0, // Local Pref 256
+
+				// NLRI
+				24,
+				192, 168, 0,
+			},
+			expectedRouteCount: 1,
+		},
+		{
+			name:              "IPv4 with double unregister",
+			afi:               packet.IPv4AFI,
+			doubleUnsubscribe: true,
+			unregister:        true,
+			input: []byte{
+				// Common Header
+				3,            // Version
+				0, 0, 0, 142, // Message Length
+				3, // Msg Type = Peer Up Notification
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// Peer Up Notification
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+				0, 123, // Local Port
+				0, 234, // Remote Port
+
+				// Sent OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 200, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 1, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// Received OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 100, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 255, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// SECOND MESSAGE:
+
+				// Common Header
+				3,            // Version
+				0, 0, 0, 116, // Message Length
+				0, // Msg Type = Route Monitoring Message
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// BGP Update
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 68, // Length
+				2, // Update
+
+				0, 0, // Withdrawn Routes Length
+				0, 41, // 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
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (MED)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+				0,          // Attribute flags
+				5,          // Attribute Type code (Local Pref)
+				4,          // Length
+				0, 0, 1, 0, // Local Pref 256
+
+				// NLRI
+				24,
+				192, 168, 0,
+			},
+			expectedRouteCount: 1,
+		},
+		{
+			name:            "IPv4 with double register",
+			afi:             packet.IPv4AFI,
+			doubleSubscribe: true,
+			unregister:      true,
+			input: []byte{
+				// Common Header
+				3,            // Version
+				0, 0, 0, 142, // Message Length
+				3, // Msg Type = Peer Up Notification
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// Peer Up Notification
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+				0, 123, // Local Port
+				0, 234, // Remote Port
+
+				// Sent OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 200, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 1, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// Received OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 100, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 255, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// SECOND MESSAGE:
+
+				// Common Header
+				3,            // Version
+				0, 0, 0, 116, // Message Length
+				0, // Msg Type = Route Monitoring Message
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// BGP Update
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 68, // Length
+				2, // Update
+
+				0, 0, // Withdrawn Routes Length
+				0, 41, // 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
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (MED)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+				0,          // Attribute flags
+				5,          // Attribute Type code (Local Pref)
+				4,          // Length
+				0, 0, 1, 0, // Local Pref 256
+
+				// NLRI
+				24,
+				192, 168, 0,
+			},
+			expectedRouteCount: 1,
+		},
+		{
+			name:       "IPv6 without unregister",
+			afi:        packet.IPv6AFI,
+			unregister: false,
+			input: []byte{
+				// Common Header
+				3,            // Version
+				0, 0, 0, 142, // Message Length
+				3, // Msg Type = Peer Up Notification
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// Peer Up Notification
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 30, 41, // 10.20.30.41 local address
+				0, 123, // Local Port
+				0, 234, // Remote Port
+
+				// Sent OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 200, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 1, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// Received OPEN message
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 37, // Length
+				1,      // Open message type
+				4,      // BGP Version
+				0, 100, // AS
+				0, 180, // Hold Time
+				1, 0, 0, 255, // BGP Identifier
+				8, // Opt param length
+				2, 6,
+				1, 4, // MP BGP
+				0, 2, // IPv6
+				0, 1, // Unicast
+
+				// SECOND MESSAGE:
+
+				// Common Header
+				3,            // Version
+				0, 0, 0, 138, // Message Length
+				0, // Msg Type = Route Monitoring Message
+
+				// Per Peer Header
+				0,                      // Peer Type
+				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, 20, 30, 40, // 10.20.30.40 peer address
+				0, 0, 0, 100, // Peer AS
+				0, 0, 0, 255, // Peer BGP ID
+				0, 0, 0, 0, // Timestamp s
+				0, 0, 0, 0, // Timestamp µs
+
+				// BGP Update
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 90, // Length
+				2, // Update
+
+				0, 0, // Withdrawn Routes Length
+				0, 67, // Total Path Attribute Length
+
+				255,
+				14,    // MP REACH NLRI
+				0, 22, // Length
+				0, 2, // IPv6
+				1,  // Unicast
+				16, // IPv6 Next Hop
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0, // Reserved
+				0, // Pfxlen /0
+
+				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
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (MED)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+				0,          // Attribute flags
+				5,          // Attribute Type code (Local Pref)
+				4,          // Length
+				0, 0, 1, 0, // Local Pref 256
+			},
+			expectedRouteCount: 1,
+		},
+	}
+
+	for _, test := range tests {
+		addr := net.IP{10, 20, 30, 40}
+		port := uint16(12346)
+
+		rib4 := locRIB.New()
+		rib6 := locRIB.New()
+
+		r := newRouter(addr, port, rib4, rib6)
+		conA, conB := net.Pipe()
+
+		client := locRIB.New()
+		r.subscribeRIBs(client, test.afi)
+		if test.doubleSubscribe {
+			r.subscribeRIBs(client, test.afi)
+		}
+
+		if test.unregister {
+			r.unsubscribeRIBs(client, test.afi)
+			if test.doubleUnsubscribe {
+				r.unsubscribeRIBs(client, test.afi)
+			}
+		}
+
+		go r.serve(conB)
+
+		_, err := conA.Write(test.input)
+
+		if err != nil {
+			panic("write failed")
+		}
+
+		time.Sleep(time.Millisecond * 50)
+
+		expectedCount := int64(1)
+		if test.unregister {
+			expectedCount = 0
+		}
+
+		count := client.RouteCount()
+		if count != expectedCount {
+			t.Errorf("Unexpected route count for test %q. Expected: %d Got: %d", test.name, expectedCount, count)
+		}
+
+		conA.Close()
+	}
+}
diff --git a/protocols/bgp/server/bmp_server.go b/protocols/bgp/server/bmp_server.go
new file mode 100644
index 0000000000000000000000000000000000000000..d49355ee030d4d68b192c8ad33a7cf751f8c10cb
--- /dev/null
+++ b/protocols/bgp/server/bmp_server.go
@@ -0,0 +1,152 @@
+package server
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"time"
+
+	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"
+	log "github.com/sirupsen/logrus"
+	"github.com/taktv6/tflow2/convert"
+)
+
+const (
+	defaultBufferLen = 4096
+)
+
+// BMPServer represents a BMP server
+type BMPServer struct {
+	routers    map[string]*router
+	ribClients map[string]map[afiClient]struct{}
+	gloablMu   sync.RWMutex
+}
+
+type afiClient struct {
+	afi    uint8
+	client routingtable.RouteTableClient
+}
+
+// NewServer creates a new BMP server
+func NewServer() *BMPServer {
+	return &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)
+}
+
+// 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) {
+	b.gloablMu.Lock()
+	defer b.gloablMu.Unlock()
+
+	r := newRouter(addr, port, rib4, rib6)
+	b.routers[fmt.Sprintf("%s", r.address.String())] = r
+
+	go func(r *router) {
+		for {
+			<-r.reconnectTimer.C
+			c, err := net.Dial("tcp", fmt.Sprintf("%s:%d", r.address.String(), r.port))
+			if err != nil {
+				log.Infof("Unable to connect to BMP router: %v", err)
+				if r.reconnectTime == 0 {
+					r.reconnectTime = r.reconnectTimeMin
+				} else if r.reconnectTime < r.reconnectTimeMax {
+					r.reconnectTime *= 2
+				}
+				r.reconnectTimer = time.NewTimer(time.Second * time.Duration(r.reconnectTime))
+				continue
+			}
+
+			r.reconnectTime = 0
+			log.Infof("Connected to %s", r.address.String())
+			r.serve(c)
+		}
+	}(r)
+}
+
+// RemoveRouter removes a BMP monitored router
+func (b *BMPServer) RemoveRouter(addr net.IP, port uint16) {
+	b.gloablMu.Lock()
+	defer b.gloablMu.Unlock()
+
+	id := addr.String()
+	r := b.routers[id]
+	r.stop <- struct{}{}
+	delete(b.routers, id)
+}
+
+func recvBMPMsg(c net.Conn) (msg []byte, err error) {
+	buffer := make([]byte, defaultBufferLen)
+	_, err = io.ReadFull(c, buffer[0:bmppkt.MinLen])
+	if err != nil {
+		return nil, fmt.Errorf("Read failed: %v", err)
+	}
+
+	l := convert.Uint32b(buffer[1:5])
+	if l > defaultBufferLen {
+		tmp := buffer
+		buffer = make([]byte, l)
+		copy(buffer, tmp)
+	}
+
+	toRead := l
+	_, err = io.ReadFull(c, buffer[bmppkt.MinLen:toRead])
+	if err != nil {
+		return nil, fmt.Errorf("Read failed: %v", err)
+	}
+
+	return buffer[0:toRead], nil
+}
diff --git a/protocols/bgp/server/bmp_server_test.go b/protocols/bgp/server/bmp_server_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d40b672d482827cf09ad4cbbbdd013b591f8b552
--- /dev/null
+++ b/protocols/bgp/server/bmp_server_test.go
@@ -0,0 +1,295 @@
+package server
+
+import (
+	"net"
+	"testing"
+
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewServer(t *testing.T) {
+	s := NewServer()
+	assert.Equal(t, &BMPServer{
+		routers:    map[string]*router{},
+		ribClients: map[string]map[afiClient]struct{}{},
+	}, s)
+}
+
+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": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+				},
+			},
+			expected: &BMPServer{
+				routers: make(map[string]*router),
+				ribClients: map[string]map[afiClient]struct{}{
+					"20.30.40.50": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+					"10.20.30.40": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+				},
+			},
+		},
+		{
+			name: "Test with routers no clients",
+			srv: &BMPServer{
+				routers: map[string]*router{
+					"10.20.30.40": &router{
+						ribClients: make(map[afiClient]struct{}),
+					},
+				},
+				ribClients: map[string]map[afiClient]struct{}{},
+			},
+			expected: &BMPServer{
+				routers: map[string]*router{
+					"10.20.30.40": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+				},
+				ribClients: map[string]map[afiClient]struct{}{
+					"10.20.30.40": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		test.srv.SubscribeRIBs(nil, net.IP{10, 20, 30, 40}, packet.IPv4AFI)
+
+		assert.Equalf(t, test.expected, test.srv, "Test %q", test.name)
+
+	}
+}
+
+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": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+					"20.30.40.50": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+				},
+				ribClients: map[string]map[afiClient]struct{}{
+					"20.30.40.50": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+					"10.20.30.40": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+				},
+			},
+			expected: &BMPServer{
+				routers: map[string]*router{
+					"10.20.30.40": &router{
+						ribClients: map[afiClient]struct{}{},
+					},
+					"20.30.40.50": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+				},
+				ribClients: map[string]map[afiClient]struct{}{
+					"20.30.40.50": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+					"10.20.30.40": map[afiClient]struct{}{
+					},
+				},
+			},
+		},
+		{
+			name: "Unsubscribe existing from non-router",
+			srv: &BMPServer{
+				routers: map[string]*router{
+					"10.20.30.60": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+					"20.30.40.50": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+				},
+				ribClients: map[string]map[afiClient]struct{}{
+					"20.30.40.50": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+					"10.20.30.60": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+				},
+			},
+			expected: &BMPServer{
+				routers: map[string]*router{
+					"10.20.30.60": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+					"20.30.40.50": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+				},
+				ribClients: map[string]map[afiClient]struct{}{
+					"20.30.40.50": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+					"10.20.30.60": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+				},
+			},
+		},
+		{
+			name: "Unsubscribe existing from non-existing client",
+			srv: &BMPServer{
+				routers: map[string]*router{
+					"10.20.30.40": &router{
+						ribClients: map[afiClient]struct{}{},
+					},
+					"20.30.40.50": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+				},
+				ribClients: map[string]map[afiClient]struct{}{
+					"20.30.40.40": map[afiClient]struct{}{},
+					"10.20.30.60": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+				},
+			},
+			expected: &BMPServer{
+				routers: map[string]*router{
+					"10.20.30.40": &router{
+						ribClients: map[afiClient]struct{}{},
+					},
+					"20.30.40.50": &router{
+						ribClients: map[afiClient]struct{}{
+							afiClient{
+								afi:    packet.IPv4AFI,
+								client: nil,
+							}: struct{}{},
+						},
+					},
+				},
+				ribClients: map[string]map[afiClient]struct{}{
+					"20.30.40.40": map[afiClient]struct{}{},
+					"10.20.30.60": map[afiClient]struct{}{
+						afiClient{
+							afi:    packet.IPv4AFI,
+							client: nil,
+						}: struct{}{},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		test.srv.UnsubscribeRIBs(nil, net.IP{10, 20, 30, 40}, packet.IPv4AFI)
+
+		assert.Equalf(t, test.expected, test.srv, "Test %q", test.name)
+
+	}
+}
diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go
index c0697f1afc7686031b151055ba45a7c831761a3b..6e11d6672aed81061d870e79c71183066e35a53e 100644
--- a/protocols/bgp/server/fsm.go
+++ b/protocols/bgp/server/fsm.go
@@ -29,6 +29,7 @@ type state interface {
 
 // FSM implements the BGP finite state machine (RFC4271)
 type FSM struct {
+	isBMP       bool
 	peer        *peer
 	eventCh     chan int
 	con         net.Conn
@@ -330,6 +331,10 @@ func recvMsg(c net.Conn) (msg []byte, err error) {
 }
 
 func stopTimer(t *time.Timer) {
+	if t == nil {
+		return
+	}
+
 	if !t.Stop() {
 		select {
 		case <-t.C:
diff --git a/protocols/bgp/server/fsm_address_family.go b/protocols/bgp/server/fsm_address_family.go
index 0b241ecca48b4a6169a918843eedc64cf042e5e3..5c3540c9afb4b586d8eb954b8f64b1207a9b3045 100644
--- a/protocols/bgp/server/fsm_address_family.go
+++ b/protocols/bgp/server/fsm_address_family.go
@@ -53,6 +53,7 @@ func (f *fsmAddressFamily) init(n *routingtable.Neighbor) {
 
 	f.adjRIBIn = adjRIBIn.New(f.importFilter, contributingASNs, f.fsm.peer.routerID, f.fsm.peer.clusterID, f.addPathRX)
 	contributingASNs.Add(f.fsm.peer.localASN)
+
 	f.adjRIBIn.Register(f.rib)
 
 	f.adjRIBOut = adjRIBOut.New(n, f.exportFilter, !f.addPathTX.BestOnly)
@@ -65,6 +66,24 @@ func (f *fsmAddressFamily) init(n *routingtable.Neighbor) {
 	f.rib.RegisterWithOptions(f.adjRIBOut, f.addPathTX)
 }
 
+func (f *fsmAddressFamily) bmpInit() {
+	f.adjRIBIn = adjRIBIn.New(filter.NewAcceptAllFilter(), &routingtable.ContributingASNs{}, f.fsm.peer.routerID, f.fsm.peer.clusterID, f.addPathRX)
+
+	if f.rib != nil {
+		f.adjRIBIn.Register(f.rib)
+	}
+}
+
+func (f *fsmAddressFamily) bmpDispose() {
+	f.rib.GetContributingASNs().Remove(f.fsm.peer.localASN)
+
+	f.adjRIBIn.(*adjRIBIn.AdjRIBIn).Flush()
+
+	f.adjRIBIn.Unregister(f.rib)
+
+	f.adjRIBIn = nil
+}
+
 func (f *fsmAddressFamily) dispose() {
 	if !f.initialized {
 		return
@@ -87,13 +106,11 @@ func (f *fsmAddressFamily) processUpdate(u *packet.BGPUpdate) {
 		return
 	}
 
-	if f.multiProtocol {
-		f.multiProtocolUpdates(u)
-		return
+	f.multiProtocolUpdates(u)
+	if f.afi == packet.IPv4AFI {
+		f.withdraws(u)
+		f.updates(u)
 	}
-
-	f.withdraws(u)
-	f.updates(u)
 }
 
 func (f *fsmAddressFamily) withdraws(u *packet.BGPUpdate) {
@@ -184,6 +201,8 @@ func (f *fsmAddressFamily) processAttributes(attrs *packet.PathAttribute, path *
 			path.BGPPath.OriginatorID = pa.Value.(uint32)
 		case packet.ClusterListAttr:
 			path.BGPPath.ClusterList = pa.Value.([]uint32)
+		case packet.MultiProtocolReachNLRICode:
+		case packet.MultiProtocolUnreachNLRICode:
 		default:
 			unknownAttr := f.processUnknownAttribute(pa)
 			if unknownAttr != nil {
diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go
index 2758a506f017593d6c9b99f7c16dd110377438f3..05e540b597394424a5fde51461cc2aa55e42888b 100644
--- a/protocols/bgp/server/fsm_established.go
+++ b/protocols/bgp/server/fsm_established.go
@@ -153,16 +153,19 @@ func (s *establishedState) msgReceived(data []byte, opt *packet.DecodeOptions) (
 			s.fsm.sendNotification(bgperr.ErrorCode, bgperr.ErrorSubCode)
 		}
 		stopTimer(s.fsm.connectRetryTimer)
-		s.fsm.con.Close()
+		if s.fsm.con != nil {
+			s.fsm.con.Close()
+		}
 		s.fsm.connectRetryCounter++
-		return newIdleState(s.fsm), "Failed to decode BGP message"
+		return newIdleState(s.fsm), fmt.Sprintf("Failed to decode BGP message: %v", err)
 	}
+
 	switch msg.Header.Type {
 	case packet.NotificationMsg:
 		fmt.Println(data)
 		return s.notification()
 	case packet.UpdateMsg:
-		return s.update(msg)
+		return s.update(msg.Body.(*packet.BGPUpdate))
 	case packet.KeepaliveMsg:
 		return s.keepaliveReceived()
 	default:
@@ -178,13 +181,11 @@ func (s *establishedState) notification() (state, string) {
 	return newIdleState(s.fsm), "Received NOTIFICATION"
 }
 
-func (s *establishedState) update(msg *packet.BGPMessage) (state, string) {
+func (s *establishedState) update(u *packet.BGPUpdate) (state, string) {
 	if s.fsm.holdTime != 0 {
 		s.fsm.holdTimer.Reset(s.fsm.holdTime)
 	}
 
-	u := msg.Body.(*packet.BGPUpdate)
-
 	if s.fsm.ipv4Unicast != nil {
 		s.fsm.ipv4Unicast.processUpdate(u)
 	}
diff --git a/protocols/bgp/server/fsm_open_sent.go b/protocols/bgp/server/fsm_open_sent.go
index a3aac48a22add9132441a589994cca7752a689d7..a66898541373f688a06aa9b2283e847095e9e332 100644
--- a/protocols/bgp/server/fsm_open_sent.go
+++ b/protocols/bgp/server/fsm_open_sent.go
@@ -92,7 +92,7 @@ func (s *openSentState) msgReceived(data []byte, opt *packet.DecodeOptions) (sta
 	case packet.NotificationMsg:
 		return s.notification(msg)
 	case packet.OpenMsg:
-		return s.openMsgReceived(msg)
+		return s.openMsgReceived(msg.Body.(*packet.BGPOpen))
 	default:
 		return s.unexpectedMessage()
 	}
@@ -106,11 +106,15 @@ func (s *openSentState) unexpectedMessage() (state, string) {
 	return newIdleState(s.fsm), "FSM Error"
 }
 
-func (s *openSentState) openMsgReceived(msg *packet.BGPMessage) (state, string) {
-	openMsg := msg.Body.(*packet.BGPOpen)
+func (s *openSentState) openMsgReceived(openMsg *packet.BGPOpen) (state, string) {
 	s.peerASNRcvd = uint32(openMsg.ASN)
 
 	s.fsm.neighborID = openMsg.BGPIdentifier
+
+	if s.fsm.isBMP {
+		return s.handleOpenMessage(openMsg)
+	}
+
 	stopTimer(s.fsm.connectRetryTimer)
 	if s.fsm.peer.collisionHandling(s.fsm) {
 		return s.cease()
diff --git a/protocols/bgp/server/fsm_open_sent_test.go b/protocols/bgp/server/fsm_open_sent_test.go
index 708617b910775a28efcd89ec0866320905912455..395432f31bfe988c0a27ac11c1565a382ee1bea9 100644
--- a/protocols/bgp/server/fsm_open_sent_test.go
+++ b/protocols/bgp/server/fsm_open_sent_test.go
@@ -1,6 +1,7 @@
 package server
 
 import (
+	"net"
 	"testing"
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
@@ -69,7 +70,19 @@ func TestOpenMsgReceived(t *testing.T) {
 			fsm := newFSM(&peer{
 				peerASN: test.asn,
 			})
-			fsm.con = &btesting.MockConn{}
+
+			conA, conB := net.Pipe()
+			fsm.con = conB
+
+			go func() {
+				for {
+					buf := make([]byte, 1)
+					_, err := conA.Read(buf)
+					if err != nil {
+						return
+					}
+				}
+			}()
 
 			s := &openSentState{
 				fsm: fsm,
diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go
index 9a5b128aacc89a7fa996b75b7e175e8a97dd4d2d..8e0a674ac112f15ce97bf58cf658f7d891a8dd8f 100644
--- a/protocols/bgp/server/peer.go
+++ b/protocols/bgp/server/peer.go
@@ -20,10 +20,11 @@ type PeerInfo struct {
 }
 
 type peer struct {
-	server   *bgpServer
-	addr     bnet.IP
-	peerASN  uint32
-	localASN uint32
+	server    *bgpServer
+	addr      bnet.IP
+	localAddr bnet.IP
+	peerASN   uint32
+	localASN  uint32
 
 	// guarded by fsmsMu
 	fsms   []*FSM
diff --git a/protocols/bmp/packet/decode.go b/protocols/bmp/packet/decode.go
index d7989e28bb5955a294b195962d06c980cd0b645b..a76f6b7853e221126b77512039d9fc5296469496 100644
--- a/protocols/bmp/packet/decode.go
+++ b/protocols/bmp/packet/decode.go
@@ -61,7 +61,7 @@ func Decode(msg []byte) (Msg, error) {
 
 		return sr, nil
 	case PeerDownNotificationType:
-		pd, err := decodePeerUpNotification(buf, ch)
+		pd, err := decodePeerDownNotification(buf, ch)
 		if err != nil {
 			return nil, fmt.Errorf("Unable to decode peer down notification: %v", err)
 		}
diff --git a/protocols/bmp/packet/decode_test.go b/protocols/bmp/packet/decode_test.go
index b3419725826e141b6225838f30d9e7d91296a2ba..b44a4d0689be0de048c9a7646fd1e51255e69cac 100644
--- a/protocols/bmp/packet/decode_test.go
+++ b/protocols/bmp/packet/decode_test.go
@@ -26,11 +26,11 @@ func TestDecode(t *testing.T) {
 		{
 			name: "Route monitoring ok",
 			input: []byte{
-				3, 0, 0, 0, 6 + 38 + 4, 0,
+				3, 0, 0, 0, 6 + PerPeerHeaderLen + 4, 0,
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -43,7 +43,7 @@ func TestDecode(t *testing.T) {
 			expected: &RouteMonitoringMsg{
 				CommonHeader: &CommonHeader{
 					Version:   3,
-					MsgLength: 6 + 38 + 4,
+					MsgLength: 6 + PerPeerHeaderLen + 4,
 					MsgType:   0,
 				},
 				PerPeerHeader: &PerPeerHeader{
@@ -62,11 +62,11 @@ func TestDecode(t *testing.T) {
 		{
 			name: "Route monitoring nok",
 			input: []byte{
-				3, 0, 0, 0, 6 + 38 + 4, 0,
+				3, 0, 0, 0, 6 + PerPeerHeaderLen + 4, 0,
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -80,11 +80,11 @@ func TestDecode(t *testing.T) {
 		{
 			name: "Statistic report ok",
 			input: []byte{
-				3, 0, 0, 0, 6 + 9 + 38, 1,
+				3, 0, 0, 0, 6 + 9 + PerPeerHeaderLen, 1,
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -98,7 +98,7 @@ func TestDecode(t *testing.T) {
 			expected: &StatsReport{
 				CommonHeader: &CommonHeader{
 					Version:   3,
-					MsgLength: 6 + 9 + 38,
+					MsgLength: 6 + 9 + PerPeerHeaderLen,
 					MsgType:   1,
 				},
 				PerPeerHeader: &PerPeerHeader{
@@ -124,11 +124,11 @@ func TestDecode(t *testing.T) {
 		{
 			name: "Statistic report nok",
 			input: []byte{
-				3, 0, 0, 0, 6 + 9 + 38, 1,
+				3, 0, 0, 0, 6 + 9 + PerPeerHeaderLen, 1,
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 			},
@@ -137,11 +137,11 @@ func TestDecode(t *testing.T) {
 		{
 			name: "peer down ok",
 			input: []byte{
-				3, 0, 0, 0, 6 + 9 + 38, 1,
+				3, 0, 0, 0, 6 + 9 + PerPeerHeaderLen, 1,
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -155,7 +155,7 @@ func TestDecode(t *testing.T) {
 			expected: &StatsReport{
 				CommonHeader: &CommonHeader{
 					Version:   3,
-					MsgLength: 6 + 9 + 38,
+					MsgLength: 6 + 9 + PerPeerHeaderLen,
 					MsgType:   1,
 				},
 				PerPeerHeader: &PerPeerHeader{
@@ -181,11 +181,11 @@ func TestDecode(t *testing.T) {
 		{
 			name: "peer down nok",
 			input: []byte{
-				3, 0, 0, 0, 6 + 9 + 38, 1,
+				3, 0, 0, 0, 6 + 9 + PerPeerHeaderLen, 1,
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -204,7 +204,7 @@ func TestDecode(t *testing.T) {
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -216,6 +216,9 @@ func TestDecode(t *testing.T) {
 				0, 200,
 
 				// OPEN Sent
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 34,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
@@ -224,6 +227,9 @@ func TestDecode(t *testing.T) {
 				1, 2, 3, 4, 5,
 
 				// OPEN Recv
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 29,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
@@ -253,6 +259,9 @@ func TestDecode(t *testing.T) {
 				LocalPort:    100,
 				RemotePort:   200,
 				SentOpenMsg: []byte{
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 34,
+					1,
 					4,    // Version
 					1, 0, // ASN
 					2, 0, // Hold Time
@@ -261,7 +270,9 @@ func TestDecode(t *testing.T) {
 					1, 2, 3, 4, 5,
 				},
 				ReceivedOpenMsg: []byte{
-					// OPEN Recv
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 29,
+					1,
 					4,    // Version
 					1, 0, // ASN
 					2, 0, // Hold Time
@@ -280,7 +291,7 @@ func TestDecode(t *testing.T) {
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -374,7 +385,7 @@ func TestDecode(t *testing.T) {
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -416,7 +427,7 @@ func TestDecode(t *testing.T) {
 
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0,
diff --git a/protocols/bmp/packet/peer_down.go b/protocols/bmp/packet/peer_down.go
index cdb72ffe33f72dc77e4c7373b6041a467bbbf836..7b0303043f489f93ce4c1ed4cc2610cbb87fa9e2 100644
--- a/protocols/bmp/packet/peer_down.go
+++ b/protocols/bmp/packet/peer_down.go
@@ -57,7 +57,7 @@ func decodePeerDownNotification(buf *bytes.Buffer, ch *CommonHeader) (*PeerDownN
 
 	err = decoder.Decode(buf, fields)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("Unable to read Data: %v", err)
 	}
 
 	return p, nil
diff --git a/protocols/bmp/packet/peer_down_test.go b/protocols/bmp/packet/peer_down_test.go
index b1df8b55e84d9ae6bf5854e4a2aea51357d73c3a..58cdbed36e9cd6ce8244f960b8cd15cc64004451 100644
--- a/protocols/bmp/packet/peer_down_test.go
+++ b/protocols/bmp/packet/peer_down_test.go
@@ -32,7 +32,7 @@ func TestDecodePeerDownNotification(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -43,12 +43,12 @@ func TestDecodePeerDownNotification(t *testing.T) {
 				1, 2, 3,
 			},
 			ch: &CommonHeader{
-				MsgLength: CommonHeaderLen + 4 + 38,
+				MsgLength: CommonHeaderLen + 4 + PerPeerHeaderLen,
 			},
 			wantFail: false,
 			expected: &PeerDownNotification{
 				CommonHeader: &CommonHeader{
-					MsgLength: CommonHeaderLen + 4 + 38,
+					MsgLength: CommonHeaderLen + 4 + PerPeerHeaderLen,
 				},
 				PerPeerHeader: &PerPeerHeader{
 					PeerType:              1,
@@ -71,7 +71,7 @@ func TestDecodePeerDownNotification(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -106,7 +106,7 @@ func TestDecodePeerDownNotification(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0,
@@ -121,7 +121,7 @@ func TestDecodePeerDownNotification(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
diff --git a/protocols/bmp/packet/peer_up.go b/protocols/bmp/packet/peer_up.go
index 58adc4cc5e6cd294c88758333f6aa26c214f46b0..34d37a7cbc9786ec4459b50a56c917a037c261a4 100644
--- a/protocols/bmp/packet/peer_up.go
+++ b/protocols/bmp/packet/peer_up.go
@@ -9,7 +9,7 @@ import (
 
 const (
 	// OpenMsgMinLen is the minimal length of a BGP open message
-	OpenMsgMinLen = 10
+	OpenMsgMinLen = 29
 )
 
 // PeerUpNotification represents a peer up notification
diff --git a/protocols/bmp/packet/peer_up_test.go b/protocols/bmp/packet/peer_up_test.go
index d29ec7550cc757ee8e3332d1de69509e590948bd..0b66d6e8e059ddc6600104d83777791f6b22ae22 100644
--- a/protocols/bmp/packet/peer_up_test.go
+++ b/protocols/bmp/packet/peer_up_test.go
@@ -18,6 +18,7 @@ func TestPeerUpMsgType(t *testing.T) {
 		t.Errorf("Unexpected result")
 	}
 }
+
 func TestDecodePeerUp(t *testing.T) {
 	tests := []struct {
 		name     string
@@ -31,7 +32,7 @@ func TestDecodePeerUp(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -43,6 +44,9 @@ func TestDecodePeerUp(t *testing.T) {
 				0, 200,
 
 				// OPEN Sent
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 34,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
@@ -51,6 +55,9 @@ func TestDecodePeerUp(t *testing.T) {
 				1, 2, 3, 4, 5,
 
 				// OPEN Recv
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 29,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
@@ -60,12 +67,12 @@ func TestDecodePeerUp(t *testing.T) {
 				120, 140, 160, // Information
 			},
 			ch: &CommonHeader{
-				MsgLength: 47,
+				MsgLength: 126,
 			},
 			wantFail: false,
 			expected: &PeerUpNotification{
 				CommonHeader: &CommonHeader{
-					MsgLength: 47,
+					MsgLength: 126,
 				},
 				PerPeerHeader: &PerPeerHeader{
 					PeerType:              1,
@@ -81,6 +88,9 @@ func TestDecodePeerUp(t *testing.T) {
 				LocalPort:    100,
 				RemotePort:   200,
 				SentOpenMsg: []byte{
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 34,
+					1,
 					4,    // Version
 					1, 0, // ASN
 					2, 0, // Hold Time
@@ -89,7 +99,9 @@ func TestDecodePeerUp(t *testing.T) {
 					1, 2, 3, 4, 5,
 				},
 				ReceivedOpenMsg: []byte{
-					// OPEN Recv
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 29,
+					1,
 					4,    // Version
 					1, 0, // ASN
 					2, 0, // Hold Time
@@ -106,7 +118,7 @@ func TestDecodePeerUp(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -118,6 +130,9 @@ func TestDecodePeerUp(t *testing.T) {
 				0, 200,
 
 				// OPEN Sent
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 34,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
@@ -126,6 +141,9 @@ func TestDecodePeerUp(t *testing.T) {
 				1, 2, 3, 4, 5,
 
 				// OPEN Recv
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 29,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
@@ -133,12 +151,12 @@ func TestDecodePeerUp(t *testing.T) {
 				0, // Opt Parm Len
 			},
 			ch: &CommonHeader{
-				MsgLength: 44,
+				MsgLength: 82,
 			},
 			wantFail: false,
 			expected: &PeerUpNotification{
 				CommonHeader: &CommonHeader{
-					MsgLength: 44,
+					MsgLength: 82,
 				},
 				PerPeerHeader: &PerPeerHeader{
 					PeerType:              1,
@@ -154,6 +172,9 @@ func TestDecodePeerUp(t *testing.T) {
 				LocalPort:    100,
 				RemotePort:   200,
 				SentOpenMsg: []byte{
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 34,
+					1,
 					4,    // Version
 					1, 0, // ASN
 					2, 0, // Hold Time
@@ -162,7 +183,9 @@ func TestDecodePeerUp(t *testing.T) {
 					1, 2, 3, 4, 5,
 				},
 				ReceivedOpenMsg: []byte{
-					// OPEN Recv
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					0, 29,
+					1,
 					4,    // Version
 					1, 0, // ASN
 					2, 0, // Hold Time
@@ -176,13 +199,13 @@ func TestDecodePeerUp(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0,
 			},
 			ch: &CommonHeader{
-				MsgLength: 47,
+				MsgLength: 51,
 			},
 			wantFail: true,
 		},
@@ -191,7 +214,7 @@ func TestDecodePeerUp(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -202,7 +225,7 @@ func TestDecodePeerUp(t *testing.T) {
 				0, 100,
 			},
 			ch: &CommonHeader{
-				MsgLength: 47,
+				MsgLength: 51,
 			},
 			wantFail: true,
 		},
@@ -211,7 +234,7 @@ func TestDecodePeerUp(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -223,12 +246,15 @@ func TestDecodePeerUp(t *testing.T) {
 				0, 200,
 
 				// OPEN Sent
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 29,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
 			},
 			ch: &CommonHeader{
-				MsgLength: 47,
+				MsgLength: 89,
 			},
 			wantFail: true,
 		},
@@ -237,7 +263,7 @@ func TestDecodePeerUp(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -248,12 +274,15 @@ func TestDecodePeerUp(t *testing.T) {
 				0, 200,
 
 				// OPEN Sent
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 29,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
 			},
 			ch: &CommonHeader{
-				MsgLength: 47,
+				MsgLength: 88,
 			},
 			wantFail: true,
 		},
@@ -265,6 +294,9 @@ func TestDecodePeerUp(t *testing.T) {
 				0, 200,
 
 				// OPEN Sent
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 29,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
@@ -273,6 +305,9 @@ func TestDecodePeerUp(t *testing.T) {
 				1, 2, 3, 4, 5,
 
 				// OPEN Recv
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+				0, 29,
+				1,
 				4,    // Version
 				1, 0, // ASN
 				2, 0, // Hold Time
@@ -280,7 +315,7 @@ func TestDecodePeerUp(t *testing.T) {
 				3, // Opt Parm Len
 			},
 			ch: &CommonHeader{
-				MsgLength: 47,
+				MsgLength: 85,
 			},
 			wantFail: true,
 		},
diff --git a/protocols/bmp/packet/per_peer_header.go b/protocols/bmp/packet/per_peer_header.go
index d03f8b43d5d52b6dd705329c0bb10f834d39a15d..f7c60ed97f98068cf2bb573b348407584ef2d3e4 100644
--- a/protocols/bmp/packet/per_peer_header.go
+++ b/protocols/bmp/packet/per_peer_header.go
@@ -9,14 +9,14 @@ import (
 
 const (
 	// PerPeerHeaderLen is the length of a per peer header
-	PerPeerHeaderLen = 38
+	PerPeerHeaderLen = 42
 )
 
 // PerPeerHeader represents a BMP per peer header
 type PerPeerHeader struct {
 	PeerType              uint8
 	PeerFlags             uint8
-	PeerDistinguisher     uint32
+	PeerDistinguisher     uint64
 	PeerAddress           [16]byte
 	PeerAS                uint32
 	PeerBGPID             uint32
@@ -28,7 +28,7 @@ type PerPeerHeader struct {
 func (p *PerPeerHeader) Serialize(buf *bytes.Buffer) {
 	buf.WriteByte(p.PeerType)
 	buf.WriteByte(p.PeerFlags)
-	buf.Write(convert.Uint32Byte(p.PeerDistinguisher))
+	buf.Write(convert.Uint64Byte(p.PeerDistinguisher))
 	buf.Write(p.PeerAddress[:])
 	buf.Write(convert.Uint32Byte(p.PeerAS))
 	buf.Write(convert.Uint32Byte(p.PeerBGPID))
@@ -57,3 +57,11 @@ func decodePerPeerHeader(buf *bytes.Buffer) (*PerPeerHeader, error) {
 
 	return p, nil
 }
+
+// GetIPVersion gets the IP version of the BGP session
+func (p *PerPeerHeader) GetIPVersion() uint8 {
+	if p.PeerFlags>>7 == 1 {
+		return 6
+	}
+	return 4
+}
diff --git a/protocols/bmp/packet/per_peer_header_test.go b/protocols/bmp/packet/per_peer_header_test.go
index de503a84edea465106787c5846ce26244c42c84a..8c51ee6c9b286faf927893d20ab093fe792ce0ce 100644
--- a/protocols/bmp/packet/per_peer_header_test.go
+++ b/protocols/bmp/packet/per_peer_header_test.go
@@ -28,7 +28,7 @@ func TestPerPeerHeaderSerialize(t *testing.T) {
 			expected: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -59,7 +59,7 @@ func TestDecodePerPeerHeader(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -83,7 +83,7 @@ func TestDecodePerPeerHeader(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -113,4 +113,47 @@ func TestDecodePerPeerHeader(t *testing.T) {
 
 		assert.Equalf(t, test.expected, p, "Test %q", test.name)
 	}
+
+}
+
+func TestGetIPVersion(t *testing.T) {
+	tests := []struct {
+		name     string
+		p        *PerPeerHeader
+		expected uint8
+	}{
+		{
+			name: "IPv4",
+			p: &PerPeerHeader{
+				PeerFlags: 0,
+			},
+			expected: 4,
+		},
+		{
+			name: "IPv4 #2",
+			p: &PerPeerHeader{
+				PeerFlags: 127,
+			},
+			expected: 4,
+		},
+		{
+			name: "IPv6",
+			p: &PerPeerHeader{
+				PeerFlags: 128,
+			},
+			expected: 6,
+		},
+		{
+			name: "IPv6 #2",
+			p: &PerPeerHeader{
+				PeerFlags: 129,
+			},
+			expected: 6,
+		},
+	}
+
+	for _, test := range tests {
+		v := test.p.GetIPVersion()
+		assert.Equal(t, test.expected, v)
+	}
 }
diff --git a/protocols/bmp/packet/route_mirroring_test.go b/protocols/bmp/packet/route_mirroring_test.go
index c62799a37a2c1bfa53c01eafb90979aa896716be..87bcf2110ed6c34f230bca7fbb3ceb4eb1c97400 100644
--- a/protocols/bmp/packet/route_mirroring_test.go
+++ b/protocols/bmp/packet/route_mirroring_test.go
@@ -31,7 +31,7 @@ func TestDecodeRouteMirroringMsg(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -78,7 +78,7 @@ func TestDecodeRouteMirroringMsg(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 			},
@@ -92,7 +92,7 @@ func TestDecodeRouteMirroringMsg(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
diff --git a/protocols/bmp/packet/route_monitoring_test.go b/protocols/bmp/packet/route_monitoring_test.go
index d28169346d6312f757fd317b879c55e20dd43847..7534e6f31d0deb79159c188ce5184443a63e672a 100644
--- a/protocols/bmp/packet/route_monitoring_test.go
+++ b/protocols/bmp/packet/route_monitoring_test.go
@@ -31,7 +31,7 @@ func TestDecodeRouteMonitoringMsg(t *testing.T) {
 			input: []byte{
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
diff --git a/protocols/bmp/packet/stats_report_test.go b/protocols/bmp/packet/stats_report_test.go
index 2fc47cbb4a1b69c55834af7db122215dfdf32a2b..0bf372238be74e55b4b4b49ca2c993bf88fbe31c 100644
--- a/protocols/bmp/packet/stats_report_test.go
+++ b/protocols/bmp/packet/stats_report_test.go
@@ -32,7 +32,7 @@ func TestDecodeStatsReport(t *testing.T) {
 				// Per Peer Header
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -83,7 +83,7 @@ func TestDecodeStatsReport(t *testing.T) {
 				// Per Peer Header
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -96,7 +96,7 @@ func TestDecodeStatsReport(t *testing.T) {
 				// Per Peer Header
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
@@ -111,7 +111,7 @@ func TestDecodeStatsReport(t *testing.T) {
 				// Per Peer Header
 				1,
 				2,
-				0, 0, 0, 3,
+				0, 0, 0, 0, 0, 0, 0, 3,
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
 				0, 0, 200, 124,
 				0, 0, 0, 123,
diff --git a/routingtable/adjRIBIn/adj_rib_in.go b/routingtable/adjRIBIn/adj_rib_in.go
index 631f1d197cabc926056f186e21e1b6cd5d719afd..5bf7862334ece9b649baf5c75e42eff4936689f9 100644
--- a/routingtable/adjRIBIn/adj_rib_in.go
+++ b/routingtable/adjRIBIn/adj_rib_in.go
@@ -37,6 +37,19 @@ func New(exportFilter *filter.Filter, contributingASNs *routingtable.Contributin
 	return a
 }
 
+// Flush drops all routes from the AdjRIBIn
+func (a *AdjRIBIn) Flush() {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+
+	routes := a.rt.Dump()
+	for _, route := range routes {
+		for _, path := range route.Paths() {
+			a.removePath(route.Prefix(), path)
+		}
+	}
+}
+
 // UpdateNewClient sends current state to a new client
 func (a *AdjRIBIn) UpdateNewClient(client routingtable.RouteTableClient) error {
 	a.mu.RLock()
@@ -124,6 +137,11 @@ func (a *AdjRIBIn) RemovePath(pfx net.Prefix, p *route.Path) bool {
 	a.mu.Lock()
 	defer a.mu.Unlock()
 
+	return a.removePath(pfx, p)
+}
+
+// removePath removes the path for prefix `pfx`
+func (a *AdjRIBIn) removePath(pfx net.Prefix, p *route.Path) bool {
 	r := a.rt.Get(pfx)
 	if r == nil {
 		return false
diff --git a/routingtable/locRIB/loc_rib.go b/routingtable/locRIB/loc_rib.go
index d9ad2b46f29ee95d8a0d6b42ff513bd7172e067f..aa60b9243aab1846bdb6dd010f9ced6155071268 100644
--- a/routingtable/locRIB/loc_rib.go
+++ b/routingtable/locRIB/loc_rib.go
@@ -64,7 +64,6 @@ func (a *LocRIB) RouteCount() int64 {
 func (a *LocRIB) AddPath(pfx net.Prefix, p *route.Path) error {
 	a.mu.Lock()
 	defer a.mu.Unlock()
-
 	logrus.WithFields(map[string]interface{}{
 		"Prefix": pfx,
 		"Route":  p,
diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel
index 02411722b3d58ed2be6fcd2e4c950da21807f8cf..ee2d86559d828f247f0631e2c3d18f3408b550ff 100644
--- a/testing/BUILD.bazel
+++ b/testing/BUILD.bazel
@@ -2,9 +2,13 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
-    srcs = ["conn_mock.go"],
+    srcs = [
+        "conn_mock.go",
+        "log.go",
+    ],
     importpath = "github.com/bio-routing/bio-rd/testing",
     visibility = ["//visibility:public"],
+    deps = ["//vendor/github.com/sirupsen/logrus:go_default_library"],
 )
 
 go_test(
diff --git a/testing/conn_mock.go b/testing/conn_mock.go
index 9c89ea64e658ad2cb5c46f91f5ac368fe9d5a436..1be39e05208aa91d67fd841b2de740bfdc15d5da 100644
--- a/testing/conn_mock.go
+++ b/testing/conn_mock.go
@@ -1,34 +1,32 @@
 package testing
 
 import (
+	"bytes"
 	"net"
 )
 
 // MockConn mock an connection
 type MockConn struct {
 	net.Conn
-
-	// Bytes are the bytes written
-	Bytes []byte
+	Buf    *bytes.Buffer
+	Closed bool
 }
 
 func NewMockConn() *MockConn {
 	return &MockConn{
-		Bytes: make([]byte, 0),
+		Buf: bytes.NewBuffer(nil),
 	}
 }
 
 func (m *MockConn) Write(b []byte) (int, error) {
-	m.Bytes = append(m.Bytes, b...)
-	return len(b), nil
+	return m.Buf.Write(b)
 }
 
 func (m *MockConn) Read(b []byte) (n int, err error) {
-	count := len(b)
-	if count > len(m.Bytes) {
-		count = len(m.Bytes)
-	}
+	return m.Buf.Read(b)
+}
 
-	copy(b, m.Bytes[0:count])
-	return count, nil
+func (m *MockConn) Close() error {
+	m.Closed = true
+	return nil
 }
diff --git a/testing/conn_mock_test.go b/testing/conn_mock_test.go
index 9d964ffa9fecef3f577dd02c498ed9dc110bcf1b..5afe2e35d0e0bbad21910b2600f084bbf088d20b 100644
--- a/testing/conn_mock_test.go
+++ b/testing/conn_mock_test.go
@@ -1,25 +1,30 @@
 package testing
 
 import (
+	"bytes"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestWrite(t *testing.T) {
-	m := &MockConn{}
+	m := &MockConn{
+		Buf: bytes.NewBuffer(nil),
+	}
 
 	payload := []byte{1, 2, 3}
 	m.Write(payload)
 
-	assert.Equal(t, payload, m.Bytes)
+	assert.Equal(t, payload, m.Buf.Bytes())
 }
 
 func TestRead(t *testing.T) {
-	m := &MockConn{}
+	m := &MockConn{
+		Buf: bytes.NewBuffer(nil),
+	}
 
 	payload := []byte{1, 2, 3}
-	m.Bytes = payload
+	m.Buf.Write(payload)
 
 	buffer := make([]byte, 4)
 	n, _ := m.Read(buffer)
diff --git a/testing/log.go b/testing/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..538b23ef199506c37fb4c392951cd960d33082cf
--- /dev/null
+++ b/testing/log.go
@@ -0,0 +1,26 @@
+package testing
+
+import (
+	"fmt"
+
+	"github.com/sirupsen/logrus"
+)
+
+// LogFormatter provides a log formatter for unit tests free of timestamps
+type LogFormatter struct{}
+
+// NewLogFormatter creates a new log formatter
+func NewLogFormatter() *LogFormatter {
+	return &LogFormatter{}
+}
+
+// Format formats a log entry
+func (l *LogFormatter) Format(e *logrus.Entry) ([]byte, error) {
+	var res string
+	if len(e.Data) == 0 {
+		res = fmt.Sprintf("level=%s msg=%q", e.Level, e.Message)
+	} else {
+		res = fmt.Sprintf("level=%s msg=%q fields=%v", e.Level, e.Message, e.Data)
+	}
+	return []byte(res), nil
+}