From cae701c54bb805d2e715ae97c97f5752923f51aa Mon Sep 17 00:00:00 2001 From: reimda Date: Wed, 11 Dec 2019 15:29:18 -0700 Subject: [PATCH] Interpret SNMP v1 traps as described in RFC 2576 3.1 (#6793) --- plugins/inputs/snmp_trap/snmp_trap.go | 38 ++++++- plugins/inputs/snmp_trap/snmp_trap_test.go | 112 +++++++++++++++++++++ 2 files changed, 147 insertions(+), 3 deletions(-) diff --git a/plugins/inputs/snmp_trap/snmp_trap.go b/plugins/inputs/snmp_trap/snmp_trap.go index 7163a853e..03f6a3a29 100644 --- a/plugins/inputs/snmp_trap/snmp_trap.go +++ b/plugins/inputs/snmp_trap/snmp_trap.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "os/exec" + "strconv" "strings" "sync" "time" @@ -150,6 +151,12 @@ func (s *SnmpTrap) Stop() { } } +func setTrapOid(tags map[string]string, oid string, e mibEntry) { + tags["oid"] = oid + tags["name"] = e.oidText + tags["mib"] = e.mibName +} + func makeTrapHandler(s *SnmpTrap) handler { return func(packet *gosnmp.SnmpPacket, addr *net.UDPAddr) { tm := s.timeFunc() @@ -159,6 +166,33 @@ func makeTrapHandler(s *SnmpTrap) handler { tags["version"] = packet.Version.String() tags["source"] = addr.IP.String() + if packet.Version == gosnmp.Version1 { + // Follow the procedure described in RFC 2576 3.1 to + // translate a v1 trap to v2. + var trapOid string + + if packet.GenericTrap > 0 && packet.GenericTrap < 6 { + trapOid = "1.3.6.1.6.3.1.1.5." + strconv.Itoa(packet.GenericTrap+1) + } else if packet.GenericTrap == 6 { + trapOid = packet.Enterprise + ".0." + strconv.Itoa(packet.SpecificTrap) + } + + if trapOid != "" { + e, err := s.lookup(trapOid) + if err != nil { + s.Log.Errorf("Error resolving V1 OID: %v", err) + return + } + setTrapOid(tags, trapOid, e) + } + + if packet.AgentAddress != "" { + tags["agent_address"] = packet.AgentAddress + } + + fields["sysUpTimeInstance"] = packet.Timestamp + } + 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 @@ -193,9 +227,7 @@ func makeTrapHandler(s *SnmpTrap) handler { // 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 + setTrapOid(tags, val, e) continue } default: diff --git a/plugins/inputs/snmp_trap/snmp_trap_test.go b/plugins/inputs/snmp_trap/snmp_trap_test.go index ed31786d8..68121b0c8 100644 --- a/plugins/inputs/snmp_trap/snmp_trap_test.go +++ b/plugins/inputs/snmp_trap/snmp_trap_test.go @@ -220,3 +220,115 @@ func TestMissingOid(t *testing.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()) + +}