From bab4dda4f75f6d4761207f54e24eee534a9042d6 Mon Sep 17 00:00:00 2001 From: Saloni <71057931+saloni-vunet@users.noreply.github.com> Date: Fri, 8 Apr 2022 19:23:17 +0530 Subject: [PATCH] ER 2839: Upgrading snmp_trap plugin to use gosmi translator (#16) --- agent/agent.go | 5 + config/config.go | 12 + docs/CONFIGURATION.md | 3 + etc/telegraf.conf | 5 + go.sum | 2 + internal/snmp/config.go | 5 + internal/snmp/translate.go | 293 ++++++++++++++ internal/snmp/translate_test.go | 153 ++++++++ internal/snmp/translator.go | 5 + plugins/inputs/snmp_trap/README.md | 44 +-- plugins/inputs/snmp_trap/gosmi.go | 21 + plugins/inputs/snmp_trap/netsnmp.go | 121 ++++++ plugins/inputs/snmp_trap/snmp_trap.go | 168 ++------ plugins/inputs/snmp_trap/snmp_trap_test.go | 432 +++++++++------------ plugins/inputs/snmp_trap/testdata/test.mib | 40 -- plugins/inputs/snmp_trap/testdata/test2 | 97 ----- 16 files changed, 861 insertions(+), 545 deletions(-) create mode 100644 internal/snmp/translate.go create mode 100644 internal/snmp/translate_test.go create mode 100644 internal/snmp/translator.go create mode 100644 plugins/inputs/snmp_trap/gosmi.go create mode 100644 plugins/inputs/snmp_trap/netsnmp.go delete mode 100644 plugins/inputs/snmp_trap/testdata/test.mib delete mode 100644 plugins/inputs/snmp_trap/testdata/test2 diff --git a/agent/agent.go b/agent/agent.go index 78097bcd47731..cb349cdc3d503 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -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" ) @@ -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", diff --git a/config/config.go b/config/config.go index 56beed8ee4910..7783337632fe6 100644 --- a/config/config.go +++ b/config/config.go @@ -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. @@ -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 = ` @@ -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)) } diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 70e7981c9450b..c069d336b9288 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -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][], diff --git a/etc/telegraf.conf b/etc/telegraf.conf index fabd2616141fb..c1dbfb8c7626c 100644 --- a/etc/telegraf.conf +++ b/etc/telegraf.conf @@ -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 # ############################################################################### diff --git a/go.sum b/go.sum index 01266f3e9cbf7..0d7d1d384f263 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/snmp/config.go b/internal/snmp/config.go index 0a200b7067787..149d196651e05 100644 --- a/internal/snmp/config.go +++ b/internal/snmp/config.go @@ -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"` diff --git a/internal/snmp/translate.go b/internal/snmp/translate.go new file mode 100644 index 0000000000000..1f4dae819fb95 --- /dev/null +++ b/internal/snmp/translate.go @@ -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 != "" { + 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 +} diff --git a/internal/snmp/translate_test.go b/internal/snmp/translate_test.go new file mode 100644 index 0000000000000..9f22947e8ad29 --- /dev/null +++ b/internal/snmp/translate_test.go @@ -0,0 +1,153 @@ +package snmp + +import ( + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/influxdata/telegraf/testutil" +) + +func TestTrapLookup(t *testing.T) { + tests := []struct { + name string + oid string + expected MibEntry + }{ + { + name: "Known trap OID", + oid: ".1.3.6.1.6.3.1.1.5.1", + expected: MibEntry{ + MibName: "TGTEST-MIB", + OidText: "coldStart", + }, + }, + { + name: "Known trap value OID", + oid: ".1.3.6.1.2.1.1.3.0", + expected: MibEntry{ + MibName: "TGTEST-MIB", + OidText: "sysUpTimeInstance", + }, + }, + { + name: "Unknown enterprise sub-OID", + oid: ".1.3.6.1.4.1.0.1.2.3", + expected: MibEntry{ + MibName: "TGTEST-MIB", + OidText: "enterprises.0.1.2.3", + }, + }, + { + name: "Unknown MIB", + oid: ".1.2.3", + expected: MibEntry{OidText: "iso.2.3"}, + }, + } + + // Load the MIBs + require.NoError(t, LoadMibsFromPath([]string{"testdata/mibs"}, testutil.Logger{}, &GosmiMibLoader{})) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Run the actual test + actual, err := TrapLookup(tt.oid) + require.NoError(t, err) + require.Equal(t, tt.expected, actual) + }) + } +} + +func TestTrapLookupFail(t *testing.T) { + tests := []struct { + name string + oid string + expected string + }{ + { + name: "New top level OID", + oid: ".3.6.1.3.0", + expected: "Could not find node for OID 3.6.1.3.0", + }, + { + name: "Malformed OID", + oid: ".1.3.dod.1.3.0", + expected: "could not convert OID .1.3.dod.1.3.0: strconv.ParseUint: parsing \"dod\": invalid syntax", + }, + } + + // Load the MIBs + require.NoError(t, LoadMibsFromPath([]string{"testdata/mibs"}, testutil.Logger{}, &GosmiMibLoader{})) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Run the actual test + _, err := TrapLookup(tt.oid) + require.EqualError(t, err, tt.expected) + }) + } +} + +type TestingMibLoader struct { + folders []string + files []string +} + +func (t *TestingMibLoader) appendPath(path string) { + t.folders = append(t.folders, path) +} + +func (t *TestingMibLoader) loadModule(path string) error { + t.files = append(t.files, path) + return nil +} +func TestFolderLookup(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on windows") + } + + tests := []struct { + name string + mibPath [][]string + paths [][]string + files []string + }{ + { + name: "loading folders", + mibPath: [][]string{{"testdata", "loadMibsFromPath", "root"}}, + paths: [][]string{ + {"testdata", "loadMibsFromPath", "root"}, + {"testdata", "loadMibsFromPath", "root", "dirOne"}, + {"testdata", "loadMibsFromPath", "root", "dirOne", "dirTwo"}, + {"testdata", "loadMibsFromPath", "linkTarget"}, + }, + files: []string{"empty", "emptyFile"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + loader := TestingMibLoader{} + + var givenPath []string + for _, paths := range tt.mibPath { + rootPath := filepath.Join(paths...) + givenPath = append(givenPath, rootPath) + } + + err := LoadMibsFromPath(givenPath, testutil.Logger{}, &loader) + require.NoError(t, err) + + var folders []string + for _, pathSlice := range tt.paths { + path := filepath.Join(pathSlice...) + folders = append(folders, path) + } + require.Equal(t, folders, loader.folders) + + require.Equal(t, tt.files, loader.files) + }) + } +} diff --git a/internal/snmp/translator.go b/internal/snmp/translator.go new file mode 100644 index 0000000000000..6a0993a6d1a04 --- /dev/null +++ b/internal/snmp/translator.go @@ -0,0 +1,5 @@ +package snmp + +type TranslatorPlugin interface { + SetTranslator(name string) // Agent calls this on inputs before Init +} diff --git a/plugins/inputs/snmp_trap/README.md b/plugins/inputs/snmp_trap/README.md index f117c35cbeb56..4a68812e93f0c 100644 --- a/plugins/inputs/snmp_trap/README.md +++ b/plugins/inputs/snmp_trap/README.md @@ -6,21 +6,13 @@ notifications (traps and inform requests). Notifications are received on plain UDP. The port to listen is configurable. -### Prerequisites +## Note about Paths -This plugin uses the `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. +Path is a global variable, separate snmp instances will append the specified +path onto the global path variable -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. +## Configuration -### Configuration ```toml [[inputs.snmp_trap]] ## Transport, local address, and port to listen on. Transport must @@ -33,8 +25,11 @@ information. # service_address = "udp://:162" ## ## Path to mib files + ## Used by the gosmi translator. + ## To add paths when translating with netsnmp, use the MIBDIRS environment variable # path = ["/usr/share/snmp/mibs"] ## + ## Deprecated in 1.20.0; no longer running snmptranslate ## Timeout running snmptranslate command # timeout = "5s" ## Snmp version @@ -55,7 +50,7 @@ information. # priv_password = "" ``` -#### Using a Privileged Port +### Using a Privileged Port On many operating systems, listening on a privileged port (a port number less than 1024) requires extra permission. Since the default @@ -73,7 +68,7 @@ the privileged port. To use a privileged port on Linux, you can use setcap to enable the CAP_NET_BIND_SERVICE capability on the telegraf binary: -``` +```shell setcap cap_net_bind_service=+ep /usr/bin/telegraf ``` @@ -84,21 +79,22 @@ On Mac OS, listening on privileged ports is unrestricted on versions - snmp_trap - tags: - - source (string, IP address of trap source) - - name (string, value from SNMPv2-MIB::snmpTrapOID.0 PDU) - - mib (string, MIB from SNMPv2-MIB::snmpTrapOID.0 PDU) - - oid (string, OID string from SNMPv2-MIB::snmpTrapOID.0 PDU) - - version (string, "1" or "2c" or "3") - - context_name (string, value from v3 trap) - - engine_id (string, value from v3 trap) - - community (string, value from 1 or 2c trap) + - source (string, IP address of trap source) + - name (string, value from SNMPv2-MIB::snmpTrapOID.0 PDU) + - mib (string, MIB from SNMPv2-MIB::snmpTrapOID.0 PDU) + - oid (string, OID string from SNMPv2-MIB::snmpTrapOID.0 PDU) + - version (string, "1" or "2c" or "3") + - context_name (string, value from v3 trap) + - engine_id (string, value from v3 trap) + - community (string, value from 1 or 2c trap) - fields: - - Fields are mapped from variables in the trap. Field names are + - Fields are mapped from variables in the trap. Field names are the trap variable names after MIB lookup. Field values are trap variable values. ### Example Output -``` + +```shell snmp_trap,mib=SNMPv2-MIB,name=coldStart,oid=.1.3.6.1.6.3.1.1.5.1,source=192.168.122.102,version=2c,community=public snmpTrapEnterprise.0="linux",sysUpTimeInstance=1i 1574109187723429814 snmp_trap,mib=NET-SNMP-AGENT-MIB,name=nsNotifyShutdown,oid=.1.3.6.1.4.1.8072.4.0.2,source=192.168.122.102,version=2c,community=public sysUpTimeInstance=5803i,snmpTrapEnterprise.0="netSnmpNotificationPrefix" 1574109186555115459 ``` diff --git a/plugins/inputs/snmp_trap/gosmi.go b/plugins/inputs/snmp_trap/gosmi.go new file mode 100644 index 0000000000000..7acc8201ef866 --- /dev/null +++ b/plugins/inputs/snmp_trap/gosmi.go @@ -0,0 +1,21 @@ +package snmp_trap + +import ( + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal/snmp" +) + +type gosmiTranslator struct { +} + +func (t *gosmiTranslator) lookup(oid string) (snmp.MibEntry, error) { + return snmp.TrapLookup(oid) +} + +func newGosmiTranslator(paths []string, log telegraf.Logger) (*gosmiTranslator, error) { + err := snmp.LoadMibsFromPath(paths, log, &snmp.GosmiMibLoader{}) + if err == nil { + return &gosmiTranslator{}, nil + } + return nil, err +} diff --git a/plugins/inputs/snmp_trap/netsnmp.go b/plugins/inputs/snmp_trap/netsnmp.go new file mode 100644 index 0000000000000..13e3ad40d46a1 --- /dev/null +++ b/plugins/inputs/snmp_trap/netsnmp.go @@ -0,0 +1,121 @@ +package snmp_trap + +import ( + "bufio" + "bytes" + "fmt" + "os/exec" + "regexp" + "strconv" + "strings" + "sync" + "time" + + "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/internal/snmp" +) + +type execer func(config.Duration, string, ...string) ([]byte, error) + +func realExecCmd(timeout config.Duration, arg0 string, args ...string) ([]byte, error) { + cmd := exec.Command(arg0, args...) + var out bytes.Buffer + cmd.Stdout = &out + err := internal.RunTimeout(cmd, time.Duration(timeout)) + if err != nil { + return nil, err + } + return out.Bytes(), nil +} + +type netsnmpTranslator struct { + // Each translator has its own cache and each plugin instance has + // its own translator. This is different than the snmp plugin + // which has one global cache. + // + // We may want to change snmp_trap to + // have a global cache although it's not as important for + // snmp_trap to be global because there is usually only one + // instance, while it's common to configure many snmp instances. + cacheLock sync.Mutex + cache map[string]snmp.MibEntry + execCmd execer + Timeout config.Duration +} + +func (s *netsnmpTranslator) lookup(oid string) (e snmp.MibEntry, err error) { + s.cacheLock.Lock() + defer s.cacheLock.Unlock() + var ok bool + if e, ok = s.cache[oid]; !ok { + // cache miss. exec snmptranslate + e, err = s.snmptranslate(oid) + if err == nil { + s.cache[oid] = e + } + return e, err + } + return e, nil +} + +func (s *netsnmpTranslator) snmptranslate(oid string) (e snmp.MibEntry, err error) { + var out []byte + out, err = s.execCmd(s.Timeout, "snmptranslate", "-Td", "-Ob", "-m", "all", oid) + + if err != nil { + return e, err + } + + scanner := bufio.NewScanner(bytes.NewBuffer(out)) + ok := scanner.Scan() + if err = scanner.Err(); !ok && err != nil { + return e, err + } + + e.OidText = scanner.Text() + + 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:] + + e.EnumMap = make(map[int]string) + syntaxRE := regexp.MustCompile(`\{(.*?)\}`) + enumComponentRE := regexp.MustCompile(`\((.*?)\)`) + for scanner.Scan() { + if err = scanner.Err(); err != nil { + return e, err + } + trapLine := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(trapLine, "SYNTAX") { + enumStringMatches := syntaxRE.FindStringSubmatch(trapLine) + if len(enumStringMatches) > 0 { + enumString := strings.TrimLeft(enumStringMatches[0], "{") + enumStringTrimmed := strings.TrimRight(enumString, "}") + enumStringList := strings.Split(enumStringTrimmed, ",") + for _, s := range enumStringList { + enumComponentString := strings.TrimSpace(s) + enumComponent := enumComponentRE.FindStringSubmatch(enumComponentString) + intVal, err := strconv.Atoi(enumComponent[1]) + if err != nil { + // handle error + return e, err + } + e.EnumMap[intVal] = enumComponentString + } + break + } + } + } + + return e, nil +} + +func newNetsnmpTranslator() *netsnmpTranslator { + return &netsnmpTranslator{ + execCmd: realExecCmd, + } +} diff --git a/plugins/inputs/snmp_trap/snmp_trap.go b/plugins/inputs/snmp_trap/snmp_trap.go index 0d5812586df94..4b04f35b10a71 100644 --- a/plugins/inputs/snmp_trap/snmp_trap.go +++ b/plugins/inputs/snmp_trap/snmp_trap.go @@ -1,39 +1,30 @@ package snmp_trap import ( - "bufio" - "bytes" "fmt" "net" - "os/exec" - "regexp" "strconv" "strings" - "sync" "time" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" - "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/internal/snmp" "github.com/influxdata/telegraf/plugins/inputs" "github.com/gosnmp/gosnmp" ) -var defaultTimeout = config.Duration(time.Second * 5) - -type execer func(config.Duration, string, ...string) ([]byte, error) - -type mibEntry struct { - mibName string - oidText string - enumMap map[int]string +type translator interface { + lookup(oid string) (snmp.MibEntry, error) } type SnmpTrap struct { ServiceAddress string `toml:"service_address"` - Timeout config.Duration `toml:"timeout"` + Timeout config.Duration `toml:"timeout" deprecated:"1.20.0;unused option"` Version string `toml:"version"` + Translator string `toml:"-"` + Path []string `toml:"path"` // Settings for version 3 // Values: "noAuthNoPriv", "authNoPriv", "authPriv" @@ -55,10 +46,7 @@ type SnmpTrap struct { Log telegraf.Logger `toml:"-"` - cacheLock sync.Mutex - cache map[string]mibEntry - - execCmd execer + translator translator //nolint:revive } var sampleConfig = ` @@ -70,8 +58,10 @@ var sampleConfig = ` ## 1024. See README.md for details ## # service_address = "udp://:162" - ## Timeout running snmptranslate command - # timeout = "5s" + ## + ## Path to mib files + # path = ["/usr/share/snmp/mibs"] + ## ## Snmp version, defaults to 2c # version = "2c" ## SNMPv3 authentication and encryption options. @@ -107,26 +97,33 @@ func init() { return &SnmpTrap{ timeFunc: time.Now, ServiceAddress: "udp://:162", - Timeout: defaultTimeout, + Path: []string{"/usr/share/snmp/mibs"}, Version: "2c", } }) } -func realExecCmd(timeout config.Duration, arg0 string, args ...string) ([]byte, error) { - cmd := exec.Command(arg0, args...) - var out bytes.Buffer - cmd.Stdout = &out - err := internal.RunTimeout(cmd, time.Duration(timeout)) - if err != nil { - return nil, err - } - return out.Bytes(), nil +func (s *SnmpTrap) SetTranslator(name string) { + s.Translator = name } func (s *SnmpTrap) Init() error { - s.cache = map[string]mibEntry{} - s.execCmd = realExecCmd + 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") + } + + if err != nil { + s.Log.Errorf("Could not get path %v", err) + } return nil } @@ -256,10 +253,10 @@ func (s *SnmpTrap) Stop() { } } -func setTrapOid(tags map[string]string, oid string, e mibEntry) { +func setTrapOid(tags map[string]string, oid string, e snmp.MibEntry) { tags["oid"] = oid - tags["name"] = e.oidText - tags["mib"] = e.mibName + tags["name"] = e.OidText + tags["mib"] = e.MibName } func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc { @@ -270,6 +267,7 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc { tags["version"] = packet.Version.String() tags["source"] = addr.IP.String() + if packet.Version == gosnmp.Version1 { // Follow the procedure described in RFC 2576 3.1 to // translate a v1 trap to v2. @@ -282,7 +280,7 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc { } if trapOid != "" { - e, err := s.lookup(trapOid) + e, err := s.translator.lookup(trapOid) if err != nil { s.Log.Errorf("Error resolving V1 OID, oid=%s, source=%s: %v", trapOid, tags["source"], err) return @@ -310,13 +308,13 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc { // only handles textual convention for ip and mac // addresses - e, err := s.lookup(v.Name) + e, err := s.translator.lookup(v.Name) if nil != err { s.Log.Errorf("Error resolving OID oid=%s, source=%s: %v", v.Name, tags["source"], err) return } - name := e.oidText + name := e.OidText switch v.Type { case gosnmp.ObjectIdentifier: @@ -326,15 +324,15 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc { return } - var e mibEntry + var e snmp.MibEntry var err error - e, err = s.lookup(val) + e, err = s.translator.lookup(val) if nil != err { s.Log.Errorf("Error resolving value OID, oid=%s, source=%s: %v", val, tags["source"], err) return } - value = e.oidText + value = e.OidText // 1.3.6.1.6.3.1.1.4.1.0 is SNMPv2-MIB::snmpTrapOID.0. // If v.Name is this oid, set a tag of the trap name. @@ -344,7 +342,7 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc { } case gosnmp.Integer: - if val, ok := e.enumMap[v.Value.(int)]; ok { + if val, ok := e.EnumMap[v.Value.(int)]; ok { value = val } @@ -372,87 +370,3 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc { s.acc.AddFields("snmp_trap", fields, tags, tm) } } - -func (s *SnmpTrap) lookup(oid string) (e mibEntry, err error) { - s.cacheLock.Lock() - defer s.cacheLock.Unlock() - var ok bool - if e, ok = s.cache[oid]; !ok { - // cache miss. exec snmptranslate - e, err = s.snmptranslate(oid) - if err == nil { - s.cache[oid] = e - } - return e, err - } - return e, nil -} - -func (s *SnmpTrap) clear() { - s.cacheLock.Lock() - defer s.cacheLock.Unlock() - s.cache = map[string]mibEntry{} -} - -func (s *SnmpTrap) load(oid string, e mibEntry) { - s.cacheLock.Lock() - defer s.cacheLock.Unlock() - s.cache[oid] = e -} - -func (s *SnmpTrap) snmptranslate(oid string) (e mibEntry, err error) { - var out []byte - out, err = s.execCmd(s.Timeout, "snmptranslate", "-Td", "-Ob", "-m", "all", oid) - - if err != nil { - return e, err - } - - scanner := bufio.NewScanner(bytes.NewBuffer(out)) - - // This reads first line - ok := scanner.Scan() - if err = scanner.Err(); !ok && err != nil { - return e, err - } - - e.oidText = scanner.Text() - - 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:] - - // read other lines here - e.enumMap = make(map[int]string) - syntaxRE := regexp.MustCompile(`\{(.*?)\}`) - enumComponentRE := regexp.MustCompile(`\((.*?)\)`) - for scanner.Scan() { - if err = scanner.Err(); err != nil { - return e, err - } - trapLine := strings.TrimSpace(scanner.Text()) - if strings.HasPrefix(trapLine, "SYNTAX") { - enumStringMatches := syntaxRE.FindStringSubmatch(trapLine) - if len(enumStringMatches) > 0 { - enumString := strings.TrimLeft(enumStringMatches[0], "{") - enumStringTrimmed := strings.TrimRight(enumString, "}") - enumStringList := strings.Split(enumStringTrimmed, ",") - for _, s := range enumStringList { - enumComponentString := strings.TrimSpace(s) - enumComponent := enumComponentRE.FindStringSubmatch(enumComponentString) - intVal, err := strconv.Atoi(enumComponent[1]) - if err != nil { - // handle error - return e, err - } - e.enumMap[intVal] = enumComponentString - } - break - } - } - } - return e, nil -} diff --git a/plugins/inputs/snmp_trap/snmp_trap_test.go b/plugins/inputs/snmp_trap/snmp_trap_test.go index f917a7bbff918..a3a16f0de97bb 100644 --- a/plugins/inputs/snmp_trap/snmp_trap_test.go +++ b/plugins/inputs/snmp_trap/snmp_trap_test.go @@ -3,20 +3,41 @@ package snmp_trap import ( "fmt" "net" - "path/filepath" "strconv" "strings" "testing" "time" "github.com/gosnmp/gosnmp" + "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal/snmp" "github.com/influxdata/telegraf/testutil" - - "github.com/stretchr/testify/require" ) +type entry struct { + oid string + e snmp.MibEntry +} + +type testTranslator struct { + entries []entry +} + +func (t *testTranslator) lookup(input string) (snmp.MibEntry, error) { + for _, entry := range t.entries { + if input == entry.oid { + return snmp.MibEntry{MibName: entry.e.MibName, OidText: entry.e.OidText}, nil + } + } + return snmp.MibEntry{}, fmt.Errorf("unexpected oid") +} + +func newTestTranslator(entries []entry) *testTranslator { + return &testTranslator{entries: entries} +} + func newMsgFlagsV3(secLevel string) gosnmp.SnmpV3MsgFlags { var msgFlags gosnmp.SnmpV3MsgFlags switch strings.ToLower(secLevel) { @@ -131,11 +152,6 @@ func TestReceiveTrap(t *testing.T) { now := uint32(123123123) fakeTime := time.Unix(456456456, 456) - type entry struct { - oid string - e mibEntry - } - // If the first pdu isn't type TimeTicks, gosnmp.SendTrap() will // prepend one with time.Now() var tests = []struct { @@ -181,23 +197,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -264,16 +280,16 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { ".1.2.3.4.5", - mibEntry{ - "valueMIB", - "valueOID", + snmp.MibEntry{ + MibName: "valueMIB", + OidText: "valueOID", }, }, { ".1.2.3.0.55", - mibEntry{ - "enterpriseMIB", - "enterpriseOID", + snmp.MibEntry{ + MibName: "enterpriseMIB", + OidText: "enterpriseOID", }, }, }, @@ -318,16 +334,16 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { ".1.2.3.4.5", - mibEntry{ - "valueMIB", - "valueOID", + snmp.MibEntry{ + MibName: "valueMIB", + OidText: "valueOID", }, }, { ".1.3.6.1.6.3.1.1.5.1", - mibEntry{ - "coldStartMIB", - "coldStartOID", + snmp.MibEntry{ + MibName: "coldStartMIB", + OidText: "coldStartOID", }, }, }, @@ -376,23 +392,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -440,23 +456,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -503,23 +519,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -565,23 +581,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -627,23 +643,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -689,23 +705,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -751,23 +767,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -813,23 +829,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -877,23 +893,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -941,23 +957,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -1005,23 +1021,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -1069,23 +1085,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -1133,23 +1149,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -1197,23 +1213,23 @@ func TestReceiveTrap(t *testing.T) { entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", - e: mibEntry{ - "SNMPv2-MIB", - "snmpTrapOID.0", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", - e: mibEntry{ - "SNMPv2-MIB", - "coldStart", + e: snmp.MibEntry{ + MibName: "SNMPv2-MIB", + OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", - e: mibEntry{ - "UNUSED_MIB_NAME", - "sysUpTimeInstance", + e: snmp.MibEntry{ + MibName: "UNUSED_MIB_NAME", + OidText: "sysUpTimeInstance", }, }, }, @@ -1261,14 +1277,6 @@ func TestReceiveTrap(t *testing.T) { timeFunc: func() time.Time { return fakeTime }, - lookupFunc: func(input string) (mibEntry, error) { - for _, entry := range tt.entries { - if input == entry.oid { - return mibEntry{entry.e.mibName, entry.e.oidText}, nil - } - } - return mibEntry{}, fmt.Errorf("Unexpected oid") - }, //if cold start be answer otherwise err Log: testutil.Logger{}, Version: tt.version.String(), @@ -1278,10 +1286,14 @@ func TestReceiveTrap(t *testing.T) { AuthPassword: tt.authPass, PrivProtocol: tt.privProto, PrivPassword: tt.privPass, + Translator: "netsnmp", } require.NoError(t, s.Init()) + //inject test translator + s.translator = newTestTranslator(tt.entries) + var acc testutil.Accumulator require.Nil(t, s.Start(&acc)) defer s.Stop() @@ -1311,98 +1323,4 @@ func TestReceiveTrap(t *testing.T) { testutil.SortMetrics()) }) } - -} - -func TestGosmiSingleMib(t *testing.T) { - // We would prefer to specify port 0 and let the network - // stack choose an unused port for us but TrapListener - // doesn't have a way to return the autoselected port. - // Instead, we'll use an unusual port and hope it's - // unused. - const port = 12399 - - // Hook into the trap handler so the test knows when the - // trap has been received - received := make(chan int) - wrap := func(f gosnmp.TrapHandlerFunc) gosnmp.TrapHandlerFunc { - return func(p *gosnmp.SnmpPacket, a *net.UDPAddr) { - f(p, a) - received <- 0 - } - } - - fakeTime := time.Unix(456456456, 456) - now := uint32(123123123) - - testDataPath, err := filepath.Abs("./testdata") - require.NoError(t, err) - - trap := gosnmp.SnmpTrap{ - Variables: []gosnmp.SnmpPDU{ - { - Name: ".1.3.6.1.2.1.1.3.0", - Type: gosnmp.TimeTicks, - Value: now, - }, - { - Name: ".1.3.6.1.6.3.1.1.4.1.0", // SNMPv2-MIB::snmpTrapOID.0 - Type: gosnmp.ObjectIdentifier, - Value: ".1.3.6.1.6.3.1.1.5.1", // coldStart - }, - }, - } - - metrics := []telegraf.Metric{ - testutil.MustMetric( - "snmp_trap", // name - map[string]string{ // tags - "oid": ".1.3.6.1.6.3.1.1.5.1", - "name": "coldStart", - "mib": "SNMPv2-MIB", - "version": "2c", - "source": "127.0.0.1", - "community": "public", - }, - map[string]interface{}{ // fields - "sysUpTimeInstance": now, - }, - fakeTime, - ), - } - - // Set up the service input plugin - s := &SnmpTrap{ - ServiceAddress: "udp://:" + strconv.Itoa(port), - makeHandlerWrapper: wrap, - timeFunc: func() time.Time { - return fakeTime - }, - lookupFunc: lookup, - Log: testutil.Logger{}, - Version: "2c", - Path: []string{testDataPath}, - } - require.NoError(t, s.Init()) - - var acc testutil.Accumulator - require.Nil(t, s.Start(&acc)) - defer s.Stop() - - goSNMP := newGoSNMP(gosnmp.Version2c, port) - - // Send the trap - sendTrap(t, goSNMP, trap) - - // Wait for trap to be received - select { - case <-received: - case <-time.After(2 * time.Second): - t.Fatal("timed out waiting for trap to be received") - } - - // Verify plugin output - testutil.RequireMetricsEqual(t, - metrics, acc.GetTelegrafMetrics(), - testutil.SortMetrics()) } diff --git a/plugins/inputs/snmp_trap/testdata/test.mib b/plugins/inputs/snmp_trap/testdata/test.mib deleted file mode 100644 index d8ff17af04eba..0000000000000 --- a/plugins/inputs/snmp_trap/testdata/test.mib +++ /dev/null @@ -1,40 +0,0 @@ -SNMPv2-MIB DEFINITIONS ::= BEGIN - -IMPORTS - NOTIFICATION-TYPE, NOTIFICATION-GROUP - FROM test2; - - -snmpMIB MODULE-IDENTITY - LAST-UPDATED "2021060900Z" - ORGANIZATION "testing" - CONTACT-INFO - "EMail: testing@emai.com" - DESCRIPTION - "MIB module for testing snmp_trap plugin - for telegraf - " - ::={ coldStart 1 } - -snmpMIBObjects OBJECT IDENTIFIER ::= { snmpMIB 1 } - -system OBJECT IDENTIFIER ::= { sysUpTimeInstance 1 } - -coldStart NOTIFICATION-TYPE - STATUS current - DESCRIPTION - "A coldStart trap signifies that the SNMP entity, - supporting a notification originator application, is - reinitializing itself and that its configuration may - have been altered." - ::= { snmpTraps 1 } - -snmpBasicNotificationsGroup NOTIFICATION-GROUP - NOTIFICATIONS { coldStart, authenticationFailure } - STATUS current - DESCRIPTION - "The basic notifications implemented by an SNMP entity - supporting command responder applications." - ::= { snmpMIBGroups 7 } - -END diff --git a/plugins/inputs/snmp_trap/testdata/test2 b/plugins/inputs/snmp_trap/testdata/test2 deleted file mode 100644 index e4950b902d803..0000000000000 --- a/plugins/inputs/snmp_trap/testdata/test2 +++ /dev/null @@ -1,97 +0,0 @@ -SNMPv2-MIB DEFINITIONS ::= BEGIN - -org OBJECT IDENTIFIER ::= { iso 3 } -- "iso" = 1 -dod OBJECT IDENTIFIER ::= { org 6 } -internet OBJECT IDENTIFIER ::= { dod 1 } - -directory OBJECT IDENTIFIER ::= { internet 1 } - -mgmt OBJECT IDENTIFIER ::= { internet 2 } -sysUpTimeInstance OBJECT IDENTIFIER ::= { mgmt 1 } -transmission OBJECT IDENTIFIER ::= { sysUpTimeInstance 10 } - -experimental OBJECT IDENTIFIER ::= { internet 3 } - -private OBJECT IDENTIFIER ::= { internet 4 } -enterprises OBJECT IDENTIFIER ::= { private 1 } - -security OBJECT IDENTIFIER ::= { internet 5 } - -snmpV2 OBJECT IDENTIFIER ::= { internet 6 } - --- transport domains -snmpDomains OBJECT IDENTIFIER ::= { snmpV2 1 } - --- transport proxies -snmpProxys OBJECT IDENTIFIER ::= { snmpV2 2 } - --- module identities -coldStart OBJECT IDENTIFIER ::= { snmpV2 3 } - -NOTIFICATION-TYPE MACRO ::= -BEGIN - TYPE NOTATION ::= - ObjectsPart - "STATUS" Status - "DESCRIPTION" Text - ReferPart - - VALUE NOTATION ::= - value(VALUE NotificationName) - - ObjectsPart ::= - "OBJECTS" "{" Objects "}" - | empty - Objects ::= - Object - - | Objects "," Object - Object ::= - value(ObjectName) - - Status ::= - "current" - | "deprecated" - | "obsolete" - - ReferPart ::= - "REFERENCE" Text - | empty - - -- a character string as defined in section 3.1.1 - Text ::= value(IA5String) -END - -NOTIFICATION-GROUP MACRO ::= -BEGIN - TYPE NOTATION ::= - NotificationsPart - "STATUS" Status - "DESCRIPTION" Text - ReferPart - - VALUE NOTATION ::= - value(VALUE OBJECT IDENTIFIER) - - NotificationsPart ::= - "NOTIFICATIONS" "{" Notifications "}" - Notifications ::= - Notification - | Notifications "," Notification - Notification ::= - value(NotificationName) - - Status ::= - "current" - | "deprecated" - | "obsolete" - - ReferPart ::= - "REFERENCE" Text - | empty - - -- a character string as defined in [2] - Text ::= value(IA5String) -END - -END \ No newline at end of file