/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.snmp.snmp4j;
import java.net.InetAddress;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.snmp.SnmpObjId;
import org.opennms.netmgt.snmp.SnmpValue;
import org.opennms.netmgt.snmp.TrapIdentity;
import org.opennms.netmgt.snmp.TrapInformation;
import org.opennms.netmgt.snmp.TrapNotificationListener;
import org.opennms.netmgt.snmp.TrapProcessor;
import org.opennms.netmgt.snmp.TrapProcessorFactory;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.MessageException;
import org.snmp4j.PDU;
import org.snmp4j.PDUv1;
import org.snmp4j.mp.StateReference;
import org.snmp4j.mp.StatusInformation;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.IpAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.SMIConstants;
import org.snmp4j.smi.TimeTicks;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
public class Snmp4JTrapNotifier implements CommandResponder {
private TrapProcessorFactory m_trapProcessorFactory;
private TrapNotificationListener m_listener;
public Snmp4JTrapNotifier(TrapNotificationListener listener, TrapProcessorFactory processorFactory) {
m_listener = listener;
m_trapProcessorFactory = processorFactory;
}
public static class Snmp4JV1TrapInformation extends TrapInformation {
private PDUv1 m_pdu;
protected Snmp4JV1TrapInformation(InetAddress agent, String community, PDUv1 pdu, TrapProcessor trapProcessor) {
super(agent, community, trapProcessor);
m_pdu = pdu;
}
protected InetAddress getTrapAddress() {
return m_pdu.getAgentAddress().getInetAddress();
}
protected String getVersion() {
return "v1";
}
protected int getPduLength() {
return m_pdu.getVariableBindings().size();
}
protected long getTimeStamp() {
return m_pdu.getTimestamp();
}
protected TrapIdentity getTrapIdentity() {
return new TrapIdentity(SnmpObjId.get(m_pdu.getEnterprise().getValue()), m_pdu.getGenericTrap(), m_pdu.getSpecificTrap());
}
protected VariableBinding getVarBindAt(int i) {
return m_pdu.get(i);
}
protected void processVarBindAt(int i) {
SnmpObjId name = SnmpObjId.get(getVarBindAt(i).getOid().getValue());
SnmpValue value = new Snmp4JValue(getVarBindAt(i).getVariable());
processVarBind(name, value);
}
}
public static class Snmp4JV2TrapInformation extends TrapInformation {
/**
* The received PDU
*/
private PDU m_pdu;
/**
* The name of the PDU's type
*/
private String m_pduTypeString;
/**
* The snmp sysUpTime OID is the first varbind
*/
static final int SNMP_SYSUPTIME_OID_INDEX = 0;
/**
* The snmp trap OID is the second varbind
*/
static final int SNMP_TRAP_OID_INDEX = 1;
/**
* The sysUpTimeOID, which should be the first varbind in a V2 trap
*/
static final OID SNMP_SYSUPTIME_OID = new OID(".1.3.6.1.2.1.1.3.0");
/**
* The sysUpTimeOID, which should be the first varbind in a V2 trap, but in
* the case of Extreme Networks only mostly
*/
static final OID EXTREME_SNMP_SYSUPTIME_OID = new OID(".1.3.6.1.2.1.1.3");
/**
* The snmpTrapOID, which should be the second varbind in a V2 trap
*/
static final OID SNMP_TRAP_OID = new OID(".1.3.6.1.6.3.1.1.4.1.0");
/**
* Constructs a new trap information instance that contains the sending
* agent, the community string, and the Protocol Data Unit.
*
* @param agent
* The sending agent's address
* @param community
* The community string from the SNMP packet.
* @param pdu
* The encapsulated Protocol Data Unit.
* @param trapProcessor The trap processor used to process the trap data
*
*/
public Snmp4JV2TrapInformation(InetAddress agent, String community, PDU pdu, TrapProcessor trapProcessor) {
super(agent, community, trapProcessor);
m_pdu = pdu;
m_pduTypeString = PDU.getTypeString(m_pdu.getType());
}
/**
* Returns the Protocol Data Unit that was encapsulated within the SNMP
* Trap message
*/
private PDU getPdu() {
return m_pdu;
}
protected int getPduLength() {
return getPdu().size();
}
protected long getTimeStamp() {
if (log().isDebugEnabled()) {
log().debug("V2 "+m_pduTypeString+" first varbind value: " + getVarBindAt(0).getVariable().toString());
}
switch (getVarBindAt(SNMP_SYSUPTIME_OID_INDEX).getVariable().getSyntax()) {
case SMIConstants.SYNTAX_TIMETICKS:
log().debug("V2 "+m_pduTypeString+" first varbind value is of type TIMETICKS (correct)");
return ((TimeTicks) getVarBindAt(SNMP_SYSUPTIME_OID_INDEX).getVariable()).getValue();
case SMIConstants.SYNTAX_INTEGER32:
log().debug("V2 "+m_pduTypeString+" first varbind value is of type INTEGER, casting to TIMETICKS");
return ((Integer32) getVarBindAt(SNMP_SYSUPTIME_OID_INDEX).getVariable()).getValue();
default:
throw new IllegalArgumentException("V2 "+m_pduTypeString+" does not have the required first varbind as TIMETICKS - cannot process "+m_pduTypeString);
}
}
protected TrapIdentity getTrapIdentity() {
OID snmpTrapOid = (OID) getVarBindAt(SNMP_TRAP_OID_INDEX).getVariable();
OID lastVarBindOid = getVarBindAt(getPduLength() - 1).getOid();
Variable lastVarBindValue = getVarBindAt(getPduLength() - 1).getVariable();
return new TrapIdentity(SnmpObjId.get(snmpTrapOid.getValue()), SnmpObjId.get(lastVarBindOid.getValue()), new Snmp4JValue(lastVarBindValue));
}
public InetAddress getTrapAddress() {
return getAgentAddress();
}
protected VariableBinding getVarBindAt(int index) {
return getPdu().get(index);
}
protected String getVersion() {
return "v2";
}
protected void validate() {
int pduType = getPdu().getType();
if (pduType != PDU.TRAP && pduType != PDU.INFORM) {
throw new IllegalArgumentException("Received not SNMPv2 Trap|Inform from host " + getTrapAddress() + " PDU Type = " + PDU.getTypeString(getPdu().getType()));
}
if (log().isDebugEnabled()) {
log().debug("V2 "+m_pduTypeString+" numVars or pdu length: " + getPduLength());
}
if (getPduLength() < 2) {
throw new IllegalArgumentException("V2 "+m_pduTypeString+" from " + getTrapAddress() + " IGNORED due to not having the required varbinds. Have " + getPduLength() + ", needed at least 2");
}
OID varBindName0 = getVarBindAt(0).getOid();
OID varBindName1 = getVarBindAt(1).getOid();
/*
* Modify the sysUpTime varbind OID to add the trailing 0 if it is
* missing, which is seen with some Extreme equipment.
*/
if (varBindName0.equals(EXTREME_SNMP_SYSUPTIME_OID)) {
log().info("V2 "+m_pduTypeString+" from " + getTrapAddress() + " has been corrected due to the sysUptime.0 varbind not having been sent with a trailing 0.\n\tVarbinds received are : " + varBindName0 + " and " + varBindName1);
varBindName0 = SNMP_SYSUPTIME_OID;
}
/*
* Confirm that the two required varbinds (sysUpTime and
* snmpTrapOID) are present and in that order.
*/
if ((!(varBindName0.equals(SNMP_SYSUPTIME_OID))) || (!(varBindName1.equals(SNMP_TRAP_OID)))) {
throw new IllegalArgumentException("V2 "+m_pduTypeString+" from " + getTrapAddress() + " IGNORED due to not having the required varbinds.\n\tThe first varbind must be sysUpTime.0 and the second snmpTrapOID.0\n\tVarbinds received are : " + varBindName0 + " and " + varBindName1);
}
}
protected void processVarBindAt(int i) {
if (i == 0) {
log().debug("Skipping processing of varbind " + i + ": it is sysuptime and the first varbind, and is not processed as a parm per RFC2089");
} else if (i == 1) {
log().debug("Skipping processing of varbind " + i + ": it is the trap OID and the second varbind, and is not processed as a parm per RFC2089");
} else {
SnmpObjId name = SnmpObjId.get(getVarBindAt(i).getOid().getValue());
SnmpValue value = new Snmp4JValue(getVarBindAt(i).getVariable());
processVarBind(name, value);
}
}
}
@Override
public void processPdu(CommandResponderEvent e) {
PDU command = new PDU(e.getPDU());
IpAddress addr = ((IpAddress)e.getPeerAddress());
if (command != null) {
if (command.getType() == PDU.INFORM) {
PDU response = new PDU(command);
response.setErrorIndex(0);
response.setErrorStatus(0);
response.setType(PDU.RESPONSE);
StatusInformation statusInformation = new StatusInformation();
StateReference ref = e.getStateReference();
try {
e.getMessageDispatcher().returnResponsePdu(e.getMessageProcessingModel(),
e.getSecurityModel(),
e.getSecurityName(),
e.getSecurityLevel(),
response,
e.getMaxSizeResponsePDU(),
ref,
statusInformation);
if (log().isDebugEnabled()) {
log().debug("Sent RESPONSE PDU to peer " + addr + " acknowledging receipt of INFORM (reqId=" + command.getRequestID() + ")");
}
} catch (MessageException ex) {
log().error("Error while sending RESPONSE PDU to peer " + addr + ": " + ex.getMessage() + "acknowledging receipt of INFORM (reqId=" + command.getRequestID() + ")");
}
}
}
if (e.getPDU() instanceof PDUv1) {
m_listener.trapReceived(new Snmp4JV1TrapInformation(addr.getInetAddress(), new String(e.getSecurityName()), (PDUv1)e.getPDU(), m_trapProcessorFactory.createTrapProcessor()));
} else {
m_listener.trapReceived(new Snmp4JV2TrapInformation(addr.getInetAddress(), new String(e.getSecurityName()), e.getPDU(), m_trapProcessorFactory.createTrapProcessor()));
}
}
private ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
}