# 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: ```xml= <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: ```xml= <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. :::warning 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: ```xml= <logmsg dest="donotpersist">&lt;p> cikeTunnelStop trap received cikePeerLocalAddr=%parm[#1]% cikePeerRemoteAddr=%parm[#2]% cikeTunActiveTime=%parm[#3]%&lt;/p> </logmsg> ``` ## Configure Scriptd :::warning If you have content on `scriptd-configuration.xml` you would have to merge the following with the existing content so both can coexist. ::: ```xml= <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: ```bash= telnet localhost 12346 ``` Note the port ends on 6 not 5. Then execute: ```bash= source("/opt/opennms/etc/scriptd-event-translator.bsh"); ``` To exit, type `Ctrl+]` and hit enter, then type quit and hit enter; for example: ```bash= [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. :::danger 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: ```java= 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](https://issues.opennms.org/browse/NMS-10720): ```java= 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); } } ``` :::danger 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: ```bash= 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. :::danger 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. :::