Try   HackMD

How to translate IP Addresses from SNMP Traps in OpenNMS

Some SNMP Traps codify IP Addresses as hex strings (or OCTET-STRING) as part of their varbinds; for instance, BGP Traps or CIKE Traps.

Unfortunately, by default, Trapd in OpenNMS won't parse or decode them and store the content as it is. Because of this, when you render the details of the events associated with those traps in the WebUI, the IP addresses appear as unreadable content.

One way to deal with this situation is by writing some custom code with Scriptd to process the hex content properly, transform it to its text form using SNMP4J (the library OpenNMS uses to handle SNMP), to then create an enhanced version of the event (and discarding the original one).

To explain a working use case with an example, we're going to use the cikeTunnelStop trap from CISCO-IPSEC-FLOW-MONITOR-MIB. The event is not part of OpenNMS by default and requires compiling the MIB.

Here is partial content of the MIB with the definition of that trap:

IPSIpAddress ::= TEXTUAL-CONVENTION
    STATUS          current
    DESCRIPTION
        "An IP V4 or V6 Address."
    SYNTAX          OCTET STRING (SIZE (4  |  16))

cikePeerLocalAddr OBJECT-TYPE
    SYNTAX          IPSIpAddress
    MAX-ACCESS      read-only
    STATUS          current
    DESCRIPTION
        "The IP address of the local peer."
    ::= { cikePeerEntry 6 }

cikePeerRemoteAddr OBJECT-TYPE
    SYNTAX          IPSIpAddress
    MAX-ACCESS      read-only
    STATUS          current
    DESCRIPTION
        "The IP address of the remote peer."
    ::= { cikePeerEntry 7 }

cikeTunnelStop NOTIFICATION-TYPE
    OBJECTS         {
                        cikePeerLocalAddr,
                        cikePeerRemoteAddr,
                        cikeTunActiveTime
                    }
    STATUS          current
    DESCRIPTION
        "This notification is generated when an IPsec Phase-1
        IKE Tunnel becomes inactive."
   ::= { cipSecMIBNotifications 2 }

Here is the corresponding event definition after compiling the MIB in OpenNMS:

