/******************************************************************************* * 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.io.IOException; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.opennms.core.utils.LogUtils; import org.opennms.core.utils.ThreadCategory; import org.opennms.netmgt.snmp.CollectionTracker; import org.opennms.netmgt.snmp.SnmpAgentConfig; import org.opennms.netmgt.snmp.SnmpConfiguration; import org.opennms.netmgt.snmp.SnmpObjId; import org.opennms.netmgt.snmp.SnmpStrategy; import org.opennms.netmgt.snmp.SnmpTrapBuilder; import org.opennms.netmgt.snmp.SnmpV1TrapBuilder; import org.opennms.netmgt.snmp.SnmpV2TrapBuilder; import org.opennms.netmgt.snmp.SnmpV3TrapBuilder; import org.opennms.netmgt.snmp.SnmpV3User; import org.opennms.netmgt.snmp.SnmpValue; import org.opennms.netmgt.snmp.SnmpValueFactory; import org.opennms.netmgt.snmp.SnmpWalker; import org.opennms.netmgt.snmp.TrapNotificationListener; import org.opennms.netmgt.snmp.TrapProcessorFactory; import org.snmp4j.CommandResponderEvent; import org.snmp4j.MessageDispatcher; import org.snmp4j.PDU; import org.snmp4j.PDUv1; import org.snmp4j.SNMP4JSettings; import org.snmp4j.ScopedPDU; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; import org.snmp4j.event.ResponseEvent; import org.snmp4j.mp.MPv3; import org.snmp4j.mp.MessageProcessingModel; import org.snmp4j.mp.PduHandle; import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; import org.snmp4j.security.UsmUser; import org.snmp4j.smi.IpAddress; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.SMIConstants; import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; public class Snmp4JStrategy implements SnmpStrategy { private static Map<TrapNotificationListener, RegistrationInfo> s_registrations = new HashMap<TrapNotificationListener, RegistrationInfo>(); private static boolean s_initialized = false; private Snmp4JValueFactory m_valueFactory; /** * Initialize for v3 communications */ private static void initialize() { if (s_initialized) { return; } // LogFactory.setLogFactory(new Log4jLogFactory()); MPv3.setEnterpriseID(5813); USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); SecurityModels.getInstance().addSecurityModel(usm); // Enable extensibility in SNMP4J so that we can subclass some SMI classes to work around // agent bugs if (System.getProperty("org.snmp4j.smisyntaxes", null) != null) { SNMP4JSettings.setExtensibilityEnabled(true); } if (Boolean.getBoolean("org.opennms.snmp.snmp4j.forwardRuntimeExceptions")) { SNMP4JSettings.setForwardRuntimeExceptions(true); } s_initialized = true; } public Snmp4JStrategy() { initialize(); } /** * SNMP4J createWalker implemenetation. * * @param snmpAgentConfig * @param name * @param tracker */ public SnmpWalker createWalker(SnmpAgentConfig snmpAgentConfig, String name, CollectionTracker tracker) { return new Snmp4JWalker(new Snmp4JAgentConfig(snmpAgentConfig), name, tracker); } /** * Not yet implemented. Use a walker. */ public SnmpValue[] getBulk(SnmpAgentConfig agentConfig, SnmpObjId[] oid) { throw new UnsupportedOperationException("Snmp4JStrategy.getBulk not yet implemented."); } public SnmpValue set(final SnmpAgentConfig agentConfig, final SnmpObjId oid, final SnmpValue value) { if (log().isDebugEnabled()) { log().debug("set: OID: " + oid + " value: " + value.toString() + " for Agent: " + agentConfig); } final SnmpObjId[] oids = { oid }; final SnmpValue[] values = { value }; final SnmpValue[] retvalues = set(agentConfig, oids, values); return retvalues[0]; } public SnmpValue[] set(final SnmpAgentConfig agentConfig, final SnmpObjId[] oids, final SnmpValue[] values) { if (log().isDebugEnabled()) { log().debug("set: OIDs: " + Arrays.toString(oids) + " values: " + Arrays.toString(values) + " for Agent: " + agentConfig); } return buildAndSendPdu(agentConfig, PDU.SET, oids, values); } /** * SNMP4J get helper that takes a single SnmpObjId * and calls get with an array.lenght =1 and returns * the first element of the returned array of SnmpValue. * * @param agentConfig * @param oid * */ public SnmpValue get(SnmpAgentConfig agentConfig, SnmpObjId oid) { if (log().isDebugEnabled()) { log().debug("get: OID: "+oid+" for Agent:"+agentConfig); } SnmpObjId[] oids = { oid }; SnmpValue[] retvalues = get(agentConfig, oids); return retvalues[0]; } /** * SnmpGet implementation. * * @param agentConfig * @param oids * @return * Returns an array of Snmp4JValues. If the * get was unsuccessful, then the first elment * of the array will be null and lenth of 1. */ public SnmpValue[] get(SnmpAgentConfig agentConfig, SnmpObjId[] oids) { if (log().isDebugEnabled()) { log().debug("get: OID: "+Arrays.toString(oids)+" for Agent:"+agentConfig); } return buildAndSendPdu(agentConfig, PDU.GET, oids, null); } /** * SNMP4J getNext implementation * * @param agentConfig * @param oid * */ public SnmpValue getNext(SnmpAgentConfig agentConfig, SnmpObjId oid) { if (log().isDebugEnabled()) { log().debug("getNext: OID: "+oid+" for Agent:"+agentConfig); } return getNext(agentConfig, new SnmpObjId[] { oid })[0]; } /** * SNMP GetNext implementation. * * @param agentConfig * @param oids * @return * Returns an array of Snmp4JValues. If the * getNext was unsuccessful, then the first element * of the array will be null and length of 1. */ public SnmpValue[] getNext(SnmpAgentConfig agentConfig, SnmpObjId[] oids) { if (log().isDebugEnabled()) { log().debug("getNext: OID: "+Arrays.toString(oids)+" for Agent:"+agentConfig); } return buildAndSendPdu(agentConfig, PDU.GETNEXT, oids, null); } private SnmpValue[] buildAndSendPdu(SnmpAgentConfig agentConfig, int type, SnmpObjId[] oids, SnmpValue[] values) { Snmp4JAgentConfig snmp4jAgentConfig = new Snmp4JAgentConfig(agentConfig); PDU pdu = buildPdu(snmp4jAgentConfig, type, oids, values); if (pdu == null) { return null; } return send(snmp4jAgentConfig, pdu, true); } /** * Sends and SNMP4J request pdu. The attributes in SnmpAgentConfig should have been * adapted from default SnmpAgentConfig values to those compatible with the SNMP4J library. * * @param agentConfig * @param pduType TODO * @param oids * @param values can be null * @return */ protected SnmpValue[] send(Snmp4JAgentConfig agentConfig, PDU pdu, boolean expectResponse) { Snmp session; try { session = agentConfig.createSnmpSession(); } catch (IOException e) { log().error("send: Could not create SNMP session for agent " + agentConfig + ": " + e, e); return new SnmpValue[] { null }; } try { if (expectResponse) { try { session.listen(); } catch (IOException e) { log().error("send: error setting up listener for SNMP responses: " + e, e); return new SnmpValue[] { null }; } } try { ResponseEvent responseEvent = session.send(pdu, agentConfig.getTarget()); if (expectResponse) { return processResponse(agentConfig, responseEvent); } else { return null; } } catch (IOException e) { log().error("send: error during SNMP operation: " + e, e); return new SnmpValue[] { null }; } catch (Throwable e) { log().error("send: unexpected error during SNMP operation: " + e, e); return new SnmpValue[] { null }; } } finally { closeQuietly(session); } } protected PDU buildPdu(Snmp4JAgentConfig agentConfig, int pduType, SnmpObjId[] oids, SnmpValue[] values) { PDU pdu = agentConfig.createPdu(pduType); if (values == null) { for (SnmpObjId oid : oids) { pdu.add(new VariableBinding(new OID(oid.toString()))); } } else { // TODO should this throw an exception? This situation is fairly bogus and probably signifies a coding error. if (oids.length != values.length) { Exception e = new Exception("This is a bogus exception so we can get a stack backtrace"); log().error("PDU to prepare has object values but not the same number as there are OIDs. There are " + oids.length + " OIDs and " + values.length + " object values.", e); return null; } for (int i = 0; i < oids.length; i++) { pdu.add(new VariableBinding(new OID(oids[i].toString()), new Snmp4JValue(values[i].getType(), values[i].getBytes()).getVariable())); } } // TODO should this throw an exception? This situation is fairly bogus. if (pdu.getVariableBindings().size() != oids.length) { Exception e = new Exception("This is a bogus exception so we can get a stack backtrace"); log().error("Prepared PDU does not have as many variable bindings as there are OIDs. There are " + oids.length + " OIDs and " + pdu.getVariableBindings() + " variable bindings.", e); return null; } return pdu; } /** * TODO: Merge this logic with {@link Snmp4JWalker.Snmp4JResponseListener#processResponse(PDU response)} */ private static SnmpValue[] processResponse(Snmp4JAgentConfig agentConfig, ResponseEvent responseEvent) throws IOException { SnmpValue[] retvalues = { null }; if (responseEvent.getResponse() == null) { log().warn("processResponse: Timeout. Agent: "+agentConfig); } else if (responseEvent.getError() != null) { log().warn("processResponse: Error during get operation. Error: "+responseEvent.getError().getLocalizedMessage(), responseEvent.getError()); } else if (responseEvent.getResponse().getType() == PDU.REPORT) { log().warn("processResponse: Error during get operation. Report returned with varbinds: "+responseEvent.getResponse().getVariableBindings()); } else if (responseEvent.getResponse().getVariableBindings().size() < 1) { log().warn("processResponse: Received PDU with 0 varbinds."); } else if (responseEvent.getResponse().get(0).getSyntax() == SMIConstants.SYNTAX_NULL) { log().info("processResponse: Null value returned in varbind: " + responseEvent.getResponse().get(0)); } else { retvalues = convertResponseToValues(responseEvent); if (log().isDebugEnabled()) { log().debug("processResponse: SNMP operation successful, value: "+Arrays.toString(retvalues)); } } return retvalues; } private static SnmpValue[] convertResponseToValues(ResponseEvent responseEvent) { SnmpValue[] retvalues = new Snmp4JValue[responseEvent.getResponse().getVariableBindings().size()]; for (int i = 0; i < retvalues.length; i++) { retvalues[i] = new Snmp4JValue(responseEvent.getResponse().get(i).getVariable()); } return retvalues; } private static ThreadCategory log() { return ThreadCategory.getInstance(Snmp4JStrategy.class); } public SnmpValueFactory getValueFactory() { if (m_valueFactory == null) { m_valueFactory = new Snmp4JValueFactory(); } return m_valueFactory; } public static class RegistrationInfo { private TrapNotificationListener m_listener; Snmp m_trapSession; Snmp4JTrapNotifier m_trapHandler; private TransportMapping m_transportMapping; private InetAddress m_address; private int m_port; RegistrationInfo(TrapNotificationListener listener, int trapPort) throws SocketException { if (listener == null) throw new NullPointerException("You must specify a trap notification listener."); LogUtils.debugf(this, "trapPort = %d", trapPort); m_listener = listener; m_port = trapPort; } public RegistrationInfo(final TrapNotificationListener listener, final InetAddress address, final int snmpTrapPort) { if (listener == null) throw new NullPointerException("You must specify a trap notification listener."); m_listener = listener; m_address = address; m_port = snmpTrapPort; } public void setSession(final Snmp trapSession) { m_trapSession = trapSession; } public Snmp getSession() { return m_trapSession; } public void setHandler(final Snmp4JTrapNotifier trapHandler) { m_trapHandler = trapHandler; } public Snmp4JTrapNotifier getHandler() { return m_trapHandler; } public InetAddress getAddress() { return m_address; } public int getPort() { return m_port; } public void setTransportMapping(final TransportMapping transport) { m_transportMapping = transport; } public TransportMapping getTransportMapping() { return m_transportMapping; } public int hashCode() { return (m_listener.hashCode() + m_address.hashCode() ^ m_port); } public boolean equals(final Object obj) { if (obj instanceof RegistrationInfo) { final RegistrationInfo info = (RegistrationInfo) obj; return (m_listener == info.m_listener) && Arrays.equals(m_address.getAddress(), info.getAddress().getAddress()) && m_port == info.getPort(); } return false; } } public void registerForTraps(final TrapNotificationListener listener, final TrapProcessorFactory processorFactory, InetAddress address, int snmpTrapPort, List<SnmpV3User> snmpUsers) throws IOException { final RegistrationInfo info = new RegistrationInfo(listener, address, snmpTrapPort); final Snmp4JTrapNotifier m_trapHandler = new Snmp4JTrapNotifier(listener, processorFactory); info.setHandler(m_trapHandler); final UdpAddress udpAddress; if (address == null) { udpAddress = new UdpAddress(snmpTrapPort); } else { udpAddress = new UdpAddress(address, snmpTrapPort); } final TransportMapping transport = new DefaultUdpTransportMapping(udpAddress); info.setTransportMapping(transport); Snmp snmp = new Snmp(transport); snmp.addCommandResponder(m_trapHandler); if (snmpUsers != null) { for (SnmpV3User user : snmpUsers) { SnmpAgentConfig config = new SnmpAgentConfig(); config.setVersion(SnmpConfiguration.VERSION3); config.setSecurityName(user.getSecurityName()); config.setAuthProtocol(user.getAuthProtocol()); config.setAuthPassPhrase(user.getAuthPassPhrase()); config.setPrivProtocol(user.getPrivProtocol()); config.setPrivPassPhrase(user.getPrivPassPhrase()); Snmp4JAgentConfig agentConfig = new Snmp4JAgentConfig(config); UsmUser usmUser = new UsmUser( agentConfig.getSecurityName(), agentConfig.getAuthProtocol(), agentConfig.getAuthPassPhrase(), agentConfig.getPrivProtocol(), agentConfig.getPrivPassPhrase() ); /* This doesn't work as expected. Basically SNMP4J is ignoring the engineId if (user.getEngineId() == null) { snmp.getUSM().addUser(agentConfig.getSecurityName(), usmUser); } else { snmp.getUSM().addUser(agentConfig.getSecurityName(), new OctetString(user.getEngineId()), usmUser); } */ snmp.getUSM().addUser(agentConfig.getSecurityName(), usmUser); } } info.setSession(snmp); s_registrations.put(listener, info); snmp.listen(); } public void registerForTraps(final TrapNotificationListener listener, final TrapProcessorFactory processorFactory, InetAddress address, int snmpTrapPort) throws IOException { registerForTraps(listener, processorFactory, address, snmpTrapPort, null); } public void registerForTraps(final TrapNotificationListener listener, final TrapProcessorFactory processorFactory, final int snmpTrapPort) throws IOException { registerForTraps(listener, processorFactory, null, snmpTrapPort); } public void unregisterForTraps(final TrapNotificationListener listener, InetAddress address, int snmpTrapPort) throws IOException { RegistrationInfo info = s_registrations.remove(listener); closeQuietly(info.getSession()); } public void unregisterForTraps(final TrapNotificationListener listener, final int snmpTrapPort) throws IOException { RegistrationInfo info = s_registrations.remove(listener); closeQuietly(info.getSession()); } public SnmpV1TrapBuilder getV1TrapBuilder() { return new Snmp4JV1TrapBuilder(this); } public SnmpTrapBuilder getV2TrapBuilder() { return new Snmp4JV2TrapBuilder(this); } public SnmpV3TrapBuilder getV3TrapBuilder() { return new Snmp4JV3TrapBuilder(this); } public SnmpV2TrapBuilder getV2InformBuilder() { return new Snmp4JV2InformBuilder(this); } public SnmpV3TrapBuilder getV3InformBuilder() { return new Snmp4JV3InformBuilder(this); } protected SnmpAgentConfig buildAgentConfig(String address, int port, String community, PDU pdu) throws UnknownHostException { SnmpAgentConfig config = new SnmpAgentConfig(); config.setAddress(InetAddress.getByName(address)); config.setPort(port); config.setVersion(pdu instanceof PDUv1 ? SnmpAgentConfig.VERSION1 : SnmpAgentConfig.VERSION2C); return config; } protected SnmpAgentConfig buildAgentConfig(String address, int port, int timeout, int retries, String community, PDU pdu) throws UnknownHostException { SnmpAgentConfig config = buildAgentConfig(address, port, community, pdu); config.setTimeout(timeout); config.setRetries(retries); return config; } protected SnmpAgentConfig buildAgentConfig(String address, int port, int securityLevel, String securityName, String authPassPhrase, String authProtocol, String privPassPhrase, String privProtocol, PDU pdu) throws UnknownHostException, Exception { if (! (pdu instanceof ScopedPDU)) throw new Exception(); SnmpAgentConfig config = new SnmpAgentConfig(); config.setAddress(InetAddress.getByName(address)); config.setPort(port); config.setVersion(SnmpAgentConfig.VERSION3); config.setSecurityLevel(securityLevel); config.setSecurityName(securityName); config.setAuthPassPhrase(authPassPhrase); config.setAuthProtocol(authProtocol); config.setPrivPassPhrase(privPassPhrase); config.setPrivProtocol(privProtocol); return config; } protected SnmpAgentConfig buildAgentConfig(String address, int port, int timeout, int retries, int securityLevel, String securityName, String authPassPhrase, String authProtocol, String privPassPhrase, String privProtocol, PDU pdu) throws UnknownHostException, Exception { SnmpAgentConfig config = buildAgentConfig(address, port, securityLevel, securityName, authPassPhrase, authProtocol, privPassPhrase, privProtocol, pdu); config.setTimeout(timeout); config.setRetries(retries); return config; } public void sendTest(String agentAddress, int port, String community, PDU pdu) { for (RegistrationInfo info : s_registrations.values()) { if (port == info.getPort()) { Snmp snmp = info.getSession(); MessageDispatcher dispatcher = snmp.getMessageDispatcher(); TransportMapping transport = info.getTransportMapping(); int securityModel = (pdu instanceof PDUv1 ? SecurityModel.SECURITY_MODEL_SNMPv1 :SecurityModel.SECURITY_MODEL_SNMPv2c); int messageModel = (pdu instanceof PDUv1 ? MessageProcessingModel.MPv1 : MessageProcessingModel.MPv2c); CommandResponderEvent e = new CommandResponderEvent(dispatcher, transport, new IpAddress(agentAddress), messageModel, securityModel, community.getBytes(), SecurityLevel.NOAUTH_NOPRIV, new PduHandle(), pdu, 1000, null); info.getHandler().processPdu(e); } } } private void closeQuietly(Snmp session) { if (session == null) { return; } try { session.close(); } catch (IOException e) { ThreadCategory.getInstance(Snmp4JStrategy.class).error("error closing SNMP connection: " + e, e); } } public byte[] getLocalEngineID() { return MPv3.createLocalEngineID(); } }