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()) } func sendV1Trap(t *testing.T, port uint16) (sentTimestamp uint) { s := &gosnmp.GoSNMP{ Port: port, Community: "public", Version: gosnmp.Version1, Timeout: time.Duration(2) * time.Second, Retries: 3, MaxOids: gosnmp.MaxOids, Target: "127.0.0.1", } err := s.Connect() if err != nil { t.Fatalf("Connect() err: %v", err) } defer s.Conn.Close() now := uint(time.Now().Unix()) pdu := gosnmp.SnmpPDU{ Name: ".1.2.3.4.5", Type: gosnmp.OctetString, Value: "payload", } trap := gosnmp.SnmpTrap{ Variables: []gosnmp.SnmpPDU{pdu}, Enterprise: ".1.2.3", AgentAddress: "10.20.30.40", GenericTrap: 6, // enterpriseSpecific SpecificTrap: 55, Timestamp: now, } _, err = s.SendTrap(trap) if err != nil { t.Fatalf("SendTrap() err: %v", err) } return now } func TestReceiveV1Trap(t *testing.T) { 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() defer s.clear() s.load(".1.2.3.4.5", mibEntry{ "valueMIB", "valueOID", }) s.load(".1.2.3.0.55", mibEntry{ "enterpriseMIB", "enterpriseOID", }) sentTimestamp := sendV1Trap(t, port) select { case <-received: case <-time.After(2 * time.Second): t.Fatal("timed out waiting for trap to be received") } expected := []telegraf.Metric{ testutil.MustMetric( "snmp_trap", // name map[string]string{ // tags "oid": ".1.2.3.0.55", "name": "enterpriseOID", "mib": "enterpriseMIB", "version": "1", "source": "127.0.0.1", "agent_address": "10.20.30.40", }, map[string]interface{}{ // fields "sysUpTimeInstance": sentTimestamp, "valueOID": "payload", }, fakeTime, ), } testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.SortMetrics()) }