diff --git a/protocols/bgp/packet/nlri.go b/protocols/bgp/packet/nlri.go
index bd55739c034a9a368ca129101ddc4308d897a8c4..84ae542cff8c1015b45b750528fba4b77a32eee7 100644
--- a/protocols/bgp/packet/nlri.go
+++ b/protocols/bgp/packet/nlri.go
@@ -65,7 +65,18 @@ func decodeNLRI(buf *bytes.Buffer, afi uint16, addPath bool) (*NLRI, uint8, erro
 
 		consumed += pathIdentifierLen
 	}
+	pfx, c, err := DecodePrefixFromNLRI(buf, afi)
+	consumed += c
+	if err != nil {
+		return nil, consumed, err
+	}
+	nlri.Prefix = pfx
+
+	return nlri, consumed, nil
+}
 
+func DecodePrefixFromNLRI(buf *bytes.Buffer, afi uint16) (*bnet.Prefix, uint8, error) {
+	var consumed uint8 = 0
 	pfxLen, err := buf.ReadByte()
 	if err != nil {
 		return nil, consumed, err
@@ -82,12 +93,8 @@ func decodeNLRI(buf *bytes.Buffer, afi uint16, addPath bool) (*NLRI, uint8, erro
 	}
 
 	pfx, err := deserializePrefix(bytes, pfxLen, afi)
-	if err != nil {
-		return nil, consumed, err
-	}
-	nlri.Prefix = pfx
+	return pfx, consumed, err
 
-	return nlri, consumed, nil
 }
 
 func (n *NLRI) serialize(buf *bytes.Buffer, addPath bool) uint8 {
diff --git a/protocols/mtr/packet/decode.go b/protocols/mtr/packet/decode.go
new file mode 100644
index 0000000000000000000000000000000000000000..b8cc1732743d6dd76a92d7da8669b3617dd57389
--- /dev/null
+++ b/protocols/mtr/packet/decode.go
@@ -0,0 +1,60 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/bio-routing/bio-rd/util/decode"
+	"github.com/pkg/errors"
+	"io"
+	"time"
+)
+
+const minimalHeaderLength = 12
+
+// The Decode function reads from the input and calls the target with every successfully
+// decoded MTR record.
+// If there is an error while decoding, all decoding stops and the error is returned
+func Decode(input io.Reader, target func(MTRRecord)) error {
+	minimalHeader := bytes.NewBuffer(make([]byte, minimalHeaderLength))
+	minimalHeader.Reset()
+	payload := bytes.NewBuffer(nil)
+	for {
+		// copy header to check if next header exits.
+		read, err := io.CopyN(minimalHeader, input, minimalHeaderLength)
+		if err == io.EOF {
+			if read == 0 {
+				return nil
+			}
+			return fmt.Errorf("read %d bytes, expected %d for next mtr header", read, minimalHeaderLength)
+		}
+		if err != nil {
+			return errors.Wrap(err, "failed to read mtr header from input")
+		}
+		record, err := decodeHeader(minimalHeader)
+		if err != nil {
+			return errors.Wrap(err, "failed to decode header")
+		}
+		_, err = io.CopyN(payload, input, int64(record.Length))
+		if err != nil {
+			return errors.Wrap(err, "failed to copy expected message length from input stream")
+		}
+		err = record.Message.Decode(payload)
+		if err != nil {
+			return errors.Wrap(err, "failed to decode mtr message")
+		}
+		target(record)
+	}
+}
+
+// decodeHeader decodes
+func decodeHeader(data *bytes.Buffer) (MTRRecord, error) {
+	var seconds uint32
+	var ret MTRRecord
+	err := decode.Decode(data, []interface{}{&seconds, &ret.Type.Type, &ret.Type.SubType, &ret.Length})
+	if err != nil {
+		return ret, errors.Wrap(err, "failed to decode mtr header")
+	}
+	ret.Message, err = messageForType(ret.Type)
+	ret.TimeStamp = time.Unix(int64(seconds), 0).UTC()
+	return ret, errors.Wrap(err, "failed to set message for given type")
+}
diff --git a/protocols/mtr/packet/decode_test.go b/protocols/mtr/packet/decode_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..93405b7233a5ae71384aee5d94099c6c1b1ff898
--- /dev/null
+++ b/protocols/mtr/packet/decode_test.go
@@ -0,0 +1,86 @@
+package packet
+
+import (
+	"bytes"
+	"github.com/stretchr/testify/assert"
+	"testing"
+	"time"
+)
+
+func TestDecodeHeader(t *testing.T) {
+	testcases := []struct {
+		name   string
+		input  []byte
+		output MTRRecord
+		error  string
+	}{
+		{
+			name: "peer table index",
+			input: []byte{0, 1, 2, 3, // TimeStamp
+				0, 13, 0, 1, // Type
+				0, 0, 0, 0, // Length
+			},
+			output: MTRRecord{
+				TimeStamp: time.Date(1970, 01, 01, 18, 20, 51, 0, time.UTC),
+				Type: MessageType{
+					TABLE_DUMP_V2, PEER_INDEX_TABLE,
+				},
+				Length:  0,
+				Message: &PeerIndexTable{},
+			},
+		},
+		{
+			name: "short read peer table index",
+			input: []byte{0, 1, 2, 3, // TimeStamp
+				0, 13, 0, 1, // Type
+				0, // Length
+			},
+			output: MTRRecord{
+				Type: MessageType{
+					TABLE_DUMP_V2, PEER_INDEX_TABLE,
+				},
+			},
+			error: "failed to decode mtr header: Unable to read from buffer: unexpected EOF",
+		},
+		{
+			name: "unknown type",
+			input: []byte{0, 1, 2, 3, // TimeStamp
+				0, 1, 0, 1, // Type
+				0, 0, 0, 0, // Length
+			},
+			output: MTRRecord{
+				TimeStamp: time.Date(1970, 01, 01, 18, 20, 51, 0, time.UTC),
+				Type: MessageType{
+					1, 1,
+				},
+			},
+			error: "failed to set message for given type: given type 1 subtype 1 not implemented",
+		},
+		{
+			name: "unknown subtype",
+			input: []byte{0, 1, 2, 3, // TimeStamp
+				0, 13, 0, 10, // Type
+				0, 0, 0, 0, // Length
+			},
+			output: MTRRecord{
+				TimeStamp: time.Date(1970, 01, 01, 18, 20, 51, 0, time.UTC),
+				Type: MessageType{
+					TABLE_DUMP_V2, 10,
+				},
+			},
+			error: "failed to set message for given type: unknown subtype 10 for TABLE_DUMP_V2",
+		},
+	}
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			buf := bytes.NewBuffer(tc.input)
+			out, err := decodeHeader(buf)
+			assert.Equal(t, tc.output, out)
+			if tc.error != "" {
+				assert.EqualError(t, err, tc.error)
+			} else {
+				assert.Nil(t, err)
+			}
+		})
+	}
+}
diff --git a/protocols/mtr/packet/table_dump_v2.go b/protocols/mtr/packet/table_dump_v2.go
new file mode 100644
index 0000000000000000000000000000000000000000..d98d53dc6e8438f5a85c3cdc188f35949816c573
--- /dev/null
+++ b/protocols/mtr/packet/table_dump_v2.go
@@ -0,0 +1,172 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/util/decode"
+	"github.com/pkg/errors"
+)
+
+func messageForTableDumpV2SubType(r RecordSubType) (MTRMessage, error) {
+	switch r {
+	case PEER_INDEX_TABLE:
+		return &PeerIndexTable{}, nil
+	case RIB_IPV4_UNICAST:
+		return &RIBIPv4Unicast{}, nil
+	case RIB_IPV4_MULTICAST:
+		return &RIBIPv4Multicast{}, nil
+	case RIB_IPV6_UNICAST:
+		return &RIBIPv6Unicast{}, nil
+	case RIB_IPV6_MULTICAST:
+		return &RIBIPv6Multicast{}, nil
+	case RIB_GENERIC:
+		return &RIBGeneric{}, nil
+	default:
+		return nil, fmt.Errorf("unknown subtype %d for TABLE_DUMP_V2", r)
+	}
+}
+
+type PeerIndexTable struct {
+	BGPID       uint32
+	NameLength  uint16
+	Name        string
+	PeerCount   uint16
+	PeerEntries []PeerIndexTableEntry
+}
+
+type PeerIndexTableEntry struct {
+	ASSizeBytes   uint8
+	IPSizeBytes   uint8
+	BGPID         uint32
+	PeerIPAddress *net.IP
+	PeerAs        []byte
+}
+
+func (p *PeerIndexTableEntry) Decode(data *bytes.Buffer) error {
+	var flags byte
+	err := decode.Decode(data, []interface{}{&flags, &p.BGPID})
+	if err != nil {
+		return errors.Wrap(err, "failed to decode peer table entry constant length values")
+	}
+	if flags&1 == 1 { // 7th bit is set
+		p.IPSizeBytes = 16
+	} else {
+		p.IPSizeBytes = 4
+	}
+	if (flags>>1)&1 == 1 { // 6th bit is set
+		p.ASSizeBytes = 2
+	} else {
+		p.ASSizeBytes = 4
+	}
+	ipBytes := make([]byte, p.IPSizeBytes)
+	_, err = data.Read(ipBytes)
+	if err != nil {
+		return errors.Wrap(err, "failed to read IP bytes from input")
+	}
+	p.PeerIPAddress, err = net.IPFromBytes(ipBytes)
+	if err != nil {
+		return errors.Wrap(err, "failed to convert bytes to IP")
+	}
+	p.PeerAs = make([]byte, p.ASSizeBytes)
+	_, err = data.Read(p.PeerAs)
+	if err != nil {
+		return errors.Wrap(err, "failed to read AS bytes from input")
+	}
+	return nil
+}
+
+func (p *PeerIndexTable) Decode(data *bytes.Buffer) error {
+	err := decode.Decode(data, []interface{}{&p.BGPID, &p.NameLength})
+	if err != nil {
+		return errors.Wrap(err, "failed to decode BGPID and length of name from input")
+	}
+	name := make([]byte, p.NameLength)
+	_, err = data.Read(name)
+	if err != nil {
+		return errors.Wrap(err, "failed to read name bytes")
+	}
+	p.Name = string(name)
+	err = decode.Decode(data, []interface{}{&p.PeerCount})
+	if err != nil {
+		return errors.Wrap(err, "failed to decode peer count")
+	}
+	p.PeerEntries = make([]PeerIndexTableEntry, p.PeerCount)
+	for i := range p.PeerEntries {
+		err = p.PeerEntries[i].Decode(data)
+		if err != nil {
+			return errors.Wrapf(err, "failed to decode %d peer table entry", i)
+		}
+	}
+	return nil
+}
+
+type RIBEntry struct {
+}
+
+type AFI uint16
+
+const (
+	IPv4 AFI = 1
+	IPv6 AFI = 2
+)
+
+// specificRIBSubType is a general implementation of the specific subtype
+type specificRIBSubtype struct {
+	SequenceNumber uint32
+	Prefix         *net.Prefix
+	RIBEntries     []RIBEntry
+}
+
+func (s *specificRIBSubtype) decodeSpecific(data *bytes.Buffer, afi AFI) error {
+	err := decode.Decode(data, []interface{}{
+		&s.SequenceNumber,
+	})
+	if err != nil {
+		return errors.Wrap(err, "failed to decode sequence number")
+	}
+	s.Prefix, _, err = packet.DecodePrefixFromNLRI(data, uint16(afi))
+	if err != nil {
+		return errors.Wrap(err, "failed to decode prefix")
+	}
+	// TODO: Implement RIB entries
+	return nil
+}
+
+type RIBIPv4Unicast struct {
+	specificRIBSubtype
+}
+
+func (R *RIBIPv4Unicast) Decode(data *bytes.Buffer) error {
+	return R.decodeSpecific(data, IPv4)
+}
+
+type RIBIPv4Multicast struct {
+}
+
+func (R *RIBIPv4Multicast) Decode(data *bytes.Buffer) error {
+	return errors.New("Not implemented")
+}
+
+type RIBIPv6Unicast struct {
+	specificRIBSubtype
+}
+
+func (R *RIBIPv6Unicast) Decode(data *bytes.Buffer) error {
+	return R.decodeSpecific(data, IPv6)
+}
+
+type RIBIPv6Multicast struct {
+}
+
+func (R *RIBIPv6Multicast) Decode(data *bytes.Buffer) error {
+	return errors.New("Not implemented")
+}
+
+type RIBGeneric struct {
+}
+
+func (R *RIBGeneric) Decode(data *bytes.Buffer) error {
+	return errors.New("Not implemented")
+}
diff --git a/protocols/mtr/packet/types.go b/protocols/mtr/packet/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..f7f618d2a15ebfc199c7c668c1d8f64cb8385564
--- /dev/null
+++ b/protocols/mtr/packet/types.go
@@ -0,0 +1,59 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+	"time"
+)
+
+type RecordType uint16
+
+const (
+	OSPFv2        RecordType = 11
+	TABLE_DUMP    RecordType = 12
+	TABLE_DUMP_V2 RecordType = 13
+	BGP4MP        RecordType = 16
+	BGP4MP_ET     RecordType = 17
+	ISIS          RecordType = 32
+	ISIS_ET       RecordType = 33
+	OSPFv3        RecordType = 48
+	OSPFv3_ET     RecordType = 48
+)
+
+type RecordSubType uint16
+
+const (
+	PEER_INDEX_TABLE   RecordSubType = 1
+	RIB_IPV4_UNICAST   RecordSubType = 2
+	RIB_IPV4_MULTICAST RecordSubType = 3
+	RIB_IPV6_UNICAST   RecordSubType = 4
+	RIB_IPV6_MULTICAST RecordSubType = 5
+	RIB_GENERIC        RecordSubType = 6
+)
+
+type MessageType struct {
+	Type    RecordType
+	SubType RecordSubType
+}
+
+// An MTRMessage decodes itself from the given Reader. It will not read until
+// the end of the reader. Instead, it reads only the parts defined by the MTR header
+type MTRMessage interface {
+	Decode(data *bytes.Buffer) error
+}
+
+type MTRRecord struct {
+	TimeStamp time.Time
+	Type      MessageType
+	Length    uint32
+	Message   MTRMessage
+}
+
+func messageForType(t MessageType) (MTRMessage, error) {
+	switch t.Type {
+	case TABLE_DUMP_V2:
+		return messageForTableDumpV2SubType(t.SubType)
+	default:
+		return nil, fmt.Errorf("given type %d subtype %d not implemented", t.Type, t.SubType)
+	}
+}