package snmp import ( "encoding/binary" "encoding/hex" "errors" "fmt" "math" "net" "strconv" "strings" "unicode/utf8" "github.com/gosnmp/gosnmp" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" ) // Field holds the configuration for a Field to look up. type Field struct { // Name will be the name of the field. Name string // OID is prefix for this field. The plugin will perform a walk through all // OIDs with this as their parent. For each value found, the plugin will strip // off the OID prefix, and use the remainder as the index. For multiple fields // to show up in the same row, they must share the same index. Oid string // OidIndexSuffix is the trailing sub-identifier on a table record OID that will be stripped off to get the record's index. OidIndexSuffix string // OidIndexLength specifies the length of the index in OID path segments. It can be used to remove sub-identifiers that vary in content or length. OidIndexLength int // IsTag controls whether this OID is output as a tag or a value. IsTag bool // Conversion controls any type conversion that is done on the value. // "float"/"float(0)" will convert the value into a float. // "float(X)" will convert the value into a float, and then move the decimal before Xth right-most digit. // "int" will convert 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) // "displayhint" will format the value according to the textual convention. (Only supported with gosmi translator) Conversion string // Translate tells if the value of the field should be snmptranslated Translate bool // Secondary index table allows to merge data from two tables with different index // that this filed will be used to join them. There can be only one secondary index table. SecondaryIndexTable bool // This field is using secondary index, and will be later merged with primary index // using SecondaryIndexTable. SecondaryIndexTable and SecondaryIndexUse are exclusive. SecondaryIndexUse bool // Controls if entries from secondary table should be added or not if joining // index is present or not. I set to true, means that join is outer, and // index is prepended with "Secondary." for missing values to avoid overlapping // indexes from both tables. // Can be set per field or globally with SecondaryIndexTable, global true overrides // per field false. SecondaryOuterJoin bool initialized bool translator Translator } // init() converts OID names to numbers, and sets the .Name attribute if unset. func (f *Field) Init(tr Translator) error { if f.initialized { return nil } f.translator = tr // check if oid needs translation or name is not set if strings.ContainsAny(f.Oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") || f.Name == "" { _, oidNum, oidText, conversion, err := f.translator.SnmpTranslate(f.Oid) if err != nil { return fmt.Errorf("translating: %w", err) } f.Oid = oidNum if f.Name == "" { f.Name = oidText } if f.Conversion == "" { f.Conversion = conversion } } if f.SecondaryIndexTable && f.SecondaryIndexUse { return errors.New("fields SecondaryIndexTable and UseSecondaryIndex are exclusive") } if !f.SecondaryIndexTable && !f.SecondaryIndexUse && f.SecondaryOuterJoin { return errors.New("field SecondaryOuterJoin set to true, but field is not being used in join") } switch f.Conversion { case "hwaddr", "enum(1)": config.PrintOptionValueDeprecationNotice("inputs.snmp", "field.conversion", f.Conversion, telegraf.DeprecationInfo{ Since: "1.33.0", Notice: "Use 'displayhint' instead", }) } f.initialized = true return nil } // fieldConvert converts from any type according to the conv specification func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) { v := ent.Value // snmptranslate table field value here if f.Translate { if entOid, ok := v.(string); ok { _, _, oidText, _, err := f.translator.SnmpTranslate(entOid) if err == nil { // If no error translating, the original value should be replaced v = oidText } } } if f.Conversion == "" { // OctetStrings may contain hex data that needs its own conversion if ent.Type == gosnmp.OctetString && !utf8.Valid(v.([]byte)[:]) { return hex.EncodeToString(v.([]byte)), nil } if bs, ok := v.([]byte); ok { return string(bs), nil } return v, nil } var d int if _, err := fmt.Sscanf(f.Conversion, "float(%d)", &d); err == nil || f.Conversion == "float" { switch vt := v.(type) { case float32: v = float64(vt) / math.Pow10(d) case float64: v = vt / math.Pow10(d) case int: v = float64(vt) / math.Pow10(d) case int8: v = float64(vt) / math.Pow10(d) case int16: v = float64(vt) / math.Pow10(d) case int32: v = float64(vt) / math.Pow10(d) case int64: v = float64(vt) / math.Pow10(d) case uint: v = float64(vt) / math.Pow10(d) case uint8: v = float64(vt) / math.Pow10(d) case uint16: v = float64(vt) / math.Pow10(d) case uint32: v = float64(vt) / math.Pow10(d) case uint64: v = float64(vt) / math.Pow10(d) case []byte: vf, err := strconv.ParseFloat(string(vt), 64) if err != nil { return nil, fmt.Errorf("failed to convert field to float with value %s: %w", vt, err) } v = vf / math.Pow10(d) case string: vf, err := strconv.ParseFloat(vt, 64) if err != nil { return nil, fmt.Errorf("failed to convert field to float with value %s: %w", vt, err) } v = vf / math.Pow10(d) } return v, nil } if f.Conversion == "int" { var err error switch vt := v.(type) { case float32: v = int64(vt) case float64: v = int64(vt) case int: v = int64(vt) case int8: v = int64(vt) case int16: v = int64(vt) case int32: v = int64(vt) case int64: v = vt case uint: v = int64(vt) case uint8: v = int64(vt) case uint16: v = int64(vt) case uint32: v = int64(vt) case uint64: v = int64(vt) case []byte: v, err = strconv.ParseInt(string(vt), 10, 64) case string: v, err = strconv.ParseInt(vt, 10, 64) } return v, err } // Deprecated: Use displayhint instead if f.Conversion == "hwaddr" { switch vt := v.(type) { case string: v = net.HardwareAddr(vt).String() case []byte: v = net.HardwareAddr(vt).String() default: return nil, fmt.Errorf("invalid type (%T) for hwaddr conversion", vt) } return v, nil } if f.Conversion == "hex" { switch vt := v.(type) { case string: switch ent.Type { case gosnmp.IPAddress: ip := net.ParseIP(vt) if ip4 := ip.To4(); ip4 != nil { v = hex.EncodeToString(ip4) } else { v = hex.EncodeToString(ip) } default: return nil, fmt.Errorf("unsupported Asn1BER (%#v) for hex conversion", ent.Type) } case []byte: v = hex.EncodeToString(vt) default: return nil, fmt.Errorf("unsupported type (%T) for hex conversion", vt) } return v, nil } split := strings.Split(f.Conversion, ":") if split[0] == "hextoint" && len(split) == 3 { endian := split[1] bit := split[2] bv, ok := v.([]byte) if !ok { return v, nil } var b []byte switch bit { case "uint64": b = make([]byte, 8) case "uint32": b = make([]byte, 4) case "uint16": b = make([]byte, 2) default: return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit) } copy(b, bv) var byteOrder binary.ByteOrder switch endian { case "LittleEndian": byteOrder = binary.LittleEndian case "BigEndian": byteOrder = binary.BigEndian default: return nil, fmt.Errorf("invalid Endian value (%s) for hex to int conversion", endian) } switch bit { case "uint64": v = byteOrder.Uint64(b) case "uint32": v = byteOrder.Uint32(b) case "uint16": v = byteOrder.Uint16(b) } return v, nil } if f.Conversion == "ipaddr" { var ipbs []byte switch vt := v.(type) { case string: ipbs = []byte(vt) case []byte: ipbs = vt default: return nil, fmt.Errorf("invalid type (%T) for ipaddr conversion", vt) } switch len(ipbs) { case 4, 16: v = net.IP(ipbs).String() default: return nil, fmt.Errorf("invalid length (%d) for ipaddr conversion", len(ipbs)) } return v, nil } if f.Conversion == "enum" { return f.translator.SnmpFormatEnum(ent.Name, ent.Value, false) } // Deprecated: Use displayhint instead if f.Conversion == "enum(1)" { return f.translator.SnmpFormatEnum(ent.Name, ent.Value, true) } if f.Conversion == "displayhint" { return f.translator.SnmpFormatDisplayHint(ent.Name, ent.Value) } return nil, fmt.Errorf("invalid conversion type %q", f.Conversion) }