Skip to content

Commit

Permalink
ER 2839: Upgrading snmp_trap plugin to use gosmi translator (influxda…
Browse files Browse the repository at this point in the history
  • Loading branch information
saloni-vunet authored Apr 8, 2022
1 parent ab5cc55 commit bab4dda
Show file tree
Hide file tree
Showing 16 changed files with 861 additions and 545 deletions.
5 changes: 5 additions & 0 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/influxdata/telegraf/models"
"github.com/influxdata/telegraf/plugins/serializers/influx"
)
Expand Down Expand Up @@ -186,6 +187,10 @@ func (a *Agent) Run(ctx context.Context) error {
// initPlugins runs the Init function on plugins.
func (a *Agent) initPlugins() error {
for _, input := range a.Config.Inputs {
// Share the snmp translator setting with plugins that need it.
if tp, ok := input.Input.(snmp.TranslatorPlugin); ok {
tp.SetTranslator(a.Config.Agent.SnmpTranslator)
}
err := input.Init()
if err != nil {
return fmt.Errorf("could not initialize input %s: %v",
Expand Down
12 changes: 12 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ type AgentConfig struct {

Hostname string
OmitHostname bool

SnmpTranslator string `toml:"snmp_translator"`
}

// InputNames returns a list of strings of the configured inputs.
Expand Down Expand Up @@ -373,6 +375,11 @@ var agentConfig = `
hostname = ""
## If set to true, do no set the "host" tag in the telegraf agent.
omit_hostname = false
## Method of translating SNMP objects. Can be "netsnmp" which
## translates by calling external programs snmptranslate and snmptable,
## or "gosmi" which translates using the built-in gosmi library.
# snmp_translator = "netsnmp"
`

var outputHeader = `
Expand Down Expand Up @@ -798,6 +805,11 @@ func (c *Config) LoadConfigData(data []byte) error {
c.Tags["host"] = c.Agent.Hostname
}

// Set snmp agent translator default
if c.Agent.SnmpTranslator == "" {
c.Agent.SnmpTranslator = "netsnmp"
}

if len(c.UnusedFields) > 0 {
return fmt.Errorf("line %d: configuration specified the fields %q, but they weren't used", tbl.Line, keys(c.UnusedFields))
}
Expand Down
3 changes: 3 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ The agent table configures Telegraf and the defaults used across all plugins.
- **omit_hostname**:
If set to true, do no set the "host" tag in the telegraf agent.

- **snmp_translator**:
Method of translating SNMP objects. Can be "netsnmp" which translates by calling external programs snmptranslate and snmptable, or "gosmi" which translates using the built-in gosmi library.

### Plugins

Telegraf plugins are divided into 4 types: [inputs][], [outputs][],
Expand Down
5 changes: 5 additions & 0 deletions etc/telegraf.conf
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
## If set to true, do no set the "host" tag in the telegraf agent.
omit_hostname = false

## Method of translating SNMP objects. Can be "netsnmp" which
## translates by calling external programs snmptranslate and snmptable,
## or "gosmi" which translates using the built-in gosmi library.
# snmp_translator = "netsnmp"

###############################################################################
# OUTPUT PLUGINS #
###############################################################################
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosnmp/gosnmp v1.32.0 h1:gctewmZx5qFI0oHMzRnjETqIZ093d9NgZy9TQr3V0iA=
github.com/gosnmp/gosnmp v1.32.0/go.mod h1:EIp+qkEpXoVsyZxXKy0AmXQx0mCHMMcIhXXvNDMpgF0=
github.com/gosnmp/gosnmp v1.34.0 h1:p96iiNTTdL4ZYspPC3leSKXiHfE1NiIYffMu9100p5E=
github.com/gosnmp/gosnmp v1.34.0/go.mod h1:QWTRprXN9haHFof3P96XTDYc46boCGAh5IXp0DniEx4=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grid-x/modbus v0.0.0-20210224155242-c4a3d042e99b h1:Y4xqzO0CDNoehCr3ncgie3IgFTO9AzV8PMMEWESFM5c=
github.com/grid-x/modbus v0.0.0-20210224155242-c4a3d042e99b/go.mod h1:YaK0rKJenZ74vZFcSSLlAQqtG74PMI68eDjpDCDDmTw=
Expand Down
5 changes: 5 additions & 0 deletions internal/snmp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ type ClientConfig struct {
// Values: 1, 2, 3
Version uint8 `toml:"version"`

// Path to mib files
Path []string `toml:"path"`
// Translator implementation
Translator string `toml:"-"`

// Parameters for Version 1 & 2
Community string `toml:"community"`

Expand Down
293 changes: 293 additions & 0 deletions internal/snmp/translate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
package snmp

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"

"github.com/influxdata/telegraf"
"github.com/sleepinggenius2/gosmi"
"github.com/sleepinggenius2/gosmi/types"
)

// must init, append path for each directory, load module for every file
// or gosmi will fail without saying why
var m sync.Mutex
var once sync.Once
var cache = make(map[string]bool)

type MibLoader interface {
// appendPath takes the path of a directory
appendPath(path string)

// loadModule takes the name of a file in one of the
// directories. Basename only, no relative or absolute path
loadModule(path string) error
}

type GosmiMibLoader struct{}

func (*GosmiMibLoader) appendPath(path string) {
m.Lock()
defer m.Unlock()

gosmi.AppendPath(path)
}

func (*GosmiMibLoader) loadModule(path string) error {
m.Lock()
defer m.Unlock()

_, err := gosmi.LoadModule(path)
return err
}

func ClearCache() {
cache = make(map[string]bool)
}

//will give all found folders to gosmi and load in all modules found in the folders
func LoadMibsFromPath(paths []string, log telegraf.Logger, loader MibLoader) error {
folders, err := walkPaths(paths, log)
if err != nil {
return err
}
for _, path := range folders {
loader.appendPath(path)
modules, err := ioutil.ReadDir(path)
if err != nil {
log.Warnf("Can't read directory %v", modules)
}

for _, info := range modules {
if info.Mode()&os.ModeSymlink != 0 {
symlink := filepath.Join(path, info.Name())
target, err := filepath.EvalSymlinks(symlink)
if err != nil {
log.Warnf("Couldn't evaluate symbolic links for %v: %v", symlink, err)
continue
}
//replace symlink's info with the target's info
info, err = os.Lstat(target)
if err != nil {
log.Warnf("Couldn't stat target %v: %v", target, err)
continue
}
}
if info.Mode().IsRegular() {
err := loader.loadModule(info.Name())
if err != nil {
log.Warnf("Couldn't load module %v: %v", info.Name(), err)
continue
}
}
}
}
return nil
}

//should walk the paths given and find all folders
func walkPaths(paths []string, log telegraf.Logger) ([]string, error) {
once.Do(gosmi.Init)
folders := []string{}

for _, mibPath := range paths {
// Check if we loaded that path already and skip it if so
m.Lock()
cached := cache[mibPath]
cache[mibPath] = true
m.Unlock()
if cached {
continue
}

err := filepath.Walk(mibPath, func(path string, info os.FileInfo, err error) error {
if info == nil {
log.Warnf("No mibs found")
if os.IsNotExist(err) {
log.Warnf("MIB path doesn't exist: %q", mibPath)
} else if err != nil {
return err
}
return nil
}

if info.Mode()&os.ModeSymlink != 0 {
target, err := filepath.EvalSymlinks(path)
if err != nil {
log.Warnf("Couldn't evaluate symbolic links for %v: %v", path, err)
}
info, err = os.Lstat(target)
if err != nil {
log.Warnf("Couldn't stat target %v: %v", target, err)
}
path = target
}
if info.IsDir() {
folders = append(folders, path)
}

return nil
})
if err != nil {
return folders, fmt.Errorf("Couldn't walk path %q: %v", mibPath, err)
}
}
return folders, nil
}

// The following is for snmp_trap
type MibEntry struct {
MibName string
OidText string
EnumMap map[int]string
}

func TrapLookup(oid string) (e MibEntry, err error) {
var givenOid types.Oid
if givenOid, err = types.OidFromString(oid); err != nil {
return e, fmt.Errorf("could not convert OID %s: %w", oid, err)
}

// Get node name
var node gosmi.SmiNode
if node, err = gosmi.GetNodeByOID(givenOid); err != nil {
return e, err
}
e.OidText = node.Name

// Generating Enum of NamedNumber fields
// eg. clogHistSeverity
if node.Type != nil {
typeEnum := node.Type.Enum
if typeEnum != nil {
e.EnumMap = make(map[int]string)
for enumEntry := range typeEnum.Values {
e.EnumMap[int(typeEnum.Values[enumEntry].Value)] = typeEnum.Values[enumEntry].Name + "(" + fmt.Sprint(typeEnum.Values[enumEntry].Value) + ")"
}
}
}

// Add not found OID part
if !givenOid.Equals(node.Oid) {
e.OidText += "." + givenOid[len(node.Oid):].String()
}

// Get module name
module := node.GetModule()
if module.Name != "<well-known>" {
e.MibName = module.Name
}

return e, nil
}

// The following is for snmp

func GetIndex(oidNum string, mibPrefix string, node gosmi.SmiNode) (col []string, tagOids map[string]struct{}, err error) {
// first attempt to get the table's tags
tagOids = map[string]struct{}{}

// mimcks grabbing INDEX {} that is returned from snmptranslate -Td MibName
for _, index := range node.GetIndex() {
//nolint:staticcheck //assaignment to nil map to keep backwards compatibilty
tagOids[mibPrefix+index.Name] = struct{}{}
}

// grabs all columns from the table
// mimmicks grabbing everything returned from snmptable -Ch -Cl -c public 127.0.0.1 oidFullName
_, col = node.GetColumns()

return col, tagOids, nil
}

//nolint:revive //Too many return variable but necessary
func SnmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, node gosmi.SmiNode, err error) {
var out gosmi.SmiNode
var end string
if strings.ContainsAny(oid, "::") {
// split given oid
// for example RFC1213-MIB::sysUpTime.0
s := strings.SplitN(oid, "::", 2)
// moduleName becomes RFC1213
moduleName := s[0]
module, err := gosmi.GetModule(moduleName)
if err != nil {
return oid, oid, oid, oid, gosmi.SmiNode{}, err
}
if s[1] == "" {
return "", oid, oid, oid, gosmi.SmiNode{}, fmt.Errorf("cannot parse %v\n", oid)
}
// node becomes sysUpTime.0
node := s[1]
if strings.ContainsAny(node, ".") {
s = strings.SplitN(node, ".", 2)
// node becomes sysUpTime
node = s[0]
end = "." + s[1]
}

out, err = module.GetNode(node)
if err != nil {
return oid, oid, oid, oid, out, err
}

if oidNum = out.RenderNumeric(); oidNum == "" {
return oid, oid, oid, oid, out, fmt.Errorf("cannot make %v numeric, please ensure all imported mibs are in the path", oid)
}

oidNum = "." + oidNum + end
} else if strings.ContainsAny(oid, "abcdefghijklnmopqrstuvwxyz") {
//handle mixed oid ex. .iso.2.3
s := strings.Split(oid, ".")
for i := range s {
if strings.ContainsAny(s[i], "abcdefghijklmnopqrstuvwxyz") {
out, err = gosmi.GetNode(s[i])
if err != nil {
return oid, oid, oid, oid, out, err
}
s[i] = out.RenderNumeric()
}
}
oidNum = strings.Join(s, ".")
out, _ = gosmi.GetNodeByOID(types.OidMustFromString(oidNum))
} else {
out, err = gosmi.GetNodeByOID(types.OidMustFromString(oid))
oidNum = oid
// ensure modules are loaded or node will be empty (might not error)
// do not return the err as the oid is numeric and telegraf can continue
//nolint:nilerr
if err != nil || out.Name == "iso" {
return oid, oid, oid, oid, out, nil
}
}

tc := out.GetSubtree()

for i := range tc {
// case where the mib doesn't have a conversion so Type struct will be nil
// prevents seg fault
if tc[i].Type == nil {
break
}
switch tc[i].Type.Name {
case "MacAddress", "PhysAddress":
conversion = "hwaddr"
case "InetAddressIPv4", "InetAddressIPv6", "InetAddress", "IPSIpAddress":
conversion = "ipaddr"
}
}

oidText = out.RenderQualified()
i := strings.Index(oidText, "::")
if i == -1 {
return "", oid, oid, oid, out, fmt.Errorf("not found")
}
mibName = oidText[:i]
oidText = oidText[i+2:] + end

return mibName, oidNum, oidText, conversion, out, nil
}
Loading

0 comments on commit bab4dda

Please sign in to comment.