SNMP Binding doesn't properly handle some forwarded traps

Hi,

I believe I’ve identified a defect in the way the OpenHAB 2.x SNMP binding handles forwarded traps.

First, I have an APC SUA2200RM2U with the network management card (AP9617, software version v3.7.3), configured to allow SNMP v1 access and to send trap notifications to my server running OpenHAB and supporting services. The IP address of the UPS management card is 10.0.1.30 and the address of the OpenHAB server is 10.0.20.20

  • OS: Ubuntu 20.04.1 LTS x64 (in LXC container on Proxmox 6.2-4)
  • OpenHAB: 2.5.7-1 from official apt repo
  • snmptrapd: 5.8+dfsg-2ubuntu2.3

Things:

Thing snmp:target:ups-rack-01b "Rack UPS 01b" [ hostname="ups-rack-01.davecorder.org", community="public", protocol="v1" ]
{
    Channels:
        Type string : TrapTest01 [ oid=".1.3.6.1.4.1.318.0.5", mode="TRAP" ]
        Type string : TrapTest02 [ oid=".1.3.6.1.4.1.318", mode="TRAP" ]
        Type string : TrapTest03 [ oid=".1.3.6.1.4.1.318.2.3.3.0", mode="TRAP" ]
}

Items:

Group gUPSRack01

String RackUPS01B_TrapTest01 "Test Trap 1: [%s]" (gUPSRack01) { channel="snmp:target:ups-rack-01b:TrapTest01" }
String RackUPS01B_TrapTest02 "Test Trap 2: [%s]" (gUPSRack01) { channel="snmp:target:ups-rack-01b:TrapTest02" }
String RackUPS01B_TrapTest03 "Test Trap 3: [%s]" (gUPSRack01) { channel="snmp:target:ups-rack-01b:TrapTest03" }

/etc/snmp/snmptrapd.conf:

authCommunity log,net public

forward default 10.0.20.20:8162

The APC card can only send SNMP traps to the default port of 162. The SNMP Binding docs describe two ways of forwarding traps from port 162 to a non-privileged port: iptables and snmptrapd

With the iptables method, the “test trap” functionality in the UPS management card web UI generates a trap like this:

2020-09-02 06:54:45.454 [TRACE] [ding.snmp.internal.SnmpTargetHandler] - snmp:target:ups-rack-01b received trap CommandResponderEvent[securityModel=1, securityLevel=1, maxSizeResponsePDU=65535, pduHandle=PduHandle[0], stateReference=StateReference[msgID=0,pduHandle=PduHandle[0],securityEngineID=null,securityModel=null,securityName=public,securityLevel=1,contextEngineID=null,contextName=null,retryMsgIDs=null], pdu=V1TRAP[reqestID=0,timestamp=0:13:56.80,enterprise=1.3.6.1.4.1.318,genericTrap=6,specificTrap=636, VBS[1.3.6.1.2.1.1.3.0 = 0:13:56.80; 1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.4.1.318.0.636]], messageProcessingModel=0, securityName=public, processed=false, peerAddress=10.0.1.30/63511, transportMapping=org.snmp4j.transport.DefaultUdpTransportMapping@22fc81e5, tmStateReference=null]

The key field here is: peerAddress=10.0.1.30/33450 (I’ll get to why that’s important in a moment).

