diff --git a/main.go b/main_bmp.go
similarity index 51%
rename from main.go
rename to main_bmp.go
index 11c2a6895ade4237dbf48249437c2d202bb8aa58..146fa4244f267d3fadfcf7fb1b5815524cfcc909 100644
--- a/main.go
+++ b/main_bmp.go
@@ -2,27 +2,20 @@ package main
 
 import (
 	"fmt"
+	"net"
 	"time"
 
-	"github.com/sirupsen/logrus"
-
-	"github.com/bio-routing/bio-rd/protocols/bgp/server"
+	"github.com/bio-routing/bio-rd/protocols/bmp/server"
 	"github.com/bio-routing/bio-rd/routingtable/locRIB"
-
-	bnet "github.com/bio-routing/bio-rd/net"
+	"github.com/sirupsen/logrus"
 )
 
-func strAddr(s string) uint32 {
-	ret, _ := bnet.StrToAddr(s)
-	return ret
-}
-
 func main() {
-	logrus.Printf("This is a BGP speaker\n")
+	logrus.Printf("This is a BMP speaker\n")
 
 	rib := locRIB.New()
-	b := server.NewBgpServer()
-	startServer(b, rib)
+	b := server.NewServer()
+	b.AddRouter(net.IP{127, 0, 0, 1}, 1234, rib, nil)
 
 	go func() {
 		for {
diff --git a/protocols/bmp/packet/common_header.go b/protocols/bmp/packet/common_header.go
new file mode 100644
index 0000000000000000000000000000000000000000..17d9a95e8da948f6b9cb90785c09a5ec0866ab86
--- /dev/null
+++ b/protocols/bmp/packet/common_header.go
@@ -0,0 +1,40 @@
+package packet
+
+import (
+	"bytes"
+
+	"github.com/bio-routing/bio-rd/util/decoder"
+	"github.com/bio-routing/tflow2/convert"
+)
+
+const (
+	CommonHeaderLen = 6
+)
+
+type CommonHeader struct {
+	Version   uint8
+	MsgLength uint32
+	MsgType   uint8
+}
+
+func (c *CommonHeader) Serialize(buf *bytes.Buffer) {
+	buf.WriteByte(c.Version)
+	buf.Write(convert.Uint32Byte(c.MsgLength))
+	buf.WriteByte(c.MsgType)
+}
+
+func decodeCommonHeader(buf *bytes.Buffer) (*CommonHeader, error) {
+	ch := &CommonHeader{}
+	fields := []interface{}{
+		&ch.Version,
+		&ch.MsgLength,
+		&ch.MsgType,
+	}
+
+	err := decoder.Decode(buf, fields)
+	if err != nil {
+		return ch, err
+	}
+
+	return ch, nil
+}
diff --git a/protocols/bmp/packet/common_header_test.go b/protocols/bmp/packet/common_header_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..aec5942ed2f690641202a4e57fbf66da85680ec6
--- /dev/null
+++ b/protocols/bmp/packet/common_header_test.go
@@ -0,0 +1,81 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCommonHeaderSerialize(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *CommonHeader
+		expected []byte
+	}{
+		{
+			name: "Test #1",
+			input: &CommonHeader{
+				Version:   3,
+				MsgLength: 100,
+				MsgType:   10,
+			},
+			expected: []byte{3, 0, 0, 0, 100, 10},
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(nil)
+		test.input.Serialize(buf)
+		assert.Equalf(t, test.expected, buf.Bytes(), "Test %q", test.name)
+	}
+}
+
+func TestDecodeCommonHeader(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *CommonHeader
+	}{
+		{
+			name: "Full packet",
+			input: []byte{
+				3, 0, 0, 0, 100, 10,
+			},
+			wantFail: false,
+			expected: &CommonHeader{
+				Version:   3,
+				MsgLength: 100,
+				MsgType:   10,
+			},
+		},
+		{
+			name: "Incomplete",
+			input: []byte{
+				3, 0, 0, 0, 100,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		ch, err := decodeCommonHeader(buf)
+		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
+		}
+
+		assert.Equalf(t, test.expected, ch, "Test %q", test.name)
+	}
+}
diff --git a/protocols/bmp/packet/decode.go b/protocols/bmp/packet/decode.go
new file mode 100644
index 0000000000000000000000000000000000000000..522e57a9ebcd814d32c188c935ee5a2122033434
--- /dev/null
+++ b/protocols/bmp/packet/decode.go
@@ -0,0 +1,89 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+)
+
+type Msg interface {
+	MsgType() uint8
+}
+
+const (
+	MinLen = 6
+
+	RouteMonitoringType       = 0
+	StatisticsReportType      = 1
+	PeerDownNotificationType  = 2
+	PeerUpNotificationType    = 3
+	InitiationMessageType     = 4
+	TerminationMessageType    = 5
+	RouteMirroringMessageType = 6
+
+	BGPMessage     = 0
+	BGPInformation = 1
+
+	ErroredPDU  = 0
+	MessageLost = 1
+)
+
+// Decode decodes a BMP message
+func Decode(msg []byte) (Msg, error) {
+	buf := bytes.NewBuffer(msg)
+
+	ch, err := decodeCommonHeader(buf)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to decode common header: %v", err)
+	}
+
+	if ch.Version != 3 {
+		return nil, fmt.Errorf("Unsupported BMP version: %d", ch.Version)
+	}
+
+	switch ch.MsgType {
+	case RouteMonitoringType:
+
+	case StatisticsReportType:
+		sr, err := decodeStatsReport(buf, ch)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode stats report: %v", err)
+		}
+
+		return sr, nil
+	case PeerDownNotificationType:
+		pd, err := decodePeerUpNotification(buf, ch)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode peer down notification: %v", err)
+		}
+
+		return pd, nil
+	case PeerUpNotificationType:
+		pu, err := decodePeerUpNotification(buf, ch)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode peer up notification: %v", err)
+		}
+
+		return pu, nil
+	case InitiationMessageType:
+		im, err := decodeInitiationMessage(buf, ch)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode initiation message: %v", err)
+		}
+
+		return im, nil
+	case TerminationMessageType:
+		tm, err := decodeTerminationMessage(buf, ch)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decide termination message: %v", err)
+		}
+
+		return tm, nil
+	case RouteMirroringMessageType:
+
+	default:
+		return nil, fmt.Errorf("Unexpected message type: %d", ch.MsgType)
+
+	}
+
+	return nil, fmt.Errorf("Unexpected message type: %d", ch.MsgType)
+}
diff --git a/protocols/bmp/packet/information_tlv.go b/protocols/bmp/packet/information_tlv.go
new file mode 100644
index 0000000000000000000000000000000000000000..f939729fa93387b00422385fd40fcc5d778f555f
--- /dev/null
+++ b/protocols/bmp/packet/information_tlv.go
@@ -0,0 +1,44 @@
+package packet
+
+import (
+	"bytes"
+
+	"github.com/bio-routing/bio-rd/util/decoder"
+)
+
+const (
+	MinInformationTLVLen = 4
+)
+
+// InformationTLV represents an information TLV
+type InformationTLV struct {
+	InformationType   uint16
+	InformationLength uint16
+	Information       []byte
+}
+
+func decodeInformationTLV(buf *bytes.Buffer) (*InformationTLV, error) {
+	infoTLV := &InformationTLV{}
+
+	fields := []interface{}{
+		&infoTLV.InformationType,
+		&infoTLV.InformationLength,
+	}
+
+	err := decoder.Decode(buf, fields)
+	if err != nil {
+		return infoTLV, err
+	}
+
+	infoTLV.Information = make([]byte, infoTLV.InformationLength)
+	fields = []interface{}{
+		&infoTLV.Information,
+	}
+
+	err = decoder.Decode(buf, fields)
+	if err != nil {
+		return infoTLV, err
+	}
+
+	return infoTLV, nil
+}
diff --git a/protocols/bmp/packet/information_tlv_test.go b/protocols/bmp/packet/information_tlv_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2e42f5b0bac69a22d5f1cfd29695718dbf143348
--- /dev/null
+++ b/protocols/bmp/packet/information_tlv_test.go
@@ -0,0 +1,59 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodeInformationTLV(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *InformationTLV
+	}{
+		{
+			name: "Full",
+			input: []byte{
+				0, 10, 0, 5,
+				1, 2, 3, 4, 5,
+			},
+			wantFail: false,
+			expected: &InformationTLV{
+				InformationType:   10,
+				InformationLength: 5,
+				Information:       []byte{1, 2, 3, 4, 5},
+			},
+		},
+		{
+			name: "Incomplete",
+			input: []byte{
+				0, 10, 0, 5,
+				1, 2, 3, 4,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		infoTLV, err := decodeInformationTLV(buf)
+		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
+		}
+
+		assert.Equalf(t, test.expected, infoTLV, "Test %q", test.name)
+	}
+}
diff --git a/protocols/bmp/packet/initiation_message.go b/protocols/bmp/packet/initiation_message.go
new file mode 100644
index 0000000000000000000000000000000000000000..6a3c02f6276f2bd610cfba56b31148427db55d90
--- /dev/null
+++ b/protocols/bmp/packet/initiation_message.go
@@ -0,0 +1,44 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// InitiationMessage represents an initiation message
+type InitiationMessage struct {
+	CommonHeader *CommonHeader
+	TLVs         []*InformationTLV
+}
+
+// MsgType returns the type of this message
+func (im *InitiationMessage) MsgType() uint8 {
+	return im.CommonHeader.MsgType
+}
+
+// SetCommonHeader sets the common header
+func (im *InitiationMessage) SetCommonHeader(ch *CommonHeader) {
+	im.CommonHeader = ch
+}
+
+func decodeInitiationMessage(buf *bytes.Buffer, ch *CommonHeader) (Msg, error) {
+	im := &InitiationMessage{
+		TLVs: make([]*InformationTLV, 0, 2),
+	}
+
+	read := uint32(0)
+	toRead := ch.MsgLength - CommonHeaderLen
+
+	for read < toRead {
+		tlv, err := decodeInformationTLV(buf)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode TLV: %v", err)
+		}
+
+		im.TLVs = append(im.TLVs, tlv)
+		read += uint32(tlv.InformationLength) + MinInformationTLVLen
+		fmt.Printf("read: %d\n", read)
+	}
+
+	return im, nil
+}
diff --git a/protocols/bmp/packet/initiation_message_test.go b/protocols/bmp/packet/initiation_message_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fdd327371f576a06ecc16aed06c254aeff96a163
--- /dev/null
+++ b/protocols/bmp/packet/initiation_message_test.go
@@ -0,0 +1,83 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodeInitiationMessage(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		ch       *CommonHeader
+		wantFail bool
+		expected *InitiationMessage
+	}{
+		{
+			name: "Full",
+			input: []byte{
+				0, 1, // sysDescr
+				0, 4, // Length
+				42, 42, 42, 42, // AAAA
+				0, 2, //sysName
+				0, 5, // Length
+				43, 43, 43, 43, 43, // BBBBB
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + 17,
+			},
+			wantFail: false,
+			expected: &InitiationMessage{
+				TLVs: []*InformationTLV{
+					{
+						InformationType:   1,
+						InformationLength: 4,
+						Information:       []byte{42, 42, 42, 42},
+					},
+					{
+						InformationType:   2,
+						InformationLength: 5,
+						Information:       []byte{43, 43, 43, 43, 43},
+					},
+				},
+			},
+		},
+		{
+			name: "Incomplete",
+			input: []byte{
+				0, 1, // sysDescr
+				0, 4, // Length
+				42, 42, 42, 42, // AAAA
+				0, 2, //sysName
+				0, 5, // Length
+				43, 43, 43, 43, // BBBB
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + 17,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		im, err := decodeInitiationMessage(buf, test.ch)
+		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
+		}
+
+		assert.Equalf(t, test.expected, im, "Test %q", test.name)
+	}
+}
diff --git a/protocols/bmp/packet/peer_down.go b/protocols/bmp/packet/peer_down.go
new file mode 100644
index 0000000000000000000000000000000000000000..347ffa2c51df7a371506183a30f3683df875795f
--- /dev/null
+++ b/protocols/bmp/packet/peer_down.go
@@ -0,0 +1,48 @@
+package packet
+
+import (
+	"bytes"
+
+	"github.com/bio-routing/bio-rd/util/decoder"
+)
+
+// PeerDownNotification represents a peer down notification
+type PeerDownNotification struct {
+	CommonHeader *CommonHeader
+	Reason       uint8
+	Data         []byte
+}
+
+// MsgType returns the type of this message
+func (p *PeerDownNotification) MsgType() uint8 {
+	return p.CommonHeader.MsgType
+}
+
+func decodePeerDownNotification(buf *bytes.Buffer, ch *CommonHeader) (*PeerDownNotification, error) {
+	p := &PeerDownNotification{}
+
+	fields := []interface{}{
+		&p.Reason,
+	}
+
+	err := decoder.Decode(buf, fields)
+	if err != nil {
+		return nil, err
+	}
+
+	if p.Reason < 1 || p.Reason > 3 {
+		return p, nil
+	}
+
+	p.Data = make([]byte, ch.MsgLength-CommonHeaderLen-1)
+	fields = []interface{}{
+		p.Data,
+	}
+
+	err = decoder.Decode(buf, fields)
+	if err != nil {
+		return nil, err
+	}
+
+	return p, nil
+}
diff --git a/protocols/bmp/packet/peer_down_test.go b/protocols/bmp/packet/peer_down_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..01d7dac8db6dd65dbebbbf68b90ff5baf65ed420
--- /dev/null
+++ b/protocols/bmp/packet/peer_down_test.go
@@ -0,0 +1,89 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodePeerDownNotification(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		ch       *CommonHeader
+		wantFail bool
+		expected *PeerDownNotification
+	}{
+		{
+			name: "Full",
+			input: []byte{
+				1,
+				1, 2, 3,
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + 4,
+			},
+			wantFail: false,
+			expected: &PeerDownNotification{
+				Reason: 1,
+				Data: []byte{
+					1, 2, 3,
+				},
+			},
+		},
+		{
+			name: "Full no data",
+			input: []byte{
+				4,
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + 4,
+			},
+			wantFail: false,
+			expected: &PeerDownNotification{
+				Reason: 4,
+				Data:   nil,
+			},
+		},
+		{
+			name: "Incomplete data",
+			input: []byte{
+				1,
+				1, 2, 3,
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + 5,
+			},
+			wantFail: true,
+		},
+		{
+			name:  "Incomplete",
+			input: []byte{},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + 5,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		p, err := decodePeerDownNotification(buf, test.ch)
+		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
+		}
+
+		assert.Equalf(t, test.expected, p, "Test %q", test.name)
+	}
+}
diff --git a/protocols/bmp/packet/peer_up.go b/protocols/bmp/packet/peer_up.go
new file mode 100644
index 0000000000000000000000000000000000000000..b8873b81a7c465ec0202a1a12ab40a8ca1f1b9b7
--- /dev/null
+++ b/protocols/bmp/packet/peer_up.go
@@ -0,0 +1,95 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/bio-routing/bio-rd/util/decoder"
+)
+
+const (
+	OpenMsgMinLen = 10
+)
+
+// PeerUpNotification represents a peer up notification
+type PeerUpNotification struct {
+	CommonHeader    *CommonHeader
+	LocalAddress    [16]byte
+	LocalPort       uint16
+	RemotePort      uint16
+	SentOpenMsg     []byte
+	ReceivedOpenMsg []byte
+	Information     []byte
+}
+
+// MsgType returns the type of this message
+func (p *PeerUpNotification) MsgType() uint8 {
+	return p.CommonHeader.MsgType
+}
+
+func decodePeerUpNotification(buf *bytes.Buffer, ch *CommonHeader) (*PeerUpNotification, error) {
+	p := &PeerUpNotification{}
+
+	fields := []interface{}{
+		&p.LocalAddress,
+		&p.LocalPort,
+		&p.RemotePort,
+	}
+
+	err := decoder.Decode(buf, fields)
+	if err != nil {
+		return nil, err
+	}
+
+	sentOpenMsg, err := getOpenMsg(buf)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to get OPEN message: %v", err)
+	}
+	p.SentOpenMsg = sentOpenMsg
+
+	recvOpenMsg, err := getOpenMsg(buf)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to get OPEN message: %v", err)
+	}
+	p.ReceivedOpenMsg = recvOpenMsg
+
+	if buf.Len() == 0 {
+		return p, nil
+	}
+
+	p.Information = make([]byte, buf.Len())
+	fields = []interface{}{
+		&p.Information,
+	}
+
+	err = decoder.Decode(buf, fields)
+	if err != nil {
+		return nil, err
+	}
+
+	fmt.Printf("%v\n", p.Information)
+
+	return p, nil
+}
+
+func getOpenMsg(buf *bytes.Buffer) ([]byte, error) {
+	msg := make([]byte, OpenMsgMinLen)
+
+	_, err := buf.Read(msg)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to read: %v", err)
+	}
+
+	if msg[OpenMsgMinLen-1] == 0 {
+		return msg, nil
+	}
+
+	optParams := make([]byte, msg[OpenMsgMinLen-1])
+	_, err = buf.Read(optParams)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to read: %v", err)
+	}
+
+	msg = append(msg, optParams...)
+	return msg, nil
+}
diff --git a/protocols/bmp/packet/peer_up_test.go b/protocols/bmp/packet/peer_up_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c3c2c35f317ab2a08ae01423db74c8b11dc56448
--- /dev/null
+++ b/protocols/bmp/packet/peer_up_test.go
@@ -0,0 +1,120 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodePeerUp(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		ch       *CommonHeader
+		wantFail bool
+		expected *PeerUpNotification
+	}{
+		{
+			name: "Full",
+			input: []byte{
+				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+				0, 100,
+				0, 200,
+
+				// OPEN Sent
+				4,    // Version
+				1, 0, // ASN
+				2, 0, // Hold Time
+				100, 110, 120, 130, // BGP Identifier
+				5, // Opt Parm Len
+				1, 2, 3, 4, 5,
+
+				// OPEN Recv
+				4,    // Version
+				1, 0, // ASN
+				2, 0, // Hold Time
+				100, 110, 120, 130, // BGP Identifier
+				0, // Opt Parm Len
+
+				120, 140, 160, // Information
+			},
+			ch: &CommonHeader{
+				MsgLength: 47,
+			},
+			wantFail: false,
+			expected: &PeerUpNotification{
+				LocalAddress: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+				LocalPort:    100,
+				RemotePort:   200,
+				SentOpenMsg: []byte{
+					4,    // Version
+					1, 0, // ASN
+					2, 0, // Hold Time
+					100, 110, 120, 130, // BGP Identifier
+					5, // Opt Parm Len
+					1, 2, 3, 4, 5,
+				},
+				ReceivedOpenMsg: []byte{
+					// OPEN Recv
+					4,    // Version
+					1, 0, // ASN
+					2, 0, // Hold Time
+					100, 110, 120, 130, // BGP Identifier
+					0, // Opt Parm Len
+				},
+				Information: []byte{
+					120, 140, 160, // Information
+				},
+			},
+		},
+		{
+			name: "Incomplete #1",
+			input: []byte{
+				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+				0, 100,
+			},
+			ch: &CommonHeader{
+				MsgLength: 47,
+			},
+			wantFail: true,
+		},
+		{
+			name: "Incomplete #2",
+			input: []byte{
+				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+				0, 100,
+				0, 200,
+
+				// OPEN Sent
+				4,    // Version
+				1, 0, // ASN
+				2, 0, // Hold Time
+			},
+			ch: &CommonHeader{
+				MsgLength: 47,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		pu, err := decodePeerUpNotification(buf, test.ch)
+		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
+		}
+
+		assert.Equalf(t, test.expected, pu, "Test %q", test.name)
+	}
+}
diff --git a/protocols/bmp/packet/per_peer_header.go b/protocols/bmp/packet/per_peer_header.go
new file mode 100644
index 0000000000000000000000000000000000000000..87231c86920a6729ce72a5de9cbb6a4f4f12e122
--- /dev/null
+++ b/protocols/bmp/packet/per_peer_header.go
@@ -0,0 +1,54 @@
+package packet
+
+import (
+	"bytes"
+
+	"github.com/bio-routing/bio-rd/util/decoder"
+	"github.com/bio-routing/tflow2/convert"
+)
+
+// PerPeerHeader represents a BMP per peer header
+type PerPeerHeader struct {
+	PeerType              uint8
+	PeerFlags             uint8
+	PeerDistinguisher     uint32
+	PeerAddress           [16]byte
+	PeerAS                uint32
+	PeerBGPID             uint32
+	Timestamp             uint32
+	TimestampMicroSeconds uint32
+}
+
+// Serialize serializes a per peer header
+func (p *PerPeerHeader) Serialize(buf *bytes.Buffer) {
+	buf.WriteByte(p.PeerType)
+	buf.WriteByte(p.PeerFlags)
+	buf.Write(convert.Uint32Byte(p.PeerDistinguisher))
+	buf.Write(p.PeerAddress[:])
+	buf.Write(convert.Uint32Byte(p.PeerAS))
+	buf.Write(convert.Uint32Byte(p.PeerBGPID))
+	buf.Write(convert.Uint32Byte(p.Timestamp))
+	buf.Write(convert.Uint32Byte(p.TimestampMicroSeconds))
+}
+
+func decodePerPeerHeader(buf *bytes.Buffer) (*PerPeerHeader, error) {
+	p := &PerPeerHeader{}
+
+	fields := []interface{}{
+		&p.PeerType,
+		&p.PeerFlags,
+		&p.PeerDistinguisher,
+		&p.PeerAddress,
+		&p.PeerAS,
+		&p.PeerBGPID,
+		&p.Timestamp,
+		&p.TimestampMicroSeconds,
+	}
+
+	err := decoder.Decode(buf, fields)
+	if err != nil {
+		return p, err
+	}
+
+	return p, nil
+}
diff --git a/protocols/bmp/packet/per_peer_header_test.go b/protocols/bmp/packet/per_peer_header_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..de503a84edea465106787c5846ce26244c42c84a
--- /dev/null
+++ b/protocols/bmp/packet/per_peer_header_test.go
@@ -0,0 +1,116 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPerPeerHeaderSerialize(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *PerPeerHeader
+		expected []byte
+	}{
+		{
+			name: "Test #1",
+			input: &PerPeerHeader{
+				PeerType:              1,
+				PeerFlags:             2,
+				PeerDistinguisher:     3,
+				PeerAddress:           [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+				PeerAS:                51324,
+				PeerBGPID:             123,
+				Timestamp:             100,
+				TimestampMicroSeconds: 200,
+			},
+			expected: []byte{
+				1,
+				2,
+				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,
+				0, 0, 0, 100,
+				0, 0, 0, 200,
+			},
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(nil)
+		test.input.Serialize(buf)
+		res := buf.Bytes()
+
+		assert.Equalf(t, test.expected, res, "Test %q", test.name)
+	}
+}
+
+func TestDecodePerPeerHeader(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *PerPeerHeader
+	}{
+		{
+			name: "Full packet",
+			input: []byte{
+				1,
+				2,
+				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,
+				0, 0, 0, 100,
+				0, 0, 0, 200,
+			},
+			wantFail: false,
+			expected: &PerPeerHeader{
+				PeerType:              1,
+				PeerFlags:             2,
+				PeerDistinguisher:     3,
+				PeerAddress:           [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+				PeerAS:                51324,
+				PeerBGPID:             123,
+				Timestamp:             100,
+				TimestampMicroSeconds: 200,
+			},
+		},
+		{
+			name: "Incomplete",
+			input: []byte{
+				1,
+				2,
+				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,
+				0, 0, 0, 100,
+				0, 0, 0,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		p, err := decodePerPeerHeader(buf)
+		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
+		}
+
+		assert.Equalf(t, test.expected, p, "Test %q", test.name)
+	}
+}
diff --git a/protocols/bmp/packet/route_mirroring.go b/protocols/bmp/packet/route_mirroring.go
new file mode 100644
index 0000000000000000000000000000000000000000..9c40969f7215a143587237b30d213bdd01db4a74
--- /dev/null
+++ b/protocols/bmp/packet/route_mirroring.go
@@ -0,0 +1 @@
+package packet
diff --git a/protocols/bmp/packet/route_monitoring.go b/protocols/bmp/packet/route_monitoring.go
new file mode 100644
index 0000000000000000000000000000000000000000..9c40969f7215a143587237b30d213bdd01db4a74
--- /dev/null
+++ b/protocols/bmp/packet/route_monitoring.go
@@ -0,0 +1 @@
+package packet
diff --git a/protocols/bmp/packet/stats_report.go b/protocols/bmp/packet/stats_report.go
new file mode 100644
index 0000000000000000000000000000000000000000..c2c4e26366016dd5a49045957fc9f8661916bc7c
--- /dev/null
+++ b/protocols/bmp/packet/stats_report.go
@@ -0,0 +1,60 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/bio-routing/bio-rd/util/decoder"
+)
+
+// StatsReport represents a stats report message
+type StatsReport struct {
+	CommonHeader  *CommonHeader
+	PerPeerHeader *PerPeerHeader
+	StatsCount    uint32
+	Stats         []*InformationTLV
+}
+
+// MsgType returns the type of this message
+func (s *StatsReport) MsgType() uint8 {
+	return s.CommonHeader.MsgType
+}
+
+// SetCommonHeader sets the common header
+func (s *StatsReport) SetCommonHeader(ch *CommonHeader) {
+	s.CommonHeader = ch
+}
+
+func decodeStatsReport(buf *bytes.Buffer, ch *CommonHeader) (Msg, error) {
+	sr := &StatsReport{
+		CommonHeader: ch,
+	}
+
+	pph, err := decodePerPeerHeader(buf)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to decode per peer header: %v", err)
+	}
+
+	sr.PerPeerHeader = pph
+
+	fields := []interface{}{
+		&sr.StatsCount,
+	}
+
+	err = decoder.Decode(buf, fields)
+	if err != nil {
+		return sr, err
+	}
+
+	sr.Stats = make([]*InformationTLV, sr.StatsCount)
+	for i := uint32(0); i < sr.StatsCount; i++ {
+		infoTLV, err := decodeInformationTLV(buf)
+		if err != nil {
+			return sr, fmt.Errorf("Unable to decode information TLV: %v", err)
+		}
+
+		sr.Stats[i] = infoTLV
+	}
+
+	return sr, nil
+}
diff --git a/protocols/bmp/packet/stats_report_test.go b/protocols/bmp/packet/stats_report_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f3a6329dbba9e7d38e90b6a8f2e97147fdc39f91
--- /dev/null
+++ b/protocols/bmp/packet/stats_report_test.go
@@ -0,0 +1,138 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodeStatsReport(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *StatsReport
+	}{
+		{
+			name: "Full",
+			input: []byte{
+				// Per Peer Header
+				1,
+				2,
+				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,
+				0, 0, 0, 100,
+				0, 0, 0, 200,
+
+				// Stats Count
+				0, 0, 0, 2,
+
+				0, 1,
+				0, 4,
+				0, 0, 0, 2,
+
+				0, 2,
+				0, 4,
+				0, 0, 0, 3,
+			},
+			wantFail: false,
+			expected: &StatsReport{
+				PerPeerHeader: &PerPeerHeader{
+					PeerType:              1,
+					PeerFlags:             2,
+					PeerDistinguisher:     3,
+					PeerAddress:           [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+					PeerAS:                51324,
+					PeerBGPID:             123,
+					Timestamp:             100,
+					TimestampMicroSeconds: 200,
+				},
+				StatsCount: 2,
+				Stats: []*InformationTLV{
+					{
+						InformationType:   1,
+						InformationLength: 4,
+						Information:       []byte{0, 0, 0, 2},
+					},
+					{
+						InformationType:   2,
+						InformationLength: 4,
+						Information:       []byte{0, 0, 0, 3},
+					},
+				},
+			},
+		},
+		{
+			name: "Incomplete per peer header",
+			input: []byte{
+				// Per Peer Header
+				1,
+				2,
+				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,
+			},
+			wantFail: true,
+		},
+		{
+			name: "Incomplete stats count",
+			input: []byte{
+				// Per Peer Header
+				1,
+				2,
+				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,
+				0, 0, 0, 100,
+				0, 0, 0, 200,
+			},
+			wantFail: true,
+		},
+		{
+			name: "Incomplete TLV",
+			input: []byte{
+				// Per Peer Header
+				1,
+				2,
+				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,
+				0, 0, 0, 100,
+				0, 0, 0, 200,
+
+				// Stats Count
+				0, 0, 0, 2,
+
+				0, 1,
+				0, 4,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		sr, err := decodeStatsReport(buf, nil)
+		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
+		}
+
+		assert.Equalf(t, test.expected, sr, "Test %q", test.name)
+	}
+}
diff --git a/protocols/bmp/packet/termination_message.go b/protocols/bmp/packet/termination_message.go
new file mode 100644
index 0000000000000000000000000000000000000000..8bf69afe3315ce38b26a4512f0ffc851c8a24af6
--- /dev/null
+++ b/protocols/bmp/packet/termination_message.go
@@ -0,0 +1,38 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// TerminationMessage represents a termination message
+type TerminationMessage struct {
+	CommonHeader *CommonHeader
+	TLVs         []*InformationTLV
+}
+
+// MsgType returns the type of this message
+func (t *TerminationMessage) MsgType() uint8 {
+	return t.CommonHeader.MsgType
+}
+
+func decodeTerminationMessage(buf *bytes.Buffer, ch *CommonHeader) (*TerminationMessage, error) {
+	tm := &TerminationMessage{
+		TLVs: make([]*InformationTLV, 0, 2),
+	}
+
+	read := uint32(0)
+	toRead := ch.MsgLength - CommonHeaderLen
+
+	for read < toRead {
+		tlv, err := decodeInformationTLV(buf)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode TLV: %v", err)
+		}
+
+		tm.TLVs = append(tm.TLVs, tlv)
+		read += uint32(tlv.InformationLength) + MinInformationTLVLen
+	}
+
+	return tm, nil
+}
diff --git a/protocols/bmp/packet/termination_message_test.go b/protocols/bmp/packet/termination_message_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..052290bdf74fc8b268aa4f18f275b10bde93c05d
--- /dev/null
+++ b/protocols/bmp/packet/termination_message_test.go
@@ -0,0 +1,83 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodeTerminationMessage(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		ch       *CommonHeader
+		wantFail bool
+		expected *TerminationMessage
+	}{
+		{
+			name: "Full",
+			input: []byte{
+				0, 1, // sysDescr
+				0, 4, // Length
+				42, 42, 42, 42, // AAAA
+				0, 2, //sysName
+				0, 5, // Length
+				43, 43, 43, 43, 43, // BBBBB
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + 17,
+			},
+			wantFail: false,
+			expected: &TerminationMessage{
+				TLVs: []*InformationTLV{
+					{
+						InformationType:   1,
+						InformationLength: 4,
+						Information:       []byte{42, 42, 42, 42},
+					},
+					{
+						InformationType:   2,
+						InformationLength: 5,
+						Information:       []byte{43, 43, 43, 43, 43},
+					},
+				},
+			},
+		},
+		{
+			name: "Incomplete",
+			input: []byte{
+				0, 1, // sysDescr
+				0, 4, // Length
+				42, 42, 42, 42, // AAAA
+				0, 2, //sysName
+				0, 5, // Length
+				43, 43, 43, 43, // BBBB
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + 17,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		im, err := decodeTerminationMessage(buf, test.ch)
+		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
+		}
+
+		assert.Equalf(t, test.expected, im, "Test %q", test.name)
+	}
+}
diff --git a/protocols/bmp/server/router.go b/protocols/bmp/server/router.go
new file mode 100644
index 0000000000000000000000000000000000000000..b720750f00724ae9d6b1ee3a56c513c756f2f7af
--- /dev/null
+++ b/protocols/bmp/server/router.go
@@ -0,0 +1,38 @@
+package server
+
+import (
+	"net"
+	"time"
+
+	"github.com/bio-routing/bio-rd/protocols/bmp/packet"
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+	log "github.com/sirupsen/logrus"
+)
+
+type router struct {
+	address        net.IP
+	port           uint16
+	con            net.Conn
+	reconnectTime  int
+	reconnectTimer *time.Timer
+	rib4           *locRIB.LocRIB
+	rib6           *locRIB.LocRIB
+}
+
+func (r *router) serve() {
+	for {
+		msg, err := recvMsg(r.con)
+		if err != nil {
+			log.Errorf("Unable to get message: %v", err)
+			return
+		}
+
+		bmpMsg, err := packet.Decode(msg)
+		if err != nil {
+			log.Errorf("Unable to decode BMP message: %v", err)
+			return
+		}
+
+	}
+
+}
diff --git a/protocols/bmp/server/server.go b/protocols/bmp/server/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..7116d0cab5a747f544c6f5301d3e61a1debb2474
--- /dev/null
+++ b/protocols/bmp/server/server.go
@@ -0,0 +1,87 @@
+package server
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"time"
+
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+	log "github.com/sirupsen/logrus"
+)
+
+const (
+	defaultBufferLen = 4096
+)
+
+type BMPServer struct {
+	routers   map[string]*router
+	routersMu sync.RWMutex
+}
+
+func NewServer() *BMPServer {
+	return &BMPServer{
+		routers: make(map[string]*router),
+	}
+}
+
+func (b *BMPServer) AddRouter(addr net.IP, port uint16, rib4 *locRIB.LocRIB, rib6 *locRIB.LocRIB) {
+	r := &router{
+		address:        addr,
+		port:           port,
+		reconnectTime:  0,
+		reconnectTimer: time.NewTimer(time.Duration(0)),
+		rib4:           rib4,
+		rib6:           rib6,
+	}
+
+	b.routersMu.Lock()
+	b.routers[fmt.Sprintf("%s:%d", r.address.String(), r.port)] = r
+	b.routersMu.Unlock()
+
+	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 = 30
+				} else if r.reconnectTime < 720 {
+					r.reconnectTime *= 2
+				}
+				r.reconnectTimer = time.NewTimer(time.Second * time.Duration(r.reconnectTime))
+				continue
+			}
+
+			r.reconnectTime = 0
+			r.con = c
+			r.serve()
+		}
+	}(r)
+}
+
+func recvMsg(c net.Conn) (msg []byte, err error) {
+	buffer := make([]byte, defaultBufferLen)
+	_, err = io.ReadFull(c, buffer[0:packet.MinLen])
+	if err != nil {
+		return nil, fmt.Errorf("Read failed: %v", err)
+	}
+
+	l := int(buffer[1])*256*256*256 + int(buffer[2])*256*256 + int(buffer[3])*256 + int(buffer[4])
+	if l > defaultBufferLen {
+		tmp := buffer
+		buffer = make([]byte, l)
+		copy(buffer, tmp)
+	}
+
+	toRead := l
+	_, err = io.ReadFull(c, buffer[packet.MinLen:toRead])
+	if err != nil {
+		return nil, fmt.Errorf("Read failed: %v", err)
+	}
+
+	return buffer, nil
+}
diff --git a/util/decoder/decoder.go b/util/decoder/decoder.go
new file mode 100644
index 0000000000000000000000000000000000000000..709e39352a32471d241112a0c205a8a70b4e34bb
--- /dev/null
+++ b/util/decoder/decoder.go
@@ -0,0 +1,19 @@
+package decoder
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+)
+
+// Decode decodes network packets
+func Decode(buf *bytes.Buffer, fields []interface{}) error {
+	var err error
+	for _, field := range fields {
+		err = binary.Read(buf, binary.BigEndian, field)
+		if err != nil {
+			return fmt.Errorf("Unable to read from buffer: %v", err)
+		}
+	}
+	return nil
+}
diff --git a/util/decoder/decoder_test.go b/util/decoder/decoder_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..acb13e220f32ce4f7032b4a432eea66390cb8d82
--- /dev/null
+++ b/util/decoder/decoder_test.go
@@ -0,0 +1,41 @@
+package decoder
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecode(t *testing.T) {
+	input := []byte{
+		3, 0, 0, 0, 100, 200,
+	}
+
+	type testData struct {
+		a uint8
+		b uint32
+		c []byte
+	}
+
+	s := testData{
+		c: make([]byte, 1),
+	}
+
+	fields := []interface{}{
+		&s.a,
+		&s.b,
+		&s.c,
+	}
+
+	buf := bytes.NewBuffer(input)
+	Decode(buf, fields)
+
+	expected := testData{
+		a: 3,
+		b: 100,
+		c: []byte{200},
+	}
+
+	assert.Equal(t, expected, s)
+}