1
0
Fork 0
telegraf/plugins/inputs/snmp_trap/snmp_trap_test.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

1574 lines
38 KiB
Go

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
}
}