Add snmp_trap input plugin (#6629)
This commit is contained in:
parent
4e8aa8ad1b
commit
cec1bdce90
|
@ -525,12 +525,12 @@
|
||||||
version = "v1.1.1"
|
version = "v1.1.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:530233672f656641b365f8efb38ed9fba80e420baff2ce87633813ab3755ed6d"
|
digest = "1:68c64bb61d55dcd17c82ca0b871ddddb5ae18b30cfe26f6bfd4b6df6287dc2e0"
|
||||||
name = "github.com/golang/mock"
|
name = "github.com/golang/mock"
|
||||||
packages = ["gomock"]
|
packages = ["gomock"]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "51421b967af1f557f93a59e0057aaf15ca02e29c"
|
revision = "9fa652df1129bef0e734c9cf9bf6dbae9ef3b9fa"
|
||||||
version = "v1.2.0"
|
version = "1.3.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
|
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
|
||||||
|
|
|
@ -136,6 +136,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/smart"
|
_ "github.com/influxdata/telegraf/plugins/inputs/smart"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
_ "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/snmp_legacy"
|
_ "github.com/influxdata/telegraf/plugins/inputs/snmp_legacy"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/snmp_trap"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/socket_listener"
|
_ "github.com/influxdata/telegraf/plugins/inputs/socket_listener"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/solr"
|
_ "github.com/influxdata/telegraf/plugins/inputs/solr"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/sqlserver"
|
_ "github.com/influxdata/telegraf/plugins/inputs/sqlserver"
|
||||||
|
|
|
@ -277,7 +277,7 @@ func (f *Field) init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, oidNum, oidText, conversion, err := snmpTranslate(f.Oid)
|
_, oidNum, oidText, conversion, err := SnmpTranslate(f.Oid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Errorf(err, "translating")
|
return Errorf(err, "translating")
|
||||||
}
|
}
|
||||||
|
@ -882,7 +882,7 @@ func snmpTable(oid string) (mibName string, oidNum string, oidText string, field
|
||||||
}
|
}
|
||||||
|
|
||||||
func snmpTableCall(oid string) (mibName string, oidNum string, oidText string, fields []Field, err error) {
|
func snmpTableCall(oid string) (mibName string, oidNum string, oidText string, fields []Field, err error) {
|
||||||
mibName, oidNum, oidText, _, err = snmpTranslate(oid)
|
mibName, oidNum, oidText, _, err = SnmpTranslate(oid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", nil, Errorf(err, "translating")
|
return "", "", "", nil, Errorf(err, "translating")
|
||||||
}
|
}
|
||||||
|
@ -952,7 +952,7 @@ var snmpTranslateCachesLock sync.Mutex
|
||||||
var snmpTranslateCaches map[string]snmpTranslateCache
|
var snmpTranslateCaches map[string]snmpTranslateCache
|
||||||
|
|
||||||
// snmpTranslate resolves the given OID.
|
// snmpTranslate resolves the given OID.
|
||||||
func snmpTranslate(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
func SnmpTranslate(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
||||||
snmpTranslateCachesLock.Lock()
|
snmpTranslateCachesLock.Lock()
|
||||||
if snmpTranslateCaches == nil {
|
if snmpTranslateCaches == nil {
|
||||||
snmpTranslateCaches = map[string]snmpTranslateCache{}
|
snmpTranslateCaches = map[string]snmpTranslateCache{}
|
||||||
|
@ -978,6 +978,28 @@ func snmpTranslate(oid string) (mibName string, oidNum string, oidText string, c
|
||||||
return stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.err
|
return stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SnmpTranslateForce(oid string, mibName string, oidNum string, oidText string, conversion string) {
|
||||||
|
snmpTranslateCachesLock.Lock()
|
||||||
|
defer snmpTranslateCachesLock.Unlock()
|
||||||
|
if snmpTranslateCaches == nil {
|
||||||
|
snmpTranslateCaches = map[string]snmpTranslateCache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stc snmpTranslateCache
|
||||||
|
stc.mibName = mibName
|
||||||
|
stc.oidNum = oidNum
|
||||||
|
stc.oidText = oidText
|
||||||
|
stc.conversion = conversion
|
||||||
|
stc.err = nil
|
||||||
|
snmpTranslateCaches[oid] = stc
|
||||||
|
}
|
||||||
|
|
||||||
|
func SnmpTranslateClear() {
|
||||||
|
snmpTranslateCachesLock.Lock()
|
||||||
|
defer snmpTranslateCachesLock.Unlock()
|
||||||
|
snmpTranslateCaches = map[string]snmpTranslateCache{}
|
||||||
|
}
|
||||||
|
|
||||||
func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
||||||
var out []byte
|
var out []byte
|
||||||
if strings.ContainsAny(oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
if strings.ContainsAny(oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
||||||
|
|
|
@ -742,7 +742,7 @@ func TestFieldConvert(t *testing.T) {
|
||||||
func TestSnmpTranslateCache_miss(t *testing.T) {
|
func TestSnmpTranslateCache_miss(t *testing.T) {
|
||||||
snmpTranslateCaches = nil
|
snmpTranslateCaches = nil
|
||||||
oid := "IF-MIB::ifPhysAddress.1"
|
oid := "IF-MIB::ifPhysAddress.1"
|
||||||
mibName, oidNum, oidText, conversion, err := snmpTranslate(oid)
|
mibName, oidNum, oidText, conversion, err := SnmpTranslate(oid)
|
||||||
assert.Len(t, snmpTranslateCaches, 1)
|
assert.Len(t, snmpTranslateCaches, 1)
|
||||||
stc := snmpTranslateCaches[oid]
|
stc := snmpTranslateCaches[oid]
|
||||||
require.NotNil(t, stc)
|
require.NotNil(t, stc)
|
||||||
|
@ -763,7 +763,7 @@ func TestSnmpTranslateCache_hit(t *testing.T) {
|
||||||
err: fmt.Errorf("e"),
|
err: fmt.Errorf("e"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mibName, oidNum, oidText, conversion, err := snmpTranslate("foo")
|
mibName, oidNum, oidText, conversion, err := SnmpTranslate("foo")
|
||||||
assert.Equal(t, "a", mibName)
|
assert.Equal(t, "a", mibName)
|
||||||
assert.Equal(t, "b", oidNum)
|
assert.Equal(t, "b", oidNum)
|
||||||
assert.Equal(t, "c", oidText)
|
assert.Equal(t, "c", oidText)
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# SNMP Trap Input Plugin
|
||||||
|
|
||||||
|
The SNMP Trap plugin is a service input plugin that receives SNMP
|
||||||
|
notifications (traps and inform requests).
|
||||||
|
|
||||||
|
Notifications are received on plain UDP. The port to listen is
|
||||||
|
configurable.
|
||||||
|
|
||||||
|
OIDs can be resolved to strings using system MIB files. This is done
|
||||||
|
in same way as the SNMP input plugin. See the section "MIB Lookups" in
|
||||||
|
the SNMP [README.md](../snmp/README.md) for details.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
```toml
|
||||||
|
# Snmp trap listener
|
||||||
|
[[inputs.snmp_trap]]
|
||||||
|
## Transport, local address, and port to listen on. Transport must
|
||||||
|
## be "udp://". Omit local address to listen on all interfaces.
|
||||||
|
## example: "udp://127.0.0.1:1234"
|
||||||
|
# service_address = udp://:162
|
||||||
|
## Timeout running snmptranslate command
|
||||||
|
# timeout = "5s"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metrics
|
||||||
|
|
||||||
|
- 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")
|
||||||
|
- fields:
|
||||||
|
- 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
|
||||||
|
```
|
||||||
|
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 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 sysUpTimeInstance=5803i,snmpTrapEnterprise.0="netSnmpNotificationPrefix" 1574109186555115459
|
||||||
|
```
|
|
@ -0,0 +1,266 @@
|
||||||
|
package snmp_trap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
|
||||||
|
"github.com/soniah/gosnmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultTimeout = internal.Duration{Duration: time.Second * 5}
|
||||||
|
|
||||||
|
type handler func(*gosnmp.SnmpPacket, *net.UDPAddr)
|
||||||
|
type execer func(internal.Duration, string, ...string) ([]byte, error)
|
||||||
|
|
||||||
|
type mibEntry struct {
|
||||||
|
mibName string
|
||||||
|
oidText string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnmpTrap struct {
|
||||||
|
ServiceAddress string `toml:"service_address"`
|
||||||
|
Timeout internal.Duration `toml:"timeout"`
|
||||||
|
|
||||||
|
acc telegraf.Accumulator
|
||||||
|
listener *gosnmp.TrapListener
|
||||||
|
timeFunc func() time.Time
|
||||||
|
errCh chan error
|
||||||
|
|
||||||
|
makeHandlerWrapper func(handler) handler
|
||||||
|
|
||||||
|
Log telegraf.Logger `toml:"-"`
|
||||||
|
|
||||||
|
cacheLock sync.Mutex
|
||||||
|
cache map[string]mibEntry
|
||||||
|
|
||||||
|
execCmd execer
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleConfig = `
|
||||||
|
## Transport, local address, and port to listen on. Transport must
|
||||||
|
## be "udp://". Omit local address to listen on all interfaces.
|
||||||
|
## example: "udp://127.0.0.1:1234"
|
||||||
|
# service_address = udp://:162
|
||||||
|
## Timeout running snmptranslate command
|
||||||
|
# timeout = "5s"
|
||||||
|
`
|
||||||
|
|
||||||
|
func (s *SnmpTrap) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SnmpTrap) Description() string {
|
||||||
|
return "Receive SNMP traps"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SnmpTrap) Gather(_ telegraf.Accumulator) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
inputs.Add("snmp_trap", func() telegraf.Input {
|
||||||
|
return &SnmpTrap{
|
||||||
|
timeFunc: time.Now,
|
||||||
|
ServiceAddress: "udp://:162",
|
||||||
|
Timeout: defaultTimeout,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func realExecCmd(Timeout internal.Duration, arg0 string, args ...string) ([]byte, error) {
|
||||||
|
cmd := exec.Command(arg0, args...)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := internal.RunTimeout(cmd, Timeout.Duration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SnmpTrap) Init() error {
|
||||||
|
s.cache = map[string]mibEntry{}
|
||||||
|
s.execCmd = realExecCmd
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SnmpTrap) Start(acc telegraf.Accumulator) error {
|
||||||
|
s.acc = acc
|
||||||
|
s.listener = gosnmp.NewTrapListener()
|
||||||
|
s.listener.OnNewTrap = makeTrapHandler(s)
|
||||||
|
s.listener.Params = gosnmp.Default
|
||||||
|
|
||||||
|
// wrap the handler, used in unit tests
|
||||||
|
if nil != s.makeHandlerWrapper {
|
||||||
|
s.listener.OnNewTrap = s.makeHandlerWrapper(s.listener.OnNewTrap)
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.SplitN(s.ServiceAddress, "://", 2)
|
||||||
|
if len(split) != 2 {
|
||||||
|
return fmt.Errorf("invalid service address: %s", s.ServiceAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := split[0]
|
||||||
|
addr := split[1]
|
||||||
|
|
||||||
|
// gosnmp.TrapListener currently supports udp only. For forward
|
||||||
|
// compatibility, require udp in the service address
|
||||||
|
if protocol != "udp" {
|
||||||
|
return fmt.Errorf("unknown protocol '%s' in '%s'", protocol, s.ServiceAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If (*TrapListener).Listen immediately returns an error we need
|
||||||
|
// to return it from this function. Use a channel to get it here
|
||||||
|
// from the goroutine. Buffer one in case Listen returns after
|
||||||
|
// Listening but before our Close is called.
|
||||||
|
s.errCh = make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
s.errCh <- s.listener.Listen(addr)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.listener.Listening():
|
||||||
|
s.Log.Infof("Listening on %s", s.ServiceAddress)
|
||||||
|
case err := <-s.errCh:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SnmpTrap) Stop() {
|
||||||
|
s.listener.Close()
|
||||||
|
err := <-s.errCh
|
||||||
|
if nil != err {
|
||||||
|
s.Log.Errorf("Error stopping trap listener %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTrapHandler(s *SnmpTrap) handler {
|
||||||
|
return func(packet *gosnmp.SnmpPacket, addr *net.UDPAddr) {
|
||||||
|
tm := s.timeFunc()
|
||||||
|
fields := map[string]interface{}{}
|
||||||
|
tags := map[string]string{}
|
||||||
|
|
||||||
|
tags["version"] = packet.Version.String()
|
||||||
|
tags["source"] = addr.IP.String()
|
||||||
|
|
||||||
|
for _, v := range packet.Variables {
|
||||||
|
// Use system mibs to resolve oids. Don't fall back to
|
||||||
|
// numeric oid because it's not useful enough to the end
|
||||||
|
// user and can be difficult to translate or remove from
|
||||||
|
// the database later.
|
||||||
|
|
||||||
|
var value interface{}
|
||||||
|
|
||||||
|
// todo: format the pdu value based on its snmp type and
|
||||||
|
// the mib's textual convention. The snmp input plugin
|
||||||
|
// only handles textual convention for ip and mac
|
||||||
|
// addresses
|
||||||
|
|
||||||
|
switch v.Type {
|
||||||
|
case gosnmp.ObjectIdentifier:
|
||||||
|
val, ok := v.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
s.Log.Errorf("Error getting value OID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var e mibEntry
|
||||||
|
var err error
|
||||||
|
e, err = s.lookup(val)
|
||||||
|
if nil != err {
|
||||||
|
s.Log.Errorf("Error resolving value OID: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
if v.Name == ".1.3.6.1.6.3.1.1.4.1.0" {
|
||||||
|
tags["oid"] = val
|
||||||
|
tags["name"] = e.oidText
|
||||||
|
tags["mib"] = e.mibName
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
value = v.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := s.lookup(v.Name)
|
||||||
|
if nil != err {
|
||||||
|
s.Log.Errorf("Error resolving OID: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := e.oidText
|
||||||
|
|
||||||
|
fields[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
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 snmptranlate
|
||||||
|
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))
|
||||||
|
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:]
|
||||||
|
return e, nil
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
package snmp_trap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/soniah/gosnmp"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoad(t *testing.T) {
|
||||||
|
s := &SnmpTrap{}
|
||||||
|
require.Nil(t, s.Init())
|
||||||
|
|
||||||
|
defer s.clear()
|
||||||
|
s.load(
|
||||||
|
".1.3.6.1.6.3.1.1.5.1",
|
||||||
|
mibEntry{
|
||||||
|
"SNMPv2-MIB",
|
||||||
|
"coldStart",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
e, err := s.lookup(".1.3.6.1.6.3.1.1.5.1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "SNMPv2-MIB", e.mibName)
|
||||||
|
require.Equal(t, "coldStart", e.oidText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendTrap(t *testing.T, port uint16) (sentTimestamp uint32) {
|
||||||
|
s := &gosnmp.GoSNMP{
|
||||||
|
Port: port,
|
||||||
|
Community: "public",
|
||||||
|
Version: gosnmp.Version2c,
|
||||||
|
Timeout: time.Duration(2) * time.Second,
|
||||||
|
Retries: 3,
|
||||||
|
MaxOids: gosnmp.MaxOids,
|
||||||
|
Target: "127.0.0.1",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.Connect()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Connect() err: %v", err)
|
||||||
|
}
|
||||||
|
defer s.Conn.Close()
|
||||||
|
|
||||||
|
// If the first pdu isn't type TimeTicks, gosnmp.SendTrap() will
|
||||||
|
// prepend one with time.Now(). The time value is part of the
|
||||||
|
// plugin output so we need to keep track of it and verify it
|
||||||
|
// later.
|
||||||
|
now := uint32(time.Now().Unix())
|
||||||
|
timePdu := gosnmp.SnmpPDU{
|
||||||
|
Name: ".1.3.6.1.2.1.1.3.0",
|
||||||
|
Type: gosnmp.TimeTicks,
|
||||||
|
Value: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
pdu := gosnmp.SnmpPDU{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
trap := gosnmp.SnmpTrap{
|
||||||
|
Variables: []gosnmp.SnmpPDU{
|
||||||
|
timePdu,
|
||||||
|
pdu,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.SendTrap(trap)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("SendTrap() err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return now
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiveTrap(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
|
||||||
|
var fakeTime = time.Now()
|
||||||
|
|
||||||
|
// hook into the trap handler so the test knows when the trap has
|
||||||
|
// been received
|
||||||
|
received := make(chan int)
|
||||||
|
wrap := func(f handler) handler {
|
||||||
|
return func(p *gosnmp.SnmpPacket, a *net.UDPAddr) {
|
||||||
|
f(p, a)
|
||||||
|
received <- 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up the service input plugin
|
||||||
|
s := &SnmpTrap{
|
||||||
|
ServiceAddress: "udp://:" + strconv.Itoa(port),
|
||||||
|
makeHandlerWrapper: wrap,
|
||||||
|
timeFunc: func() time.Time {
|
||||||
|
return fakeTime
|
||||||
|
},
|
||||||
|
Log: testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.Nil(t, s.Init())
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
require.Nil(t, s.Start(&acc))
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
// Preload the cache with the oids we'll use in this test so
|
||||||
|
// snmptranslate and mibs don't need to be installed.
|
||||||
|
defer s.clear()
|
||||||
|
s.load(".1.3.6.1.6.3.1.1.4.1.0",
|
||||||
|
mibEntry{
|
||||||
|
"SNMPv2-MIB",
|
||||||
|
"snmpTrapOID.0",
|
||||||
|
})
|
||||||
|
s.load(".1.3.6.1.6.3.1.1.5.1",
|
||||||
|
mibEntry{
|
||||||
|
"SNMPv2-MIB",
|
||||||
|
"coldStart",
|
||||||
|
})
|
||||||
|
s.load(".1.3.6.1.2.1.1.3.0",
|
||||||
|
mibEntry{
|
||||||
|
"UNUSED_MIB_NAME",
|
||||||
|
"sysUpTimeInstance",
|
||||||
|
})
|
||||||
|
|
||||||
|
// send the trap
|
||||||
|
sentTimestamp := sendTrap(t, port)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
expected := []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",
|
||||||
|
},
|
||||||
|
map[string]interface{}{ // fields
|
||||||
|
"sysUpTimeInstance": sentTimestamp,
|
||||||
|
},
|
||||||
|
fakeTime,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
testutil.RequireMetricsEqual(t,
|
||||||
|
expected, acc.GetTelegrafMetrics(),
|
||||||
|
testutil.SortMetrics())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeExecCmd(_ internal.Duration, _ string, _ ...string) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("intentional failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingOid(t *testing.T) {
|
||||||
|
// should fail even if snmptranslate is installed
|
||||||
|
const port = 12399
|
||||||
|
var fakeTime = time.Now()
|
||||||
|
|
||||||
|
received := make(chan int)
|
||||||
|
wrap := func(f handler) handler {
|
||||||
|
return func(p *gosnmp.SnmpPacket, a *net.UDPAddr) {
|
||||||
|
f(p, a)
|
||||||
|
received <- 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &SnmpTrap{
|
||||||
|
ServiceAddress: "udp://:" + strconv.Itoa(port),
|
||||||
|
makeHandlerWrapper: wrap,
|
||||||
|
timeFunc: func() time.Time {
|
||||||
|
return fakeTime
|
||||||
|
},
|
||||||
|
Log: testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.Nil(t, s.Init())
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
require.Nil(t, s.Start(&acc))
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
// make sure the cache is empty
|
||||||
|
s.clear()
|
||||||
|
|
||||||
|
// don't call the real snmptranslate
|
||||||
|
s.execCmd = fakeExecCmd
|
||||||
|
|
||||||
|
_ = sendTrap(t, port)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-received:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("timed out waiting for trap to be received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// oid lookup should fail so we shouldn't get a metric
|
||||||
|
expected := []telegraf.Metric{}
|
||||||
|
|
||||||
|
testutil.RequireMetricsEqual(t,
|
||||||
|
expected, acc.GetTelegrafMetrics(),
|
||||||
|
testutil.SortMetrics())
|
||||||
|
}
|
Loading…
Reference in New Issue