From 07c6b78c8fd21f62dfbff5f2de496e22c44dca3f Mon Sep 17 00:00:00 2001 From: Steven Soroka Date: Wed, 29 Apr 2020 17:28:55 -0400 Subject: [PATCH] Sflow rework (#7253) --- plugins/inputs/sflow/README.md | 7 + plugins/inputs/sflow/binaryio/minreader.go | 37 ++ .../inputs/sflow/binaryio/minreader_test.go | 39 ++ plugins/inputs/sflow/decoder.go | 306 --------- plugins/inputs/sflow/decoder/directives.go | 402 ------------ .../inputs/sflow/decoder/directives_test.go | 582 ------------------ plugins/inputs/sflow/decoder/funcs.go | 216 ------- plugins/inputs/sflow/decoder/ops.go | 490 --------------- plugins/inputs/sflow/decoder/ops_test.go | 383 ------------ plugins/inputs/sflow/decoder_test.go | 469 ++++---------- plugins/inputs/sflow/metricencoder.go | 46 ++ plugins/inputs/sflow/packetdecoder.go | 483 +++++++++++++++ plugins/inputs/sflow/packetdecoder_test.go | 207 +++++++ plugins/inputs/sflow/sflow.go | 36 +- plugins/inputs/sflow/sflow_test.go | 8 +- plugins/inputs/sflow/types.go | 285 +++++++++ 16 files changed, 1254 insertions(+), 2742 deletions(-) create mode 100644 plugins/inputs/sflow/binaryio/minreader.go create mode 100644 plugins/inputs/sflow/binaryio/minreader_test.go delete mode 100644 plugins/inputs/sflow/decoder.go delete mode 100644 plugins/inputs/sflow/decoder/directives.go delete mode 100644 plugins/inputs/sflow/decoder/directives_test.go delete mode 100644 plugins/inputs/sflow/decoder/funcs.go delete mode 100644 plugins/inputs/sflow/decoder/ops.go delete mode 100644 plugins/inputs/sflow/decoder/ops_test.go create mode 100644 plugins/inputs/sflow/metricencoder.go create mode 100644 plugins/inputs/sflow/packetdecoder.go create mode 100644 plugins/inputs/sflow/packetdecoder_test.go create mode 100644 plugins/inputs/sflow/types.go diff --git a/plugins/inputs/sflow/README.md b/plugins/inputs/sflow/README.md index 73bbcb1e0..66d556e17 100644 --- a/plugins/inputs/sflow/README.md +++ b/plugins/inputs/sflow/README.md @@ -105,6 +105,12 @@ $ sudo tcpdump -s 0 -i eth0 -w telegraf-sflow.pcap host 127.0.0.1 and port 6343 sflow,agent_address=0.0.0.0,dst_ip=10.0.0.2,dst_mac=ff:ff:ff:ff:ff:ff,dst_port=40042,ether_type=IPv4,header_protocol=ETHERNET-ISO88023,input_ifindex=6,ip_dscp=27,ip_ecn=0,output_ifindex=1073741823,source_id_index=3,source_id_type=0,src_ip=10.0.0.1,src_mac=ff:ff:ff:ff:ff:ff,src_port=443 bytes=1570i,drops=0i,frame_length=157i,header_length=128i,ip_flags=2i,ip_fragment_offset=0i,ip_total_length=139i,ip_ttl=42i,sampling_rate=10i,tcp_header_length=0i,tcp_urgent_pointer=0i,tcp_window_size=14i 1584473704793580447 ``` +### Reference Documentation + +This sflow implementation was built from the reference document +[sflow.org/sflow_version_5.txt](sflow_version_5) + + [metric filtering]: https://github.com/influxdata/telegraf/blob/master/docs/CONFIGURATION.md#metric-filtering [retention policy]: https://docs.influxdata.com/influxdb/latest/guides/downsampling_and_retention/ [max-series-per-database]: https://docs.influxdata.com/influxdb/latest/administration/config/#max-series-per-database-1000000 @@ -112,3 +118,4 @@ sflow,agent_address=0.0.0.0,dst_ip=10.0.0.2,dst_mac=ff:ff:ff:ff:ff:ff,dst_port=4 [tsi]: https://docs.influxdata.com/influxdb/latest/concepts/time-series-index/ [series cardinality]: https://docs.influxdata.com/influxdb/latest/query_language/spec/#show-cardinality [influx-docs]: https://docs.influxdata.com/influxdb/latest/ +[sflow_version_5]: https://sflow.org/sflow_version_5.txt diff --git a/plugins/inputs/sflow/binaryio/minreader.go b/plugins/inputs/sflow/binaryio/minreader.go new file mode 100644 index 000000000..35ccdbcf2 --- /dev/null +++ b/plugins/inputs/sflow/binaryio/minreader.go @@ -0,0 +1,37 @@ +package binaryio + +import "io" + +// MinimumReader is the implementation for MinReader. +type MinimumReader struct { + R io.Reader + MinNumberOfBytesToRead int64 // Min number of bytes we need to read from the reader +} + +// MinReader reads from R but ensures there is at least N bytes read from the reader. +// The reader should call Close() when they are done reading. +// Closing the MinReader will read and discard any unread bytes up to MinNumberOfBytesToRead. +// CLosing the MinReader does NOT close the underlying reader. +// The underlying implementation is a MinimumReader, which implements ReaderCloser. +func MinReader(r io.Reader, minNumberOfBytesToRead int64) *MinimumReader { + return &MinimumReader{ + R: r, + MinNumberOfBytesToRead: minNumberOfBytesToRead, + } +} + +func (r *MinimumReader) Read(p []byte) (n int, err error) { + n, err = r.R.Read(p) + r.MinNumberOfBytesToRead -= int64(n) + return n, err +} + +// Close does not close the underlying reader, only the MinimumReader +func (r *MinimumReader) Close() error { + if r.MinNumberOfBytesToRead > 0 { + b := make([]byte, r.MinNumberOfBytesToRead) + _, err := r.R.Read(b) + return err + } + return nil +} diff --git a/plugins/inputs/sflow/binaryio/minreader_test.go b/plugins/inputs/sflow/binaryio/minreader_test.go new file mode 100644 index 000000000..081564b3e --- /dev/null +++ b/plugins/inputs/sflow/binaryio/minreader_test.go @@ -0,0 +1,39 @@ +package binaryio + +import ( + "bytes" + "testing" +) + +func TestMinReader(t *testing.T) { + b := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + r := bytes.NewBuffer(b) + + mr := MinReader(r, 10) + + toRead := make([]byte, 5) + n, err := mr.Read(toRead) + if err != nil { + t.Error(err) + } + if n != 5 { + t.Error("Expected n to be 5, but was ", n) + } + if string(toRead) != string([]byte{1, 2, 3, 4, 5}) { + t.Error("expected 5 specific bytes to be read") + } + err = mr.Close() + if err != nil { + t.Error(err) + } + n, err = r.Read(toRead) // read from the outer stream + if err != nil { + t.Error(err) + } + if n != 5 { + t.Error("Expected n to be 5, but was ", n) + } + if string(toRead) != string([]byte{11, 12, 13, 14, 15}) { + t.Error("expected the last 5 bytes to be read") + } +} diff --git a/plugins/inputs/sflow/decoder.go b/plugins/inputs/sflow/decoder.go deleted file mode 100644 index 51a534881..000000000 --- a/plugins/inputs/sflow/decoder.go +++ /dev/null @@ -1,306 +0,0 @@ -package sflow - -import ( - "fmt" - "math" - "net" - - "github.com/influxdata/telegraf/plugins/inputs/sflow/decoder" -) - -const ( - addressTypeIPv4 = uint32(1) // line: 1383 - addressTypeIPv6 = uint32(2) // line: 1384 - - sampleTypeFlowSample = uint32(1) // line: 1614 - sampleTypeFlowSampleExpanded = uint32(3) // line: 1698 - - flowDataRawPacketHeaderFormat = uint32(1) // line: 1938 - - headerProtocolEthernetIso88023 = uint32(1) // line: 1920 - - ipProtocolTCP = byte(6) - ipProtocolUDP = byte(17) - - metricName = "sflow" -) - -var headerProtocolMap = map[uint32]string{ - headerProtocolEthernetIso88023: "ETHERNET-ISO88023", // line: 1920 -} - -var etypeMap = map[uint16]string{ - 0x0800: "IPv4", - 0x86DD: "IPv6", -} - -func bytesToIPStr(b []byte) string { - return net.IP(b).String() -} - -func bytesToMACStr(b []byte) string { - return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", b[0], b[1], b[2], b[3], b[4], b[5]) -} - -var ipvMap = map[uint32]string{ - 1: "IPV4", // line: 1383 - 2: "IPV6", // line: 1384 -} - -// V5FormatOptions captures configuration for controlling the processing of an SFlow V5 packet. -type V5FormatOptions struct { - MaxFlowsPerSample uint32 - MaxSamplesPerPacket uint32 - MaxFlowHeaderLength uint32 - MaxSampleLength uint32 -} - -// NewDefaultV5FormatOptions answers a new V5FormatOptions with default values initialised -func NewDefaultV5FormatOptions() V5FormatOptions { - return V5FormatOptions{ - MaxFlowsPerSample: math.MaxUint32, - MaxSamplesPerPacket: math.MaxUint32, - MaxFlowHeaderLength: math.MaxUint32, - MaxSampleLength: math.MaxUint32, - } -} - -// V5Format answers and decoder.Directive capable of decoding sFlow v5 packets in accordance -// with SFlow v5 specification at https://sflow.org/sflow_version_5.txt -func V5Format(options V5FormatOptions) decoder.Directive { - return decoder.Seq( // line: 1823 - decoder.U32().Do(decoder.U32Assert(func(v uint32) bool { return v == 5 }, "Version %d not supported, only version 5")), - decoder.U32().Switch( // agent_address line: 1787 - decoder.Case(addressTypeIPv4, decoder.Bytes(4).Do(decoder.BytesToStr(4, bytesToIPStr).AsT("agent_address"))), // line: 1390 - decoder.Case(addressTypeIPv6, decoder.Bytes(16).Do(decoder.BytesToStr(16, bytesToIPStr).AsT("agent_address"))), // line: 1393 - ), - decoder.U32(), // sub_agent_id line: 1790 - decoder.U32(), // sequence_number line: 1801 - decoder.U32(), // uptime line: 1804 - decoder.U32().Iter(options.MaxSamplesPerPacket, sampleRecord(options)), // samples line: 1812 - ) -} - -func sampleRecord(options V5FormatOptions) decoder.Directive { - var sampleType interface{} - return decoder.Seq( // line: 1760 - decoder.U32().Ref(&sampleType), // sample_type line: 1761 - decoder.U32().Encapsulated(options.MaxSampleLength, // sample_data line: 1762 - decoder.Ref(sampleType).Switch( - decoder.Case(sampleTypeFlowSample, flowSample(sampleType, options)), // line: 1614 - decoder.Case(sampleTypeFlowSampleExpanded, flowSampleExpanded(sampleType, options)), // line: 1698 - decoder.DefaultCase(nil), // this allows other cases to just be ignored rather than cause an error - ), - ), - ) -} - -func flowSample(sampleType interface{}, options V5FormatOptions) decoder.Directive { - var samplingRate = new(uint32) - var sourceIDIndex = new(uint32) - return decoder.Seq( // line: 1616 - decoder.U32(), // sequence_number line: 1617 - decoder.U32(). // source_id line: 1622 - Do(decoder.U32ToU32(func(v uint32) uint32 { return v >> 24 }).AsT("source_id_type")). // source_id_type Line 1465 - Do(decoder.U32ToU32(func(v uint32) uint32 { return v & 0x00ffffff }).Set(sourceIDIndex).AsT("source_id_index")), // line: 1468 - decoder.U32().Do(decoder.Set(samplingRate).AsF("sampling_rate")), // line: 1631 - decoder.U32(), // samplePool: Line 1632 - decoder.U32().Do(decoder.AsF("drops")), // Line 1636 - decoder.U32(). // line: 1651 - Do(decoder.U32ToU32(func(v uint32) uint32 { return v & 0x3fffffff }).AsT("input_ifindex")). // line: 1477 - Do(decoder.U32ToU32(func(v uint32) uint32 { return v & 0x3fffffff }). - ToString(func(v uint32) string { - if v == *sourceIDIndex { - return "ingress" - } - return "" - }). - BreakIf(""). - AsT("sample_direction")), - decoder.U32(). // line: 1652 - Do(decoder.U32ToU32(func(v uint32) uint32 { return v & 0x3fffffff }).AsT("output_ifindex")). // line: 1477 - Do(decoder.U32ToU32(func(v uint32) uint32 { return v & 0x3fffffff }). - ToString(func(v uint32) string { - if v == *sourceIDIndex { - return "egress" - } - return "" - }). - BreakIf(""). - AsT("sample_direction")), - decoder.U32().Iter(options.MaxFlowsPerSample, flowRecord(samplingRate, options)), // line: 1654 - ) -} - -func flowSampleExpanded(sampleType interface{}, options V5FormatOptions) decoder.Directive { - var samplingRate = new(uint32) - var sourceIDIndex = new(uint32) - return decoder.Seq( // line: 1700 - decoder.U32(), // sequence_number line: 1701 - decoder.U32().Do(decoder.AsT("source_id_type")), // line: 1706 + 16878 - decoder.U32().Do(decoder.Set(sourceIDIndex).AsT("source_id_index")), // line 1689 - decoder.U32().Do(decoder.Set(samplingRate).AsF("sampling_rate")), // sample_rate line: 1707 - decoder.U32(), // saple_pool line: 1708 - decoder.U32().Do(decoder.AsF("drops")), // line: 1712 - decoder.U32(), // inputt line: 1727 - decoder.U32(). // input line: 1727 - Do(decoder.AsT("input_ifindex")). // line: 1728 - Do(decoder.U32ToStr(func(v uint32) string { - if v == *sourceIDIndex { - return "ingress" - } - return "" - }). - BreakIf(""). - AsT("sample_direction")), - decoder.U32(), // output line: 1728 - decoder.U32(). // outpuit line: 1728 - Do(decoder.AsT("output_ifindex")). // line: 1729 CHANFE AS FOR NON EXPANDED - Do(decoder.U32ToStr(func(v uint32) string { - if v == *sourceIDIndex { - return "egress" - } - return "" - }). - BreakIf(""). - AsT("sample_direction")), - decoder.U32().Iter(options.MaxFlowsPerSample, flowRecord(samplingRate, options)), // line: 1730 - ) -} - -func flowRecord(samplingRate *uint32, options V5FormatOptions) decoder.Directive { - var flowFormat interface{} - return decoder.Seq( // line: 1597 - decoder.U32().Ref(&flowFormat), // line 1598 - decoder.U32().Encapsulated(options.MaxFlowHeaderLength, // line 1599 - decoder.Ref(flowFormat).Switch( - decoder.Case(flowDataRawPacketHeaderFormat, rawPacketHeaderFlowData(samplingRate, options)), // line: 1938 - decoder.DefaultCase(nil), - ), - ), - ) -} - -func rawPacketHeaderFlowData(samplingRate *uint32, options V5FormatOptions) decoder.Directive { - var protocol interface{} - var headerLength interface{} - return decoder.Seq( // line: 1940 - decoder.U32().Ref(&protocol).Do(decoder.MapU32ToStr(headerProtocolMap).AsT("header_protocol")), // line: 1941 - decoder.U32(). // line: 1942 - Do(decoder.AsF("frame_length")). - Do(decoder.U32ToU32(func(in uint32) uint32 { - return in * (*samplingRate) - }).AsF("bytes")), - decoder.U32(), // stripped line: 1967 - decoder.U32().Ref(&headerLength).Do(decoder.AsF("header_length")), - decoder.Ref(headerLength).Encapsulated(options.MaxFlowHeaderLength, - decoder.Ref(protocol).Switch( - decoder.Case(headerProtocolEthernetIso88023, ethHeader(options)), - decoder.DefaultCase(nil), - )), - ) -} - -// ethHeader answers a decode Directive that will decode an ethernet frame header -// according to https://en.wikipedia.org/wiki/Ethernet_frame -func ethHeader(options V5FormatOptions) decoder.Directive { - var tagOrEType interface{} - etype := new(uint16) - return decoder.Seq( - decoder.OpenMetric(metricName), - decoder.Bytes(6).Do(decoder.BytesToStr(6, bytesToMACStr).AsT("dst_mac")), - decoder.Bytes(6).Do(decoder.BytesToStr(6, bytesToMACStr).AsT("src_mac")), - decoder.U16().Ref(&tagOrEType).Switch( - decoder.Case(uint16(0x8100), - decoder.Seq( - decoder.U16(), - decoder.U16().Do(decoder.Set(etype)), // just follows on from vlan id - ), - ), - decoder.DefaultCase( // Not an 802.1Q VLAN Tag, just treat as an ether type - decoder.Ref(tagOrEType).Do(decoder.Set(etype)), - ), - ), - decoder.U16Value(etype).Do(decoder.MapU16ToStr(etypeMap).AsT("ether_type")), - decoder.U16Value(etype).Switch( - decoder.Case(uint16(0x0800), ipv4Header(options)), - decoder.Case(uint16(0x86DD), ipv6Header(options)), - decoder.DefaultCase(nil), - ), - decoder.CloseMetric(), - ) - -} - -// ipv4Header answers a decode Directive that decode an IPv4 header according to -// https://en.wikipedia.org/wiki/IPv4 -func ipv4Header(options V5FormatOptions) decoder.Directive { - var proto interface{} - return decoder.Seq( - decoder.U16(). - Do(decoder.U16ToU16(func(in uint16) uint16 { return (in & 0xFC) >> 2 }).AsT("ip_dscp")). - Do(decoder.U16ToU16(func(in uint16) uint16 { return in & 0x3 }).AsT("ip_ecn")), - decoder.U16().Do(decoder.AsF("ip_total_length")), - decoder.U16(), - decoder.U16(). - Do(decoder.U16ToU16(func(v uint16) uint16 { return (v & 0xE000) >> 13 }).AsF("ip_flags")). - Do(decoder.U16ToU16(func(v uint16) uint16 { return v & 0x1FFF }).AsF("ip_fragment_offset")), - decoder.Bytes(1).Do(decoder.BytesTo(1, func(b []byte) interface{} { return uint8(b[0]) }).AsF("ip_ttl")), - decoder.Bytes(1).Ref(&proto), - decoder.U16(), - decoder.Bytes(4).Do(decoder.BytesToStr(4, bytesToIPStr).AsT("src_ip")), - decoder.Bytes(4).Do(decoder.BytesToStr(4, bytesToIPStr).AsT("dst_ip")), - decoder.Ref(proto).Switch( // Does not consider IHL and Options - decoder.Case(ipProtocolTCP, tcpHeader(options)), - decoder.Case(ipProtocolUDP, udpHeader(options)), - decoder.DefaultCase(nil), - ), - ) -} - -// ipv6Header answers a decode Directive that decode an IPv6 header according to -// https://en.wikipedia.org/wiki/IPv6_packet -func ipv6Header(options V5FormatOptions) decoder.Directive { - nextHeader := new(uint16) - return decoder.Seq( - decoder.U32(). - Do(decoder.U32ToU32(func(in uint32) uint32 { return (in & 0xFC00000) >> 22 }).AsF("ip_dscp")). - Do(decoder.U32ToU32(func(in uint32) uint32 { return (in & 0x300000) >> 20 }).AsF("ip_ecn")), - decoder.U16(), - decoder.U16(). - Do(decoder.U16ToU16(func(in uint16) uint16 { return (in & 0xFF00) >> 8 }).Set(nextHeader)), - decoder.Bytes(16).Do(decoder.BytesToStr(16, bytesToIPStr).AsT("src_ip")), - decoder.Bytes(16).Do(decoder.BytesToStr(16, bytesToIPStr).AsT("dst_ip")), - decoder.U16Value(nextHeader).Switch( - decoder.Case(uint16(ipProtocolTCP), tcpHeader(options)), - decoder.Case(uint16(ipProtocolUDP), udpHeader(options)), - decoder.DefaultCase(nil), - ), - ) -} - -func tcpHeader(options V5FormatOptions) decoder.Directive { - return decoder.Seq( - decoder.U16(). - Do(decoder.AsT("src_port")), - decoder.U16(). - Do(decoder.AsT("dst_port")), - decoder.U32(), //"sequence"), - decoder.U32(), //"ack_number"), - decoder.Bytes(2). - Do(decoder.BytesToU32(2, func(b []byte) uint32 { return uint32((b[0] & 0xF0) * 4) }).AsF("tcp_header_length")), - decoder.U16().Do(decoder.AsF("tcp_window_size")), - decoder.U16(), // "checksum"), - decoder.U16().Do(decoder.AsF("tcp_urgent_pointer")), - ) -} - -func udpHeader(options V5FormatOptions) decoder.Directive { - return decoder.Seq( - decoder.U16(). - Do(decoder.AsT("src_port")), - decoder.U16(). - Do(decoder.AsT("dst_port")), - decoder.U16().Do(decoder.AsF("udp_length")), - ) -} diff --git a/plugins/inputs/sflow/decoder/directives.go b/plugins/inputs/sflow/decoder/directives.go deleted file mode 100644 index 9b20e1c33..000000000 --- a/plugins/inputs/sflow/decoder/directives.go +++ /dev/null @@ -1,402 +0,0 @@ -package decoder - -import ( - "bytes" - "encoding/binary" - "fmt" - "time" - - "github.com/influxdata/telegraf" - "github.com/influxdata/telegraf/metric" -) - -// Directive is a Decode Directive, the basic building block of a decoder -type Directive interface { - - // Execute performs the function of the decode directive. If DecodeContext is nil then the - // ask is to check that a subsequent execution (with non nill DecodeContext) is expted to work. - Execute(*bytes.Buffer, *DecodeContext) error -} - -type IterOption struct { - EOFTerminateIter bool - RemainingToGreaterEqualOrTerminate uint32 -} - -// ValueDirective is a decode directive that extracts some data from the packet, an integer or byte maybe, -// which it then processes by using it, for example, as the counter for the number of iterations to perform -// of downstream decode directives. -// -// A ValueDirective can be used to either Switch, Iter(ate), Encapsulate or Do mutually exclusively. -type ValueDirective interface { - Directive - - // Switch attaches a set of conditional decode directives downstream of this decode directive - Switch(paths ...CaseValueDirective) ValueDirective - - // Iter attaches a single downstream decode directive that will be executed repeatedly according to the iteration count - Iter(maxIterations uint32, dd Directive, iterOptions ...IterOption) ValueDirective - - // Encapsulated will form a new buffer of the encapsulated length and pass that buffer on to the downsstream decode directive - Encapsulated(maxSize uint32, dd Directive) ValueDirective - - // Ref records this decode directive in the passed reference - Ref(*interface{}) ValueDirective - - // Do attaches a Decode Operation - these are uses of the decoded information to perform work on, transform, write out etc. - Do(ddo DirectiveOp) ValueDirective -} - -type valueDirective struct { - reference *valueDirective - - value interface{} - noDecode bool - - cases []CaseValueDirective - iter Directive - maxIterations uint32 - encapsulated Directive - maxEncapsulation uint32 - ops []DirectiveOp - err error - - iterOption IterOption -} - -func valueToString(in interface{}) string { - switch v := in.(type) { - case *uint16: - return fmt.Sprintf("%d", *v) - case uint16: - return fmt.Sprintf("%d", v) - case *uint32: - return fmt.Sprintf("%d", *v) - case uint32: - return fmt.Sprintf("%d", v) - default: - return fmt.Sprintf("%v", in) - } -} - -func (dd *valueDirective) Execute(buffer *bytes.Buffer, dc *DecodeContext) error { - if dd.reference == nil && !dd.noDecode { - if e := binary.Read(buffer, binary.BigEndian, dd.value); e != nil { - return e - } - } - - // Switch downstream? - if dd.cases != nil && len(dd.cases) > 0 { - for _, c := range dd.cases { - if c.Equals(dd.value) { - return c.Execute(buffer, dc) - } - } - switch v := dd.value.(type) { - case *uint32: - return fmt.Errorf("(%T).Switch,unmatched case %d", v, *v) - case *uint16: - return fmt.Errorf("(%T).Switch,unmatched case %d", v, *v) - default: - return fmt.Errorf("(%T).Switch,unmatched case %v", dd.value, dd.value) - } - } - - // Iter downstream? - if dd.iter != nil { - fn := func(id interface{}) error { - if dd.iterOption.RemainingToGreaterEqualOrTerminate > 0 && uint32(buffer.Len()) < dd.iterOption.RemainingToGreaterEqualOrTerminate { - return nil - } - if dd.iterOption.EOFTerminateIter && buffer.Len() == 0 { - return nil - } - if e := dd.iter.Execute(buffer, dc); e != nil { - return e - } - return nil - } - switch v := dd.value.(type) { - case *uint32: - if *v > dd.maxIterations { - return fmt.Errorf("iter exceeds configured max - value %d, limit %d", *v, dd.maxIterations) - } - for i := uint32(0); i < *v; i++ { - if e := fn(i); e != nil { - return e - } - } - case *uint16: - if *v > uint16(dd.maxIterations) { - return fmt.Errorf("iter exceeds configured max - value %d, limit %d", *v, dd.maxIterations) - } - for i := uint16(0); i < *v; i++ { - if e := fn(i); e != nil { - return e - } - } - default: - // Can't actually get here if .Iter method check types (and it does) - return fmt.Errorf("(%T).Iter, cannot iterator over this type", dd.value) - } - } - - // Encapsualted downstream> - if dd.encapsulated != nil { - switch v := dd.value.(type) { - case *uint32: - if *v > dd.maxEncapsulation { - return fmt.Errorf("encap exceeds configured max - value %d, limit %d", *v, dd.maxEncapsulation) - } - return dd.encapsulated.Execute(bytes.NewBuffer(buffer.Next(int(*v))), dc) - case *uint16: - if *v > uint16(dd.maxEncapsulation) { - return fmt.Errorf("encap exceeds configured max - value %d, limit %d", *v, dd.maxEncapsulation) - } - return dd.encapsulated.Execute(bytes.NewBuffer(buffer.Next(int(*v))), dc) - } - } - - // Perform the attached operations - for _, op := range dd.ops { - if err := op.process(dc, dd.value); err != nil { - return err - } - } - - return nil -} - -// panickIfNotBlackCanvas checks the state of this value directive to see if it is has -// alrady been configured in a manner inconsistent with another configuration change -func (dd *valueDirective) panickIfNotBlackCanvas(change string, checkDOs bool) { - if dd.cases != nil { - panic(fmt.Sprintf("already have switch cases assigned, cannot assign %s", change)) - } - if dd.iter != nil { - panic(fmt.Sprintf("already have iter assigned, cannot assign %s", change)) - } - if dd.encapsulated != nil { - panic(fmt.Sprintf("already have encap assigned, cannot assign %s @", change)) - } - if checkDOs && dd.ops != nil && len(dd.ops) > 0 { - panic(fmt.Sprintf("already have do assigned, cannot assign %s", change)) - } -} - -func (dd *valueDirective) Switch(paths ...CaseValueDirective) ValueDirective { - dd.panickIfNotBlackCanvas("new switch", true) - dd.cases = paths - return dd -} - -func (dd *valueDirective) Iter(maxIterations uint32, iter Directive, iterOptions ...IterOption) ValueDirective { - dd.panickIfNotBlackCanvas("new iter", true) - switch dd.value.(type) { - case *uint32: - case *uint16: - default: - panic(fmt.Sprintf("cannot iterate a %T", dd.value)) - } - - dd.iter = iter - dd.maxIterations = maxIterations - for _, io := range iterOptions { - dd.iterOption = io - } - return dd -} - -func (dd *valueDirective) Encapsulated(maxSize uint32, encapsulated Directive) ValueDirective { - dd.panickIfNotBlackCanvas("new encapsulated", true) - switch dd.value.(type) { - case *uint32: - case *uint16: - default: - panic(fmt.Sprintf("cannot encapsulated on a %T", dd.value)) - } - - dd.encapsulated = encapsulated - dd.maxEncapsulation = maxSize - return dd -} - -func (dd *valueDirective) Do(ddo DirectiveOp) ValueDirective { - dd.panickIfNotBlackCanvas("new do", false) - for { - if ddo.prev() == nil { - break - } - ddo = ddo.prev() - } - if err := ddo.process(nil, dd.value); err != nil { - panic(fmt.Sprintf("directive operation %T cannot process %T - %s", ddo, dd.value, err)) - } - if dd.ops == nil { - dd.ops = make([]DirectiveOp, 0, 5) - } - dd.ops = append(dd.ops, ddo) - - return dd -} - -func (dd *valueDirective) Ref(ref *interface{}) ValueDirective { - if *ref != nil { - panic("ref already assigned, not overwritting") - } - *ref = dd - return dd -} - -// errorDirective a decode directive that reports an error -type errorDirective struct { - Directive -} - -func (dd *errorDirective) Execute(buffer *bytes.Buffer, dc *DecodeContext) error { - return fmt.Errorf("Error Directive") -} - -// CaseValueDirective is a decode directive that also has a switch/case test -type CaseValueDirective interface { - Directive - Equals(interface{}) bool -} - -type caseValueDirective struct { - caseValue interface{} - isDefault bool - equalsDd Directive -} - -func (dd *caseValueDirective) Execute(buffer *bytes.Buffer, dc *DecodeContext) error { - if dd.equalsDd == nil { - return nil - } - return dd.equalsDd.Execute(buffer, dc) -} - -func (dd *caseValueDirective) Equals(value interface{}) bool { - if dd.isDefault { - return true - } - switch ourV := dd.caseValue.(type) { - case uint32: - ov, ok := value.(*uint32) - if ok { - return ourV == *ov - } - case uint16: - ov, ok := value.(*uint16) - if ok { - return ourV == *ov - } - case byte: - ov, ok := value.([]byte) - if ok { - if len(ov) == 1 { - return ourV == ov[0] - } - } - } - return false -} - -// sequenceDirective is a decode directive that is a simple sequentially executed list of other decode directives -type sequenceDirective struct { - decoders []Directive -} - -func (di *sequenceDirective) Execute(buffer *bytes.Buffer, dc *DecodeContext) error { - for _, innerDD := range di.decoders { - if err := innerDD.Execute(buffer, dc); err != nil { - return err - } - } - return nil -} - -// openMetric a decode directive that opens the recording of new fields and tags -type openMetric struct { - name string -} - -func (di *openMetric) Execute(buffer *bytes.Buffer, dc *DecodeContext) error { - dc.openMetric(di.name) - return nil -} - -// closeMetric a decode directive that closes the current open metric -type closeMetric struct { -} - -func (di *closeMetric) Execute(buffer *bytes.Buffer, dc *DecodeContext) error { - dc.closeMetric() - return nil -} - -// DecodeContext provides context for the decoding of a packet and primarily acts -// as a repository for metrics that are collected during the packet decode process -type DecodeContext struct { - metrics []telegraf.Metric - timeHasBeenSet bool - - // oreMetric is used to capture tags or fields that may be recored before a metric has been openned - // these fields and tags are then copied into metrics that are then subsequently opened - preMetric telegraf.Metric - current telegraf.Metric - nano int -} - -func (dc *DecodeContext) openMetric(name string) { - t := dc.preMetric.Time() - if !dc.timeHasBeenSet { - t = time.Now().Add(time.Duration(dc.nano)) - } - m, _ := metric.New(name, make(map[string]string), make(map[string]interface{}), t) - dc.nano++ - // make sure to copy any fields and tags that were capture prior to the metric being openned - for t, v := range dc.preMetric.Tags() { - m.AddTag(t, v) - } - for f, v := range dc.preMetric.Fields() { - m.AddField(f, v) - } - dc.current = m -} - -func (dc *DecodeContext) closeMetric() { - if dc.current != nil { - dc.metrics = append(dc.metrics, dc.current) - } - dc.current = nil -} - -func (dc *DecodeContext) currentMetric() telegraf.Metric { - if dc.current == nil { - return dc.preMetric - } - return dc.current -} - -// Decode initiates the decoding of the supplied buffer according to the root decode directive that is provided -func (dc *DecodeContext) Decode(dd Directive, buffer *bytes.Buffer) error { - return dd.Execute(buffer, dc) -} - -// GetMetrics answers the metrics that have been collected during the packet decode -func (dc *DecodeContext) GetMetrics() []telegraf.Metric { - return dc.metrics -} - -type notifyDirective struct { - fn func() -} - -func (nd *notifyDirective) Execute(_ *bytes.Buffer, dc *DecodeContext) error { - if dc != nil { - nd.fn() - } - return nil -} diff --git a/plugins/inputs/sflow/decoder/directives_test.go b/plugins/inputs/sflow/decoder/directives_test.go deleted file mode 100644 index 0a8d99e7a..000000000 --- a/plugins/inputs/sflow/decoder/directives_test.go +++ /dev/null @@ -1,582 +0,0 @@ -package decoder - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" - "testing" - - "github.com/influxdata/telegraf" - "github.com/stretchr/testify/require" -) - -// Execute will ececute the decode directive relative to the supplied buffer -func Execute(dd Directive, buffer *bytes.Buffer) error { - dc := &DecodeContext{} - return dd.Execute(buffer, dc) -} - -func Test_basicUI32NotEnoughBytes(t *testing.T) { - dd := U32() - value := uint16(1001) // not enough bytes to read a U32 out as only a U16 in - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.Error(t, Execute(dd, &buffer)) -} - -func Test_basicUI32(t *testing.T) { - dd := U32() - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, 0, buffer.Len()) - x, _ := dd.(*valueDirective) - require.Equal(t, &value, x.value) -} - -func Test_basicBytes(t *testing.T) { - dd := Bytes(4) - value := []byte{0x01, 0x02, 0x03, 0x04} - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, 0, buffer.Len()) - x, _ := dd.(*valueDirective) - require.Equal(t, value, x.value) -} - -func Test_basicSeq(t *testing.T) { - - // Seq with no members compiles and executed but buffer is left untouched - dd := Seq() - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - originalLen := buffer.Len() - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, originalLen, buffer.Len()) - - u := U32() - dd = Seq( - u, - ) - value = uint32(1001) - buffer.Reset() - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, 0, buffer.Len()) - x, _ := u.(*valueDirective) - require.Equal(t, &value, x.value) -} - -func Test_basicSeqOf(t *testing.T) { - // SeqOf with no members compiles and executed but buffer is left untouched - dd := SeqOf([]Directive{}) - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - originalLen := buffer.Len() - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, originalLen, buffer.Len()) - - u := U32() - dd = SeqOf( - []Directive{u}, - ) - value = uint32(1001) - buffer.Reset() - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, 0, buffer.Len()) - x, _ := u.(*valueDirective) - require.Equal(t, &value, x.value) -} - -func Test_errorInSeq(t *testing.T) { - // Seq with no members compiles and executed but buffer is left untouched - dd := Seq(U32(), ErrorDirective()) - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.Error(t, Execute(dd, &buffer)) -} - -func Test_basicU32Switch(t *testing.T) { - c1 := U32() - c2 := U32() - dd := U32().Switch( - Case(uint32(1), c1), - Case(uint32(2), c2), - ) - - value1 := uint32(3) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value1)) - value2 := uint32(4) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value2)) - require.Error(t, Execute(dd, &buffer)) // should error as no path - - value1 = uint32(1) - buffer.Reset() - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value1)) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value2)) - require.NoError(t, Execute(dd, &buffer)) - x, _ := c1.(*valueDirective) - y, _ := c2.(*valueDirective) - value0 := uint32(0) - require.Equal(t, &value2, x.value) - require.Equal(t, &value0, y.value) - - // bad path shoudl raise error - // path 1 should be able to fina value in c1 and not in c2 - // then other way around -} - -func Test_basicBinSwitch(t *testing.T) { - c1 := U32() - c2 := U32() - dd := Bytes(1).Switch( - Case(byte(1), c1), - Case(byte(2), c2), - ) - - value1 := byte(3) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value1)) - value2 := uint32(4) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value2)) - require.Error(t, Execute(dd, &buffer)) // should error as no path - - value1 = byte(1) - buffer.Reset() - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value1)) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value2)) - require.NoError(t, Execute(dd, &buffer)) - x, _ := c1.(*valueDirective) - y, _ := c2.(*valueDirective) - value0 := uint32(0) - require.Equal(t, &value2, x.value) - require.Equal(t, &value0, y.value) - - // bad path shoudl raise error - // path 1 should be able to fina value in c1 and not in c2 - // then other way around -} - -func Test_basicIter(t *testing.T) { - innerDD := U32() - dd := U32().Iter(math.MaxInt32, innerDD) - - var buffer bytes.Buffer - iterations := uint32(2) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &iterations)) - it1Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it1Val)) - it2Val := uint32(4) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it2Val)) - require.NoError(t, Execute(dd, &buffer)) - x, _ := dd.(*valueDirective) - require.Equal(t, &iterations, x.value) - y, _ := innerDD.(*valueDirective) - // we can't test it1Val as it gets overwritten! - require.Equal(t, &it2Val, y.value) -} - -func Test_IterLimit(t *testing.T) { - innerDD := U32() - dd := U32().Iter(1, innerDD) // limit set at 1 - var buffer bytes.Buffer - iterations := uint32(2) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &iterations)) - it1Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it1Val)) - it2Val := uint32(4) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it2Val)) - require.Error(t, Execute(dd, &buffer)) -} - -func Test_errorWithinIter(t *testing.T) { - dd := U32().Iter(math.MaxInt32, ErrorDirective()) - - var buffer bytes.Buffer - iterations := uint32(1) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &iterations)) - - require.Error(t, Execute(dd, &buffer)) -} - -func Test_errorWithinIter2(t *testing.T) { - dd := U32().Iter(math.MaxInt32, U32().Do(ErrorOp(false))) - var buffer bytes.Buffer - iterations := uint32(1) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &iterations)) - innerValue := uint32(1) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &innerValue)) - require.Error(t, Execute(dd, &buffer)) -} - -func Test_errorWithinIter3(t *testing.T) { - defer expectPanic(t, "Test_cantIterBytes") - U32().Iter(math.MaxInt32, U32().Do(ErrorOp(true))) -} - -func Test_alreadyEncapsulated(t *testing.T) { - defer expectPanic(t, "Test_cantIterBytes") - u := U32() - inner := U32() - u.Encapsulated(math.MaxInt32, inner) - u.Encapsulated(math.MaxInt32, inner) -} - -func Test_alreadyDoAssigned(t *testing.T) { - defer expectPanic(t, "Test_cantIterBytes") - u := U32() - u.Do(AsF("foo")) - inner := U32() - u.Encapsulated(math.MaxInt32, inner) -} - -func Test_cantIterBytes(t *testing.T) { - defer expectPanic(t, "Test_cantIterBytes") - _ = Bytes(1).Iter(math.MaxInt32, U32()) -} - -// then open metric -func Test_OpenMetric(t *testing.T) { - innerDD := U32() - dd := U32().Iter(math.MaxInt32, Seq( - OpenMetric(""), - innerDD, - CloseMetric(), - )) - - var buffer bytes.Buffer - iterations := uint32(2) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &iterations)) - it1Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it1Val)) - it2Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it2Val)) - dc := NewDecodeContext() - require.NoError(t, dc.Decode(dd, &buffer)) - require.Equal(t, 2, len(dc.GetMetrics())) -} - -func Test_AsF(t *testing.T) { - innerDD := U32().Do(AsF("foo")) - dd := U32().Iter(math.MaxInt32, Seq( - OpenMetric(""), - innerDD, - CloseMetric(), - )) - - var buffer bytes.Buffer - iterations := uint32(2) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &iterations)) - it1Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it1Val)) - it2Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it2Val)) - dc := NewDecodeContext() - require.NoError(t, dc.Decode(dd, &buffer)) - require.Equal(t, 2, len(dc.GetMetrics())) - m := dc.GetMetrics() - require.Equal(t, uint64(it1Val), getField(m[0], "foo")) - require.Equal(t, uint64(it2Val), getField(m[1], "foo")) -} - -func Test_AsT(t *testing.T) { - innerDD := U32().Do(AsT("foo")) - dd := U32().Iter(math.MaxInt32, Seq( - OpenMetric(""), - innerDD, - CloseMetric(), - )) - - var buffer bytes.Buffer - iterations := uint32(2) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &iterations)) - it1Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it1Val)) - it2Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it2Val)) - dc := NewDecodeContext() - require.NoError(t, dc.Decode(dd, &buffer)) - require.Equal(t, 2, len(dc.GetMetrics())) - m := dc.GetMetrics() - require.Equal(t, fmt.Sprintf("%d", it1Val), getTag(m[0], "foo")) - require.Equal(t, fmt.Sprintf("%d", it2Val), getTag(m[1], "foo")) -} - -func getField(m telegraf.Metric, name string) interface{} { - v, _ := m.GetField(name) - return v -} - -func getTag(m telegraf.Metric, name string) string { - v, _ := m.GetTag(name) - return v -} - -func Test_preMetricNesting(t *testing.T) { - innerDD := U32().Do(AsF("foo")) - dd := Seq( - U32().Do(AsF("bar")), - U32().Do(AsT("baz")), - U32().Iter(math.MaxInt32, - Seq( - OpenMetric(""), - innerDD, - CloseMetric(), - ), - ), - ) - - var buffer bytes.Buffer - barVal := uint32(55) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &barVal)) - bazVal := uint32(56) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &bazVal)) - iterations := uint32(2) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &iterations)) - it1Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it1Val)) - it2Val := uint32(3) - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &it2Val)) - dc := NewDecodeContext() - require.NoError(t, dc.Decode(dd, &buffer)) - require.Equal(t, 2, len(dc.GetMetrics())) - m := dc.GetMetrics() - require.Equal(t, uint64(barVal), getField(m[0], "bar")) - require.Equal(t, fmt.Sprintf("%d", bazVal), getTag(m[0], "baz")) - require.Equal(t, uint64(it1Val), getField(m[0], "foo")) - require.Equal(t, uint64(barVal), getField(m[1], "bar")) - require.Equal(t, fmt.Sprintf("%d", bazVal), getTag(m[1], "baz")) - require.Equal(t, uint64(it2Val), getField(m[1], "foo")) -} - -func Test_BasicEncapsulated(t *testing.T) { - - encap1Value := uint32(2) - encap2Value := uint32(3) - var encapBuffer bytes.Buffer - require.NoError(t, binary.Write(&encapBuffer, binary.BigEndian, &encap1Value)) - require.NoError(t, binary.Write(&encapBuffer, binary.BigEndian, &encap2Value)) - - encapSize := uint32(encapBuffer.Len()) - envelopeValue := uint32(4) - var envelopeBuffer bytes.Buffer - - require.NoError(t, binary.Write(&envelopeBuffer, binary.BigEndian, &encapSize)) - l, e := envelopeBuffer.Write(encapBuffer.Bytes()) - require.NoError(t, e) - require.Equal(t, encapSize, uint32(l)) - require.NoError(t, binary.Write(&envelopeBuffer, binary.BigEndian, &envelopeValue)) - - innerDD := U32() - envelopeDD := U32() // the buffer contains another U32 but the encpaultation will ignore it - dd := Seq( - U32().Encapsulated(math.MaxInt32, innerDD), - envelopeDD, - ) - require.NoError(t, Execute(dd, &envelopeBuffer)) - - require.Equal(t, 0, envelopeBuffer.Len()) - x, _ := envelopeDD.(*valueDirective) - require.Equal(t, &envelopeValue, x.value) - y, _ := innerDD.(*valueDirective) - require.Equal(t, &encap1Value, y.value) -} - -func Test_EncapsulationLimit(t *testing.T) { - - encap1Value := uint32(2) - encap2Value := uint32(3) - var encapBuffer bytes.Buffer - require.NoError(t, binary.Write(&encapBuffer, binary.BigEndian, &encap1Value)) - require.NoError(t, binary.Write(&encapBuffer, binary.BigEndian, &encap2Value)) - - encapSize := uint32(encapBuffer.Len()) - envelopeValue := uint32(4) - var envelopeBuffer bytes.Buffer - - require.NoError(t, binary.Write(&envelopeBuffer, binary.BigEndian, &encapSize)) - l, e := envelopeBuffer.Write(encapBuffer.Bytes()) - require.NoError(t, e) - require.Equal(t, encapSize, uint32(l)) - require.NoError(t, binary.Write(&envelopeBuffer, binary.BigEndian, &envelopeValue)) - - innerDD := U32() - envelopeDD := U32() - dd := Seq( - U32().Encapsulated(4, innerDD), // 4 bytes, not 8 bytes or higher as max - envelopeDD, - ) - require.Error(t, Execute(dd, &envelopeBuffer)) -} - -func Test_cantEncapulatedBytes(t *testing.T) { - defer expectPanic(t, "cantEncapulatedBytes") - _ = Bytes(1).Encapsulated(math.MaxInt32, U32()) -} - -func Test_BasicRef(t *testing.T) { - var x interface{} - dd1 := U32().Ref(&x) - dd2 := Ref(x) - dd := Seq( - dd1, - dd2, - ) - y, ok := dd2.(*valueDirective) - require.True(t, ok) - require.Equal(t, y.reference, x) - - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.NoError(t, Execute(dd, &buffer)) - - y, _ = dd1.(*valueDirective) - require.Equal(t, &value, y.value) - - y, _ = dd2.(*valueDirective) - require.Equal(t, &value, y.value) -} - -func Test_RefReassignError(t *testing.T) { - defer expectPanic(t, "iter iter") - var x interface{} - U32().Ref(&x) - U32().Ref(&x) -} - -func Test_ToU32(t *testing.T) { - u := U32().Do(U32ToU32(func(in uint32) uint32 { return in >> 2 }).AsF("x")) - dd := Seq(OpenMetric(""), u, CloseMetric()) - - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - - dc := NewDecodeContext() - require.NoError(t, dc.Decode(dd, &buffer)) - - // require original value decoded - x, _ := u.(*valueDirective) - require.Equal(t, &value, x.value) - - // require field ejected - require.Equal(t, 1, len(dc.GetMetrics())) - m := dc.GetMetrics() - require.Equal(t, uint64(value>>2), getField(m[0], "x")) -} - -func expectPanic(t *testing.T, msg string) { - if r := recover(); r == nil { - t.Errorf(msg) - } -} - -func Test_U32BlankCanvasIter(t *testing.T) { - u := U32().Iter(math.MaxInt32, U32()) - func() { - defer expectPanic(t, "iter iter") - u.Iter(math.MaxInt32, U32()) - }() - func() { - defer expectPanic(t, "iter switch") - u.Switch(Case(uint32(0), U32())) - }() - func() { - defer expectPanic(t, "iter encap") - u.Encapsulated(math.MaxInt32, U32()) - }() - func() { - defer expectPanic(t, "iter do") - u.Do(AsF("foo")) - }() -} -func Test_U32BlankCanvasSwitch(t *testing.T) { - u := U32().Switch(Case(uint32(0), U32())) - func() { - defer expectPanic(t, "switch iter") - u.Iter(math.MaxInt32, U32()) - }() - func() { - defer expectPanic(t, "switch switch") - u.Switch(Case(uint32(0), U32())) - }() - func() { - defer expectPanic(t, "switch encap") - u.Encapsulated(math.MaxInt32, U32()) - }() - func() { - defer expectPanic(t, "switch do") - u.Do(AsF("foo")) - }() -} - -func Test_U32BasicSwitch(t *testing.T) { - s := U32().Switch(Case(uint32(0), nil)) - value := uint32(0) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - dc := NewDecodeContext() - require.NoError(t, dc.Decode(s, &buffer)) -} - -func Test_U32BasicSwitchDefault(t *testing.T) { - s := U32().Switch(Case(uint32(0), nil), DefaultCase(nil)) - value := uint32(2) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - dc := NewDecodeContext() - require.NoError(t, dc.Decode(s, &buffer)) -} - -func Test_U16(t *testing.T) { - dd := U16() - value := uint16(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, 0, buffer.Len()) - x, _ := dd.(*valueDirective) - require.Equal(t, &value, x.value) -} - -func Test_U16Value(t *testing.T) { - myU16 := uint16(5) - dd := U16Value(&myU16) - var buffer bytes.Buffer - require.NoError(t, Execute(dd, &buffer)) - x, _ := dd.(*valueDirective) - require.Equal(t, &myU16, x.value) -} - -func Test_Bytes(t *testing.T) { - dd := Bytes(4) - value := []byte{0x01, 0x02, 0x03, 0x04} - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, 0, buffer.Len()) - x, _ := dd.(*valueDirective) - require.Equal(t, value, x.value) -} - -func Test_nilRefAnfWongTypeRef(t *testing.T) { - func() { - defer expectPanic(t, "Test_nilRef") - Ref(nil) - }() - - func() { - defer expectPanic(t, "Test_nilRef") - f := new(uint32) - Ref(f) - }() -} diff --git a/plugins/inputs/sflow/decoder/funcs.go b/plugins/inputs/sflow/decoder/funcs.go deleted file mode 100644 index c90e1488f..000000000 --- a/plugins/inputs/sflow/decoder/funcs.go +++ /dev/null @@ -1,216 +0,0 @@ -package decoder - -import ( - "fmt" - "time" - - "github.com/influxdata/telegraf/metric" -) - -// U32 answers a directive for 32bit Unsigned Integers -func U32() ValueDirective { - return &valueDirective{value: new(uint32)} -} - -// U64 answers a directive for 64bit Unsigned Integers -func U64() ValueDirective { - return &valueDirective{value: new(uint64)} -} - -// U8 answers a directive for 8bit Unsigned Integers -func U8() ValueDirective { - return &valueDirective{value: new(uint8)} -} - -// U16 answers a directive for 32bit Unsigned Integers -func U16() ValueDirective { - return &valueDirective{value: new(uint16)} -} - -// U16Value answers a directive that doesn't actually decode itself but reused a value previously decoded of type uint16 -func U16Value(value *uint16) ValueDirective { - return &valueDirective{value: value, noDecode: true} -} - -// Bytes answers a value directive that will decode the specified number (len) of bytes from the packet -func Bytes(len int) ValueDirective { - return &valueDirective{value: make([]byte, len)} -} - -// Case answers a directive to be used within a Switch clause of a U32 directive -func Case(caseValue interface{}, dd Directive) CaseValueDirective { - return &caseValueDirective{caseValue: caseValue, isDefault: false, equalsDd: dd} -} - -// DefaultCase answers a case decoder directive that can be used as the default, catch all, of a Switch -func DefaultCase(dd Directive) CaseValueDirective { - return &caseValueDirective{caseValue: nil, isDefault: true, equalsDd: dd} -} - -// Ref answers a decoder that reuses, through referal, an existing U32 directive -func Ref(target interface{}) ValueDirective { - if target == nil { - panic("Ref given a nil reference") - } - r, ok := target.(*valueDirective) - if !ok { - panic(fmt.Sprintf("Ref not given a ValueDirective reference but a %T", target)) - } - return &valueDirective{reference: r, value: r.value} -} - -// Seq ansers a directive that sequentially executes a list of provided directives -func Seq(decoders ...Directive) Directive { - return &sequenceDirective{decoders: decoders} -} - -func SeqOf(decoders []Directive) Directive { - return &sequenceDirective{decoders: decoders} -} - -// OpenMetric answers a directive that opens a new metrics for collecting tags and fields -func OpenMetric(name string) Directive { - return &openMetric{name: name} -} - -// CloseMetric answers a directive that close the current metrics -func CloseMetric() Directive { - return &closeMetric{} -} - -// NewDecodeContext ansewers a new Decode Contect to support the process of decoding -func NewDecodeContext() *DecodeContext { - m, _ := metric.New("sflow", make(map[string]string), make(map[string]interface{}), time.Now()) - return &DecodeContext{preMetric: m} -} - -// U32ToU32 answers a decode operation that transforms a uint32 to a uint32 via the supplied fn -func U32ToU32(fn func(uint32) uint32) *U32ToU32DOp { - result := &U32ToU32DOp{fn: fn, baseDOp: baseDOp{}} - result.do = result - return result -} - -// U32ToStr answers a decode operation that transforms a uint32 to a string via the supplied fn -func U32ToStr(fn func(uint32) string) *U32ToStrDOp { - result := &U32ToStrDOp{baseDOp: baseDOp{}, fn: fn} - result.do = result - return result -} - -// U16ToStr answers a decode operation that transforms a uint16 to a string via the supplied fn -func U16ToStr(fn func(uint16) string) *U16ToStrDOp { - result := &U16ToStrDOp{baseDOp: baseDOp{}, fn: fn} - result.do = result - return result -} - -// U16ToU16 answers a decode operation that transforms a uint16 to a uint16 via the supplied fn -func U16ToU16(fn func(uint16) uint16) *U16ToU16DOp { - result := &U16ToU16DOp{baseDOp: baseDOp{}, fn: fn} - result.do = result - return result -} - -// AsF answers a decode operation that will output a field into the open metric with the given name -func AsF(name string) *AsFDOp { - result := &AsFDOp{baseDOp: baseDOp{}, name: name} - result.do = result - return result -} - -// AsT answers a decode operation that will output a tag into the open metric with the given name -func AsT(name string) *AsTDOp { - result := &AsTDOp{name: name, baseDOp: baseDOp{}} - result.do = result - return result -} - -// AsTimestamp answers a decode operation that will set the tiemstamp on the metric -func AsTimestamp() *AsTimestampDOp { - result := &AsTimestampDOp{baseDOp: baseDOp{}} - result.do = result - return result -} - -// BytesToStr answers a decode operation that transforms a []bytes to a string via the supplied fn -func BytesToStr(len int, fn func([]byte) string) *BytesToStrDOp { - result := &BytesToStrDOp{baseDOp: baseDOp{}, len: len, fn: fn} - result.do = result - return result -} - -// BytesTo answers a decode operation that transforms a []bytes to a interface{} via the supplied fn -func BytesTo(len int, fn func([]byte) interface{}) *BytesToDOp { - result := &BytesToDOp{baseDOp: baseDOp{}, len: len, fn: fn} - result.do = result - return result -} - -// BytesToU32 answers a decode operation that transforms a []bytes to an uint32 via the supplied fn -func BytesToU32(len int, fn func([]byte) uint32) *BytesToU32DOp { - result := &BytesToU32DOp{baseDOp: baseDOp{}, len: len, fn: fn} - result.do = result - return result -} - -// MapU32ToStr answers a decode operation that maps an uint32 to a string via the supplied map -func MapU32ToStr(m map[uint32]string) *U32ToStrDOp { - result := &U32ToStrDOp{fn: func(in uint32) string { - return m[in] - }, baseDOp: baseDOp{}} - result.do = result - return result -} - -// U32Assert answers a decode operation that will assert the uint32 is a particulr value or generate an error -func U32Assert(fn func(v uint32) bool, fmtStr string) *U32AssertDOp { - result := &U32AssertDOp{baseDOp: baseDOp{}, fn: fn, fmtStr: fmtStr} - result.do = result - return result -} - -func U16Assert(fn func(v uint16) bool, fmtStr string) *U16AssertDOp { - result := &U16AssertDOp{baseDOp: baseDOp{}, fn: fn, fmtStr: fmtStr} - result.do = result - return result -} - -// MapU16ToStr answers a decode operation that maps an uint16 to a string via the supplied map -func MapU16ToStr(m map[uint16]string) *U16ToStrDOp { - result := &U16ToStrDOp{baseDOp: baseDOp{}, fn: func(in uint16) string { - return m[in] - }} - result.do = result - return result -} - -// Set answers a decode operation that will set the supplied *value to the value passed through the operation -func Set(ptr interface{}) *SetDOp { - result := &SetDOp{ptr: ptr, baseDOp: baseDOp{}} - result.do = result - return result -} - -// ErrorDirective answers a decode directive that will generate an error -func ErrorDirective() Directive { - return &errorDirective{} -} - -// ErrorOp answers a decode operation that will generate an error -func ErrorOp(errorOnTestProcess bool) *ErrorDOp { - result := &ErrorDOp{baseDOp: baseDOp{}, errorOnTestProcess: errorOnTestProcess} - result.do = result - return result - -} - -// Notify answers a decode directive that will notify the supplied function upon execution -func Notify(fn func()) Directive { - return ¬ifyDirective{fn} -} - -// Nop answer a decode directive that is the null, benign, deocder -func Nop() Directive { - return Notify(func() {}) -} diff --git a/plugins/inputs/sflow/decoder/ops.go b/plugins/inputs/sflow/decoder/ops.go deleted file mode 100644 index 2a1e0c72b..000000000 --- a/plugins/inputs/sflow/decoder/ops.go +++ /dev/null @@ -1,490 +0,0 @@ -package decoder - -import ( - "fmt" - "time" - - "github.com/influxdata/telegraf" -) - -// DirectiveOp are operations that are performed on values that have been decoded. -// They are expected to be chained together, in a flow programming style, and the -// Decode Directive that they are assigned to then walks back up the linked list to find the root -// operation that will then be performed (passing the value down through various transformations) -type DirectiveOp interface { - prev() DirectiveOp - // process method can be executed in two contexts, one to check that the given type - // of upstream value can be processed (not to process it) and then to actually process - // the upstream value. The difference in reqwuired behaviour is signalled by the presence - // of the DecodeContect - if nil. just test, if !nil process - process(dc *DecodeContext, upstreamValue interface{}) error -} - -type baseDOp struct { - p DirectiveOp - do DirectiveOp - n DirectiveOp -} - -func (op *baseDOp) prev() DirectiveOp { - return op.p -} - -func (op *baseDOp) AsF(name string) DirectiveOp { - result := &AsFDOp{baseDOp: baseDOp{p: op.do}, name: name} - result.do = result - op.n = result - return result -} - -func (op *baseDOp) AsT(name string) DirectiveOp { - result := &AsTDOp{baseDOp: baseDOp{p: op.do}, name: name} - result.do = result - op.n = result - return result -} - -func (op *baseDOp) Set(ptr interface{}) *SetDOp { - result := &SetDOp{baseDOp: baseDOp{p: op.do}, ptr: ptr} - result.do = result - op.n = result - return result -} - -// U32ToU32DOp is a deode operation that can process U32 to U32 -type U32ToU32DOp struct { - baseDOp - fn func(uint32) uint32 -} - -func (op *U32ToU32DOp) process(dc *DecodeContext, upstreamValue interface{}) error { - var out uint32 - switch v := upstreamValue.(type) { - case *uint32: - if dc != nil { - out = op.fn(*v) - } - default: - return fmt.Errorf("cannot process %T", v) - } - - if dc != nil && op.n != nil { - return op.n.process(dc, out) - } - return nil -} - -// ToString answers a U32ToStr decode operation that will transform this output of thie U32ToU32 into a string -func (op *U32ToU32DOp) ToString(fn func(uint32) string) *U32ToStrDOp { - result := &U32ToStrDOp{baseDOp: baseDOp{p: op}, fn: fn} - result.do = result - op.n = result - return result -} - -// AsFDOp is a deode operation that writes fields to metrics -type AsFDOp struct { - baseDOp - name string -} - -func (op *AsFDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - var m telegraf.Metric - if dc != nil { - m = dc.currentMetric() - } - switch v := upstreamValue.(type) { - case *uint64: - if dc != nil { - m.AddField(op.name, *v) - } - case *uint32: - if dc != nil { - m.AddField(op.name, *v) - } - case uint32: - if dc != nil { - m.AddField(op.name, v) - } - case *uint16: - if dc != nil { - m.AddField(op.name, *v) - } - case uint16: - if dc != nil { - m.AddField(op.name, v) - } - case *uint8: - if dc != nil { - m.AddField(op.name, *v) - } - case uint8: - if dc != nil { - m.AddField(op.name, v) - } - case string: - if dc != nil { - m.AddField(op.name, v) - } - default: - return fmt.Errorf("AsF cannot process %T", v) - } - return nil -} - -// AsTimestampDOp is a deode operation that sets the timestamp on the metric -type AsTimestampDOp struct { - baseDOp -} - -func (op *AsTimestampDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - var m telegraf.Metric - if dc != nil { - m = dc.currentMetric() - } - switch v := upstreamValue.(type) { - case *uint32: - if dc != nil { - m.SetTime(time.Unix(int64(*v), 0)) - dc.timeHasBeenSet = true - } - default: - return fmt.Errorf("can't process %T", upstreamValue) - } - return nil -} - -// AsTDOp is a deode operation that writes tags to metrics -type AsTDOp struct { - baseDOp - name string - skipEmpty bool -} - -func (op *AsTDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - var m telegraf.Metric - if dc != nil { - m = dc.currentMetric() - } - switch v := upstreamValue.(type) { - case *uint32: - if dc != nil { - m.AddTag(op.name, fmt.Sprintf("%d", *v)) - } - case uint32: - if dc != nil { - m.AddTag(op.name, fmt.Sprintf("%d", v)) - } - case *uint16: - if dc != nil { - m.AddTag(op.name, fmt.Sprintf("%d", *v)) - } - case uint16: - if dc != nil { - m.AddTag(op.name, fmt.Sprintf("%d", v)) - } - case *uint8: - if dc != nil { - m.AddTag(op.name, fmt.Sprintf("%d", *v)) - } - case uint8: - if dc != nil { - m.AddTag(op.name, fmt.Sprintf("%d", v)) - } - case string: - if dc != nil { - if !op.skipEmpty || v != "" { - m.AddTag(op.name, v) - } - } - default: - return fmt.Errorf("can't process %T", upstreamValue) - } - return nil -} - -func (op *AsTDOp) prev() DirectiveOp { - return op.p -} - -// BytesToStrDOp is a decode operation that transforms []bytes to strings -type BytesToStrDOp struct { - baseDOp - len int - fn func([]byte) string -} - -func (op *BytesToStrDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case []byte: - if len(v) == op.len { - if dc != nil { - out := op.fn(v) - if op.n != nil { - return op.n.process(dc, out) - } - } - } else { - return fmt.Errorf("cannot process len(%d) as requrire %d", len(v), op.len) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - return nil -} - -// U32AssertDOp is a decode operation that asserts a particular uint32 value -type U32AssertDOp struct { - baseDOp - fn func(uint32) bool - fmtStr string -} - -func (op *U32AssertDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case *uint32: - if dc != nil && !op.fn(*v) { - return fmt.Errorf(op.fmtStr, *v) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - return nil -} - -// U16AssertDOp is a decode operation that asserts a particular uint32 value -type U16AssertDOp struct { - baseDOp - fn func(uint16) bool - fmtStr string -} - -func (op *U16AssertDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case *uint16: - if dc != nil && !op.fn(*v) { - return fmt.Errorf(op.fmtStr, *v) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - return nil -} - -// U32ToStrDOp is a decod eoperation that transforms a uint32 to a string -type U32ToStrDOp struct { - baseDOp - fn func(uint32) string -} - -func (op *U32ToStrDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case uint32: - if dc != nil && op.n != nil { - op.n.process(dc, (op.fn(v))) - } - case *uint32: - if dc != nil && op.n != nil { - return op.n.process(dc, (op.fn(*v))) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - return nil -} - -// BreakIf answers a BreakIf operation that will break the current decode operation chain, without an error, if the value processed -// is the supplied value -func (op *U32ToStrDOp) BreakIf(value string) *BreakIfDOp { - result := &BreakIfDOp{baseDOp: baseDOp{p: op}, value: value} - result.do = result - op.n = result - return result -} - -// U16ToStrDOp is a decode operation that transforms a uint16 to a string -type U16ToStrDOp struct { - baseDOp - fn func(uint16) string -} - -func (op *U16ToStrDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case *uint16: - if dc != nil { - return op.n.process(dc, (op.fn(*v))) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - return nil -} - -// BreakIfDOp is a decode operation that will break the current outer iteration -type BreakIfDOp struct { - baseDOp - value string -} - -func (op *BreakIfDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case string: - if dc != nil { - if v != op.value { - op.n.process(dc, v) - } - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - return nil -} - -// U16ToU16DOp is a decode operation that transfirms one uint16 to another uint16 -type U16ToU16DOp struct { - baseDOp - fn func(uint16) uint16 -} - -func (op *U16ToU16DOp) process(dc *DecodeContext, upstreamValue interface{}) error { - var out uint16 - var err error - switch v := upstreamValue.(type) { - case *uint16: - if dc != nil { - out = op.fn(*v) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - if err != nil { - return err - } - if op.n != nil && dc != nil { - return op.n.process(dc, out) - } - return nil -} - -// BytesToU32DOp is a decode operation that transforms a []byte to a uint32 -type BytesToU32DOp struct { - baseDOp - len int - fn func([]byte) uint32 -} - -func (op *BytesToU32DOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case []byte: - if len(v) == op.len { - out := op.fn(v) - if op.n != nil { - return op.n.process(dc, out) - } - } else { - return fmt.Errorf("cannot process %T as len(%d) != %d", upstreamValue, v, op.len) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - return nil -} - -// SetDOp is a decode operation that will Set a pointer to a value to be the value processed -type SetDOp struct { - baseDOp - ptr interface{} -} - -func (op *SetDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case *uint32: - ptr, ok := op.ptr.(*uint32) - if ok { - if dc != nil { - *ptr = *v - } - } else { - return fmt.Errorf("cannot process as ptr %T and not *uint32", op.ptr) - } - case uint32: - ptr, ok := op.ptr.(*uint32) - if ok { - if dc != nil { - *ptr = v - } - } else { - return fmt.Errorf("cannot process as ptr %T and not *uint32", op.ptr) - } - case *uint16: - ptr, ok := op.ptr.(*uint16) - if ok { - if dc != nil { - *ptr = *v - } - } else { - return fmt.Errorf("cannot process as ptr %T and not *uint16", op.ptr) - } - case uint16: - ptr, ok := op.ptr.(*uint16) - if ok { - if dc != nil { - *ptr = v - } - } else { - return fmt.Errorf("cannot process as ptr %T and not *uint16", op.ptr) - } - case string: - ptr, ok := op.ptr.(*string) - if ok { - if dc != nil { - *ptr = v - } - } else { - return fmt.Errorf("cannot process as ptr %T and not *string", op.ptr) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - if op.n != nil && dc != nil { - return op.n.process(dc, upstreamValue) - } - return nil -} - -// BytesToDOp is a decode operation that will transform []byte to interface{} according to a suppied function -type BytesToDOp struct { - baseDOp - len int - fn func([]byte) interface{} -} - -func (op *BytesToDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - switch v := upstreamValue.(type) { - case []byte: - if len(v) == op.len { - if dc != nil { - out := op.fn(v) - return op.n.process(dc, out) - } - } else { - return fmt.Errorf("cannot process as len:%d required %d", len(v), op.len) - } - default: - return fmt.Errorf("cannot process %T", upstreamValue) - } - return nil -} - -// ErrorDOp is a decode operation that will generate an error -type ErrorDOp struct { - baseDOp - errorOnTestProcess bool -} - -func (op *ErrorDOp) process(dc *DecodeContext, upstreamValue interface{}) error { - if dc == nil && !op.errorOnTestProcess { - return nil - } - return fmt.Errorf("Error Op") -} diff --git a/plugins/inputs/sflow/decoder/ops_test.go b/plugins/inputs/sflow/decoder/ops_test.go deleted file mode 100644 index 2b626b55d..000000000 --- a/plugins/inputs/sflow/decoder/ops_test.go +++ /dev/null @@ -1,383 +0,0 @@ -package decoder - -import ( - "bytes" - "encoding/binary" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func Test_U64AsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsF("out") - in := uint64(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, in, getField(m, "out")) -} - -func Test_U32AsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsF("out") - in := uint32(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, uint64(in), getField(m, "out")) -} - -func Test_U16PtrAsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsF("out") - in := uint16(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, uint64(in), getField(m, "out")) -} - -func Test_U16AsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsF("out") - in := uint16(5) - require.NoError(t, ddo.process(dc, in)) - m := dc.currentMetric() - require.Equal(t, uint64(in), getField(m, "out")) -} - -func Test_U8AsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsF("out") - in := uint8(5) - require.NoError(t, ddo.process(dc, in)) - m := dc.currentMetric() - require.Equal(t, uint64(in), getField(m, "out")) -} - -func Test_U8PtrAsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsF("out") - in := uint8(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, uint64(in), getField(m, "out")) -} - -func Test_U32AsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsT("out") - in := uint32(5) - require.NoError(t, ddo.process(dc, in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d", in), getTag(m, "out")) -} - -func Test_U32PtrAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsT("out") - in := uint32(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d", in), getTag(m, "out")) -} - -func Test_U16AsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsT("out") - in := uint16(5) - require.NoError(t, ddo.process(dc, in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d", in), getTag(m, "out")) -} - -func Test_U16PtrAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsT("out") - in := uint16(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d", in), getTag(m, "out")) -} - -func Test_U8AsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsT("out") - in := uint8(5) - require.NoError(t, ddo.process(dc, in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d", in), getTag(m, "out")) -} - -func Test_U8PtrAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsT("out") - in := uint8(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d", in), getTag(m, "out")) -} - -func Test_U32ToU32AsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := U32ToU32(func(i uint32) uint32 { return i * 2 }) - ddo2 := ddo.AsF("out") - require.Equal(t, ddo, ddo2.prev()) - in := uint32(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, uint64(in*2), getField(m, "out")) -} - -func Test_U16ToU16AsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := U16ToU16(func(i uint16) uint16 { return i * 2 }) - ddo2 := ddo.AsF("out") - require.Equal(t, ddo, ddo2.prev()) - in := uint16(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, uint64(in*2), getField(m, "out")) -} - -func Test_U32ToStrAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := U32ToStr(func(i uint32) string { return fmt.Sprintf("%d", i*2) }) - ddo2 := ddo.AsT("out") - require.Equal(t, ddo, ddo2.prev()) - in := uint32(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d", (in*2)), getTag(m, "out")) -} - -func Test_U16ToStrAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := U16ToStr(func(i uint16) string { return fmt.Sprintf("%d", i*2) }) - ddo2 := ddo.AsT("out") - require.Equal(t, ddo, ddo2.prev()) - in := uint16(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d", (in*2)), getTag(m, "out")) -} - -func Test_MapU32ToStrAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - myMap := map[uint32]string{5: "five"} - ddo := MapU32ToStr(myMap) - ddo2 := ddo.AsT("out") - require.Equal(t, ddo, ddo2.prev()) - in := uint32(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, "five", getTag(m, "out")) -} - -func Test_MapU16ToStrAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - myMap := map[uint16]string{5: "five"} - ddo := MapU16ToStr(myMap) - ddo2 := ddo.AsT("out") - require.Equal(t, ddo, ddo2.prev()) - in := uint16(5) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, "five", getTag(m, "out")) -} - -func Test_DecDir_ToU32(t *testing.T) { - u := U32(). - Do(U32ToU32(func(in uint32) uint32 { return in >> 2 }).AsF("out1")). - Do(U32ToU32(func(in uint32) uint32 { return in * 2 }).AsF("out2")) - dd := Seq(OpenMetric(""), u, CloseMetric()) - - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - - dc := NewDecodeContext() - require.NoError(t, dc.Decode(dd, &buffer)) - - x, _ := u.(*valueDirective) - require.Equal(t, &value, x.value) - - // require field ejected - require.Equal(t, 1, len(dc.GetMetrics())) - m := dc.GetMetrics() - require.Equal(t, uint64(value>>2), getField(m[0], "out1")) - require.Equal(t, uint64(value*2), getField(m[0], "out2")) -} - -func Test_BytesToStrAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - f := func(b []byte) string { return fmt.Sprintf("%d:%d", b[0], b[1]) } - ddo := BytesToStr(2, f) - ddo2 := ddo.AsT("out") - require.Equal(t, ddo, ddo2.prev()) - in := []byte{0x01, 0x02} - require.NoError(t, ddo.process(dc, in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d:%d", in[0], in[1]), getTag(m, "out")) -} - -func Test_BytesToAsT(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - f := func(b []byte) interface{} { return fmt.Sprintf("%d:%d", b[0], b[1]) } - ddo := BytesTo(2, f) - ddo2 := ddo.AsT("out") - require.Equal(t, ddo, ddo2.prev()) - in := []byte{0x01, 0x02} - require.NoError(t, ddo.process(dc, in)) - m := dc.currentMetric() - require.Equal(t, fmt.Sprintf("%d:%d", in[0], in[1]), getTag(m, "out")) -} - -func Test_BytesToU32AsF(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - f := func(b []byte) uint32 { return uint32(b[0] * b[1]) } - ddo := BytesToU32(2, f) - ddo2 := ddo.AsF("out") - require.Equal(t, ddo, ddo2.prev()) - in := []byte{0x01, 0x02} - require.NoError(t, ddo.process(dc, in)) - m := dc.currentMetric() - require.Equal(t, uint64(in[0]*in[1]), getField(m, "out")) -} - -func Test_U32require(t *testing.T) { - dc := NewDecodeContext() - ddo := U32Assert(func(in uint32) bool { return false }, "bad") - in := uint32(5) - require.Error(t, ddo.process(dc, &in)) -} - -func Test_U16require(t *testing.T) { - dc := NewDecodeContext() - ddo := U16Assert(func(in uint16) bool { return false }, "bad") - in := uint16(5) - require.Error(t, ddo.process(dc, &in)) -} - -func Test_Set(t *testing.T) { - dc := NewDecodeContext() - ptr := new(uint32) - ddo := Set(ptr) - in := uint32(5) - require.NoError(t, ddo.process(dc, &in)) - require.Equal(t, *ptr, in) -} - -func Test_U16Set(t *testing.T) { - dc := NewDecodeContext() - ptr := new(uint16) - ddo := Set(ptr) - in := uint16(5) - require.NoError(t, ddo.process(dc, in)) - require.Equal(t, *ptr, in) -} - -func Test_U16PtrSet(t *testing.T) { - dc := NewDecodeContext() - ptr := new(uint16) - ddo := Set(ptr) - in := uint16(5) - require.NoError(t, ddo.process(dc, &in)) - require.Equal(t, *ptr, in) -} - -func Test_U32toU32Set(t *testing.T) { - dc := NewDecodeContext() - ptr := new(uint32) - ddo := U32ToU32(func(in uint32) uint32 { return in * 2 }).Set(ptr).prev() - in := uint32(5) - require.NoError(t, ddo.process(dc, &in)) - require.Equal(t, *ptr, in*2) -} - -func Test_U32toU32toString(t *testing.T) { - dc := NewDecodeContext() - ptr := new(string) - ddo := U32ToU32(func(in uint32) uint32 { return in * 2 }).ToString(func(in uint32) string { return fmt.Sprintf("%d", in*2) }).Set(ptr).prev().prev() - in := uint32(2) - require.NoError(t, ddo.process(dc, &in)) - require.Equal(t, "8", *ptr) -} - -func Test_U32toU32toStringBreakIf(t *testing.T) { - dc := NewDecodeContext() - ptr := new(string) - ddo := U32ToU32(func(in uint32) uint32 { return in * 2 }).ToString(func(in uint32) string { return fmt.Sprintf("%d", in*2) }).BreakIf("8").Set(ptr).prev().prev().prev() - in := uint32(2) - require.NoError(t, ddo.process(dc, &in)) - require.Equal(t, "", *ptr) - - in = uint32(1) - require.NoError(t, ddo.process(dc, &in)) - require.Equal(t, "4", *ptr) -} - -func Test_notify(t *testing.T) { - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - - ptr := new(uint32) - *ptr = uint32(2002) - var notificationOne uint32 - var notificationTwo uint32 - dd := Seq( - Notify(func() { notificationOne = *ptr }), - U32().Do(Set(ptr)), - Notify(func() { notificationTwo = *ptr }), - ) - - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, uint32(2002), notificationOne) - require.Equal(t, uint32(1001), notificationTwo) -} - -func Test_nop(t *testing.T) { - value := uint32(1001) - var buffer bytes.Buffer - require.NoError(t, binary.Write(&buffer, binary.BigEndian, &value)) - originalLen := buffer.Len() - dd := Seq( - Nop(), - ) - - require.NoError(t, Execute(dd, &buffer)) - require.Equal(t, originalLen, buffer.Len()) -} - -func Test_AsTimestamp(t *testing.T) { - dc := NewDecodeContext() - dc.openMetric("") - ddo := AsTimestamp() - now := time.Now() - in := uint32(now.Unix()) // only handles as uin32 (not uint64) - require.NoError(t, ddo.process(dc, &in)) - m := dc.currentMetric() - require.Equal(t, now.Unix(), m.Time().Unix()) -} diff --git a/plugins/inputs/sflow/decoder_test.go b/plugins/inputs/sflow/decoder_test.go index 33db1d1d2..c6e3916b8 100644 --- a/plugins/inputs/sflow/decoder_test.go +++ b/plugins/inputs/sflow/decoder_test.go @@ -7,17 +7,67 @@ import ( "time" "github.com/influxdata/telegraf" - "github.com/influxdata/telegraf/plugins/inputs/sflow/decoder" "github.com/influxdata/telegraf/testutil" "github.com/stretchr/testify/require" ) func TestIPv4SW(t *testing.T) { - packet, err := hex.DecodeString("0000000500000001c0a80102000000100000f3d40bfa047f0000000200000001000000d00001210a000001fe000004000484240000000000000001fe00000200000000020000000100000090000000010000010b0000000400000080000c2936d3d694c691aa97600800450000f9f19040004011b4f5c0a80913c0a8090a00a1ba0500e5641f3081da02010104066d6f746f6770a281cc02047b46462e0201000201003081bd3012060d2b06010201190501010281dc710201003013060d2b06010201190501010281e66802025acc3012060d2b0601020119050101000003e9000000100000000900000000000000090000000000000001000000d00000e3cc000002100000400048eb740000000000000002100000020000000002000000010000009000000001000000970000000400000080000c2936d3d6fcecda44008f81000009080045000081186440003f119098c0a80815c0a8090a9a690202006d23083c33303e4170722031312030393a33333a3031206b6e6f64653120736e6d70645b313039385d3a20436f6e6e656374696f6e2066726f6d205544503a205b3139322e3136382e392e31305d3a34393233362d000003e90000001000000009000000000000000900000000") + str := `00000005` + // version + `00000001` + //address type + `c0a80102` + // ip address + `00000010` + // sub agent id + `0000f3d4` + // sequence number + `0bfa047f` + // uptime + `00000002` + // sample count + `00000001` + // sample type + `000000d0` + // sample data length + `0001210a` + // sequence number + `000001fe` + // source id 00 = source id type, 0001fe = source id index + `00000400` + // sampling rate.. apparently this should be input if index???? + `04842400` + // sample pool + `00000000` + // drops + `000001fe` + // input if index + `00000200` + // output if index + `00000002` + // flow records count + `00000001` + // FlowFormat + `00000090` + // flow length + `00000001` + // header protocol + `0000010b` + // Frame length + `00000004` + // stripped octets + `00000080` + // header length + `000c2936d3d6` + // dest mac + `94c691aa9760` + // source mac + `0800` + // etype code: ipv4 + `4500` + // dscp + ecn + `00f9` + // total length + `f190` + // identification + `4000` + // fragment offset + flags + `40` + // ttl + `11` + // protocol + `b4f5` + // header checksum + `c0a80913` + // source ip + `c0a8090a` + // dest ip + `00a1` + // source port + `ba05` + // dest port + `00e5` + // udp length + // rest of header/flowSample we ignore + `641f3081da02010104066d6f746f6770a281cc02047b46462e0201000201003081bd3012060d2b06010201190501010281dc710201003013060d2b06010201190501010281e66802025acc3012060d2b0601020119050101` + + // next flow record - ignored + `000003e90000001000000009000000000000000900000000` + + // next sample + `00000001000000d00000e3cc000002100000400048eb740000000000000002100000020000000002000000010000009000000001000000970000000400000080000c2936d3d6fcecda44008f81000009080045000081186440003f119098c0a80815c0a8090a9a690202006d23083c33303e4170722031312030393a33333a3031206b6e6f64653120736e6d70645b313039385d3a20436f6e6e656374696f6e2066726f6d205544503a205b3139322e3136382e392e31305d3a34393233362d000003e90000001000000009000000000000000900000000` + packet, err := hex.DecodeString(str) require.NoError(t, err) - dc := decoder.NewDecodeContext() - err = dc.Decode(V5Format(NewDefaultV5FormatOptions()), bytes.NewBuffer(packet)) + actual := []telegraf.Metric{} + dc := NewDecoder() + dc.OnPacket(func(p *V5Format) { + metrics, err := makeMetrics(p) + require.NoError(t, err) + actual = append(actual, metrics...) + }) + buf := bytes.NewReader(packet) + err = dc.Decode(buf) require.NoError(t, err) expected := []telegraf.Metric{ @@ -31,8 +81,6 @@ func TestIPv4SW(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "510", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "512", "sample_direction": "ingress", "source_id_index": "510", @@ -52,6 +100,8 @@ func TestIPv4SW(t *testing.T) { "ip_ttl": uint64(0x40), "sampling_rate": uint64(0x0400), "udp_length": uint64(0xe5), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -65,8 +115,6 @@ func TestIPv4SW(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "528", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "512", "sample_direction": "ingress", "source_id_index": "528", @@ -86,11 +134,12 @@ func TestIPv4SW(t *testing.T) { "ip_ttl": uint64(0x3f), "sampling_rate": uint64(0x4000), "udp_length": uint64(0x6d), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), } - actual := dc.GetMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) } @@ -98,25 +147,15 @@ func BenchmarkDecodeIPv4SW(b *testing.B) { packet, err := hex.DecodeString("0000000500000001c0a80102000000100000f3d40bfa047f0000000200000001000000d00001210a000001fe000004000484240000000000000001fe00000200000000020000000100000090000000010000010b0000000400000080000c2936d3d694c691aa97600800450000f9f19040004011b4f5c0a80913c0a8090a00a1ba0500e5641f3081da02010104066d6f746f6770a281cc02047b46462e0201000201003081bd3012060d2b06010201190501010281dc710201003013060d2b06010201190501010281e66802025acc3012060d2b0601020119050101000003e9000000100000000900000000000000090000000000000001000000d00000e3cc000002100000400048eb740000000000000002100000020000000002000000010000009000000001000000970000000400000080000c2936d3d6fcecda44008f81000009080045000081186440003f119098c0a80815c0a8090a9a690202006d23083c33303e4170722031312030393a33333a3031206b6e6f64653120736e6d70645b313039385d3a20436f6e6e656374696f6e2066726f6d205544503a205b3139322e3136382e392e31305d3a34393233362d000003e90000001000000009000000000000000900000000") require.NoError(b, err) - dc := decoder.NewDecodeContext() - err = dc.Decode(V5Format(NewDefaultV5FormatOptions()), bytes.NewBuffer(packet)) + dc := NewDecoder() require.NoError(b, err) - format := V5Format(NewDefaultV5FormatOptions()) b.ResetTimer() for n := 0; n < b.N; n++ { - err := dc.Decode(format, bytes.NewBuffer(packet)) + _, err = dc.DecodeOnePacket(bytes.NewBuffer(packet)) if err != nil { panic(err) } - - _ = dc.GetMetrics() - } -} - -func BenchmarkNewV5FormatDirective(b *testing.B) { - for n := 0; n < b.N; n++ { - _ = V5Format(NewDefaultV5FormatOptions()) } } @@ -124,8 +163,10 @@ func TestExpandFlow(t *testing.T) { packet, err := hex.DecodeString("00000005000000010a00015000000000000f58998ae119780000000300000003000000c4000b62a90000000000100c840000040024fb7e1e0000000000000000001017840000000000100c8400000001000000010000009000000001000005bc0000000400000080001b17000130001201f58d44810023710800450205a6305440007e06ee92ac100016d94d52f505997e701fa1e17aff62574a50100200355f000000ffff00000b004175746f72697a7a6174610400008040ffff000400008040050031303030320500313030302004000000000868a200000000000000000860a200000000000000000003000000c40003cecf000000000010170400004000a168ac1c000000000000000000101784000000000010170400000001000000010000009000000001000005f200000004000000800024e8324338d4ae52aa0b54810020060800450005dc5420400080061397c0a8060cc0a806080050efcfbb25bad9a21c839a501000fff54000008a55f70975a0ff88b05735597ae274bd81fcba17e6e9206b8ea0fb07d05fc27dad06cfe3fdba5d2fc4d057b0add711e596cbe5e9b4bbe8be59cd77537b7a89f7414a628b736d00000003000000c0000c547a0000000000100c04000004005bc3c3b50000000000000000001017840000000000100c0400000001000000010000008c000000010000007e000000040000007a001b17000130001201f58d448100237108004500006824ea4000ff32c326d94d5105501018f02e88d003000001dd39b1d025d1c68689583b2ab21522d5b5a959642243804f6d51e63323091cc04544285433eb3f6b29e1046a6a2fa7806319d62041d8fa4bd25b7cd85b8db54202054a077ac11de84acbe37a550004") require.NoError(t, err) - dc := decoder.NewDecodeContext() - err = dc.Decode(V5Format(NewDefaultV5FormatOptions()), bytes.NewBuffer(packet)) + dc := NewDecoder() + p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet)) + require.NoError(t, err) + actual, err := makeMetrics(p) require.NoError(t, err) expected := []telegraf.Metric{ @@ -139,8 +180,6 @@ func TestExpandFlow(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "1054596", - "ip_dscp": "0", - "ip_ecn": "2", "output_ifindex": "1051780", "sample_direction": "egress", "source_id_index": "1051780", @@ -159,9 +198,11 @@ func TestExpandFlow(t *testing.T) { "ip_total_length": uint64(0x05a6), "ip_ttl": uint64(0x7e), "sampling_rate": uint64(0x0400), - "tcp_header_length": uint64(0x40), + "tcp_header_length": uint64(0x14), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0x0200), + "ip_dscp": "0", + "ip_ecn": "2", }, time.Unix(0, 0), ), @@ -175,8 +216,6 @@ func TestExpandFlow(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "1054596", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "1054468", "sample_direction": "egress", "source_id_index": "1054468", @@ -195,9 +234,11 @@ func TestExpandFlow(t *testing.T) { "ip_total_length": uint64(0x05dc), "ip_ttl": uint64(0x80), "sampling_rate": uint64(0x4000), - "tcp_header_length": uint64(0x40), + "tcp_header_length": uint64(0x14), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0xff), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -210,8 +251,6 @@ func TestExpandFlow(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "1054596", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "1051652", "sample_direction": "egress", "source_id_index": "1051652", @@ -229,11 +268,12 @@ func TestExpandFlow(t *testing.T) { "ip_total_length": uint64(0x68), "ip_ttl": uint64(0xff), "sampling_rate": uint64(0x0400), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), } - actual := dc.GetMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) } @@ -241,8 +281,10 @@ func TestIPv4SWRT(t *testing.T) { packet, err := hex.DecodeString("000000050000000189dd4f010000000000003d4f21151ad40000000600000001000000bc354b97090000020c000013b175792bea000000000000028f0000020c0000000300000001000000640000000100000058000000040000005408b2587a57624c16fc0b61a5080045000046c3e440003a1118a0052aada7569e5ab367a6e35b0032d7bbf1f2fb2eb2490a97f87abc31e135834be367000002590000ffffffffffffffff02add830d51e0aec14cf000003e90000001000000000000000000000000000000000000003ea0000001000000001c342e32a000000160000000b00000001000000a88b8ffb57000002a2000013b12e344fd800000000000002a20000028f0000000300000001000000500000000100000042000000040000003e4c16fc0b6202c03e0fdecafe080045000030108000007d11fe45575185a718693996f0570e8c001c20614ad602003fd6d4afa6a6d18207324000271169b00000000003e90000001000000000000000000000000000000000000003ea000000100000000189dd4f210000000f0000001800000001000000e8354b970a0000020c000013b175793f9b000000000000028f0000020c00000003000000010000009000000001000001a500000004000000800231466d0b2c4c16fc0b61a5080045000193198f40003a114b75052aae1f5f94c778678ef24d017f50ea7622287c30799e1f7d45932d01ca92c46d930000927c0000ffffffffffffffff02ad0eea6498953d1c7ebb6dbdf0525c80e1a9a62bacfea92f69b7336c2f2f60eba0593509e14eef167eb37449f05ad70b8241c1a46d000003e90000001000000000000000000000000000000000000003ea0000001000000001c342e1fd000000160000001000000001000000e8354b970b0000020c000013b17579534c000000000000028f0000020c00000003000000010000009000000001000000b500000004000000800231466d0b2c4c16fc0b61a50800450000a327c240003606fd67b93c706a021ff365045fe8a0976d624df8207083501800edb31b0000485454502f312e3120323030204f4b0d0a5365727665723a2050726f746f636f6c20485454500d0a436f6e74656e742d4c656e6774683a20313430340d0a436f6e6e656374696f6e3a20000003e90000001000000000000000000000000000000000000003ea0000001000000001c342e1fd000000170000001000000001000000e8354b970c0000020c000013b1757966fd000000000000028f0000020c000000030000000100000090000000010000018e00000004000000800231466d0b2c4c16fc0b61a508004500017c7d2c40003a116963052abd8d021c940e67e7e0d501682342dbe7936bd47ef487dee5591ec1b24d83622e000072250000ffffffffffffffff02ad0039d8ba86a90017071d76b177de4d8c4e23bcaaaf4d795f77b032f959e0fb70234d4c28922d4e08dd3330c66e34bff51cc8ade5000003e90000001000000000000000000000000000000000000003ea0000001000000001c342e1fd000000160000001000000001000000e80d6146ac000002a1000013b17880b49d00000000000002a10000028f00000003000000010000009000000001000005ee00000004000000804c16fc0b6201d8b122766a2c0800450005dc04574000770623a11fcd80a218691d4cf2fe01bbd4f47482065fd63a5010fabd7987000052a20002c8c43ea91ca1eaa115663f5218a37fbb409dfbbedff54731ef41199b35535905ac2366a05a803146ced544abf45597f3714327d59f99e30c899c39fc5a4b67d12087bf8db2bc000003e90000001000000000000000000000000000000000000003ea000000100000000189dd4f210000001000000018") require.NoError(t, err) - dc := decoder.NewDecodeContext() - err = dc.Decode(V5Format(NewDefaultV5FormatOptions()), bytes.NewBuffer(packet)) + dc := NewDecoder() + p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet)) + require.NoError(t, err) + actual, err := makeMetrics(p) require.NoError(t, err) expected := []telegraf.Metric{ @@ -256,8 +298,6 @@ func TestIPv4SWRT(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "655", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "524", "sample_direction": "egress", "source_id_index": "524", @@ -277,6 +317,8 @@ func TestIPv4SWRT(t *testing.T) { "ip_ttl": uint64(0x3a), "sampling_rate": uint64(0x13b1), "udp_length": uint64(0x32), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -290,8 +332,6 @@ func TestIPv4SWRT(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "674", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "655", "sample_direction": "ingress", "source_id_index": "674", @@ -311,6 +351,8 @@ func TestIPv4SWRT(t *testing.T) { "ip_ttl": uint64(0x7d), "sampling_rate": uint64(0x13b1), "udp_length": uint64(0x1c), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -324,8 +366,6 @@ func TestIPv4SWRT(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "655", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "524", "sample_direction": "egress", "source_id_index": "524", @@ -345,6 +385,8 @@ func TestIPv4SWRT(t *testing.T) { "ip_ttl": uint64(0x3a), "sampling_rate": uint64(0x13b1), "udp_length": uint64(0x017f), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -358,8 +400,6 @@ func TestIPv4SWRT(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "655", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "524", "sample_direction": "egress", "source_id_index": "524", @@ -378,9 +418,11 @@ func TestIPv4SWRT(t *testing.T) { "ip_total_length": uint64(0xa3), "ip_ttl": uint64(0x36), "sampling_rate": uint64(0x13b1), - "tcp_header_length": uint64(0x40), + "tcp_header_length": uint64(0x14), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0xed), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -394,8 +436,6 @@ func TestIPv4SWRT(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "655", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "524", "sample_direction": "egress", "source_id_index": "524", @@ -415,6 +455,8 @@ func TestIPv4SWRT(t *testing.T) { "ip_ttl": uint64(0x3a), "sampling_rate": uint64(0x13b1), "udp_length": uint64(0x0168), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -428,8 +470,6 @@ func TestIPv4SWRT(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "673", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "655", "sample_direction": "ingress", "source_id_index": "673", @@ -448,14 +488,15 @@ func TestIPv4SWRT(t *testing.T) { "ip_total_length": uint64(0x05dc), "ip_ttl": uint64(0x77), "sampling_rate": uint64(0x13b1), - "tcp_header_length": uint64(0x40), + "tcp_header_length": uint64(0x14), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0xfabd), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), } - actual := dc.GetMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) } @@ -463,8 +504,10 @@ func TestIPv6SW(t *testing.T) { packet, err := hex.DecodeString("00000005000000010ae0648100000002000093d824ac82340000000100000001000000d000019f94000001010000100019f94000000000000000010100000000000000020000000100000090000000010000058c00000008000000800008e3fffc10d4f4be04612486dd60000000054e113a2607f8b0400200140000000000000008262000edc000e804a25e30c581af36fa01bbfa6f054e249810b584bcbf12926c2e29a779c26c72db483e8191524fe2288bfdaceaf9d2e724d04305706efcfdef70db86873bbacf29698affe4e7d6faa21d302f9b4b023291a05a000003e90000001000000001000000000000000100000000") require.NoError(t, err) - dc := decoder.NewDecodeContext() - err = dc.Decode(V5Format(NewDefaultV5FormatOptions()), bytes.NewBuffer(packet)) + dc := NewDecoder() + p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet)) + require.NoError(t, err) + actual, err := makeMetrics(p) require.NoError(t, err) expected := []telegraf.Metric{ @@ -488,19 +531,19 @@ func TestIPv6SW(t *testing.T) { "src_port": "443", }, map[string]interface{}{ - "bytes": uint64(0x58c000), - "drops": uint64(0x00), - "frame_length": uint64(0x058c), - "header_length": uint64(0x80), - "ip_dscp": uint64(0x00), - "ip_ecn": uint64(0x00), - "sampling_rate": uint64(0x1000), - "udp_length": uint64(0x054e), + "bytes": uint64(0x58c000), + "drops": uint64(0x00), + "frame_length": uint64(0x058c), + "header_length": uint64(0x80), + "sampling_rate": uint64(0x1000), + "payload_length": uint64(0x054e), + "udp_length": uint64(0x054e), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), } - actual := dc.GetMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) } @@ -508,8 +551,10 @@ func TestExpandFlowCounter(t *testing.T) { packet, err := hex.DecodeString("00000005000000010a00015000000000000f58898ae0fa380000000700000004000000ec00006ece0000000000101784000000030000000200000034000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000058001017840000000600000002540be400000000010000000300007b8ebd37b97e61ff94860803e8e908ffb2b500000000000000000000000000018e7c31ee7ba4195f041874579ff021ba936300000000000000000000000100000007000000380011223344550003f8b15645e7e7d6960000002fe2fc02fc01edbf580000000000000000000000000000000001dcb9cf000000000000000000000004000000ec00006ece0000000000100184000000030000000200000034000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000058001001840000000600000002540be400000000010000000300000841131d1fd9f850bfb103617cb401e6598900000000000000000000000000000bec1902e5da9212e3e96d7996e922513250000000000000000000000001000000070000003800112233445500005c260acbddb3000100000003e2fc02fc01ee414f0000000000000000000000000000000001dccdd30000000000000000000000030000008400004606000000000010030400004000ad9dc19b0000000000000000001017840000000000100304000000010000000100000050000000010000004400000004000000400012815116c4001517cf426d8100200608004500002895da40008006d74bc0a8060ac0a8064f04ef04aab1797122cf7eaf4f5010ffff7727000000000000000000000003000000b0001bd698000000000010148400000400700b180f000000000000000000101504000000000010148400000001000000010000007c000000010000006f000000040000006b001b17000131f0f755b9afc081000439080045000059045340005206920c1f0d4703d94d52e201bbf14977d1e9f15498af36801800417f1100000101080afdf3c70400e043871503010020ff268cfe2e2fd5fffe1d3d704a91d57b895f174c4b4428c66679d80a307294303f00000003000000c40003ceca000000000010170400004000a166aa7a000000000000000000101784000000000010170400000001000000010000009000000001000005f200000004000000800024e8369e2bd4ae52aa0b54810020060800450005dc4c71400080061b45c0a8060cc0a806090050f855692a7a94a1154ae1801001046b6a00000101080a6869a48d151016d046a84a7aa1c6743fa05179f7ecbd4e567150cb6f2077ff89480ae730637d26d2237c08548806f672c7476eb1b5a447b42cb9ce405994d152fa3e000000030000008c001bd699000000000010148400000400700b180f0000000000000000001015040000000000101484000000010000000100000058000000010000004a0000000400000046001b17000131f0f755b9afc0810004390800450000340ce040003a06bea5c1ce8793d94d528f00504c3b08b18f275b83d5df8010054586ad00000101050a5b83d5de5b83d5df11d800000003000000c400004e07000000000010028400004000c7ec97f2000000000000000000100784000000000010028400000001000000010000009000000001000005f2000000040000008000005e0001ff005056800dd18100000a0800450005dc5a42400040066ef70a000ac8c0a8967201bbe17c81597908caf8a05f5010010328610000f172263da0ba5d6223c079b8238bc841256bf17c4ffb08ad11c4fbff6f87ae1624a6b057b8baa9342114e5f5b46179083020cb560c4e9eadcec6dfd83e102ddbc27024803eb5") require.NoError(t, err) - dc := decoder.NewDecodeContext() - err = dc.Decode(V5Format(NewDefaultV5FormatOptions()), bytes.NewBuffer(packet)) + dc := NewDecoder() + p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet)) + require.NoError(t, err) + actual, err := makeMetrics(p) require.NoError(t, err) expected := []telegraf.Metric{ @@ -523,8 +568,6 @@ func TestExpandFlowCounter(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "1054596", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "1049348", "sample_direction": "egress", "source_id_index": "1049348", @@ -543,9 +586,11 @@ func TestExpandFlowCounter(t *testing.T) { "ip_total_length": uint64(0x28), "ip_ttl": uint64(0x80), "sampling_rate": uint64(0x4000), - "tcp_header_length": uint64(0x40), + "tcp_header_length": uint64(0x14), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0xffff), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -559,8 +604,6 @@ func TestExpandFlowCounter(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "1053956", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "1053828", "sample_direction": "egress", "source_id_index": "1053828", @@ -579,9 +622,11 @@ func TestExpandFlowCounter(t *testing.T) { "ip_total_length": uint64(0x59), "ip_ttl": uint64(0x52), "sampling_rate": uint64(0x0400), - "tcp_header_length": uint64(0x00), + "tcp_header_length": uint64(0x20), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0x41), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -595,8 +640,6 @@ func TestExpandFlowCounter(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "1054596", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "1054468", "sample_direction": "egress", "source_id_index": "1054468", @@ -615,9 +658,11 @@ func TestExpandFlowCounter(t *testing.T) { "ip_total_length": uint64(0x05dc), "ip_ttl": uint64(0x80), "sampling_rate": uint64(0x4000), - "tcp_header_length": uint64(0x00), + "tcp_header_length": uint64(0x20), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0x0104), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -631,8 +676,6 @@ func TestExpandFlowCounter(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "1053956", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "1053828", "sample_direction": "egress", "source_id_index": "1053828", @@ -651,9 +694,11 @@ func TestExpandFlowCounter(t *testing.T) { "ip_total_length": uint64(0x34), "ip_ttl": uint64(0x3a), "sampling_rate": uint64(0x0400), - "tcp_header_length": uint64(0x00), + "tcp_header_length": uint64(0x20), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0x0545), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -667,8 +712,6 @@ func TestExpandFlowCounter(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "1050500", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "1049220", "sample_direction": "egress", "source_id_index": "1049220", @@ -687,14 +730,15 @@ func TestExpandFlowCounter(t *testing.T) { "ip_total_length": uint64(0x05dc), "ip_ttl": uint64(0x40), "sampling_rate": uint64(0x4000), - "tcp_header_length": uint64(0x40), + "tcp_header_length": uint64(0x14), "tcp_urgent_pointer": uint64(0x00), "tcp_window_size": uint64(0x0103), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), } - actual := dc.GetMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) } @@ -702,274 +746,13 @@ func TestFlowExpandCounter(t *testing.T) { packet, err := hex.DecodeString("00000005000000010a000150000000000006d14d8ae0fe200000000200000004000000ac00006d15000000004b00ca000000000200000002000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000584b00ca0000000001000000000000000000000001000000010000308ae33bb950eb92a8a3004d0bb406899571000000000000000000000000000012f7ed9c9db8c24ed90604eaf0bd04636edb00000000000000000000000100000004000000ac00006d15000000004b0054000000000200000002000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000584b00540000000001000000003b9aca000000000100000003000067ba8e64fd23fa65f26d0215ec4a0021086600000000000000000000000000002002c3b21045c2378ad3001fb2f300061872000000000000000000000001") require.NoError(t, err) - dc := decoder.NewDecodeContext() - err = dc.Decode(V5Format(NewDefaultV5FormatOptions()), bytes.NewBuffer(packet)) + dc := NewDecoder() + p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet)) + require.NoError(t, err) + actual, err := makeMetrics(p) require.NoError(t, err) // we don't do anything with samples yet expected := []telegraf.Metric{} - actual := dc.GetMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) } - -func TestUDPHeader(t *testing.T) { - options := NewDefaultV5FormatOptions() - octets := bytes.NewBuffer([]byte{ - 0x00, 0x01, // src_port - 0x00, 0x02, // dst_port - 0x00, 0x03, // udp_length - }) - - directive := decoder.Seq( - decoder.OpenMetric("sflow"), - udpHeader(options), - decoder.CloseMetric(), - ) - dc := decoder.NewDecodeContext() - err := directive.Execute(octets, dc) - require.NoError(t, err) - - expected := []telegraf.Metric{ - testutil.MustMetric( - "sflow", - map[string]string{ - "src_port": "1", - "dst_port": "2", - }, - map[string]interface{}{ - "udp_length": uint64(3), - }, - time.Unix(0, 0), - ), - } - - testutil.RequireMetricsEqual(t, expected, dc.GetMetrics(), testutil.IgnoreTime()) -} - -func BenchmarkUDPHeader(b *testing.B) { - options := NewDefaultV5FormatOptions() - octets := bytes.NewBuffer([]byte{ - 0x00, 0x01, // src_port - 0x00, 0x02, // dst_port - 0x00, 0x03, // udp_length - }) - - directive := decoder.Seq( - decoder.OpenMetric("sflow"), - udpHeader(options), - decoder.CloseMetric(), - ) - dc := decoder.NewDecodeContext() - - b.ResetTimer() - for n := 0; n < b.N; n++ { - _ = directive.Execute(octets, dc) - } -} - -func TestIPv4Header(t *testing.T) { - octets := bytes.NewBuffer( - []byte{ - 0x45, // version + IHL - 0x00, // ip_dscp + ip_ecn - 0x00, 0x00, // total length - 0x00, 0x00, // identification - 0x00, 0x00, // flags + frag offset - 0x00, // ttl - 0x11, // protocol; 0x11 = udp - 0x00, 0x00, // header checksum - 0x7f, 0x00, 0x00, 0x01, // src ip - 0x7f, 0x00, 0x00, 0x02, // dst ip - 0x00, 0x01, // src_port - 0x00, 0x02, // dst_port - 0x00, 0x03, // udp_length - }, - ) - dc := decoder.NewDecodeContext() - - options := NewDefaultV5FormatOptions() - directive := decoder.Seq( - decoder.OpenMetric("sflow"), - ipv4Header(options), - decoder.CloseMetric(), - ) - - err := directive.Execute(octets, dc) - require.NoError(t, err) - - expected := []telegraf.Metric{ - testutil.MustMetric( - "sflow", - map[string]string{ - "src_ip": "127.0.0.1", - "dst_ip": "127.0.0.2", - "ip_dscp": "0", - "ip_ecn": "0", - "src_port": "1", - "dst_port": "2", - }, - map[string]interface{}{ - "ip_flags": uint64(0), - "ip_fragment_offset": uint64(0), - "ip_total_length": uint64(0), - "ip_ttl": uint64(0), - "udp_length": uint64(3), - }, - time.Unix(0, 0), - ), - } - - testutil.RequireMetricsEqual(t, expected, dc.GetMetrics(), testutil.IgnoreTime()) -} - -// Using the same Directive instance, prior paths through the parse tree should -// not affect the latest parse. -func TestIPv4HeaderSwitch(t *testing.T) { - options := NewDefaultV5FormatOptions() - directive := decoder.Seq( - decoder.OpenMetric("sflow"), - ipv4Header(options), - decoder.CloseMetric(), - ) - - octets := bytes.NewBuffer( - []byte{ - 0x45, // version + IHL - 0x00, // ip_dscp + ip_ecn - 0x00, 0x00, // total length - 0x00, 0x00, // identification - 0x00, 0x00, // flags + frag offset - 0x00, // ttl - 0x11, // protocol; 0x11 = udp - 0x00, 0x00, // header checksum - 0x7f, 0x00, 0x00, 0x01, // src ip - 0x7f, 0x00, 0x00, 0x02, // dst ip - 0x00, 0x01, // src_port - 0x00, 0x02, // dst_port - 0x00, 0x03, // udp_length - }, - ) - dc := decoder.NewDecodeContext() - err := directive.Execute(octets, dc) - require.NoError(t, err) - - octets = bytes.NewBuffer( - []byte{ - 0x45, // version + IHL - 0x00, // ip_dscp + ip_ecn - 0x00, 0x00, // total length - 0x00, 0x00, // identification - 0x00, 0x00, // flags + frag offset - 0x00, // ttl - 0x06, // protocol; 0x06 = tcp - 0x00, 0x00, // header checksum - 0x7f, 0x00, 0x00, 0x01, // src ip - 0x7f, 0x00, 0x00, 0x02, // dst ip - 0x00, 0x01, // src_port - 0x00, 0x02, // dst_port - 0x00, 0x00, 0x00, 0x00, // sequence - 0x00, 0x00, 0x00, 0x00, // ack_number - 0x00, 0x00, // tcp_header_length - 0x00, 0x00, // tcp_window_size - 0x00, 0x00, // checksum - 0x00, 0x00, // tcp_urgent_pointer - }, - ) - dc = decoder.NewDecodeContext() - err = directive.Execute(octets, dc) - require.NoError(t, err) - - expected := []telegraf.Metric{ - testutil.MustMetric( - "sflow", - map[string]string{ - "src_ip": "127.0.0.1", - "dst_ip": "127.0.0.2", - "ip_dscp": "0", - "ip_ecn": "0", - "src_port": "1", - "dst_port": "2", - }, - map[string]interface{}{ - "ip_flags": uint64(0), - "ip_fragment_offset": uint64(0), - "ip_total_length": uint64(0), - "ip_ttl": uint64(0), - "tcp_header_length": uint64(0), - "tcp_window_size": uint64(0), - "tcp_urgent_pointer": uint64(0), - }, - time.Unix(0, 0), - ), - } - - // check that udp fields are not set on the tcp metric - testutil.RequireMetricsEqual(t, expected, dc.GetMetrics(), testutil.IgnoreTime()) -} - -func TestUnknownProtocol(t *testing.T) { - octets := bytes.NewBuffer( - []byte{ - 0x45, // version + IHL - 0x00, // ip_dscp + ip_ecn - 0x00, 0x00, // total length - 0x00, 0x00, // identification - 0x00, 0x00, // flags + frag offset - 0x00, // ttl - 0x99, // protocol - 0x00, 0x00, // header checksum - 0x7f, 0x00, 0x00, 0x01, // src ip - 0x7f, 0x00, 0x00, 0x02, // dst ip - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - }, - ) - dc := decoder.NewDecodeContext() - - options := NewDefaultV5FormatOptions() - directive := decoder.Seq( - decoder.OpenMetric("sflow"), - ipv4Header(options), - decoder.CloseMetric(), - ) - - err := directive.Execute(octets, dc) - require.NoError(t, err) - - expected := []telegraf.Metric{ - testutil.MustMetric( - "sflow", - map[string]string{ - "src_ip": "127.0.0.1", - "dst_ip": "127.0.0.2", - "ip_dscp": "0", - "ip_ecn": "0", - }, - map[string]interface{}{ - "ip_flags": uint64(0), - "ip_fragment_offset": uint64(0), - "ip_total_length": uint64(0), - "ip_ttl": uint64(0), - }, - time.Unix(0, 0), - ), - } - - testutil.RequireMetricsEqual(t, expected, dc.GetMetrics(), testutil.IgnoreTime()) -} diff --git a/plugins/inputs/sflow/metricencoder.go b/plugins/inputs/sflow/metricencoder.go new file mode 100644 index 000000000..ffc9d8e02 --- /dev/null +++ b/plugins/inputs/sflow/metricencoder.go @@ -0,0 +1,46 @@ +package sflow + +import ( + "strconv" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/metric" +) + +func makeMetrics(p *V5Format) ([]telegraf.Metric, error) { + now := time.Now() + metrics := []telegraf.Metric{} + tags := map[string]string{ + "agent_address": p.AgentAddress.String(), + } + fields := map[string]interface{}{} + for _, sample := range p.Samples { + tags["input_ifindex"] = strconv.FormatUint(uint64(sample.SampleData.InputIfIndex), 10) + tags["output_ifindex"] = strconv.FormatUint(uint64(sample.SampleData.OutputIfIndex), 10) + tags["sample_direction"] = sample.SampleData.SampleDirection + tags["source_id_index"] = strconv.FormatUint(uint64(sample.SampleData.SourceIDIndex), 10) + tags["source_id_type"] = strconv.FormatUint(uint64(sample.SampleData.SourceIDType), 10) + fields["drops"] = sample.SampleData.Drops + fields["sampling_rate"] = sample.SampleData.SamplingRate + + for _, flowRecord := range sample.SampleData.FlowRecords { + if flowRecord.FlowData != nil { + tags2 := flowRecord.FlowData.GetTags() + fields2 := flowRecord.FlowData.GetFields() + for k, v := range tags { + tags2[k] = v + } + for k, v := range fields { + fields2[k] = v + } + m, err := metric.New("sflow", tags2, fields2, now) + if err != nil { + return nil, err + } + metrics = append(metrics, m) + } + } + } + return metrics, nil +} diff --git a/plugins/inputs/sflow/packetdecoder.go b/plugins/inputs/sflow/packetdecoder.go new file mode 100644 index 000000000..9e6b2a4fe --- /dev/null +++ b/plugins/inputs/sflow/packetdecoder.go @@ -0,0 +1,483 @@ +package sflow + +import ( + "encoding/binary" + "fmt" + "io" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs/sflow/binaryio" + "github.com/pkg/errors" +) + +type PacketDecoder struct { + onPacket func(p *V5Format) + Log telegraf.Logger +} + +func NewDecoder() *PacketDecoder { + return &PacketDecoder{} +} + +func (d *PacketDecoder) debug(args ...interface{}) { + if d.Log != nil { + d.Log.Debug(args...) + } +} + +func (d *PacketDecoder) OnPacket(f func(p *V5Format)) { + d.onPacket = f +} + +func (d *PacketDecoder) Decode(r io.Reader) error { + var err error + var packet *V5Format + for err == nil { + packet, err = d.DecodeOnePacket(r) + if err != nil { + break + } + d.onPacket(packet) + } + if err != nil && errors.Cause(err) == io.EOF { + return nil + } + return err +} + +type AddressType uint32 // must be uint32 + +const ( + AddressTypeUnknown AddressType = 0 + AddressTypeIPV4 AddressType = 1 + AddressTypeIPV6 AddressType = 2 +) + +func (d *PacketDecoder) DecodeOnePacket(r io.Reader) (*V5Format, error) { + p := &V5Format{} + err := read(r, &p.Version, "version") + if err != nil { + return nil, err + } + if p.Version != 5 { + return nil, fmt.Errorf("Version %d not supported, only version 5", p.Version) + } + var addressIPType AddressType + if err = read(r, &addressIPType, "address ip type"); err != nil { + return nil, err + } + switch addressIPType { + case AddressTypeUnknown: + p.AgentAddress.IP = make([]byte, 0) + case AddressTypeIPV4: + p.AgentAddress.IP = make([]byte, 4) + case AddressTypeIPV6: + p.AgentAddress.IP = make([]byte, 16) + default: + return nil, fmt.Errorf("Unknown address IP type %d", addressIPType) + } + if err = read(r, &p.AgentAddress.IP, "Agent Address IP"); err != nil { + return nil, err + } + if err = read(r, &p.SubAgentID, "SubAgentID"); err != nil { + return nil, err + } + if err = read(r, &p.SequenceNumber, "SequenceNumber"); err != nil { + return nil, err + } + if err = read(r, &p.Uptime, "Uptime"); err != nil { + return nil, err + } + + p.Samples, err = d.decodeSamples(r) + return p, err +} + +func (d *PacketDecoder) decodeSamples(r io.Reader) ([]Sample, error) { + result := []Sample{} + // # of samples + var numOfSamples uint32 + if err := read(r, &numOfSamples, "sample count"); err != nil { + return nil, err + } + + for i := 0; i < int(numOfSamples); i++ { + sam, err := d.decodeSample(r) + if err != nil { + return result, err + } + result = append(result, sam) + } + + return result, nil +} + +func (d *PacketDecoder) decodeSample(r io.Reader) (Sample, error) { + var err error + sam := Sample{} + if err := read(r, &sam.SampleType, "SampleType"); err != nil { + return sam, err + } + sampleDataLen := uint32(0) + if err := read(r, &sampleDataLen, "Sample data length"); err != nil { + return sam, err + } + mr := binaryio.MinReader(r, int64(sampleDataLen)) + defer mr.Close() + + switch sam.SampleType { + case SampleTypeFlowSample: + sam.SampleData, err = d.decodeFlowSample(mr) + case SampleTypeFlowSampleExpanded: + sam.SampleData, err = d.decodeFlowSampleExpanded(mr) + default: + d.debug("Unknown sample type: ", sam.SampleType) + } + return sam, err +} + +type InterfaceFormatType uint8 // sflow_version_5.txt line 1497 +const ( + InterfaceFormatTypeSingleInterface InterfaceFormatType = 0 + InterfaceFormatTypePacketDiscarded InterfaceFormatType = 1 +) + +func (d *PacketDecoder) decodeFlowSample(r io.Reader) (t SampleDataFlowSampleExpanded, err error) { + if err := read(r, &t.SequenceNumber, "SequenceNumber"); err != nil { + return t, err + } + var sourceID uint32 + if err := read(r, &sourceID, "SourceID"); err != nil { // source_id sflow_version_5.txt line: 1622 + return t, err + } + // split source id to source id type and source id index + t.SourceIDIndex = sourceID & 0x00ffffff // sflow_version_5.txt line: 1468 + t.SourceIDType = sourceID >> 24 // source_id_type sflow_version_5.txt Line 1465 + if err := read(r, &t.SamplingRate, "SamplingRate"); err != nil { + return t, err + } + if err := read(r, &t.SamplePool, "SamplePool"); err != nil { + return t, err + } + if err := read(r, &t.Drops, "Drops"); err != nil { // sflow_version_5.txt line 1636 + return t, err + } + if err := read(r, &t.InputIfIndex, "InputIfIndex"); err != nil { + return t, err + } + t.InputIfFormat = t.InputIfIndex >> 30 + t.InputIfIndex = t.InputIfIndex & 0x3FFFFFFF + + if err := read(r, &t.OutputIfIndex, "OutputIfIndex"); err != nil { + return t, err + } + t.OutputIfFormat = t.OutputIfIndex >> 30 + t.OutputIfIndex = t.OutputIfIndex & 0x3FFFFFFF + + switch t.SourceIDIndex { + case t.OutputIfIndex: + t.SampleDirection = "egress" + case t.InputIfIndex: + t.SampleDirection = "ingress" + } + + t.FlowRecords, err = d.decodeFlowRecords(r, t.SamplingRate) + return t, err +} + +func (d *PacketDecoder) decodeFlowSampleExpanded(r io.Reader) (t SampleDataFlowSampleExpanded, err error) { + if err := read(r, &t.SequenceNumber, "SequenceNumber"); err != nil { // sflow_version_5.txt line 1701 + return t, err + } + if err := read(r, &t.SourceIDType, "SourceIDType"); err != nil { // sflow_version_5.txt line: 1706 + 16878 + return t, err + } + if err := read(r, &t.SourceIDIndex, "SourceIDIndex"); err != nil { // sflow_version_5.txt line: 1689 + return t, err + } + if err := read(r, &t.SamplingRate, "SamplingRate"); err != nil { // sflow_version_5.txt line: 1707 + return t, err + } + if err := read(r, &t.SamplePool, "SamplePool"); err != nil { // sflow_version_5.txt line: 1708 + return t, err + } + if err := read(r, &t.Drops, "Drops"); err != nil { // sflow_version_5.txt line: 1712 + return t, err + } + if err := read(r, &t.InputIfFormat, "InputIfFormat"); err != nil { // sflow_version_5.txt line: 1727 + return t, err + } + if err := read(r, &t.InputIfIndex, "InputIfIndex"); err != nil { + return t, err + } + if err := read(r, &t.OutputIfFormat, "OutputIfFormat"); err != nil { // sflow_version_5.txt line: 1728 + return t, err + } + if err := read(r, &t.OutputIfIndex, "OutputIfIndex"); err != nil { + return t, err + } + + switch t.SourceIDIndex { + case t.OutputIfIndex: + t.SampleDirection = "egress" + case t.InputIfIndex: + t.SampleDirection = "ingress" + } + + t.FlowRecords, err = d.decodeFlowRecords(r, t.SamplingRate) + return t, err +} + +func (d *PacketDecoder) decodeFlowRecords(r io.Reader, samplingRate uint32) (recs []FlowRecord, err error) { + var flowDataLen uint32 + var count uint32 + if err := read(r, &count, "FlowRecord count"); err != nil { + return recs, err + } + for i := uint32(0); i < count; i++ { + fr := FlowRecord{} + if err := read(r, &fr.FlowFormat, "FlowFormat"); err != nil { // sflow_version_5.txt line 1597 + return recs, err + } + if err := read(r, &flowDataLen, "Flow data length"); err != nil { + return recs, err + } + + mr := binaryio.MinReader(r, int64(flowDataLen)) + + switch fr.FlowFormat { + case FlowFormatTypeRawPacketHeader: // sflow_version_5.txt line 1938 + fr.FlowData, err = d.decodeRawPacketHeaderFlowData(mr, samplingRate) + default: + d.debug("Unknown flow format: ", fr.FlowFormat) + } + if err != nil { + mr.Close() + return recs, err + } + + recs = append(recs, fr) + mr.Close() + } + + return recs, err +} + +func (d *PacketDecoder) decodeRawPacketHeaderFlowData(r io.Reader, samplingRate uint32) (h RawPacketHeaderFlowData, err error) { + if err := read(r, &h.HeaderProtocol, "HeaderProtocol"); err != nil { // sflow_version_5.txt line 1940 + return h, err + } + if err := read(r, &h.FrameLength, "FrameLength"); err != nil { // sflow_version_5.txt line 1942 + return h, err + } + h.Bytes = h.FrameLength * samplingRate + + if err := read(r, &h.StrippedOctets, "StrippedOctets"); err != nil { // sflow_version_5.txt line 1967 + return h, err + } + if err := read(r, &h.HeaderLength, "HeaderLength"); err != nil { + return h, err + } + + mr := binaryio.MinReader(r, int64(h.HeaderLength)) + defer mr.Close() + + switch h.HeaderProtocol { + case HeaderProtocolTypeEthernetISO88023: + h.Header, err = d.decodeEthHeader(mr) + default: + d.debug("Unknown header protocol type: ", h.HeaderProtocol) + } + + return h, err +} + +// ethHeader answers a decode Directive that will decode an ethernet frame header +// according to https://en.wikipedia.org/wiki/Ethernet_frame +func (d *PacketDecoder) decodeEthHeader(r io.Reader) (h EthHeader, err error) { + // we may have to read out StrippedOctets bytes and throw them away first? + if err := read(r, &h.DestinationMAC, "DestinationMAC"); err != nil { + return h, err + } + if err := read(r, &h.SourceMAC, "SourceMAC"); err != nil { + return h, err + } + var tagOrEType uint16 + if err := read(r, &tagOrEType, "tagOrEtype"); err != nil { + return h, err + } + switch tagOrEType { + case 0x8100: // could be? + var discard uint16 + if err := read(r, &discard, "unknown"); err != nil { + return h, err + } + if err := read(r, &h.EtherTypeCode, "EtherTypeCode"); err != nil { + return h, err + } + default: + h.EtherTypeCode = tagOrEType + } + h.EtherType = ETypeMap[h.EtherTypeCode] + switch h.EtherType { + case "IPv4": + h.IPHeader, err = d.decodeIPv4Header(r) + case "IPv6": + h.IPHeader, err = d.decodeIPv6Header(r) + default: + } + if err != nil { + return h, err + } + return h, err +} + +// https://en.wikipedia.org/wiki/IPv4#Header +func (d *PacketDecoder) decodeIPv4Header(r io.Reader) (h IPV4Header, err error) { + if err := read(r, &h.Version, "Version"); err != nil { + return h, err + } + h.InternetHeaderLength = h.Version & 0x0F + h.Version = h.Version & 0xF0 + if err := read(r, &h.DSCP, "DSCP"); err != nil { + return h, err + } + h.ECN = h.DSCP & 0x03 + h.DSCP = h.DSCP >> 2 + if err := read(r, &h.TotalLength, "TotalLength"); err != nil { + return h, err + } + if err := read(r, &h.Identification, "Identification"); err != nil { + return h, err + } + if err := read(r, &h.FragmentOffset, "FragmentOffset"); err != nil { + return h, err + } + h.Flags = uint8(h.FragmentOffset >> 13) + h.FragmentOffset = h.FragmentOffset & 0x1FFF + if err := read(r, &h.TTL, "TTL"); err != nil { + return h, err + } + if err := read(r, &h.Protocol, "Protocol"); err != nil { + return h, err + } + if err := read(r, &h.HeaderChecksum, "HeaderChecksum"); err != nil { + return h, err + } + if err := read(r, &h.SourceIP, "SourceIP"); err != nil { + return h, err + } + if err := read(r, &h.DestIP, "DestIP"); err != nil { + return h, err + } + switch h.Protocol { + case IPProtocolTCP: + h.ProtocolHeader, err = d.decodeTCPHeader(r) + case IPProtocolUDP: + h.ProtocolHeader, err = d.decodeUDPHeader(r) + default: + d.debug("Unknown IP protocol: ", h.Protocol) + } + return h, err +} + +// https://en.wikipedia.org/wiki/IPv6_packet +func (d *PacketDecoder) decodeIPv6Header(r io.Reader) (h IPV6Header, err error) { + var fourByteBlock uint32 + if err := read(r, &fourByteBlock, "IPv6 header octet 0"); err != nil { + return h, err + } + version := fourByteBlock >> 28 + if version != 0x6 { + return h, fmt.Errorf("Unexpected IPv6 header version 0x%x", version) + } + h.DSCP = uint8((fourByteBlock & 0xFC00000) >> 22) + h.ECN = uint8((fourByteBlock & 0x300000) >> 20) + + // flowLabel := fourByteBlock & 0xFFFFF // not currently being used. + if err := read(r, &h.PayloadLength, "PayloadLength"); err != nil { + return h, err + } + if err := read(r, &h.NextHeaderProto, "NextHeaderProto"); err != nil { + return h, err + } + if err := read(r, &h.HopLimit, "HopLimit"); err != nil { + return h, err + } + if err := read(r, &h.SourceIP, "SourceIP"); err != nil { + return h, err + } + if err := read(r, &h.DestIP, "DestIP"); err != nil { + return h, err + } + switch h.NextHeaderProto { + case IPProtocolTCP: + h.ProtocolHeader, err = d.decodeTCPHeader(r) + case IPProtocolUDP: + h.ProtocolHeader, err = d.decodeUDPHeader(r) + default: + // not handled + d.debug("Unknown IP protocol: ", h.NextHeaderProto) + } + return h, err +} + +// https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure +func (d *PacketDecoder) decodeTCPHeader(r io.Reader) (h TCPHeader, err error) { + if err := read(r, &h.SourcePort, "SourcePort"); err != nil { + return h, err + } + if err := read(r, &h.DestinationPort, "DestinationPort"); err != nil { + return h, err + } + if err := read(r, &h.Sequence, "Sequence"); err != nil { + return h, err + } + if err := read(r, &h.AckNumber, "AckNumber"); err != nil { + return h, err + } + // Next up: bit reading! + // data offset 4 bits + // reserved 3 bits + // flags 9 bits + var dataOffsetAndReservedAndFlags uint16 + if err := read(r, &dataOffsetAndReservedAndFlags, "TCP Header Octet offset 12"); err != nil { + return h, err + } + h.TCPHeaderLength = uint8((dataOffsetAndReservedAndFlags >> 12) * 4) + h.Flags = dataOffsetAndReservedAndFlags & 0x1FF + // done bit reading + + if err := read(r, &h.TCPWindowSize, "TCPWindowSize"); err != nil { + return h, err + } + if err := read(r, &h.Checksum, "Checksum"); err != nil { + return h, err + } + if err := read(r, &h.TCPUrgentPointer, "TCPUrgentPointer"); err != nil { + return h, err + } + + return h, err +} + +func (d *PacketDecoder) decodeUDPHeader(r io.Reader) (h UDPHeader, err error) { + if err := read(r, &h.SourcePort, "SourcePort"); err != nil { + return h, err + } + if err := read(r, &h.DestinationPort, "DestinationPort"); err != nil { + return h, err + } + if err := read(r, &h.UDPLength, "UDPLength"); err != nil { + return h, err + } + if err := read(r, &h.Checksum, "Checksum"); err != nil { + return h, err + } + return h, err +} + +func read(r io.Reader, data interface{}, name string) error { + err := binary.Read(r, binary.BigEndian, data) + return errors.Wrapf(err, "failed to read %s", name) +} diff --git a/plugins/inputs/sflow/packetdecoder_test.go b/plugins/inputs/sflow/packetdecoder_test.go new file mode 100644 index 000000000..f078eaf31 --- /dev/null +++ b/plugins/inputs/sflow/packetdecoder_test.go @@ -0,0 +1,207 @@ +package sflow + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUDPHeader(t *testing.T) { + octets := bytes.NewBuffer([]byte{ + 0x00, 0x01, // src_port + 0x00, 0x02, // dst_port + 0x00, 0x03, // udp_length + 0x00, 0x00, // checksum + }) + + dc := NewDecoder() + actual, err := dc.decodeUDPHeader(octets) + require.NoError(t, err) + + expected := UDPHeader{ + SourcePort: 1, + DestinationPort: 2, + UDPLength: 3, + } + + require.Equal(t, expected, actual) +} + +func BenchmarkUDPHeader(b *testing.B) { + octets := bytes.NewBuffer([]byte{ + 0x00, 0x01, // src_port + 0x00, 0x02, // dst_port + 0x00, 0x03, // udp_length + 0x00, 0x00, // checksum + }) + + dc := NewDecoder() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + dc.decodeUDPHeader(octets) + } +} + +func TestIPv4Header(t *testing.T) { + octets := bytes.NewBuffer( + []byte{ + 0x45, // version + IHL + 0x00, // ip_dscp + ip_ecn + 0x00, 0x00, // total length + 0x00, 0x00, // identification + 0x00, 0x00, // flags + frag offset + 0x00, // ttl + 0x11, // protocol; 0x11 = udp + 0x00, 0x00, // header checksum + 0x7f, 0x00, 0x00, 0x01, // src ip + 0x7f, 0x00, 0x00, 0x02, // dst ip + 0x00, 0x01, // src_port + 0x00, 0x02, // dst_port + 0x00, 0x03, // udp_length + 0x00, 0x00, // checksum + }, + ) + dc := NewDecoder() + actual, err := dc.decodeIPv4Header(octets) + require.NoError(t, err) + + expected := IPV4Header{ + Version: 0x40, + InternetHeaderLength: 0x05, + DSCP: 0, + ECN: 0, + TotalLength: 0, + Identification: 0, + Flags: 0, + FragmentOffset: 0, + TTL: 0, + Protocol: 0x11, + HeaderChecksum: 0, + SourceIP: [4]byte{127, 0, 0, 1}, + DestIP: [4]byte{127, 0, 0, 2}, + ProtocolHeader: UDPHeader{ + SourcePort: 1, + DestinationPort: 2, + UDPLength: 3, + Checksum: 0, + }, + } + + require.Equal(t, expected, actual) +} + +// Using the same Directive instance, prior paths through the parse tree should +// not affect the latest parse. +func TestIPv4HeaderSwitch(t *testing.T) { + octets := bytes.NewBuffer( + []byte{ + 0x45, // version + IHL + 0x00, // ip_dscp + ip_ecn + 0x00, 0x00, // total length + 0x00, 0x00, // identification + 0x00, 0x00, // flags + frag offset + 0x00, // ttl + 0x11, // protocol; 0x11 = udp + 0x00, 0x00, // header checksum + 0x7f, 0x00, 0x00, 0x01, // src ip + 0x7f, 0x00, 0x00, 0x02, // dst ip + 0x00, 0x01, // src_port + 0x00, 0x02, // dst_port + 0x00, 0x03, // udp_length + 0x00, 0x00, // checksum + }, + ) + dc := NewDecoder() + _, err := dc.decodeIPv4Header(octets) + require.NoError(t, err) + + octets = bytes.NewBuffer( + []byte{ + 0x45, // version + IHL + 0x00, // ip_dscp + ip_ecn + 0x00, 0x00, // total length + 0x00, 0x00, // identification + 0x00, 0x00, // flags + frag offset + 0x00, // ttl + 0x06, // protocol; 0x06 = tcp + 0x00, 0x00, // header checksum + 0x7f, 0x00, 0x00, 0x01, // src ip + 0x7f, 0x00, 0x00, 0x02, // dst ip + 0x00, 0x01, // src_port + 0x00, 0x02, // dst_port + 0x00, 0x00, 0x00, 0x00, // sequence + 0x00, 0x00, 0x00, 0x00, // ack_number + 0x00, 0x00, // tcp_header_length + 0x00, 0x00, // tcp_window_size + 0x00, 0x00, // checksum + 0x00, 0x00, // tcp_urgent_pointer + }, + ) + dc = NewDecoder() + actual, err := dc.decodeIPv4Header(octets) + require.NoError(t, err) + + expected := IPV4Header{ + Version: 64, + InternetHeaderLength: 5, + Protocol: 6, + SourceIP: [4]byte{127, 0, 0, 1}, + DestIP: [4]byte{127, 0, 0, 2}, + ProtocolHeader: TCPHeader{ + SourcePort: 1, + DestinationPort: 2, + }, + } + + require.Equal(t, expected, actual) +} + +func TestUnknownProtocol(t *testing.T) { + octets := bytes.NewBuffer( + []byte{ + 0x45, // version + IHL + 0x00, // ip_dscp + ip_ecn + 0x00, 0x00, // total length + 0x00, 0x00, // identification + 0x00, 0x00, // flags + frag offset + 0x00, // ttl + 0x99, // protocol + 0x00, 0x00, // header checksum + 0x7f, 0x00, 0x00, 0x01, // src ip + 0x7f, 0x00, 0x00, 0x02, // dst ip + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + }, + ) + dc := NewDecoder() + actual, err := dc.decodeIPv4Header(octets) + require.NoError(t, err) + + expected := IPV4Header{ + Version: 64, + InternetHeaderLength: 5, + Protocol: 153, + SourceIP: [4]byte{127, 0, 0, 1}, + DestIP: [4]byte{127, 0, 0, 2}, + } + + require.Equal(t, expected, actual) +} diff --git a/plugins/inputs/sflow/sflow.go b/plugins/inputs/sflow/sflow.go index 7d113dd1e..2e3fbc0cf 100644 --- a/plugins/inputs/sflow/sflow.go +++ b/plugins/inputs/sflow/sflow.go @@ -13,7 +13,6 @@ import ( "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/plugins/inputs" - "github.com/influxdata/telegraf/plugins/inputs/sflow/decoder" ) const sampleConfig = ` @@ -38,11 +37,11 @@ type SFlow struct { Log telegraf.Logger `toml:"-"` - addr net.Addr - decoderOpts decoder.Directive - closer io.Closer - cancel context.CancelFunc - wg sync.WaitGroup + addr net.Addr + decoder *PacketDecoder + closer io.Closer + cancel context.CancelFunc + wg sync.WaitGroup } // Description answers a description of this input plugin @@ -56,14 +55,24 @@ func (s *SFlow) SampleConfig() string { } func (s *SFlow) Init() error { - - config := NewDefaultV5FormatOptions() - s.decoderOpts = V5Format(config) + s.decoder = NewDecoder() + s.decoder.Log = s.Log return nil } // Start starts this sFlow listener listening on the configured network for sFlow packets func (s *SFlow) Start(acc telegraf.Accumulator) error { + s.decoder.OnPacket(func(p *V5Format) { + metrics, err := makeMetrics(p) + if err != nil { + s.Log.Errorf("Failed to make metric from packet: %s", err) + return + } + for _, m := range metrics { + acc.AddMetric(m) + } + }) + u, err := url.Parse(s.ServiceAddress) if err != nil { return err @@ -122,14 +131,9 @@ func (s *SFlow) read(acc telegraf.Accumulator, conn net.PacketConn) { } func (s *SFlow) process(acc telegraf.Accumulator, buf []byte) { - decoder := decoder.NewDecodeContext() - if err := decoder.Decode(s.decoderOpts, bytes.NewBuffer(buf)); err != nil { - acc.AddError(fmt.Errorf("unable to parse incoming packet: %s", err)) - } - metrics := decoder.GetMetrics() - for _, m := range metrics { - acc.AddMetric(m) + if err := s.decoder.Decode(bytes.NewBuffer(buf)); err != nil { + acc.AddError(fmt.Errorf("unable to parse incoming packet: %s", err)) } } diff --git a/plugins/inputs/sflow/sflow_test.go b/plugins/inputs/sflow/sflow_test.go index 90f3a7c6d..2df56c2ae 100644 --- a/plugins/inputs/sflow/sflow_test.go +++ b/plugins/inputs/sflow/sflow_test.go @@ -44,8 +44,6 @@ func TestSFlow(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "510", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "512", "sample_direction": "ingress", "source_id_index": "510", @@ -65,6 +63,8 @@ func TestSFlow(t *testing.T) { "ip_ttl": uint64(64), "sampling_rate": uint64(1024), "udp_length": uint64(229), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), @@ -78,8 +78,6 @@ func TestSFlow(t *testing.T) { "ether_type": "IPv4", "header_protocol": "ETHERNET-ISO88023", "input_ifindex": "528", - "ip_dscp": "0", - "ip_ecn": "0", "output_ifindex": "512", "sample_direction": "ingress", "source_id_index": "528", @@ -99,6 +97,8 @@ func TestSFlow(t *testing.T) { "ip_ttl": uint64(63), "sampling_rate": uint64(16384), "udp_length": uint64(109), + "ip_dscp": "0", + "ip_ecn": "0", }, time.Unix(0, 0), ), diff --git a/plugins/inputs/sflow/types.go b/plugins/inputs/sflow/types.go new file mode 100644 index 000000000..a48857803 --- /dev/null +++ b/plugins/inputs/sflow/types.go @@ -0,0 +1,285 @@ +package sflow + +import ( + "net" + "strconv" +) + +const ( + AddressTypeIPv6 uint32 = 2 // sflow_version_5.txt line: 1384 + AddressTypeIPv4 uint32 = 1 // sflow_version_5.txt line: 1383 + + IPProtocolTCP uint8 = 6 + IPProtocolUDP uint8 = 17 + + metricName = "sflow" +) + +var ETypeMap = map[uint16]string{ + 0x0800: "IPv4", + 0x86DD: "IPv6", +} + +var IPvMap = map[uint32]string{ + 1: "IPV4", // sflow_version_5.txt line: 1383 + 2: "IPV6", // sflow_version_5.txt line: 1384 +} + +type ContainsMetricData interface { + GetTags() map[string]string + GetFields() map[string]interface{} +} + +// V5Format answers and decoder.Directive capable of decoding sFlow v5 packets in accordance +// with SFlow v5 specification at https://sflow.org/sflow_version_5.txt +type V5Format struct { + Version uint32 + AgentAddress net.IPAddr + SubAgentID uint32 + SequenceNumber uint32 + Uptime uint32 + Samples []Sample +} + +type SampleType uint32 + +const ( + SampleTypeFlowSample SampleType = 1 // sflow_version_5.txt line: 1614 + SampleTypeFlowSampleExpanded SampleType = 3 // sflow_version_5.txt line: 1698 +) + +type SampleData interface{} + +type Sample struct { + SampleType SampleType + SampleData SampleDataFlowSampleExpanded +} + +type SampleDataFlowSampleExpanded struct { + SequenceNumber uint32 + SourceIDType uint32 + SourceIDIndex uint32 + SamplingRate uint32 + SamplePool uint32 + Drops uint32 + SampleDirection string // ingress/egress + InputIfFormat uint32 + InputIfIndex uint32 + OutputIfFormat uint32 + OutputIfIndex uint32 + FlowRecords []FlowRecord +} + +type FlowFormatType uint32 + +const ( + FlowFormatTypeRawPacketHeader FlowFormatType = 1 // sflow_version_5.txt line: 1938 +) + +type FlowData ContainsMetricData + +type FlowRecord struct { + FlowFormat FlowFormatType + FlowData FlowData +} + +type HeaderProtocolType uint32 + +const ( + HeaderProtocolTypeEthernetISO88023 HeaderProtocolType = 1 + HeaderProtocolTypeISO88024TokenBus HeaderProtocolType = 2 + HeaderProtocolTypeISO88025TokenRing HeaderProtocolType = 3 + HeaderProtocolTypeFDDI HeaderProtocolType = 4 + HeaderProtocolTypeFrameRelay HeaderProtocolType = 5 + HeaderProtocolTypeX25 HeaderProtocolType = 6 + HeaderProtocolTypePPP HeaderProtocolType = 7 + HeaderProtocolTypeSMDS HeaderProtocolType = 8 + HeaderProtocolTypeAAL5 HeaderProtocolType = 9 + HeaderProtocolTypeAAL5IP HeaderProtocolType = 10 /* e.g. Cisco AAL5 mux */ + HeaderProtocolTypeIPv4 HeaderProtocolType = 11 + HeaderProtocolTypeIPv6 HeaderProtocolType = 12 + HeaderProtocolTypeMPLS HeaderProtocolType = 13 + HeaderProtocolTypePOS HeaderProtocolType = 14 /* RFC 1662, 2615 */ +) + +var HeaderProtocolMap = map[HeaderProtocolType]string{ + HeaderProtocolTypeEthernetISO88023: "ETHERNET-ISO88023", // sflow_version_5.txt line: 1920 +} + +type Header ContainsMetricData + +type RawPacketHeaderFlowData struct { + HeaderProtocol HeaderProtocolType + FrameLength uint32 + Bytes uint32 + StrippedOctets uint32 + HeaderLength uint32 + Header Header +} + +func (h RawPacketHeaderFlowData) GetTags() map[string]string { + t := h.Header.GetTags() + t["header_protocol"] = HeaderProtocolMap[h.HeaderProtocol] + return t +} +func (h RawPacketHeaderFlowData) GetFields() map[string]interface{} { + f := h.Header.GetFields() + f["bytes"] = h.Bytes + f["frame_length"] = h.FrameLength + f["header_length"] = h.HeaderLength + return f +} + +type IPHeader ContainsMetricData + +type EthHeader struct { + DestinationMAC [6]byte + SourceMAC [6]byte + TagProtocolIdentifier uint16 + TagControlInformation uint16 + EtherTypeCode uint16 + EtherType string + IPHeader IPHeader +} + +func (h EthHeader) GetTags() map[string]string { + t := h.IPHeader.GetTags() + t["src_mac"] = net.HardwareAddr(h.SourceMAC[:]).String() + t["dst_mac"] = net.HardwareAddr(h.DestinationMAC[:]).String() + t["ether_type"] = h.EtherType + return t +} +func (h EthHeader) GetFields() map[string]interface{} { + return h.IPHeader.GetFields() +} + +type ProtocolHeader ContainsMetricData + +// https://en.wikipedia.org/wiki/IPv4#Header +type IPV4Header struct { + Version uint8 // 4 bit + InternetHeaderLength uint8 // 4 bit + DSCP uint8 + ECN uint8 + TotalLength uint16 + Identification uint16 + Flags uint8 + FragmentOffset uint16 + TTL uint8 + Protocol uint8 // https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers + HeaderChecksum uint16 + SourceIP [4]byte + DestIP [4]byte + ProtocolHeader ProtocolHeader +} + +func (h IPV4Header) GetTags() map[string]string { + var t map[string]string + if h.ProtocolHeader != nil { + t = h.ProtocolHeader.GetTags() + } else { + t = map[string]string{} + } + t["src_ip"] = net.IP(h.SourceIP[:]).String() + t["dst_ip"] = net.IP(h.DestIP[:]).String() + return t +} +func (h IPV4Header) GetFields() map[string]interface{} { + var f map[string]interface{} + if h.ProtocolHeader != nil { + f = h.ProtocolHeader.GetFields() + } else { + f = map[string]interface{}{} + } + f["ip_dscp"] = strconv.FormatUint(uint64(h.DSCP), 10) + f["ip_ecn"] = strconv.FormatUint(uint64(h.ECN), 10) + f["ip_flags"] = h.Flags + f["ip_fragment_offset"] = h.FragmentOffset + f["ip_total_length"] = h.TotalLength + f["ip_ttl"] = h.TTL + return f +} + +// https://en.wikipedia.org/wiki/IPv6_packet +type IPV6Header struct { + DSCP uint8 + ECN uint8 + PayloadLength uint16 + NextHeaderProto uint8 // tcp/udp? + HopLimit uint8 + SourceIP [16]byte + DestIP [16]byte + ProtocolHeader ProtocolHeader +} + +func (h IPV6Header) GetTags() map[string]string { + var t map[string]string + if h.ProtocolHeader != nil { + t = h.ProtocolHeader.GetTags() + } else { + t = map[string]string{} + } + t["src_ip"] = net.IP(h.SourceIP[:]).String() + t["dst_ip"] = net.IP(h.DestIP[:]).String() + return t +} +func (h IPV6Header) GetFields() map[string]interface{} { + var f map[string]interface{} + if h.ProtocolHeader != nil { + f = h.ProtocolHeader.GetFields() + } else { + f = map[string]interface{}{} + } + f["ip_dscp"] = strconv.FormatUint(uint64(h.DSCP), 10) + f["ip_ecn"] = strconv.FormatUint(uint64(h.ECN), 10) + f["payload_length"] = h.PayloadLength + return f +} + +// https://en.wikipedia.org/wiki/Transmission_Control_Protocol +type TCPHeader struct { + SourcePort uint16 + DestinationPort uint16 + Sequence uint32 + AckNumber uint32 + TCPHeaderLength uint8 + Flags uint16 + TCPWindowSize uint16 + Checksum uint16 + TCPUrgentPointer uint16 +} + +func (h TCPHeader) GetTags() map[string]string { + t := map[string]string{ + "dst_port": strconv.FormatUint(uint64(h.DestinationPort), 10), + "src_port": strconv.FormatUint(uint64(h.SourcePort), 10), + } + return t +} +func (h TCPHeader) GetFields() map[string]interface{} { + return map[string]interface{}{ + "tcp_header_length": h.TCPHeaderLength, + "tcp_urgent_pointer": h.TCPUrgentPointer, + "tcp_window_size": h.TCPWindowSize, + } +} + +type UDPHeader struct { + SourcePort uint16 + DestinationPort uint16 + UDPLength uint16 + Checksum uint16 +} + +func (h UDPHeader) GetTags() map[string]string { + t := map[string]string{ + "dst_port": strconv.FormatUint(uint64(h.DestinationPort), 10), + "src_port": strconv.FormatUint(uint64(h.SourcePort), 10), + } + return t +} +func (h UDPHeader) GetFields() map[string]interface{} { + return map[string]interface{}{ + "udp_length": h.UDPLength, + } +}