diff --git a/BUILD.bazel b/BUILD.bazel index ae2f1b1751ac95160a2e2097c520c3f99339211f..e246217503abd868585d80d2f8e1c6ee6cd3f679 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,3 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/bio-routing/bio-rd @@ -7,28 +6,3 @@ gazelle( external = "vendored", prefix = "github.com/bio-routing/bio-rd", ) - -go_library( - name = "go_default_library", - srcs = [ - "main.go", - "main_ipv4.go", - ], - importpath = "github.com/bio-routing/bio-rd", - visibility = ["//visibility:private"], - deps = [ - "//config:go_default_library", - "//net:go_default_library", - "//protocols/bgp/server:go_default_library", - "//routingtable:go_default_library", - "//routingtable/filter:go_default_library", - "//routingtable/locRIB:go_default_library", - "//vendor/github.com/sirupsen/logrus:go_default_library", - ], -) - -go_binary( - name = "bio-rd", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/examples/bgp/BUILD.bazel b/examples/bgp/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..d8bbb32432924040e60d2e9bdbab42bdf9e05ab1 --- /dev/null +++ b/examples/bgp/BUILD.bazel @@ -0,0 +1,26 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "main.go", + "main_ipv4.go", + ], + importpath = "github.com/bio-routing/bio-rd/examples/bgp", + visibility = ["//visibility:private"], + deps = [ + "//config:go_default_library", + "//net:go_default_library", + "//protocols/bgp/server:go_default_library", + "//routingtable:go_default_library", + "//routingtable/filter:go_default_library", + "//routingtable/locRIB:go_default_library", + "//vendor/github.com/sirupsen/logrus:go_default_library", + ], +) + +go_binary( + name = "bgp", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/main.go b/examples/bgp/main.go similarity index 100% rename from main.go rename to examples/bgp/main.go diff --git a/main_ipv4.go b/examples/bgp/main_ipv4.go similarity index 100% rename from main_ipv4.go rename to examples/bgp/main_ipv4.go diff --git a/main_ipv6.go b/examples/bgp/main_ipv6.go similarity index 100% rename from main_ipv6.go rename to examples/bgp/main_ipv6.go diff --git a/examples/bmp/BUILD.bazel b/examples/bmp/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..63c52adaba65907513eda97e260a79955d622d34 --- /dev/null +++ b/examples/bmp/BUILD.bazel @@ -0,0 +1,19 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["main_bmp.go"], + importpath = "github.com/bio-routing/bio-rd/examples/bmp", + visibility = ["//visibility:private"], + deps = [ + "//protocols/bmp/server:go_default_library", + "//routingtable/locRIB:go_default_library", + "//vendor/github.com/sirupsen/logrus:go_default_library", + ], +) + +go_binary( + name = "bmp", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/examples/bmp/main_bmp.go b/examples/bmp/main_bmp.go new file mode 100644 index 0000000000000000000000000000000000000000..146fa4244f267d3fadfcf7fb1b5815524cfcc909 --- /dev/null +++ b/examples/bmp/main_bmp.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "net" + "time" + + "github.com/bio-routing/bio-rd/protocols/bmp/server" + "github.com/bio-routing/bio-rd/routingtable/locRIB" + "github.com/sirupsen/logrus" +) + +func main() { + logrus.Printf("This is a BMP speaker\n") + + rib := locRIB.New() + b := server.NewServer() + b.AddRouter(net.IP{127, 0, 0, 1}, 1234, rib, nil) + + go func() { + for { + fmt.Printf("LocRIB count: %d\n", rib.Count()) + time.Sleep(time.Second * 10) + } + }() + + select {} +} diff --git a/fuzzing/packet/BUILD.bazel b/fuzzing/packet/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..86236b2a53413f8fbeb09531ade599310f84ff7d --- /dev/null +++ b/fuzzing/packet/BUILD.bazel @@ -0,0 +1,14 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["gen_initial_corpus.go"], + importpath = "github.com/bio-routing/bio-rd/fuzzing/packet", + visibility = ["//visibility:private"], +) + +go_binary( + name = "packet", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/protocols/bmp/packet/BUILD.bazel b/protocols/bmp/packet/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..599b586984491877b921ba92cb54d1367ea3e8c4 --- /dev/null +++ b/protocols/bmp/packet/BUILD.bazel @@ -0,0 +1,43 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "common_header.go", + "decode.go", + "information_tlv.go", + "initiation_message.go", + "peer_down.go", + "peer_up.go", + "per_peer_header.go", + "route_mirroring.go", + "route_monitoring.go", + "stats_report.go", + "termination_message.go", + ], + importpath = "github.com/bio-routing/bio-rd/protocols/bmp/packet", + visibility = ["//visibility:public"], + deps = [ + "//util/decoder:go_default_library", + "//vendor/github.com/taktv6/tflow2/convert:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "common_header_test.go", + "decode_test.go", + "information_tlv_test.go", + "initiation_message_test.go", + "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", + ], + embed = [":go_default_library"], + deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"], +) diff --git a/protocols/bmp/packet/common_header.go b/protocols/bmp/packet/common_header.go new file mode 100644 index 0000000000000000000000000000000000000000..5b268355fb07c67ddb619fc5c1f04702bb338035 --- /dev/null +++ b/protocols/bmp/packet/common_header.go @@ -0,0 +1,43 @@ +package packet + +import ( + "bytes" + + "github.com/bio-routing/bio-rd/util/decoder" + "github.com/taktv6/tflow2/convert" +) + +const ( + // CommonHeaderLen is the length of a common header + CommonHeaderLen = 6 +) + +// CommonHeader represents a common header +type CommonHeader struct { + Version uint8 + MsgLength uint32 + MsgType uint8 +} + +// Serialize serializes a common header +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..d7989e28bb5955a294b195962d06c980cd0b645b --- /dev/null +++ b/protocols/bmp/packet/decode.go @@ -0,0 +1,102 @@ +package packet + +import ( + "bytes" + "fmt" +) + +const ( + // MinLen is the minimal length of a BMP message + MinLen = 6 + + RouteMonitoringType = 0 + StatisticsReportType = 1 + PeerDownNotificationType = 2 + PeerUpNotificationType = 3 + InitiationMessageType = 4 + TerminationMessageType = 5 + RouteMirroringMessageType = 6 + + BGPMessage = 0 + BGPInformation = 1 + + 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) + + ch, err := decodeCommonHeader(buf) + if err != nil { + return nil, fmt.Errorf("Unable to decode common header: %v", err) + } + + 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 { + 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 decode termination message: %v", err) + } + + return tm, nil + case RouteMirroringMessageType: + rm, err := decodeRouteMirroringMsg(buf, ch) + if err != nil { + return nil, fmt.Errorf("Unable to decode route mirroring message: %v", err) + } + + return rm, nil + default: + return nil, fmt.Errorf("Unexpected message type: %d", ch.MsgType) + + } +} diff --git a/protocols/bmp/packet/decode_test.go b/protocols/bmp/packet/decode_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b3419725826e141b6225838f30d9e7d91296a2ba --- /dev/null +++ b/protocols/bmp/packet/decode_test.go @@ -0,0 +1,453 @@ +package packet + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecode(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + expected Msg + }{ + { + name: "incomplete common header", + input: []byte{1, 2}, + wantFail: true, + }, + { + name: "Invalid version", + input: []byte{0, 0, 0, 0, 6, 5}, + wantFail: true, + }, + { + name: "Route monitoring ok", + input: []byte{ + 3, 0, 0, 0, 6 + 38 + 4, 0, + + 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, + + 1, 2, 3, 4, + }, + wantFail: false, + expected: &RouteMonitoringMsg{ + CommonHeader: &CommonHeader{ + Version: 3, + MsgLength: 6 + 38 + 4, + MsgType: 0, + }, + 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, + }, + BGPUpdate: []byte{1, 2, 3, 4}, + }, + }, + { + name: "Route monitoring nok", + input: []byte{ + 3, 0, 0, 0, 6 + 38 + 4, 0, + + 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, + + 1, 2, + }, + wantFail: true, + }, + { + name: "Statistic report ok", + input: []byte{ + 3, 0, 0, 0, 6 + 9 + 38, 1, + + 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, 0, 0, 1, + 0, 1, 0, 1, 1, + }, + wantFail: false, + expected: &StatsReport{ + CommonHeader: &CommonHeader{ + Version: 3, + MsgLength: 6 + 9 + 38, + MsgType: 1, + }, + 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: 1, + Stats: []*InformationTLV{ + { + InformationType: 1, + InformationLength: 1, + Information: []byte{1}, + }, + }, + }, + }, + { + name: "Statistic report nok", + input: []byte{ + 3, 0, 0, 0, 6 + 9 + 38, 1, + + 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, + }, + wantFail: true, + }, + { + name: "peer down ok", + input: []byte{ + 3, 0, 0, 0, 6 + 9 + 38, 1, + + 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, 0, 0, 1, + 0, 1, 0, 1, 1, + }, + wantFail: false, + expected: &StatsReport{ + CommonHeader: &CommonHeader{ + Version: 3, + MsgLength: 6 + 9 + 38, + MsgType: 1, + }, + 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: 1, + Stats: []*InformationTLV{ + { + InformationType: 1, + InformationLength: 1, + Information: []byte{1}, + }, + }, + }, + }, + { + name: "peer down nok", + input: []byte{ + 3, 0, 0, 0, 6 + 9 + 38, 1, + + 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, 0, 0, 1, + 0, 1, 0, 1, + }, + wantFail: true, + }, + { + name: "peer up ok", + input: []byte{ + 3, 0, 0, 0, 54, 3, + + 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, + + 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 + }, + wantFail: false, + expected: &PeerUpNotification{ + CommonHeader: &CommonHeader{ + Version: 3, + MsgLength: 54, + MsgType: 3, + }, + 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, + }, + 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: "peer up nok", + input: []byte{ + 3, 0, 0, 0, 54, 3, + + 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, + + 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, + }, + wantFail: true, + }, + { + name: "initiation message ok", + input: []byte{ + 3, 0, 0, 0, 11, 4, + + 0, 1, 0, 1, 5, + }, + wantFail: false, + expected: &InitiationMessage{ + CommonHeader: &CommonHeader{ + Version: 3, + MsgLength: 11, + MsgType: 4, + }, + TLVs: []*InformationTLV{ + { + InformationType: 1, + InformationLength: 1, + Information: []byte{5}, + }, + }, + }, + }, + { + name: "initiation message nok", + input: []byte{ + 3, 0, 0, 0, + }, + wantFail: true, + }, + { + name: "termination message ok", + input: []byte{ + 3, 0, 0, 0, 11, 5, + + 0, 1, 0, 1, 5, + }, + wantFail: false, + expected: &TerminationMessage{ + CommonHeader: &CommonHeader{ + Version: 3, + MsgLength: 11, + MsgType: 5, + }, + TLVs: []*InformationTLV{ + { + InformationType: 1, + InformationLength: 1, + Information: []byte{5}, + }, + }, + }, + }, + { + name: "termination message nok", + input: []byte{ + 3, 0, 0, 0, 11, 5, + + 0, 1, 0, + }, + wantFail: true, + }, + { + name: "route mirror message ok", + input: []byte{ + 3, 0, 0, 0, 49, 6, + + 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, 1, 5, + }, + wantFail: false, + expected: &RouteMirroringMsg{ + CommonHeader: &CommonHeader{ + Version: 3, + MsgLength: 49, + MsgType: 6, + }, + 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: 1, + Information: []byte{5}, + }, + }, + }, + }, + { + name: "route mirror message nok", + input: []byte{ + 3, 0, 0, 0, 49, 6, + + 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, + }, + wantFail: true, + }, + { + name: "invalid msg type", + input: []byte{ + 3, 0, 0, 0, 49, 7, + }, + wantFail: true, + }, + } + + for _, test := range tests { + m, err := Decode(test.input) + 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, m, "Test %q", test.name) + } +} 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..f718a1b54f0aec63cee5d03311c292b089830986 --- /dev/null +++ b/protocols/bmp/packet/information_tlv_test.go @@ -0,0 +1,66 @@ +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 Value", + input: []byte{ + 0, 10, 0, 5, + 1, 2, 3, 4, + }, + wantFail: true, + }, + { + name: "Incomplete Header", + input: []byte{ + 0, 10, + }, + 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..6fbc3f30c2c0f09e178b981964661990425ca597 --- /dev/null +++ b/protocols/bmp/packet/initiation_message.go @@ -0,0 +1,39 @@ +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 +} + +func decodeInitiationMessage(buf *bytes.Buffer, ch *CommonHeader) (Msg, error) { + im := &InitiationMessage{ + CommonHeader: ch, + 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 + } + + 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..d5c3a129b7adc3a0ec46778d10f05da46b175ec9 --- /dev/null +++ b/protocols/bmp/packet/initiation_message_test.go @@ -0,0 +1,97 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInitiationMessageMsgType(t *testing.T) { + pd := &InitiationMessage{ + CommonHeader: &CommonHeader{ + MsgType: 100, + }, + } + + if pd.MsgType() != 100 { + t.Errorf("Unexpected result") + } +} +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{ + CommonHeader: &CommonHeader{ + MsgLength: CommonHeaderLen + 17, + }, + 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..cdb72ffe33f72dc77e4c7373b6041a467bbbf836 --- /dev/null +++ b/protocols/bmp/packet/peer_down.go @@ -0,0 +1,64 @@ +package packet + +import ( + "bytes" + "fmt" + + "github.com/bio-routing/bio-rd/util/decoder" +) + +const ( + reasonMin = 1 + reasonMax = 3 +) + +// PeerDownNotification represents a peer down notification +type PeerDownNotification struct { + CommonHeader *CommonHeader + PerPeerHeader *PerPeerHeader + 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{ + CommonHeader: ch, + } + + pph, err := decodePerPeerHeader(buf) + if err != nil { + return nil, fmt.Errorf("Unable to decode per peer header: %v", err) + } + + p.PerPeerHeader = pph + + fields := []interface{}{ + &p.Reason, + } + + err = decoder.Decode(buf, fields) + if err != nil { + return nil, err + } + + if p.Reason < reasonMin || p.Reason > reasonMax { + return p, nil + } + + p.Data = make([]byte, ch.MsgLength-PerPeerHeaderLen-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..b1df8b55e84d9ae6bf5854e4a2aea51357d73c3a --- /dev/null +++ b/protocols/bmp/packet/peer_down_test.go @@ -0,0 +1,168 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPeerDownMsgType(t *testing.T) { + pd := &PeerDownNotification{ + CommonHeader: &CommonHeader{ + MsgType: 100, + }, + } + + if pd.MsgType() != 100 { + t.Errorf("Unexpected result") + } +} + +func TestDecodePeerDownNotification(t *testing.T) { + tests := []struct { + name string + input []byte + ch *CommonHeader + wantFail bool + expected *PeerDownNotification + }{ + { + 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, + + 1, + 1, 2, 3, + }, + ch: &CommonHeader{ + MsgLength: CommonHeaderLen + 4 + 38, + }, + wantFail: false, + expected: &PeerDownNotification{ + CommonHeader: &CommonHeader{ + MsgLength: CommonHeaderLen + 4 + 38, + }, + 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, + }, + Reason: 1, + Data: []byte{ + 1, 2, 3, + }, + }, + }, + { + name: "Full no data", + 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, + 4, + }, + ch: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 4, + }, + wantFail: false, + expected: &PeerDownNotification{ + CommonHeader: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 4, + }, + 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, + }, + Reason: 4, + Data: nil, + }, + }, + { + name: "Incomplete per peer header", + 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, + }, + ch: &CommonHeader{ + MsgLength: CommonHeaderLen + 5, + }, + wantFail: true, + }, + { + name: "Incomplete data", + 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, + + 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..58adc4cc5e6cd294c88758333f6aa26c214f46b0 --- /dev/null +++ b/protocols/bmp/packet/peer_up.go @@ -0,0 +1,109 @@ +package packet + +import ( + "bytes" + "fmt" + + "github.com/bio-routing/bio-rd/util/decoder" +) + +const ( + // OpenMsgMinLen is the minimal length of a BGP open message + OpenMsgMinLen = 10 +) + +// PeerUpNotification represents a peer up notification +type PeerUpNotification struct { + CommonHeader *CommonHeader + PerPeerHeader *PerPeerHeader + 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{ + CommonHeader: ch, + } + + pph, err := decodePerPeerHeader(buf) + if err != nil { + return nil, fmt.Errorf("Unable to decode per peer header: %v", err) + } + + p.PerPeerHeader = pph + + 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, + } + + // This can not fail as p.Information has exactly the size of what is left in buf + decoder.Decode(buf, fields) + + return p, nil +} + +func getOpenMsg(buf *bytes.Buffer) ([]byte, error) { + msg := make([]byte, OpenMsgMinLen) + + fields := []interface{}{ + &msg, + } + err := decoder.Decode(buf, fields) + 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]) + fields = []interface{}{ + &optParams, + } + + err = decoder.Decode(buf, fields) + 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..d29ec7550cc757ee8e3332d1de69509e590948bd --- /dev/null +++ b/protocols/bmp/packet/peer_up_test.go @@ -0,0 +1,308 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPeerUpMsgType(t *testing.T) { + pd := &PeerUpNotification{ + CommonHeader: &CommonHeader{ + MsgType: 100, + }, + } + + if pd.MsgType() != 100 { + t.Errorf("Unexpected result") + } +} +func TestDecodePeerUp(t *testing.T) { + tests := []struct { + name string + input []byte + ch *CommonHeader + wantFail bool + expected *PeerUpNotification + }{ + { + 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, + + 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{ + CommonHeader: &CommonHeader{ + MsgLength: 47, + }, + 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, + }, + 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: "Full #2", + 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, + + 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 + }, + ch: &CommonHeader{ + MsgLength: 44, + }, + wantFail: false, + expected: &PeerUpNotification{ + CommonHeader: &CommonHeader{ + MsgLength: 44, + }, + 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, + }, + 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 + }, + }, + }, + { + name: "Incomplete #0", + 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, + }, + ch: &CommonHeader{ + MsgLength: 47, + }, + wantFail: true, + }, + { + name: "Incomplete #1", + 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, + + 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, + 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, + + 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, + }, + { + name: "Incomplete #3", + 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, + 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, + }, + { + name: "Incomplete #4", + 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 + 3, // Opt Parm Len + }, + 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..d03f8b43d5d52b6dd705329c0bb10f834d39a15d --- /dev/null +++ b/protocols/bmp/packet/per_peer_header.go @@ -0,0 +1,59 @@ +package packet + +import ( + "bytes" + + "github.com/bio-routing/bio-rd/util/decoder" + "github.com/taktv6/tflow2/convert" +) + +const ( + // PerPeerHeaderLen is the length of a per peer header + PerPeerHeaderLen = 38 +) + +// 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..e3d013a0f1d86248996613525236b58c88ab09ca --- /dev/null +++ b/protocols/bmp/packet/route_mirroring.go @@ -0,0 +1,46 @@ +package packet + +import ( + "bytes" + "fmt" +) + +// RouteMirroringMsg represents a route mirroring message +type RouteMirroringMsg struct { + CommonHeader *CommonHeader + PerPeerHeader *PerPeerHeader + TLVs []*InformationTLV +} + +// MsgType returns the type of this message +func (rm *RouteMirroringMsg) MsgType() uint8 { + return rm.CommonHeader.MsgType +} + +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..c62799a37a2c1bfa53c01eafb90979aa896716be --- /dev/null +++ b/protocols/bmp/packet/route_mirroring_test.go @@ -0,0 +1,131 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRouteMirrorType(t *testing.T) { + pd := &RouteMirroringMsg{ + CommonHeader: &CommonHeader{ + MsgType: 100, + }, + } + + if pd.MsgType() != 100 { + t.Errorf("Unexpected result") + } +} +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) + } +} diff --git a/protocols/bmp/packet/route_monitoring.go b/protocols/bmp/packet/route_monitoring.go new file mode 100644 index 0000000000000000000000000000000000000000..d76aa453fd9ecb5869f4f6e1436db4db83029eee --- /dev/null +++ b/protocols/bmp/packet/route_monitoring.go @@ -0,0 +1,46 @@ +package packet + +import ( + "bytes" + "fmt" + + "github.com/bio-routing/bio-rd/util/decoder" +) + +// RouteMonitoringMsg represents a Route Monitoring Message +type RouteMonitoringMsg struct { + CommonHeader *CommonHeader + PerPeerHeader *PerPeerHeader + BGPUpdate []byte +} + +// MsgType returns the type of this message +func (rm *RouteMonitoringMsg) MsgType() uint8 { + return rm.CommonHeader.MsgType +} + +func decodeRouteMonitoringMsg(buf *bytes.Buffer, ch *CommonHeader) (*RouteMonitoringMsg, error) { + rm := &RouteMonitoringMsg{ + CommonHeader: ch, + } + + pph, err := decodePerPeerHeader(buf) + if err != nil { + return nil, fmt.Errorf("Unable to decode per peer header: %v", err) + } + + rm.PerPeerHeader = pph + + rm.BGPUpdate = make([]byte, ch.MsgLength-CommonHeaderLen-PerPeerHeaderLen) + + fields := []interface{}{ + &rm.BGPUpdate, + } + + err = decoder.Decode(buf, fields) + if err != nil { + return nil, err + } + + return rm, nil +} diff --git a/protocols/bmp/packet/route_monitoring_test.go b/protocols/bmp/packet/route_monitoring_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d28169346d6312f757fd317b879c55e20dd43847 --- /dev/null +++ b/protocols/bmp/packet/route_monitoring_test.go @@ -0,0 +1,100 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRouteMonitoringMsgType(t *testing.T) { + pd := &RouteMonitoringMsg{ + CommonHeader: &CommonHeader{ + MsgType: 100, + }, + } + + if pd.MsgType() != 100 { + t.Errorf("Unexpected result") + } +} +func TestDecodeRouteMonitoringMsg(t *testing.T) { + tests := []struct { + name string + input []byte + ch *CommonHeader + wantFail bool + expected *RouteMonitoringMsg + }{ + { + 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, + + 100, 110, 120, + }, + ch: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 3, + }, + wantFail: false, + expected: &RouteMonitoringMsg{ + CommonHeader: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 3, + }, + 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, + }, + BGPUpdate: []byte{ + 100, 110, 120, + }, + }, + }, + { + name: "Incomplete per peer header", + input: []byte{ + 1, + 2, + 0, 0, 0, 3, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + }, + ch: &CommonHeader{ + MsgLength: CommonHeaderLen + PerPeerHeaderLen + 3, + }, + wantFail: true, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(test.input) + r, err := decodeRouteMonitoringMsg(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) + } +} diff --git a/protocols/bmp/packet/stats_report.go b/protocols/bmp/packet/stats_report.go new file mode 100644 index 0000000000000000000000000000000000000000..3fa53995a48e6217a59a5a7ea7369dc71fc084a8 --- /dev/null +++ b/protocols/bmp/packet/stats_report.go @@ -0,0 +1,55 @@ +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 +} + +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..2fc47cbb4a1b69c55834af7db122215dfdf32a2b --- /dev/null +++ b/protocols/bmp/packet/stats_report_test.go @@ -0,0 +1,150 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStatsReportMsgType(t *testing.T) { + pd := &StatsReport{ + CommonHeader: &CommonHeader{ + MsgType: 100, + }, + } + + if pd.MsgType() != 100 { + t.Errorf("Unexpected result") + } +} + +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..8021fbebacfce9b8f642c2493fedd39acc9a45f6 --- /dev/null +++ b/protocols/bmp/packet/termination_message.go @@ -0,0 +1,39 @@ +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{ + CommonHeader: ch, + 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..9f33bbfe0a07ef2de80ed2ce850c3742ed613916 --- /dev/null +++ b/protocols/bmp/packet/termination_message_test.go @@ -0,0 +1,97 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTerminationMessageType(t *testing.T) { + pd := &TerminationMessage{ + CommonHeader: &CommonHeader{ + MsgType: 100, + }, + } + + if pd.MsgType() != 100 { + t.Errorf("Unexpected result") + } +} +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{ + CommonHeader: &CommonHeader{ + MsgLength: CommonHeaderLen + 17, + }, + 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/BUILD.bazel b/protocols/bmp/server/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..922bed33e9464e8029fcb68dbbbf4bb4389827b1 --- /dev/null +++ b/protocols/bmp/server/BUILD.bazel @@ -0,0 +1,18 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "router.go", + "server.go", + ], + importpath = "github.com/bio-routing/bio-rd/protocols/bmp/server", + visibility = ["//visibility:public"], + deps = [ + "//protocols/bgp/packet:go_default_library", + "//protocols/bmp/packet: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", + ], +) diff --git a/protocols/bmp/server/router.go b/protocols/bmp/server/router.go new file mode 100644 index 0000000000000000000000000000000000000000..3e54e25c6c750933a07e2a1899c4e57e2405104b --- /dev/null +++ b/protocols/bmp/server/router.go @@ -0,0 +1,43 @@ +package server + +import ( + "fmt" + "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 + reconnectTimeMin int + reconnectTimeMax int + 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 + } + + // TODO: Finish implementation + fmt.Printf("%v\n", bmpMsg) + } + +} diff --git a/protocols/bmp/server/server.go b/protocols/bmp/server/server.go new file mode 100644 index 0000000000000000000000000000000000000000..2415929aaa1c7f92e6ba7e7d2683b1d8d496764a --- /dev/null +++ b/protocols/bmp/server/server.go @@ -0,0 +1,90 @@ +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" + "github.com/taktv6/tflow2/convert" +) + +const ( + defaultBufferLen = 4096 +) + +type BMPServer struct { + routers map[string]*router + routersMu sync.RWMutex + reconnectTime uint +} + +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, + reconnectTimeMin: 30, // Suggested by RFC 7854 + reconnectTimeMax: 720, // Suggested by RFC 7854 + 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 = 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 + 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 := convert.Uint32b(buffer[1:3]) + 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/BUILD.bazel b/util/decoder/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..42deb7927c538a3d38e1d2f16740a106be5193b1 --- /dev/null +++ b/util/decoder/BUILD.bazel @@ -0,0 +1,15 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["decoder.go"], + importpath = "github.com/bio-routing/bio-rd/util/decoder", + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["decoder_test.go"], + embed = [":go_default_library"], + deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"], +) 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) +}