773 lines
20 KiB
Go
773 lines
20 KiB
Go
//go:generate ../../../tools/readme_config_includer/generator
|
|
package snmp
|
|
|
|
import (
|
|
_ "embed"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gosnmp/gosnmp"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/config"
|
|
"github.com/influxdata/telegraf/internal/snmp"
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
)
|
|
|
|
//go:embed sample.conf
|
|
var sampleConfig string
|
|
|
|
type Translator interface {
|
|
SnmpTranslate(oid string) (
|
|
mibName string, oidNum string, oidText string,
|
|
conversion string,
|
|
err error,
|
|
)
|
|
|
|
SnmpTable(oid string) (
|
|
mibName string, oidNum string, oidText string,
|
|
fields []Field,
|
|
err error,
|
|
)
|
|
}
|
|
|
|
// Snmp holds the configuration for the plugin.
|
|
type Snmp struct {
|
|
// The SNMP agent to query. Format is [SCHEME://]ADDR[:PORT] (e.g.
|
|
// udp://1.2.3.4:161). If the scheme is not specified then "udp" is used.
|
|
Agents []string `toml:"agents"`
|
|
|
|
// The tag used to name the agent host
|
|
AgentHostTag string `toml:"agent_host_tag"`
|
|
|
|
snmp.ClientConfig
|
|
|
|
Tables []Table `toml:"table"`
|
|
|
|
// Name & Fields are the elements of a Table.
|
|
// Telegraf chokes if we try to embed a Table. So instead we have to embed the
|
|
// fields of a Table, and construct a Table during runtime.
|
|
Name string `toml:"name"`
|
|
Fields []Field `toml:"field"`
|
|
|
|
connectionCache []snmpConnection
|
|
|
|
Log telegraf.Logger `toml:"-"`
|
|
|
|
translator Translator
|
|
}
|
|
|
|
func (s *Snmp) SetTranslator(name string) {
|
|
s.Translator = name
|
|
}
|
|
|
|
func (*Snmp) SampleConfig() string {
|
|
return sampleConfig
|
|
}
|
|
|
|
func (s *Snmp) Init() error {
|
|
var err error
|
|
switch s.Translator {
|
|
case "gosmi":
|
|
s.translator, err = NewGosmiTranslator(s.Path, s.Log)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "netsnmp":
|
|
s.translator = NewNetsnmpTranslator()
|
|
default:
|
|
return fmt.Errorf("invalid translator value")
|
|
}
|
|
|
|
s.connectionCache = make([]snmpConnection, len(s.Agents))
|
|
|
|
for i := range s.Tables {
|
|
if err := s.Tables[i].Init(s.translator); err != nil {
|
|
return fmt.Errorf("initializing table %s: %w", s.Tables[i].Name, err)
|
|
}
|
|
}
|
|
|
|
for i := range s.Fields {
|
|
if err := s.Fields[i].init(s.translator); err != nil {
|
|
return fmt.Errorf("initializing field %s: %w", s.Fields[i].Name, err)
|
|
}
|
|
}
|
|
|
|
if len(s.AgentHostTag) == 0 {
|
|
s.AgentHostTag = "agent_host"
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Table holds the configuration for a SNMP table.
|
|
type Table struct {
|
|
// Name will be the name of the measurement.
|
|
Name string
|
|
|
|
// Which tags to inherit from the top-level config.
|
|
InheritTags []string
|
|
|
|
// Adds each row's table index as a tag.
|
|
IndexAsTag bool
|
|
|
|
// Fields is the tags and values to look up.
|
|
Fields []Field `toml:"field"`
|
|
|
|
// OID for automatic field population.
|
|
// If provided, init() will populate Fields with all the table columns of the
|
|
// given OID.
|
|
Oid string
|
|
|
|
initialized bool
|
|
}
|
|
|
|
// Init() builds & initializes the nested fields.
|
|
func (t *Table) Init(tr Translator) error {
|
|
//makes sure oid or name is set in config file
|
|
//otherwise snmp will produce metrics with an empty name
|
|
if t.Oid == "" && t.Name == "" {
|
|
return fmt.Errorf("SNMP table in config file is not named. One or both of the oid and name settings must be set")
|
|
}
|
|
|
|
if t.initialized {
|
|
return nil
|
|
}
|
|
|
|
if err := t.initBuild(tr); err != nil {
|
|
return err
|
|
}
|
|
|
|
secondaryIndexTablePresent := false
|
|
// initialize all the nested fields
|
|
for i := range t.Fields {
|
|
if err := t.Fields[i].init(tr); err != nil {
|
|
return fmt.Errorf("initializing field %s: %w", t.Fields[i].Name, err)
|
|
}
|
|
if t.Fields[i].SecondaryIndexTable {
|
|
if secondaryIndexTablePresent {
|
|
return fmt.Errorf("only one field can be SecondaryIndexTable")
|
|
}
|
|
secondaryIndexTablePresent = true
|
|
}
|
|
}
|
|
|
|
t.initialized = true
|
|
return nil
|
|
}
|
|
|
|
// initBuild initializes the table if it has an OID configured. If so, the
|
|
// net-snmp tools will be used to look up the OID and auto-populate the table's
|
|
// fields.
|
|
func (t *Table) initBuild(tr Translator) error {
|
|
if t.Oid == "" {
|
|
return nil
|
|
}
|
|
|
|
_, _, oidText, fields, err := tr.SnmpTable(t.Oid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if t.Name == "" {
|
|
t.Name = oidText
|
|
}
|
|
|
|
knownOIDs := map[string]bool{}
|
|
for _, f := range t.Fields {
|
|
knownOIDs[f.Oid] = true
|
|
}
|
|
for _, f := range fields {
|
|
if !knownOIDs[f.Oid] {
|
|
t.Fields = append(t.Fields, f)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 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.
|
|
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 overlaping
|
|
// indexes from both tables.
|
|
// Can be set per field or globally with SecondaryIndexTable, global true overrides
|
|
// per field false.
|
|
SecondaryOuterJoin bool
|
|
|
|
initialized bool
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// check if oid needs translation or name is not set
|
|
if strings.ContainsAny(f.Oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") || f.Name == "" {
|
|
_, oidNum, oidText, conversion, err := tr.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
|
|
}
|
|
//TODO use textual convention conversion from the MIB
|
|
}
|
|
|
|
if f.SecondaryIndexTable && f.SecondaryIndexUse {
|
|
return fmt.Errorf("SecondaryIndexTable and UseSecondaryIndex are exclusive")
|
|
}
|
|
|
|
if !f.SecondaryIndexTable && !f.SecondaryIndexUse && f.SecondaryOuterJoin {
|
|
return fmt.Errorf("SecondaryOuterJoin set to true, but field is not being used in join")
|
|
}
|
|
|
|
f.initialized = true
|
|
return nil
|
|
}
|
|
|
|
// RTable is the resulting table built from a Table.
|
|
type RTable struct {
|
|
// Name is the name of the field, copied from Table.Name.
|
|
Name string
|
|
// Time is the time the table was built.
|
|
Time time.Time
|
|
// Rows are the rows that were found, one row for each table OID index found.
|
|
Rows []RTableRow
|
|
}
|
|
|
|
// RTableRow is the resulting row containing all the OID values which shared
|
|
// the same index.
|
|
type RTableRow struct {
|
|
// Tags are all the Field values which had IsTag=true.
|
|
Tags map[string]string
|
|
// Fields are all the Field values which had IsTag=false.
|
|
Fields map[string]interface{}
|
|
}
|
|
|
|
type walkError struct {
|
|
msg string
|
|
err error
|
|
}
|
|
|
|
func (e *walkError) Error() string {
|
|
return e.msg
|
|
}
|
|
|
|
func (e *walkError) Unwrap() error {
|
|
return e.err
|
|
}
|
|
|
|
// Gather retrieves all the configured fields and tables.
|
|
// Any error encountered does not halt the process. The errors are accumulated
|
|
// and returned at the end.
|
|
func (s *Snmp) Gather(acc telegraf.Accumulator) error {
|
|
var wg sync.WaitGroup
|
|
for i, agent := range s.Agents {
|
|
wg.Add(1)
|
|
go func(i int, agent string) {
|
|
defer wg.Done()
|
|
gs, err := s.getConnection(i)
|
|
if err != nil {
|
|
acc.AddError(fmt.Errorf("agent %s: %w", agent, err))
|
|
return
|
|
}
|
|
|
|
// First is the top-level fields. We treat the fields as table prefixes with an empty index.
|
|
t := Table{
|
|
Name: s.Name,
|
|
Fields: s.Fields,
|
|
}
|
|
topTags := map[string]string{}
|
|
if err := s.gatherTable(acc, gs, t, topTags, false); err != nil {
|
|
acc.AddError(fmt.Errorf("agent %s: %w", agent, err))
|
|
}
|
|
|
|
// Now is the real tables.
|
|
for _, t := range s.Tables {
|
|
if err := s.gatherTable(acc, gs, t, topTags, true); err != nil {
|
|
acc.AddError(fmt.Errorf("agent %s: gathering table %s: %w", agent, t.Name, err))
|
|
}
|
|
}
|
|
}(i, agent)
|
|
}
|
|
wg.Wait()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table, topTags map[string]string, walk bool) error {
|
|
rt, err := t.Build(gs, walk, s.translator)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, tr := range rt.Rows {
|
|
if !walk {
|
|
// top-level table. Add tags to topTags.
|
|
for k, v := range tr.Tags {
|
|
topTags[k] = v
|
|
}
|
|
} else {
|
|
// real table. Inherit any specified tags.
|
|
for _, k := range t.InheritTags {
|
|
if v, ok := topTags[k]; ok {
|
|
tr.Tags[k] = v
|
|
}
|
|
}
|
|
}
|
|
if _, ok := tr.Tags[s.AgentHostTag]; !ok {
|
|
tr.Tags[s.AgentHostTag] = gs.Host()
|
|
}
|
|
acc.AddFields(rt.Name, tr.Fields, tr.Tags, rt.Time)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Build retrieves all the fields specified in the table and constructs the RTable.
|
|
func (t Table) Build(gs snmpConnection, walk bool, tr Translator) (*RTable, error) {
|
|
rows := map[string]RTableRow{}
|
|
|
|
//translation table for secondary index (when preforming join on two tables)
|
|
secIdxTab := make(map[string]string)
|
|
secGlobalOuterJoin := false
|
|
for i, f := range t.Fields {
|
|
if f.SecondaryIndexTable {
|
|
secGlobalOuterJoin = f.SecondaryOuterJoin
|
|
if i != 0 {
|
|
t.Fields[0], t.Fields[i] = t.Fields[i], t.Fields[0]
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
tagCount := 0
|
|
for _, f := range t.Fields {
|
|
if f.IsTag {
|
|
tagCount++
|
|
}
|
|
|
|
if len(f.Oid) == 0 {
|
|
return nil, fmt.Errorf("cannot have empty OID on field %s", f.Name)
|
|
}
|
|
var oid string
|
|
if f.Oid[0] == '.' {
|
|
oid = f.Oid
|
|
} else {
|
|
// make sure OID has "." because the BulkWalkAll results do, and the prefix needs to match
|
|
oid = "." + f.Oid
|
|
}
|
|
|
|
// ifv contains a mapping of table OID index to field value
|
|
ifv := map[string]interface{}{}
|
|
|
|
if !walk {
|
|
// This is used when fetching non-table fields. Fields configured a the top
|
|
// scope of the plugin.
|
|
// We fetch the fields directly, and add them to ifv as if the index were an
|
|
// empty string. This results in all the non-table fields sharing the same
|
|
// index, and being added on the same row.
|
|
if pkt, err := gs.Get([]string{oid}); err != nil {
|
|
if errors.Is(err, gosnmp.ErrUnknownSecurityLevel) {
|
|
return nil, fmt.Errorf("unknown security level (sec_level)")
|
|
} else if errors.Is(err, gosnmp.ErrUnknownUsername) {
|
|
return nil, fmt.Errorf("unknown username (sec_name)")
|
|
} else if errors.Is(err, gosnmp.ErrWrongDigest) {
|
|
return nil, fmt.Errorf("wrong digest (auth_protocol, auth_password)")
|
|
} else if errors.Is(err, gosnmp.ErrDecryption) {
|
|
return nil, fmt.Errorf("decryption error (priv_protocol, priv_password)")
|
|
} else {
|
|
return nil, fmt.Errorf("performing get on field %s: %w", f.Name, err)
|
|
}
|
|
} 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)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting %q (OID %s) for field %s: %w", ent.Value, ent.Name, f.Name, err)
|
|
}
|
|
ifv[""] = fv
|
|
}
|
|
} else {
|
|
err := gs.Walk(oid, func(ent gosnmp.SnmpPDU) error {
|
|
if len(ent.Name) <= len(oid) || ent.Name[:len(oid)+1] != oid+"." {
|
|
return &walkError{} // break the walk
|
|
}
|
|
|
|
idx := ent.Name[len(oid):]
|
|
if f.OidIndexSuffix != "" {
|
|
if !strings.HasSuffix(idx, f.OidIndexSuffix) {
|
|
// this entry doesn't match our OidIndexSuffix. skip it
|
|
return nil
|
|
}
|
|
idx = idx[:len(idx)-len(f.OidIndexSuffix)]
|
|
}
|
|
if f.OidIndexLength != 0 {
|
|
i := f.OidIndexLength + 1 // leading separator
|
|
idx = strings.Map(func(r rune) rune {
|
|
if r == '.' {
|
|
i--
|
|
}
|
|
if i < 1 {
|
|
return -1
|
|
}
|
|
return r
|
|
}, idx)
|
|
}
|
|
|
|
// snmptranslate table field value here
|
|
if f.Translate {
|
|
if entOid, ok := ent.Value.(string); ok {
|
|
_, _, oidText, _, err := tr.SnmpTranslate(entOid)
|
|
if err == nil {
|
|
// If no error translating, the original value for ent.Value should be replaced
|
|
ent.Value = oidText
|
|
}
|
|
}
|
|
}
|
|
|
|
fv, err := fieldConvert(f.Conversion, ent.Value)
|
|
if err != nil {
|
|
return &walkError{
|
|
msg: fmt.Sprintf("converting %q (OID %s) for field %s", ent.Value, ent.Name, f.Name),
|
|
err: err,
|
|
}
|
|
}
|
|
ifv[idx] = fv
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
// Our callback always wraps errors in a walkError.
|
|
// If this error isn't a walkError, we know it's not
|
|
// from the callback
|
|
if _, ok := err.(*walkError); !ok {
|
|
return nil, fmt.Errorf("performing bulk walk for field %s: %w", f.Name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
for idx, v := range ifv {
|
|
if f.SecondaryIndexUse {
|
|
if newidx, ok := secIdxTab[idx]; ok {
|
|
idx = newidx
|
|
} else {
|
|
if !secGlobalOuterJoin && !f.SecondaryOuterJoin {
|
|
continue
|
|
}
|
|
idx = ".Secondary" + idx
|
|
}
|
|
}
|
|
rtr, ok := rows[idx]
|
|
if !ok {
|
|
rtr = RTableRow{}
|
|
rtr.Tags = map[string]string{}
|
|
rtr.Fields = map[string]interface{}{}
|
|
rows[idx] = rtr
|
|
}
|
|
if t.IndexAsTag && idx != "" {
|
|
if idx[0] == '.' {
|
|
idx = idx[1:]
|
|
}
|
|
rtr.Tags["index"] = idx
|
|
}
|
|
// don't add an empty string
|
|
if vs, ok := v.(string); !ok || vs != "" {
|
|
if f.IsTag {
|
|
if ok {
|
|
rtr.Tags[f.Name] = vs
|
|
} else {
|
|
rtr.Tags[f.Name] = fmt.Sprintf("%v", v)
|
|
}
|
|
} else {
|
|
rtr.Fields[f.Name] = v
|
|
}
|
|
if f.SecondaryIndexTable {
|
|
//indexes are stored here with prepending "." so we need to add them if needed
|
|
var vss string
|
|
if ok {
|
|
vss = "." + vs
|
|
} else {
|
|
vss = fmt.Sprintf(".%v", v)
|
|
}
|
|
if idx[0] == '.' {
|
|
secIdxTab[vss] = idx
|
|
} else {
|
|
secIdxTab[vss] = "." + idx
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rt := RTable{
|
|
Name: t.Name,
|
|
Time: time.Now(), //TODO record time at start
|
|
Rows: make([]RTableRow, 0, len(rows)),
|
|
}
|
|
for _, r := range rows {
|
|
rt.Rows = append(rt.Rows, r)
|
|
}
|
|
return &rt, nil
|
|
}
|
|
|
|
// snmpConnection is an interface which wraps a *gosnmp.GoSNMP object.
|
|
// We interact through an interface so we can mock it out in tests.
|
|
type snmpConnection interface {
|
|
Host() string
|
|
//BulkWalkAll(string) ([]gosnmp.SnmpPDU, error)
|
|
Walk(string, gosnmp.WalkFunc) error
|
|
Get(oids []string) (*gosnmp.SnmpPacket, error)
|
|
Reconnect() error
|
|
}
|
|
|
|
// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the
|
|
// result using `agentIndex` as the cache key. This is done to allow multiple
|
|
// connections to a single address. It is an error to use a connection in
|
|
// more than one goroutine.
|
|
func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
|
|
if gs := s.connectionCache[idx]; gs != nil {
|
|
if err := gs.Reconnect(); err != nil {
|
|
return gs, fmt.Errorf("reconnecting: %w", err)
|
|
}
|
|
|
|
return gs, nil
|
|
}
|
|
|
|
agent := s.Agents[idx]
|
|
|
|
var err error
|
|
var gs snmp.GosnmpWrapper
|
|
gs, err = snmp.NewWrapper(s.ClientConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = gs.SetAgent(agent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.connectionCache[idx] = gs
|
|
|
|
if err := gs.Connect(); err != nil {
|
|
return nil, fmt.Errorf("setting up connection: %w", err)
|
|
}
|
|
|
|
return gs, nil
|
|
}
|
|
|
|
// fieldConvert converts from any type according to the conv specification
|
|
func fieldConvert(conv string, v interface{}) (interface{}, error) {
|
|
if conv == "" {
|
|
if bs, ok := v.([]byte); ok {
|
|
return string(bs), nil
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
var d int
|
|
if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "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, _ := strconv.ParseFloat(string(vt), 64)
|
|
v = vf / math.Pow10(d)
|
|
case string:
|
|
vf, _ := strconv.ParseFloat(vt, 64)
|
|
v = vf / math.Pow10(d)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
if conv == "int" {
|
|
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, _ = strconv.ParseInt(string(vt), 10, 64)
|
|
case string:
|
|
v, _ = strconv.ParseInt(vt, 10, 64)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
if conv == "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", v)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
split := strings.Split(conv, ":")
|
|
if split[0] == "hextoint" && len(split) == 3 {
|
|
endian := split[1]
|
|
bit := split[2]
|
|
|
|
bv, ok := v.([]byte)
|
|
if !ok {
|
|
return v, nil
|
|
}
|
|
|
|
switch endian {
|
|
case "LittleEndian":
|
|
switch bit {
|
|
case "uint64":
|
|
v = binary.LittleEndian.Uint64(bv)
|
|
case "uint32":
|
|
v = binary.LittleEndian.Uint32(bv)
|
|
case "uint16":
|
|
v = binary.LittleEndian.Uint16(bv)
|
|
default:
|
|
return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit)
|
|
}
|
|
case "BigEndian":
|
|
switch bit {
|
|
case "uint64":
|
|
v = binary.BigEndian.Uint64(bv)
|
|
case "uint32":
|
|
v = binary.BigEndian.Uint32(bv)
|
|
case "uint16":
|
|
v = binary.BigEndian.Uint16(bv)
|
|
default:
|
|
return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("invalid Endian value (%s) for hex to int conversion", endian)
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
if conv == "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", v)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid conversion type '%s'", conv)
|
|
}
|
|
|
|
func init() {
|
|
inputs.Add("snmp", func() telegraf.Input {
|
|
return &Snmp{
|
|
Name: "snmp",
|
|
ClientConfig: snmp.ClientConfig{
|
|
Retries: 3,
|
|
MaxRepetitions: 10,
|
|
Timeout: config.Duration(5 * time.Second),
|
|
Version: 2,
|
|
Path: []string{"/usr/share/snmp/mibs"},
|
|
Community: "public",
|
|
},
|
|
}
|
|
})
|
|
}
|