1
0
Fork 0
telegraf/internal/snmp/field.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

327 lines
8.7 KiB
Go

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)
}