<event> <mask> <maskelement> <mename>id</mename> <mevalue>.1.3.6.1.4.1.9.9.171.2</mevalue> </maskelement> <maskelement> <mename>generic</mename> <mevalue>6</mevalue> </maskelement> <maskelement> <mename>specific</mename> <mevalue>2</mevalue> </maskelement> </mask> <uei>uei.opennms.org/traps/CISCO-IPSEC-FLOW-MONITOR-MIB/cikeTunnelStop</uei> <event-label>CISCO-IPSEC-FLOW-MONITOR-MIB defined trap event: cikeTunnelStop</event-label> <descr> &lt;p>This notification is generated when an IPsec Phase-1 IKE Tunnel becomes inactive.&lt;/p>&lt;table> &lt;tr>&lt;td>&lt;b> cikePeerLocalAddr&lt;/b>&lt;/td>&lt;td> %parm[#1]%;&lt;/td>&lt;td>&lt;p>&lt;/p>&lt;/td>&lt;/tr> &lt;tr>&lt;td>&lt;b> cikePeerRemoteAddr&lt;/b>&lt;/td>&lt;td> %parm[#2]%;&lt;/td>&lt;td>&lt;p>&lt;/p>&lt;/td>&lt;/tr> &lt;tr>&lt;td>&lt;b> cikeTunActiveTime&lt;/b>&lt;/td>&lt;td> %parm[#3]%;&lt;/td>&lt;td>&lt;p>&lt;/p>&lt;/td>&lt;/tr>&lt;/table> </descr> <logmsg dest="logndisplay">&lt;p> cikeTunnelStop trap received cikePeerLocalAddr=%parm[#1]% cikePeerRemoteAddr=%parm[#2]% cikeTunActiveTime=%parm[#3]%&lt;/p> </logmsg> <severity>Warning</severity> </event>

Create a clone definition of the original event.

As the objective is to create an enhanced version of the event, we need a copy of it that holds the new content we're planning to inject:

<event> <uei>uei.opennms.org/traps/CISCO-IPSEC-FLOW-MONITOR-MIB/enhanced/cikeTunnelStop</uei> <event-label>CISCO-IPSEC-FLOW-MONITOR-MIB defined trap event: cikeTunnelStop (ENHANCED)</event-label> <descr>This notification is generated when an IPsec Phase-1 IKE Tunnel becomes inactive. cikePeerLocalAddr: %parm[cikePeerLocalAddr]% cikePeerRemoteAddr: %parm[cikePeerRemoteAddr]% </descr> <logmsg dest="logndisplay">cikeTunnelStop trap received cikePeerLocalAddr=%parm[cikePeerLocalAddr]% cikePeerRemoteAddr=%parm[cikePeerRemoteAddr]% </logmsg> <severity>Warning</severity> </event>

The above is just an example, but as you can see it represents the enhanced or translated version of uei.opennms.org/traps/CISCO-IPSEC-FLOW-MONITOR-MIB/cikeTunnelStop with a different UEI that uses the new translated parameters.

The new definition can live on a new events file or the same file where the original event lives.

Make sure the copy DOES NOT contain the mask element from the original definition.

Mark the original event with "donotpersist"

The following is required to avoid duplicates and because the original event is not required anymore.

Find the file on which the original cikeTunnelStop is defined and make sure the logmsg looks like this:

<logmsg dest="donotpersist">&lt;p> cikeTunnelStop trap received cikePeerLocalAddr=%parm[#1]% cikePeerRemoteAddr=%parm[#2]% cikeTunActiveTime=%parm[#3]%&lt;/p> </logmsg>

Configure Scriptd

If you have content on scriptd-configuration.xml you would have to merge the following with the existing content so both can coexist.

<scriptd-configuration xmlns="http://xmlns.opennms.org/xsd/config/scriptd"> <engine language="beanshell" className="bsh.util.BeanShellBSFEngine" extensions="bsh"/> <start-script language="beanshell"> server(12345); log = bsf.lookupBean("log"); source("/opt/opennms/etc/scriptd-event-translator.bsh"); </start-script> <event-script language="beanshell"> event = bsf.lookupBean("event"); if (event.snmp != null) { translateTrap(event); } </event-script> </scriptd-configuration>

The line server(12345); is optional, but it can be handy if the external script has to be changed to avoid restart OpenNMS. When there is a need to update the content of the external script, you can do:

telnet localhost 12346

Note the port ends on 6 not 5. Then execute:

source("/opt/opennms/etc/scriptd-event-translator.bsh");

To exit, type Ctrl+] and hit enter, then type quit and hit enter; for example:

[root@horizon24 ~]# telnet localhost 12346 Trying ::1... Connected to localhost. Escape character is '^]'. BeanShell 1.3.0 - by Pat Niemeyer (pat@pat.net) bsh % source("/opt/opennms/etc/scriptd-event-translator.bsh"); bsh % ^] telnet> quit Connection closed.

Do not type quit directly as that will end the BeanShell session, and you would have to restart OpenNMS.

The server entry is only for development and testing purposes as it represents a security hole; do not apply this in production. Its purpose is valid only when developing the content of the external script.

Create the external BeanShell script with the desired logic

For Horizon 25 (Meridian 2019) or older versions of OpenNMS:

import org.opennms.core.spring.BeanUtils; import org.opennms.netmgt.xml.event.Event; import org.opennms.netmgt.model.events.EventBuilder; import org.opennms.netmgt.events.api.EventConstants; import org.opennms.netmgt.events.api.EventForwarder; import org.snmp4j.smi.IpAddress; import org.snmp4j.smi.OctetString; log.info("Initializing custom Trap translator."); eventForwarder = BeanUtils.getBean("daemonContext", "eventForwarder", EventForwarder.class); String hexStringToIP(String hexIpString) { octetString = OctetString.fromHexStringPairs(hexIpString.substring(2)); snmpAddr = new IpAddress(octetString.getValue()); return snmpAddr.toString(); } void translateTrap(event) { try { if (event.uei.equals("uei.opennms.org/traps/CISCO-IPSEC-FLOW-MONITOR-MIB/cikeTunnelStop")) { cikePeerLocalAddr = EventConstants.getValueAsString(event.parmCollection.get(0).value); cikePeerRemoteAddr = EventConstants.getValueAsString(event.parmCollection.get(1).value); cikeTunActiveTime = EventConstants.getValueAsString(event.parmCollection.get(2).value); target = new EventBuilder("uei.opennms.org/traps/CISCO-IPSEC-FLOW-MONITOR-MIB/enhanced/cikeTunnelStop", "Scriptd", new Date()) .setNodeid(event.nodeid) .setInterface(event.interfaceAddress) .setService(event.service) .addParam("cikePeerLocalAddr", hexStringToIP(cikePeerLocalAddr)) .addParam("cikePeerRemoteAddr", hexStringToIP(cikePeerRemoteAddr)) .addParam("cikeTunActiveTime", cikeTunActiveTime) .getEvent(); log.debug("Sending event {}", target.toStringSimple()); eventForwarder.sendNow(target); } else { log.debug("Ignoring event {}", event.toStringSimple()); } } catch (e) { log.error("Cannot translate event", e); } }

For Horizon 26 (Meridian 2020) or newer versions of OpenNMS due to NMS-10720:

import org.opennms.core.spring.BeanUtils; import org.opennms.netmgt.xml.event.Parm; import org.opennms.netmgt.events.api.model.IEvent; import org.opennms.netmgt.events.api.model.IValue; import org.opennms.netmgt.model.events.EventBuilder; import org.opennms.netmgt.events.api.EventConstants; import org.opennms.netmgt.events.api.EventForwarder; import org.snmp4j.smi.IpAddress; import org.snmp4j.smi.OctetString; log.info("Initializing custom Trap translator."); eventForwarder = BeanUtils.getBean("daemonContext", "eventForwarder", EventForwarder.class); String hexStringToIP(String hexIpString) { octetString = OctetString.fromHexStringPairs(hexIpString.substring(2)); snmpAddr = new IpAddress(octetString.getValue()); return snmpAddr.toString(); } String getValueAsString(IParm p) { param = Parm.copyFrom(p); return EventConstants.getValueAsString(param.value); } void translateTrap(IEvent event) { try { if (event.uei.equals("uei.opennms.org/traps/CISCO-IPSEC-FLOW-MONITOR-MIB/cikeTunnelStop")) { cikePeerLocalAddr = getValueAsString(event.parmCollection.get(0)); cikePeerRemoteAddr = getValueAsString(event.parmCollection.get(1)); cikeTunActiveTime = getValueAsString(event.parmCollection.get(2)); target = new EventBuilder("uei.opennms.org/traps/CISCO-IPSEC-FLOW-MONITOR-MIB/enhanced/cikeTunnelStop", "Scriptd", new Date()) .setNodeid(event.nodeid) .setInterface(event.interfaceAddress) .setService(event.service) .addParam("cikePeerLocalAddr", hexStringToIP(cikePeerLocalAddr)) .addParam("cikePeerRemoteAddr", hexStringToIP(cikePeerRemoteAddr)) .addParam("cikeTunActiveTime", cikeTunActiveTime) .getEvent(); log.debug("Sending event {}", target.toStringSimple()); eventForwarder.sendNow(target); } else { log.debug("Ignoring event {}", event.toStringSimple()); } } catch (e) { log.error("Cannot translate event", e); } }

Once you're done with the changes, restart OpenNMS

Testing

The following can be used to emulate emulate a cikeTunnelStop trap from CISCO-IPSEC-FLOW-MONITOR-MIB from the command line using the snmptrap command from the Net-SNMP project:

snmptrap -v 1 -c public 127.0.0.1:162 .1.3.6.1.4.1.9.9.171.2 10.0.0.1 6 2 0 \ .1.3.6.1.4.1.9.9.171.1.2.2.1.6 x "d047b002" \ .1.3.6.1.4.1.9.9.171.1.2.2.1.7 x "d047b002" \ .1.3.6.1.4.1.9.9.171.1.2.3.1.16 i 10

For testing purposes, I'm using the same IP on both Local and Remote addresses.

The command will send the trap to 127.0.0.1:162 (where UDP 162 is the default port for SNMP Traps, so make sure to adjust it if Trapd is listening to a different port or you're sending the trap via Minion). As this is an SNMPv1, it would use 10.0.0.1 as the "trap sender,"; meaning OpenNMS or Minion would believe it was 10.0.0.1, not the machine that sent the command, the originator of the trap.

You can use the WebUI or the ReST API to retrieve the enhanced or translated version of the event to verify that the IP addresses are processed correctly.

Even if the term translate has been used, do not confuse this procedure with the OpenNMS Event Translator functionality. That feature cannot use arbitrary code, and it is intended to use SQL queries or simple conversions to create an enhanced version of the events, which is why Scriptd is involved.