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) + } +}