Skip to content

Commit

Permalink
refactor: snmp to use gosmi (#9518)
Browse files Browse the repository at this point in the history
  • Loading branch information
MyaLongmire authored Nov 30, 2021
1 parent c875e45 commit 7675ce6
Show file tree
Hide file tree
Showing 25 changed files with 7,574 additions and 808 deletions.
2 changes: 2 additions & 0 deletions internal/snmp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type ClientConfig struct {
Retries int `toml:"retries"`
// Values: 1, 2, 3
Version uint8 `toml:"version"`
// Path to mib files
Path []string `toml:"path"`

// Parameters for Version 1 & 2
Community string `toml:"community"`
Expand Down
188 changes: 188 additions & 0 deletions internal/snmp/translate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package snmp

import (
"fmt"
"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

func LoadMibsFromPath(paths []string, log telegraf.Logger) error {
m.Lock()
defer m.Unlock()
gosmi.Init()
var folders []string
for _, mibPath := range paths {
gosmi.AppendPath(mibPath)
folders = append(folders, mibPath)
err := filepath.Walk(mibPath, func(path string, info os.FileInfo, err error) error {
// symlinks are files so we need to double check if any of them are folders
// Will check file vs directory later on
if info.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(path)
if err != nil {
log.Warnf("Bad symbolic link %v", link)
}
folders = append(folders, link)
}
return nil
})
if err != nil {
return fmt.Errorf("Filepath could not be walked %v", err)
}
for _, folder := range folders {
err := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
// checks if file or directory
if info.IsDir() {
gosmi.AppendPath(path)
} else if info.Mode()&os.ModeSymlink == 0 {
_, err := gosmi.LoadModule(info.Name())
if err != nil {
log.Warnf("Module could not be loaded %v", err)
}
}
return nil
})
if err != nil {
return fmt.Errorf("Filepath could not be walked %v", err)
}
}
folders = []string{}
}
return nil
}

// The following is for snmp_trap
type MibEntry struct {
MibName string
OidText string
}

func TrapLookup(oid string) (e MibEntry, err error) {
var node gosmi.SmiNode
node, err = gosmi.GetNodeByOID(types.OidMustFromString(oid))

// ensure modules are loaded or node will be empty (might not error)
if err != nil {
return e, err
}

e.OidText = node.RenderQualified()

i := strings.Index(e.OidText, "::")
if i == -1 {
return e, fmt.Errorf("not found")
}
e.MibName = e.OidText[:i]
e.OidText = e.OidText[i+2:]
return e, nil
}

// The following is for snmp

func GetIndex(oidNum string, mibPrefix string) (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
node, err := gosmi.GetNodeByOID(types.OidMustFromString(oidNum))

if err != nil {
return []string{}, map[string]struct{}{}, fmt.Errorf("getting submask: %w", err)
}

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.GetRow().AsTable().ColumnOrder

return col, tagOids, nil
}

//nolint:revive //Too many return variable but necessary
func SnmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
var out gosmi.SmiNode
var end string
if strings.ContainsAny(oid, "::") {
// split given oid
// for example RFC1213-MIB::sysUpTime.0
s := strings.Split(oid, "::")
// node becomes sysUpTime.0
node := s[1]
if strings.ContainsAny(node, ".") {
s = strings.Split(node, ".")
// node becomes sysUpTime
node = s[0]
end = "." + s[1]
}

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

oidNum = "." + out.RenderNumeric() + 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, 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, 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, fmt.Errorf("not found")
}
mibName = oidText[:i]
oidText = oidText[i+2:] + end

return mibName, oidNum, oidText, conversion, nil
}
24 changes: 8 additions & 16 deletions plugins/inputs/snmp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,10 @@ The `snmp` input plugin uses polling to gather metrics from SNMP agents.
Support for gathering individual OIDs as well as complete SNMP tables is
included.

## Prerequisites
## Note about Paths

This plugin uses the `snmptable` and `snmptranslate` programs from the
[net-snmp][] project. These tools will need to be installed into the `PATH` in
order to be located. Other utilities from the net-snmp project may be useful
for troubleshooting, but are not directly used by the plugin.

These programs will load available MIBs on the system. Typically the default
directory for MIBs is `/usr/share/snmp/mibs`, but if your MIBs are in a
different location you may need to make the paths known to net-snmp. The
location of these files can be configured in the `snmp.conf` or via the
`MIBDIRS` environment variable. See [`man 1 snmpcmd`][man snmpcmd] for more
information.
Path is a global variable, separate snmp instances will append the specified
path onto the global path variable

## Configuration

Expand All @@ -38,6 +29,9 @@ information.
## SNMP version; can be 1, 2, or 3.
# version = 2

## Path to mib files
# path = ["/usr/share/snmp/mibs"]

## SNMP community string.
# community = "public"

Expand Down Expand Up @@ -260,7 +254,7 @@ oid = "CISCO-POWER-ETHERNET-EXT-MIB::cpeExtPsePortEntPhyIndex"

Partial result (removed agent_host and host columns from all following outputs in this section):

```shell
```text
> ciscoPower,index=1.2 EntPhyIndex=1002i,PortPwrConsumption=6643i 1621460628000000000
> ciscoPower,index=1.6 EntPhyIndex=1006i,PortPwrConsumption=10287i 1621460628000000000
> ciscoPower,index=1.5 EntPhyIndex=1005i,PortPwrConsumption=8358i 1621460628000000000
Expand Down Expand Up @@ -313,7 +307,7 @@ is_tag = true

Result:

```shell
```text
> ciscoPowerEntity,EntPhysicalName=GigabitEthernet1/2,index=1.2 EntPhyIndex=1002i,PortPwrConsumption=6643i 1621461148000000000
> ciscoPowerEntity,EntPhysicalName=GigabitEthernet1/6,index=1.6 EntPhyIndex=1006i,PortPwrConsumption=10287i 1621461148000000000
> ciscoPowerEntity,EntPhysicalName=GigabitEthernet1/5,index=1.5 EntPhyIndex=1005i,PortPwrConsumption=8358i 1621461148000000000
Expand Down Expand Up @@ -357,7 +351,5 @@ interface,agent_host=127.0.0.1,ifDescr=eth0,ifIndex=2,source=example.org ifAdmin
interface,agent_host=127.0.0.1,ifDescr=lo,ifIndex=1,source=example.org ifAdminStatus=1i,ifInDiscards=0i,ifInErrors=0i,ifInNUcastPkts=0i,ifInOctets=51555569i,ifInUcastPkts=339097i,ifInUnknownProtos=0i,ifLastChange=0i,ifMtu=65536i,ifOperStatus=1i,ifOutDiscards=0i,ifOutErrors=0i,ifOutNUcastPkts=0i,ifOutOctets=51555569i,ifOutQLen=0i,ifOutUcastPkts=339097i,ifSpecific=".0.0",ifSpeed=10000000i,ifType=24i 1575509815000000000
```

[net-snmp]: http://www.net-snmp.org/
[man snmpcmd]: http://net-snmp.sourceforge.net/docs/man/snmpcmd.html#lbAK
[metric filtering]: /docs/CONFIGURATION.md#metric-filtering
[metric]: /docs/METRICS.md
Loading

0 comments on commit 7675ce6

Please sign in to comment.