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>
<p>This notification is generated when an IPsec Phase-1
IKE Tunnel becomes inactive.</p><table>
<tr><td><b>
cikePeerLocalAddr</b></td><td>
%parm[#1]%;</td><td><p></p></td></tr>
<tr><td><b>
cikePeerRemoteAddr</b></td><td>
%parm[#2]%;</td><td><p></p></td></tr>
<tr><td><b>
cikeTunActiveTime</b></td><td>
%parm[#3]%;</td><td><p></p></td></tr></table>
</descr>
<logmsg dest="logndisplay"><p>
cikeTunnelStop trap received
cikePeerLocalAddr=%parm[#1]%
cikePeerRemoteAddr=%parm[#2]%
cikeTunActiveTime=%parm[#3]%</p>
</logmsg>
<severity>Warning</severity>
</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.
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"><p>
cikeTunnelStop trap received
cikePeerLocalAddr=%parm[#1]%
cikePeerRemoteAddr=%parm[#2]%
cikeTunActiveTime=%parm[#3]%</p>
</logmsg>
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.
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
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.