328 lines
8.7 KiB
Go
328 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)
|
||
|
}
|