diff --git a/protocols/bmp/packet/BUILD.bazel b/protocols/bmp/packet/BUILD.bazel index 9eab9b8a4e84dbb3ab07bd484969216d32096874..4f2b195ecf31c771122c40cfde3036e9b8538c45 100644 --- a/protocols/bmp/packet/BUILD.bazel +++ b/protocols/bmp/packet/BUILD.bazel @@ -32,6 +32,8 @@ go_test( "peer_down_test.go", "peer_up_test.go", "per_peer_header_test.go", + "route_mirroring_test.go", + "route_monitoring_test.go", "stats_report_test.go", "termination_message_test.go", ], diff --git a/protocols/bmp/packet/decode.go b/protocols/bmp/packet/decode.go index 522e57a9ebcd814d32c188c935ee5a2122033434..2e0a0682f17f352782d638a544bdbf7b9b75535f 100644 --- a/protocols/bmp/packet/decode.go +++ b/protocols/bmp/packet/decode.go @@ -5,11 +5,8 @@ import ( "fmt" ) -type Msg interface { - MsgType() uint8 -} - const ( + // MinLen is the minimal length of a BMP message MinLen = 6 RouteMonitoringType = 0 @@ -25,8 +22,16 @@ const ( ErroredPDU = 0 MessageLost = 1 + + // BMPVersion is the supported BMP version + BMPVersion = 3 ) +// Msg is an interface that every BMP message must fulfill +type Msg interface { + MsgType() uint8 +} + // Decode decodes a BMP message func Decode(msg []byte) (Msg, error) { buf := bytes.NewBuffer(msg) @@ -36,13 +41,18 @@ func Decode(msg []byte) (Msg, error) { return nil, fmt.Errorf("Unable to decode common header: %v", err) } - if ch.Version != 3 { + if ch.Version != BMPVersion { return nil, fmt.Errorf("Unsupported BMP version: %d", ch.Version) } switch ch.MsgType { case RouteMonitoringType: + rm, err := decodeRouteMonitoringMsg(buf, ch) + if err != nil { + return nil, fmt.Errorf("Unable to decode route monitoring message: %v", err) + } + return rm, err case StatisticsReportType: sr, err := decodeStatsReport(buf, ch) if err != nil { diff --git a/protocols/bmp/packet/route_mirroring.go b/protocols/bmp/packet/route_mirroring.go new file mode 100644 index 0000000000000000000000000000000000000000..007f126204efb6f746c605c917c366eb19807829 --- /dev/null +++ b/protocols/bmp/packet/route_mirroring.go @@ -0,0 +1,41 @@ +package packet + +import ( + "bytes" + "fmt" +) + +// RouteMirroringMsg represents a route mirroring message +type RouteMirroringMsg struct { + CommonHeader *CommonHeader + PerPeerHeader *PerPeerHeader + TLVs []*InformationTLV +} + +func decodeRouteMirroringMsg(buf *bytes.Buffer, ch *CommonHeader) (*RouteMirroringMsg, error) { + rm := &RouteMirroringMsg{ + CommonHeader: ch, + } + + pph, err := decodePerPeerHeader(buf) + if err != nil { + return nil, fmt.Errorf("Unable to decode per peer header: %v", err) + } + + rm.PerPeerHeader = pph + + toRead := buf.Len() + read := 0 + + for read < toRead { + tlv, err := decodeInformationTLV(buf) + if err != nil { + return nil, fmt.Errorf("Unable to decode TLV: %v", err) + } + + rm.TLVs = append(rm.TLVs, tlv) + read += int(tlv.InformationLength) + MinInformationTLVLen + } + + return rm, nil +} diff --git a/protocols/bmp/packet/route_mirroring_test.go b/protocols/bmp/packet/route_mirroring_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dd885aca49d74c545b05798cb47df882e18693c7 --- /dev/null +++ b/protocols/bmp/packet/route_mirroring_test.go @@ -0,0 +1,120 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeRouteMirroringMsg(t *testing.T) { + tests := []struct { + name string + input []byte + ch *CommonHeader + wantFail bool + expected *RouteMirroringMsg + }{ + { + name: "Full", + 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, + + 0, 1, 0, 2, 100, 200, + 0, 1, 0, 2, 100, 200, + }, + ch: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 12, + }, + wantFail: false, + expected: &RouteMirroringMsg{ + CommonHeader: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 12, + }, + 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, + }, + TLVs: []*InformationTLV{ + { + InformationType: 1, + InformationLength: 2, + Information: []byte{100, 200}, + }, + { + InformationType: 1, + InformationLength: 2, + Information: []byte{100, 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, + }, + ch: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 12, + }, + wantFail: true, + }, + { + name: "Incomplete TLV", + 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, + + 0, 1, 0, 2, 100, 200, + 0, 1, 0, 2, + }, + ch: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 12, + }, + wantFail: true, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(test.input) + r, err := decodeRouteMirroringMsg(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, r, "Test %q", test.name) + } +}