This trap works properly. The channel for the enterpriseOID (1.3.6.1.4.1.318) is updated, as well as the channels for the OIDs in the variables (1.3.6.1.2.1.1.3.0 and 1.3.6.1.6.3.1.1.4.1.0) (or they would be, if I had channels set up for them. I don’t, so I instead see this in the log:

2020-09-02 06:54:45.454 [DEBUG] [ding.snmp.internal.SnmpTargetHandler] - received value 1.3.6.1.4.1.318.0.636 for unknown OID 1.3.6.1.6.3.1.1.4.1.0, skipping
2020-09-02 06:54:45.455 [DEBUG] [ding.snmp.internal.SnmpTargetHandler] - received value 0:13:56.80 for unknown OID 1.3.6.1.2.1.1.3.0, skipping

With the second method (snmptrapd running on the same system as OpenHAB), that same test trap looks like this:

2020-09-02 06:37:24.734 [TRACE] [ding.snmp.internal.SnmpTargetHandler] - snmp:target:ups-rack-01b received trap CommandResponderEvent[securityModel=1, securityLevel=1, maxSizeResponsePDU=65535, pduHandle=PduHandle[0], stateReference=StateReference[msgID=0,pduHandle=PduHandle[0],securityEngineID=null,securityModel=null,securityName=public,securityLevel=1,contextEngineID=null,contextName=null,retryMsgIDs=null], pdu=V1TRAP[reqestID=0,timestamp=16 days, 12:42:25.70,enterprise=1.3.6.1.4.1.318,genericTrap=6,specificTrap=636, VBS[1.3.6.1.2.1.1.3.0 = 16 days, 12:42:25.70; 1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.4.1.318.0.636]], messageProcessingModel=0, securityName=public, processed=false, peerAddress=10.0.20.20/56816, transportMapping=org.snmp4j.transport.DefaultUdpTransportMapping@22fc81e5, tmStateReference=null]

The peerAddress field here is different. It’s now: peerAddress=10.0.20.20/56816 (the IP address of the server that sent the trap, in this case the IP address of where snmptrapd is running).

This trap does not work properly. Only the channel for the enterpriseOID in the trap is updated. The OIDs in the variables are skipped.

I’ve had a look a the source code for the binding and I’ve traced the difference in behavior to the processPdu() function in the SnmpTargetHandler.java file. Specifically, lines 223-230:

        if ((pdu.getType() == PDU.TRAP || pdu.getType() == PDU.V1TRAP) && config.community.equals(community)
                && targetAddressString.equals(address)) {
            pdu.getVariableBindings().forEach(variable -> {
                OID oid = variable.getOid();
                Variable value = variable.getVariable();
                updateChannels(oid, value, trapChannelSet);
            });
        }

The address variable is compared to targetAddressString and if they’re the same (along with the other conditions), only then is updateChannels called for all the variables in the trap message.

address is defined earlier as:

final String address = ((UdpAddress) event.getPeerAddress()).getInetAddress().getHostAddress();

So address is the peerAddress field from the trap and targetAddressString is the resolved IP of the target thing (line 415)

As highlighted above in the trap message traces, only when using the iptables method does the peer address match the target address. With the snmptrapd method, peer address is instead the address of the forwarder, not the address of where the trap originated.

I looked around and couldn’t find a way to have snmptrapd set peerAddress to the address where the trap originated. The closest I got was a proposed patch to allow it to set another field to the address of the origin (https://sourceforge.net/p/net-snmp/patches/1320/), but it looks like it hasn’t been approved/merged in.

Unfortunately, I don’t know enough about SNMP to know what the issue actually is and what the proper behavior is.

  • Is the code incorrect - should it actually be processing the OIDs in the VBS section for snmptrapd-forwarded traps just like it does for directly received traps?
  • Or is the documentation incorrect and this issue should be called out as a limitation of the snmptrapd method or updated with the proper configuration entires for snmptrapd to set peerAddress (if that’s even possible)?

My hunch is the code is incorrect and that it needs to be changed, but changed to what I’m not exactly sure.

Thoughts?

SNMPv2c is incompatible with SNMP v1.

SNMPv2c is incompatible with SNMPv1 in two key areas: message formats and protocol operations. SNMPv2c messages use different header and protocol data unit (PDU) formats than SNMPv1 messages. SNMPv2c also uses two protocol operations that are not specified in SNMPv1. To overcome incompatibility, RFC 3584 defines two SNMPv1/v2c coexistence strategies: proxy agents and bilingual network-management systems.

from https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol#SNMPv1_&_SNMPv2c_interoperability

I’m referring to the v2 version of the SNMP binding for OpenHAB, not the protocol (as opposed to the legacy/deprecated 1.x binding)

Thanks for the call-out, though. I’ve edited the original post to clarify.

1 Like