From 25154e50fd5924402da592dd05ee31c4ea970420 Mon Sep 17 00:00:00 2001 From: Thomas Casteleyn Date: Mon, 28 Nov 2022 16:01:18 +0100 Subject: [PATCH] feat(inputs.snmp): convert enum values (#11872) --- plugins/inputs/snmp/README.md | 4 ++++ plugins/inputs/snmp/gosmi.go | 19 +++++++++++++++++ plugins/inputs/snmp/gosmi_test.go | 5 ++++- plugins/inputs/snmp/netsnmp.go | 5 +++++ plugins/inputs/snmp/snmp.go | 34 +++++++++++++++++++++++-------- plugins/inputs/snmp/snmp_test.go | 2 +- 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/plugins/inputs/snmp/README.md b/plugins/inputs/snmp/README.md index 888084370..3fcd1146b 100644 --- a/plugins/inputs/snmp/README.md +++ b/plugins/inputs/snmp/README.md @@ -147,6 +147,10 @@ option operate similar to the `snmpget` utility. ## or hextoint:BigEndian:uint32. Valid options for the Endian are: ## BigEndian and LittleEndian. For the bit size: uint16, uint32 ## and uint64. + ## enum(1): Convert the value according to its syntax in the MIB (full). + ## (Only supported with gosmi translator) + ## enum: Convert the value according to its syntax in the MIB. + ## (Only supported with gosmi translator) ## # conversion = "" ``` diff --git a/plugins/inputs/snmp/gosmi.go b/plugins/inputs/snmp/gosmi.go index 732340779..be9bd1eef 100644 --- a/plugins/inputs/snmp/gosmi.go +++ b/plugins/inputs/snmp/gosmi.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/sleepinggenius2/gosmi" + "github.com/sleepinggenius2/gosmi/models" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/internal/snmp" @@ -122,3 +123,21 @@ func (g *gosmiTranslator) SnmpTableCall(oid string) (mibName string, oidNum stri return mibName, oidNum, oidText, fields, nil } + +func (g *gosmiTranslator) SnmpFormatEnum(oid string, value interface{}, full bool) (string, error) { + //nolint:dogsled // only need to get the node + _, _, _, _, node, err := g.SnmpTranslateFull(oid) + + if err != nil { + return "", err + } + + var v models.Value + if full { + v = node.FormatValue(value, models.FormatEnumName, models.FormatEnumValue) + } else { + v = node.FormatValue(value, models.FormatEnumName) + } + + return v.Formatted, nil +} diff --git a/plugins/inputs/snmp/gosmi_test.go b/plugins/inputs/snmp/gosmi_test.go index c5f4b8a4b..26125599f 100644 --- a/plugins/inputs/snmp/gosmi_test.go +++ b/plugins/inputs/snmp/gosmi_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/gosnmp/gosnmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -580,10 +581,12 @@ func TestFieldConvertGosmi(t *testing.T) { {[]byte{0x00, 0x09, 0x3E, 0xE3, 0xF6, 0xD5, 0x3B, 0x60}, "hextoint:LittleEndian:uint64", uint64(6934371307618175232)}, {[]byte{0x00, 0x09, 0x3E, 0xE3}, "hextoint:LittleEndian:uint32", uint32(3812493568)}, {[]byte{0x00, 0x09}, "hextoint:LittleEndian:uint16", uint16(2304)}, + {3, "enum", "testing"}, + {3, "enum(1)", "testing(3)"}, } for _, tc := range testTable { - act, err := fieldConvert(tc.conv, tc.input) + act, err := fieldConvert(getGosmiTr(t), tc.conv, gosnmp.SnmpPDU{Name: ".1.3.6.1.2.1.2.2.1.8", Value: tc.input}) assert.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) assert.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) } diff --git a/plugins/inputs/snmp/netsnmp.go b/plugins/inputs/snmp/netsnmp.go index cd8cce7ac..c992d02f2 100644 --- a/plugins/inputs/snmp/netsnmp.go +++ b/plugins/inputs/snmp/netsnmp.go @@ -3,6 +3,7 @@ package snmp import ( "bufio" "bytes" + "errors" "fmt" "log" //nolint:depguard // Allow exceptional but valid use of log here. "os/exec" @@ -256,3 +257,7 @@ func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText strin return mibName, oidNum, oidText, conversion, nil } + +func (n *netsnmpTranslator) SnmpFormatEnum(_ string, _ interface{}, _ bool) (string, error) { + return "", errors.New("not implemented in netsnmp translator") +} diff --git a/plugins/inputs/snmp/snmp.go b/plugins/inputs/snmp/snmp.go index 95e7baf75..eeca2dfa0 100644 --- a/plugins/inputs/snmp/snmp.go +++ b/plugins/inputs/snmp/snmp.go @@ -36,6 +36,11 @@ type Translator interface { fields []Field, err error, ) + + SnmpFormatEnum(oid string, value interface{}, full bool) ( + formatted string, + err error, + ) } // Snmp holds the configuration for the plugin. @@ -214,6 +219,7 @@ type Field struct { // "int" will conver the value into an integer. // "hwaddr" will convert a 6-byte string to a MAC address. // "ipaddr" will convert the value to an IPv4 or IPv6 address. + // "enum"/"enum(1)" will convert the value according to its syntax. (Only supported with gosmi translator) Conversion string // Translate tells if the value of the field should be snmptranslated Translate bool @@ -424,7 +430,7 @@ func (t Table) Build(gs snmpConnection, walk bool, tr Translator) (*RTable, erro } } else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject && pkt.Variables[0].Type != gosnmp.NoSuchInstance { ent := pkt.Variables[0] - fv, err := fieldConvert(f.Conversion, ent.Value) + fv, err := fieldConvert(tr, f.Conversion, ent) if err != nil { return nil, fmt.Errorf("converting %q (OID %s) for field %s: %w", ent.Value, ent.Name, f.Name, err) } @@ -468,7 +474,7 @@ func (t Table) Build(gs snmpConnection, walk bool, tr Translator) (*RTable, erro } } - fv, err := fieldConvert(f.Conversion, ent.Value) + fv, err := fieldConvert(tr, f.Conversion, ent) if err != nil { return &walkError{ msg: fmt.Sprintf("converting %q (OID %s) for field %s", ent.Value, ent.Name, f.Name), @@ -599,16 +605,17 @@ func (s *Snmp) getConnection(idx int) (snmpConnection, error) { } // fieldConvert converts from any type according to the conv specification -func fieldConvert(conv string, v interface{}) (interface{}, error) { +func fieldConvert(tr Translator, conv string, ent gosnmp.SnmpPDU) (v interface{}, err error) { if conv == "" { - if bs, ok := v.([]byte); ok { + if bs, ok := ent.Value.([]byte); ok { return string(bs), nil } - return v, nil + return ent.Value, nil } var d int if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "float" { + v = ent.Value switch vt := v.(type) { case float32: v = float64(vt) / math.Pow10(d) @@ -645,6 +652,7 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) { } if conv == "int" { + v = ent.Value switch vt := v.(type) { case float32: v = int64(vt) @@ -679,7 +687,7 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) { } if conv == "hwaddr" { - switch vt := v.(type) { + switch vt := ent.Value.(type) { case string: v = net.HardwareAddr(vt).String() case []byte: @@ -695,9 +703,9 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) { endian := split[1] bit := split[2] - bv, ok := v.([]byte) + bv, ok := ent.Value.([]byte) if !ok { - return v, nil + return ent.Value, nil } switch endian { @@ -733,7 +741,7 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) { if conv == "ipaddr" { var ipbs []byte - switch vt := v.(type) { + switch vt := ent.Value.(type) { case string: ipbs = []byte(vt) case []byte: @@ -752,6 +760,14 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) { return v, nil } + if conv == "enum" { + return tr.SnmpFormatEnum(ent.Name, ent.Value, false) + } + + if conv == "enum(1)" { + return tr.SnmpFormatEnum(ent.Name, ent.Value, true) + } + return nil, fmt.Errorf("invalid conversion type '%s'", conv) } diff --git a/plugins/inputs/snmp/snmp_test.go b/plugins/inputs/snmp/snmp_test.go index 6b10d969c..b3a049aa3 100644 --- a/plugins/inputs/snmp/snmp_test.go +++ b/plugins/inputs/snmp/snmp_test.go @@ -901,7 +901,7 @@ func TestFieldConvert(t *testing.T) { } for _, tc := range testTable { - act, err := fieldConvert(tc.conv, tc.input) + act, err := fieldConvert(NewNetsnmpTranslator(), tc.conv, gosnmp.SnmpPDU{Value: tc.input}) if !assert.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) { continue }