package snmp_trap import ( "errors" "fmt" "strconv" "strings" "testing" "time" "github.com/gosnmp/gosnmp" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/internal/snmp" "github.com/influxdata/telegraf/metric" "github.com/influxdata/telegraf/testutil" ) func TestReceiveTrapV1(t *testing.T) { now := uint32(123123123) // If the first pdu isn't type TimeTicks, gosnmp.SendTrap() will // prepend one with time.Now() var tests = []struct { name string // send trap gosnmp.SnmpTrap // include pdus // receive entries []entry expected []telegraf.Metric }{ { name: "trap enterprise", trap: gosnmp.SnmpTrap{ Variables: []gosnmp.SnmpPDU{ { Name: ".1.2.3.4.5", Type: gosnmp.OctetString, Value: "payload", }, }, Enterprise: ".1.2.3", AgentAddress: "10.20.30.40", GenericTrap: 6, // enterpriseSpecific SpecificTrap: 55, Timestamp: uint(now), }, entries: []entry{ { ".1.2.3.4.5", snmp.MibEntry{ MibName: "valueMIB", OidText: "valueOID", }, }, { ".1.2.3.0.55", snmp.MibEntry{ MibName: "enterpriseMIB", OidText: "enterpriseOID", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", map[string]string{ "oid": ".1.2.3.0.55", "name": "enterpriseOID", "mib": "enterpriseMIB", "version": "1", "source": "127.0.0.1", "agent_address": "10.20.30.40", "community": "public", }, map[string]interface{}{ "sysUpTimeInstance": uint(now), "valueOID": "payload", }, time.Unix(0, 0), ), }, }, // v1 generic trap { name: "trap generic", trap: gosnmp.SnmpTrap{ Variables: []gosnmp.SnmpPDU{ { Name: ".1.2.3.4.5", Type: gosnmp.OctetString, Value: "payload", }, { Name: ".1.2.3.4.6", Type: gosnmp.OctetString, Value: []byte{0x7, 0xe8, 0x1, 0x4, 0xe, 0x2, 0x19, 0x0, 0x0, 0xe, 0x2}, }, }, Enterprise: ".1.2.3", AgentAddress: "10.20.30.40", GenericTrap: 0, // coldStart SpecificTrap: 0, Timestamp: uint(now), }, entries: []entry{ { ".1.2.3.4.5", snmp.MibEntry{ MibName: "valueMIB", OidText: "valueOID", }, }, { ".1.2.3.4.6", snmp.MibEntry{ MibName: "valueMIB", OidText: "valueHexOID", }, }, { ".1.3.6.1.6.3.1.1.5.1", snmp.MibEntry{ MibName: "coldStartMIB", OidText: "coldStartOID", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", map[string]string{ "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStartOID", "mib": "coldStartMIB", "version": "1", "source": "127.0.0.1", "agent_address": "10.20.30.40", "community": "public", }, map[string]interface{}{ "sysUpTimeInstance": uint(now), "valueOID": "payload", "valueHexOID": "07e801040e021900000e02", }, time.Unix(0, 0), ), }, }, } for _, tt := range tests { t.Run(tt.name, func(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 // Set up the service input plugin plugin := &SnmpTrap{ ServiceAddress: "udp://:" + strconv.Itoa(port), Version: "1", Log: testutil.Logger{}, transl: &testTranslator{entries: tt.entries}, } require.NoError(t, plugin.Init()) // Start the plugin var acc testutil.Accumulator require.NoError(t, plugin.Start(&acc)) defer plugin.Stop() // Create a v1 client and send the trap client := &gosnmp.GoSNMP{ Port: port, Version: gosnmp.Version1, Timeout: 2 * time.Second, Retries: 1, MaxOids: gosnmp.MaxOids, Target: "127.0.0.1", Community: "public", } require.NoError(t, client.Connect(), "connecting failed") defer client.Conn.Close() _, err := client.SendTrap(tt.trap) require.NoError(t, err, "sending failed") require.NoError(t, client.Conn.Close(), "closing failed") // Wait for trap to be received require.Eventually(t, func() bool { return acc.NMetrics() >= uint64(len(tt.expected)) }, 3*time.Second, 100*time.Millisecond, "timed out waiting for trap to be received") // Verify plugin output testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) }) } } func TestReceiveTrapV2c(t *testing.T) { now := uint32(123123123) // If the first pdu isn't type TimeTicks, gosnmp.SendTrap() will // prepend one with time.Now() var tests = []struct { name string // send trap gosnmp.SnmpTrap // include pdus // receive entries []entry expected []telegraf.Metric }{ // ordinary v2c coldStart trap { name: "v2c coldStart", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "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, }, time.Unix(0, 0), ), }, }, } for _, tt := range tests { t.Run(tt.name, func(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 // Set up the service input plugin plugin := &SnmpTrap{ ServiceAddress: "udp://:" + strconv.Itoa(port), Version: "2c", Log: testutil.Logger{}, transl: &testTranslator{entries: tt.entries}, } require.NoError(t, plugin.Init()) var acc testutil.Accumulator require.NoError(t, plugin.Start(&acc)) defer plugin.Stop() // Create a v1 client and send the trap client := &gosnmp.GoSNMP{ Port: port, Version: gosnmp.Version2c, Timeout: 2 * time.Second, Retries: 1, MaxOids: gosnmp.MaxOids, Target: "127.0.0.1", Community: "public", } require.NoError(t, client.Connect(), "connecting failed") defer client.Conn.Close() _, err := client.SendTrap(tt.trap) require.NoError(t, err, "sending failed") require.NoError(t, client.Conn.Close(), "closing failed") // Wait for trap to be received require.Eventually(t, func() bool { return acc.NMetrics() >= uint64(len(tt.expected)) }, 3*time.Second, 100*time.Millisecond, "timed out waiting for trap to be received") // Verify plugin output testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) }) } } func TestReceiveTrapV3(t *testing.T) { now := uint32(123123123) // If the first pdu isn't type TimeTicks, gosnmp.SendTrap() will // prepend one with time.Now() var tests = []struct { name string // send trap gosnmp.SnmpTrap // include pdus // auth and priv parameters secName string // v3 username secLevel string // v3 security level authProto string // Auth protocol: "", MD5 or SHA authPass string // Auth passphrase privProto string // Priv protocol: "", DES or AES privPass string // Priv passphrase // sender context contextName string engineID string // receive entries []entry expected []telegraf.Metric }{ // ordinary v3 coldStart trap no auth and no priv { name: "noAuthNoPriv", secName: "peter", secLevel: "noAuthNoPriv", contextName: "foo_context_name", engineID: "bar_engine_id", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", "context_name": "foo_context_name", "engine_id": "6261725f656e67696e655f6964", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldstart trap SHA auth and no priv { name: "authNoPriv SHA", secName: "peter", secLevel: "authNoPriv", authProto: "SHA", authPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldstart trap SHA224 auth and no priv { name: "authNoPriv SHA224", secName: "peter", secLevel: "authNoPriv", authProto: "SHA224", authPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldstart trap SHA256 auth and no priv { name: "authNoPriv SHA256", secName: "peter", secLevel: "authNoPriv", authProto: "SHA256", authPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldstart trap SHA384 auth and no priv { name: "authNoPriv SHA384", secName: "peter", secLevel: "authNoPriv", authProto: "SHA384", authPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldstart trap SHA512 auth and no priv { name: "authNoPriv SHA512", secName: "peter", secLevel: "authNoPriv", authProto: "SHA512", authPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldstart trap MD5 auth and no priv { name: "authNoPriv MD5", secName: "peter", secLevel: "authNoPriv", authProto: "MD5", authPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldStart SHA trap auth and AES priv { name: "authPriv SHA-AES", secName: "peter", secLevel: "authPriv", authProto: "SHA", authPass: "passpass", privProto: "AES", privPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldStart SHA trap auth and DES priv { name: "authPriv SHA-DES", secName: "peter", secLevel: "authPriv", authProto: "SHA", authPass: "passpass", privProto: "DES", privPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldStart SHA trap auth and AES192 priv { name: "authPriv SHA-AES192", secName: "peter", secLevel: "authPriv", authProto: "SHA", authPass: "passpass", privProto: "AES192", privPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldStart SHA trap auth and AES192C priv { name: "authPriv SHA-AES192C", secName: "peter", secLevel: "authPriv", authProto: "SHA", authPass: "passpass", privProto: "AES192C", privPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldStart SHA trap auth and AES256 priv { name: "authPriv SHA-AES256", secName: "peter", secLevel: "authPriv", authProto: "SHA", authPass: "passpass", privProto: "AES256", privPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, // ordinary v3 coldStart SHA trap auth and AES256C priv { name: "authPriv SHA-AES256C", secName: "peter", secLevel: "authPriv", authProto: "SHA", authPass: "passpass", privProto: "AES256C", privPass: "passpass", 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 }, }, }, entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, expected: []telegraf.Metric{ metric.New( "snmp_trap", // name map[string]string{ // tags "oid": ".1.3.6.1.6.3.1.1.5.1", "name": "coldStart", "mib": "SNMPv2-MIB", "version": "3", "source": "127.0.0.1", }, map[string]interface{}{ // fields "sysUpTimeInstance": now, }, time.Unix(0, 0), ), }, }, } for _, tt := range tests { t.Run(tt.name, func(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 // Set up the service input plugin plugin := &SnmpTrap{ ServiceAddress: "udp://:" + strconv.Itoa(port), Version: "3", SecName: config.NewSecret([]byte(tt.secName)), SecLevel: tt.secLevel, AuthProtocol: tt.authProto, AuthPassword: config.NewSecret([]byte(tt.authPass)), PrivProtocol: tt.privProto, PrivPassword: config.NewSecret([]byte(tt.privPass)), Log: testutil.Logger{}, transl: &testTranslator{entries: tt.entries}, } require.NoError(t, plugin.Init()) var acc testutil.Accumulator require.NoError(t, plugin.Start(&acc)) defer plugin.Stop() // Create a v3 client and send the trap var msgFlags gosnmp.SnmpV3MsgFlags switch strings.ToLower(tt.secLevel) { case "noauthnopriv", "": msgFlags = gosnmp.NoAuthNoPriv case "authnopriv": msgFlags = gosnmp.AuthNoPriv case "authpriv": msgFlags = gosnmp.AuthPriv default: require.FailNowf(t, "unknown security level %q", tt.secLevel) } security := createSecurityParameters(tt.authProto, tt.privProto, tt.secName, tt.privPass, tt.authPass) client := &gosnmp.GoSNMP{ Port: port, Version: gosnmp.Version3, Timeout: 2 * time.Second, Retries: 1, MaxOids: gosnmp.MaxOids, Target: "127.0.0.1", SecurityParameters: security, SecurityModel: gosnmp.UserSecurityModel, MsgFlags: msgFlags, ContextName: tt.contextName, ContextEngineID: tt.engineID, } require.NoError(t, client.Connect(), "connecting failed") defer client.Conn.Close() _, err := client.SendTrap(tt.trap) require.NoError(t, err, "sending failed") require.NoError(t, client.Conn.Close(), "closing failed") // Wait for trap to be received require.Eventually(t, func() bool { return acc.NMetrics() >= uint64(len(tt.expected)) }, 3*time.Second, 100*time.Millisecond, "timed out waiting for trap to be received") // Verify plugin output testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) }) } } func TestOidLookupFail(t *testing.T) { now := uint32(123123123) // Check that we're not running snmptranslate to look up oids // when we shouldn't. This sends and receives a valid trap // but metric production should fail because the oids aren't in // the cache and oid lookup is intentionally mocked to fail. 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 }, }, } // 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 // Set up the service input plugin logger := &testutil.CaptureLogger{} fail := make(chan bool, 1) plugin := &SnmpTrap{ ServiceAddress: "udp://:" + strconv.Itoa(port), Version: "2c", Log: logger, transl: &testTranslator{fail: fail}, } require.NoError(t, plugin.Init()) var acc testutil.Accumulator require.NoError(t, plugin.Start(&acc)) defer plugin.Stop() // Create a v1 client and send the trap client := &gosnmp.GoSNMP{ Port: port, Version: gosnmp.Version2c, Timeout: 2 * time.Second, Retries: 1, MaxOids: gosnmp.MaxOids, Target: "127.0.0.1", Community: "public", } require.NoError(t, client.Connect(), "connecting failed") defer client.Conn.Close() _, err := client.SendTrap(trap) require.NoError(t, err, "sending failed") require.NoError(t, client.Conn.Close(), "closing failed") // Wait for lookup to fail select { case <-fail: case <-time.After(time.Second): t.Log("timeout waiting for failing OID lookup") t.Fail() } // Verify plugin output require.Empty(t, acc.GetTelegrafMetrics()) var found bool for _, msg := range logger.Errors() { if found = strings.Contains(msg, "unexpected oid"); found { break } } require.True(t, found, "did not receive expected error message") } func TestInvalidAuth(t *testing.T) { now := uint32(time.Now().Unix()) 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 }, }, } translator := &testTranslator{entries: []entry{ { oid: ".1.3.6.1.6.3.1.1.4.1.0", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "snmpTrapOID.0", }, }, { oid: ".1.3.6.1.6.3.1.1.5.1", e: snmp.MibEntry{ MibName: "SNMPv2-MIB", OidText: "coldStart", }, }, { oid: ".1.3.6.1.2.1.1.3.0", e: snmp.MibEntry{ MibName: "UNUSED_MIB_NAME", OidText: "sysUpTimeInstance", }, }, }, } // If the first pdu isn't type TimeTicks, gosnmp.SendTrap() will // prepend one with time.Now() var tests = []struct { name string user string // v3 username secLevel string // v3 security level authProto string // Auth protocol: "", MD5 or SHA authPass string // Auth passphrase privProto string // Priv protocol: "", DES or AES privPass string // Priv passphrase expected string }{ { name: "no authentication", user: "franz", secLevel: "NoAuthNoPriv", }, { name: "wrong username", user: "foo", secLevel: "authPriv", authProto: "sha", authPass: "what a nice day", privProto: "aes", privPass: "for my privacy", }, { name: "wrong password", user: "franz", secLevel: "authPriv", authProto: "sha", authPass: "passpass", privProto: "aes", privPass: "for my privacy", }, { name: "wrong auth protocol", user: "franz", secLevel: "authPriv", authProto: "md5", authPass: "what a nice day", privProto: "aes", privPass: "for my privacy", }, } for _, tt := range tests { t.Run(tt.name, func(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 // Set up the service input plugin plugin := &SnmpTrap{ ServiceAddress: "udp://:" + strconv.Itoa(port), Version: "3", SecName: config.NewSecret([]byte("franz")), SecLevel: "authPriv", AuthProtocol: "sha", AuthPassword: config.NewSecret([]byte("what a nice day")), PrivProtocol: "aes", PrivPassword: config.NewSecret([]byte("for my privacy")), Log: testutil.Logger{}, transl: translator, } require.NoError(t, plugin.Init()) // Inject special logger found := make(chan bool, 1) plugin.listener.Params.Logger = gosnmp.NewLogger( &testLogger{matcher: "incoming packet is not authentic", out: found}, ) var acc testutil.Accumulator require.NoError(t, plugin.Start(&acc)) defer plugin.Stop() // Create a v3 client and send the trap var msgFlags gosnmp.SnmpV3MsgFlags switch strings.ToLower(tt.secLevel) { case "noauthnopriv", "": msgFlags = gosnmp.NoAuthNoPriv case "authnopriv": msgFlags = gosnmp.AuthNoPriv case "authpriv": msgFlags = gosnmp.AuthPriv default: require.FailNowf(t, "unknown security level %q", tt.secLevel) } security := createSecurityParameters(tt.authProto, tt.privProto, tt.user, tt.privPass, tt.authPass) client := &gosnmp.GoSNMP{ Port: port, Version: gosnmp.Version3, Timeout: 2 * time.Second, Retries: 1, MaxOids: gosnmp.MaxOids, Target: "127.0.0.1", SecurityParameters: security, SecurityModel: gosnmp.UserSecurityModel, MsgFlags: msgFlags, ContextName: "context-name", ContextEngineID: "engine no 5", } require.NoError(t, client.Connect(), "connecting failed") defer client.Conn.Close() _, err := client.SendTrap(trap) require.NoError(t, err) require.NoError(t, client.Conn.Close(), "closing failed") // Wait for error to be logged select { case <-found: case <-time.After(3 * time.Second): t.Fatal("timed out waiting for unauthenticated log") } // Verify plugin output require.Empty(t, acc.GetTelegrafMetrics()) }) } } func createSecurityParameters(authProto, privProto, username, privPass, authPass string) *gosnmp.UsmSecurityParameters { var authenticationProtocol gosnmp.SnmpV3AuthProtocol switch strings.ToLower(authProto) { case "md5": authenticationProtocol = gosnmp.MD5 case "sha": authenticationProtocol = gosnmp.SHA case "sha224": authenticationProtocol = gosnmp.SHA224 case "sha256": authenticationProtocol = gosnmp.SHA256 case "sha384": authenticationProtocol = gosnmp.SHA384 case "sha512": authenticationProtocol = gosnmp.SHA512 case "": authenticationProtocol = gosnmp.NoAuth default: authenticationProtocol = gosnmp.NoAuth } var privacyProtocol gosnmp.SnmpV3PrivProtocol switch strings.ToLower(privProto) { case "aes": privacyProtocol = gosnmp.AES case "des": privacyProtocol = gosnmp.DES case "aes192": privacyProtocol = gosnmp.AES192 case "aes192c": privacyProtocol = gosnmp.AES192C case "aes256": privacyProtocol = gosnmp.AES256 case "aes256c": privacyProtocol = gosnmp.AES256C case "": privacyProtocol = gosnmp.NoPriv default: privacyProtocol = gosnmp.NoPriv } return &gosnmp.UsmSecurityParameters{ AuthoritativeEngineID: "deadbeef", // has to be between 5 & 32 chars AuthoritativeEngineBoots: 1, AuthoritativeEngineTime: 1, UserName: username, PrivacyProtocol: privacyProtocol, PrivacyPassphrase: privPass, AuthenticationPassphrase: authPass, AuthenticationProtocol: authenticationProtocol, } } type entry struct { oid string e snmp.MibEntry } type testTranslator struct { entries []entry fail chan bool } 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 } } if t.fail != nil { t.fail <- true } return snmp.MibEntry{}, errors.New("unexpected oid") } type testLogger struct { matcher string out chan bool } func (l *testLogger) Print(v ...interface{}) { if strings.Contains(fmt.Sprint(v...), l.matcher) { l.out <- true } } func (l *testLogger) Printf(format string, v ...interface{}) { if strings.Contains(fmt.Sprintf(format, v...), l.matcher) { l.out <- true } }