/* * JBoss, Home of Professional Open Source * Copyright 2005, JBoss Inc., and individual contributors as indicated * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.jmx.adaptor.snmp.agent; import java.io.InputStream; import java.net.InetAddress; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import javax.management.Attribute; import javax.management.MBeanServer; import javax.management.ObjectName; import org.jboss.jmx.adaptor.snmp.config.attribute.AttributeMappings; import org.jboss.jmx.adaptor.snmp.config.attribute.ManagedBean; import org.jboss.jmx.adaptor.snmp.config.attribute.MappedAttribute; import org.jboss.logging.Logger; import org.jboss.xb.binding.ObjectModelFactory; import org.jboss.xb.binding.Unmarshaller; import org.jboss.xb.binding.UnmarshallerFactory; import org.opennms.protocols.snmp.SnmpAgentSession; import org.opennms.protocols.snmp.SnmpInt32; import org.opennms.protocols.snmp.SnmpNull; import org.opennms.protocols.snmp.SnmpObjectId; import org.opennms.protocols.snmp.SnmpOctetString; import org.opennms.protocols.snmp.SnmpPduPacket; import org.opennms.protocols.snmp.SnmpPduRequest; import org.opennms.protocols.snmp.SnmpSyntax; import org.opennms.protocols.snmp.SnmpUInt32; import org.opennms.protocols.snmp.SnmpVarBind; /** * Implement RequestHandler with mapping of snmp get/set requests * to JMX mbean attribute gets/sets * * @author Heiko W. Rupp * @author Dimitris Andreadis * @version $Revision: 1.1.2.1 $ */ public class RequestHandlerImpl extends RequestHandlerSupport implements Reconfigurable { // Protected Data ------------------------------------------------ private static final String NO_ENTRY_FOUND_FOR_OID = "No entry found for oid "; private static final String SKIP_ENTRY = " - skipping entry"; /** Bindings from oid to mbean */ protected SortedMap bindings = new TreeMap(); /** Has this RequestHandler instance been initialized? */ private boolean initialized = false; // Constructors -------------------------------------------------- /** * Default CTOR */ public RequestHandlerImpl() { // empty } // RequestHandler Implementation --------------------------------- /** * Initialize * * @param resourceName A file containing get/set mappings * @param server Our MBean-Server * @param log The logger we use * @param uptime The uptime of the snmp-agent subsystem. */ public void initialize(String resourceName, MBeanServer server, Logger log, Clock uptime) throws Exception { super.initialize(resourceName, server, log, uptime); log.debug("initialized() with res=" + resourceName); if (resourceName != null) initializeBindings(); else log.warn("No RequestHandlerResName configured, disabling snmp-get"); initialized = true; } // Reconfigurable Implementation --------------------------------- /** * Reconfigures the RequestHandler */ public void reconfigure(String resName) throws Exception { if (resName == null || resName.equals("")) throw new IllegalArgumentException("Null or empty resName, cannot reconfigure"); if (initialized == false) throw new IllegalStateException("Cannot reconfigure, not initialized yet"); this.resourceName = resName; // Wipe out old entries bindings.clear(); // Fetch them again initializeBindings(); } // SnmpAgentHandler Implementation ------------------------------- /** *

* This method is defined to handle SNMP Get requests that are received by * the session. The request has already been validated by the system. This * routine will build a response and pass it back to the caller. *

* * @param pdu * The SNMP pdu * @param getNext * The agent is requesting the lexically NEXT item after each * item in the pdu. * * @return SnmpPduRequest filled in with the proper response, or null if * cannot process NOTE: this might be changed to throw an exception. */ public SnmpPduRequest snmpReceivedGet(SnmpPduPacket pdu, boolean getNext) { SnmpPduRequest response = null; int pduLength = pdu.getLength(); final boolean trace = log.isTraceEnabled(); if (trace) log.trace("requestId=" + pdu.getRequestId() + ", pduLength=" + pduLength + ", getNext=" + getNext); SnmpVarBind[] vblist = new SnmpVarBind[pduLength]; int errorStatus = SnmpPduPacket.ErrNoError; int errorIndex = 0; // Process for each varibind in the request for (int i = 0; i < pduLength; i++) { boolean good = true; SnmpVarBind vb = pdu.getVarBindAt(i); SnmpObjectId oid = vb.getName(); if (getNext) { /* * We call getNextOid() to find out what is the next valid OID * instance in the supported MIB (sub-)tree. Assign that OID to * the VB List and then proceed same as that of get request. If * the passed oid is already the last, we flag it. */ String oids = oid.toString(); try { oids = getNextOid(oid.toString()); oid = new SnmpObjectId(oids); pdu.setVarBindAt(i, new SnmpVarBind(oid)); } catch (NoSuchFieldException nse) { // abused good = false; // no next oid found } } vblist[i] = new SnmpVarBind(oid); if (trace) log.trace("oid=" + oid.toString()); SnmpSyntax result = null; if (bindings != null) result = getValueFor(oid.toString()); if (trace) log.trace("got result of " + result); if (result == null || !good) { errorStatus = SnmpPduPacket.ErrNoSuchName; errorIndex = i + 1; // log.debug("Error Occured " + vb.getName().toString()); } else { vblist[i].setValue(result); log.debug("Varbind[" + i + "] := " + vblist[i].getName().toString()); log.debug(" --> " + vblist[i].getValue().toString()); } } response = new SnmpPduRequest(SnmpPduPacket.RESPONSE, vblist); response.setErrorStatus(errorStatus); response.setErrorIndex(errorIndex); return response; } /** *

* This method is defined to handle SNMP Set requests that are received by * the session. The request has already been validated by the system. This * routine will build a response and pass it back to the caller. *

* * @param pdu * The SNMP pdu * * @return SnmpPduRequest filled in with the proper response, or null if * cannot process NOTE: this might be changed to throw an exception. */ public SnmpPduRequest snmpReceivedSet(SnmpPduPacket pdu) { final boolean trace = log.isTraceEnabled(); SnmpPduRequest response = null; int errorStatus = SnmpPduPacket.ErrNoError; int errorIndex = 0; int k = pdu.getLength(); SnmpVarBind[] vblist = new SnmpVarBind[k]; for (int i = 0; i < k; i++) { SnmpVarBind vb = pdu.getVarBindAt(i); vblist[i] = new SnmpVarBind(vb); SnmpObjectId oid = vb.getName(); SnmpSyntax newVal = vb.getValue(); if (trace) log.trace("set: received oid " + oid.toString() + " with value " + newVal.toString()); SnmpSyntax result = null; try { result = setValueFor(oid.toString(),newVal); } catch (ReadOnlyException e) { errorStatus = SnmpPduPacket.ErrReadOnly; errorIndex = i + 1; } if (result != null) { errorStatus = SnmpPduPacket.ErrReadOnly; errorIndex = i + 1; log.debug("Error occured " + vb.getName().toString()); } if (trace) { log.trace("Varbind[" + i + "] := " + vb.getName().toString()); log.trace(" --> " + vb.getValue().toString()); } } response = new SnmpPduRequest(SnmpPduPacket.RESPONSE, vblist); response.setErrorStatus(errorStatus); response.setErrorIndex(errorIndex); return response; } /** *

* This method is defined to handle SNMP requests that are received by the * session. The parameters allow the handler to determine the host, port, * and community string of the received PDU *

* * @param session * The SNMP session * @param manager * The remote sender * @param port * The remote senders port * @param community * The community string * @param pdu * The SNMP pdu * */ public void snmpReceivedPdu(SnmpAgentSession session, InetAddress manager, int port, SnmpOctetString community, SnmpPduPacket pdu) { log.error("Message from manager " + manager.toString() + " on port " + port); int cmd = pdu.getCommand(); log.error("Unsupported PDU command......... " + cmd); } /** *

* This method is invoked if an error occurs in the session. The error code * that represents the failure will be passed in the second parameter, * 'error'. The error codes can be found in the class SnmpAgentSession * class. *

* *

* If a particular PDU is part of the error condition it will be passed in * the third parameter, 'pdu'. The pdu will be of the type SnmpPduRequest or * SnmpPduTrap object. The handler should use the "instanceof" operator to * determine which type the object is. Also, the object may be null if the * error condition is not associated with a particular PDU. *

* * @param session * The SNMP Session * @param error * The error condition value. * @param ref * The PDU reference, or potentially null. It may also be an * exception. */ public void SnmpAgentSessionError(SnmpAgentSession session, int error, Object ref) { log.error("An error occured in the trap session"); log.error("Session error code = " + error); if (ref != null) { log.error("Session error reference: " + ref.toString()); } if (error == SnmpAgentSession.ERROR_EXCEPTION) { synchronized (session) { session.notify(); // close the session } } } // Private ------------------------------------------------------- /** * Initialize the bindings from the file given in resourceName */ private void initializeBindings() throws Exception { log.debug("Reading resource: '" + resourceName + "'"); ObjectModelFactory omf = new AttributeMappingsBinding(); InputStream is = null; AttributeMappings mappings = null; try { // locate resource is = getClass().getResourceAsStream(resourceName); // create unmarshaller Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller(); // let JBossXB do it's magic using the AttributeMappingsBinding mappings = (AttributeMappings)unmarshaller.unmarshal(is, omf, null); } catch (Exception e) { log.error("Accessing resource '" + resourceName + "'"); throw e; } finally { if (is != null) { // close the XML stream is.close(); } } if (mappings == null) { log.warn("No bindings found in " + resourceName); return; } log.debug("Found " + mappings.size() + " attribute mappings"); // Turn the parsed mappings into bindings. Iterator it = mappings.iterator(); while (it.hasNext()) { ManagedBean mmb = (ManagedBean)it.next(); String oidPrefix = mmb.getOidPrefix(); List attrs = mmb.getAttributes(); Iterator aIt = attrs.iterator(); while (aIt.hasNext()) { MappedAttribute ma = (MappedAttribute)aIt.next(); String oid; if (oidPrefix != null) oid = oidPrefix + ma.getOid(); else oid = ma.getOid(); BindEntry be = new BindEntry(oid, mmb.getName(), ma.getName()); be.isReadWrite = ma.isReadWrite(); if (log.isTraceEnabled()) log.trace("New bind entry " + be); if (bindings.containsKey(oid)) { log.info("Duplicate oid " + oid + SKIP_ENTRY); continue; } if (mmb.getName() == null || mmb.getName().equals("")) { log.info("Invalid mbean name for oid " + oid + SKIP_ENTRY); continue; } if (ma.getName() == null || ma.getName().equals("")) { log.info("Invalid attribute name " + ma.getName() + " for oid " + oid + SKIP_ENTRY); continue; } bindings.put(oid, be); } } } /** * Return the current value for the given oid * * @param oid * The oid we want a value for * @return SnmpNull if no value present */ private SnmpSyntax getValueFor(final String oid) { BindEntry be = findBindEntryForOid(oid); SnmpSyntax ssy = null; if (be != null) { if (log.isTraceEnabled()) log.trace("Found entry " + be.toString() + " for oid " + oid); try { Object val = server.getAttribute(be.mbean, be.attr.getName()); if (val instanceof Long) { Long uin = (Long) val; ssy = new SnmpUInt32(uin); } else if (val instanceof String) { String in = (String) val; ssy = new SnmpOctetString(in.getBytes()); } else if (val instanceof Integer) { Integer in = (Integer) val; ssy = new SnmpInt32(in); } else if (val instanceof SnmpObjectId) { ssy = (SnmpObjectId)val; } else if (val instanceof Boolean) { Boolean in = (Boolean) val; ssy = new SnmpOctetString(in.toString().getBytes()); } else log.info("Unknown type for " + be); } catch (Exception e) { log.warn("getValueFor (" + be.mbean.toString() + ", " + be.attr.getName() + ": " + e.toString()); } } else { ssy = new SnmpNull(); log.info(NO_ENTRY_FOUND_FOR_OID + oid); } return ssy; } /** * Set a jmx attribute * @param oid The oid to set. This is translated into a mbean / attribute pair * @param newVal The new value to set * @return null on success, non-null on failure * @throws ReadOnlyException If the referred entry is read only. */ private SnmpSyntax setValueFor(final String oid, final SnmpSyntax newVal) throws ReadOnlyException { final boolean trace = log.isTraceEnabled(); BindEntry be = findBindEntryForOid(oid); if (trace) log.trace("setValueFor: found bind entry for " + oid); SnmpSyntax ssy = null; if (be != null) { if (trace) log.trace("setValueFor: " + be.toString()); if (be.isReadWrite == false) { if (trace) log.trace("setValueFor: this is marked read only"); throw new ReadOnlyException(oid); } try { Object val = null; if (newVal instanceof SnmpOctetString) { val = newVal.toString(); } else if (newVal instanceof SnmpInt32) { val = new Integer(((SnmpInt32)newVal).getValue()); } else if (newVal instanceof SnmpUInt32) { val = new Long(((SnmpUInt32)newVal).getValue()); } // TODO do more mumbo jumbo for type casting / changing if (val != null) { Attribute at = new Attribute(be.attr.getName(), val); server.setAttribute(be.mbean, at); if (trace) log.trace("setValueFor: set attribute in mbean-Server"); } else { log.debug("Did not find a suitable data type for newVal " + newVal); ssy = new SnmpNull(); } // TODO } catch (Exception e ) { log.debug("setValueFor: exception " + e.getMessage()); ssy = new SnmpNull(); } } else { ssy = new SnmpNull(); log.info(NO_ENTRY_FOUND_FOR_OID + oid); } return ssy; } /** * Lookup a BinEntry on the given oid. If the oid ends in .0, * then the .0 will be stripped of before the search. * @param oid The oid look up. * @return a bind entry or null. */ private BindEntry findBindEntryForOid(final String oid) { String localOid = oid; if (oid.endsWith(".0")) localOid = oid.substring(0,oid.length()-2); BindEntry be = (BindEntry) bindings.get(localOid); return be; } /** * Tries to retrieve the next oid following the current one * * @param in * an oid * @return an oid * @throws NoSuchFieldException * if no next oid can be found */ private String getNextOid(final String in) throws NoSuchFieldException { final boolean trace = log.isTraceEnabled(); String roid = in.substring(0, in.lastIndexOf('.')); if (trace) log.trace("in: " + in + " roi: " + roid); Set oids = bindings.keySet(); Iterator it = oids.iterator(); while (it.hasNext()) { String id = (String) it.next(); int comp = compareOid(in, id); if (trace) log.trace("--- found id " + id + " comp = " + comp); if (comp >= 0) continue; if (trace) log.trace("Returning " + id); return id; } if (trace) log.trace("## no next oid found for " + in); throw new NoSuchFieldException(); } /** * Compare the two passed oid-strings. * * @param oid1 * @param oid2 * @return 0 when equal, 1 if oid2 > oid1 and -1 if oid1>oid2 */ private int compareOid(final String oid1, final String oid2) { int res; int nd1 = countDots(oid1); int nd2 = countDots(oid2); res = nd1 - nd2; if (res != 0) // different number of dots -> not equal return res; // equal number of dots. Check for same prefix (subtree) String tpref = oid1.substring(0, oid1.lastIndexOf(".")); String opref = oid2.substring(0, oid2.lastIndexOf(".")); res = tpref.compareTo(opref); // same prefix if (res == 0) { String tsuf = oid1.substring(oid1.lastIndexOf(".") + 1); String osuf = oid2.substring(oid2.lastIndexOf(".") + 1); int tval = Integer.parseInt(tsuf); int oval = Integer.parseInt(osuf); res = tval - oval; } return res; } /** * Count the number of dots in the passed String (an oid actually) */ private int countDots(final String in) { int count = 0; for (int i = 0; i < in.length(); i++) if (in.charAt(i) == '.') count++; return count; } // Inner Class --------------------------------------------------- /** * An entry containing the mapping between oid and mbean/attribute * * @author Heiko W. Rupp */ private class BindEntry implements Comparable { private final String oid; private final int numDots; private ObjectName mbean; private Attribute attr; private String mName; private String aName; private boolean isReadWrite = false; /** * Constructs a new BindEntry * * @param oid * The SNMP-oid, this entry will use. * @param mbName * The name of an MBean with attribute to query * @param attrName * The name of the attribute to query */ BindEntry(final String oid, final String mbName, final String attrName) { this.oid = oid; numDots = countDots(oid); this.mName = mbName; this.aName = attrName; try { mbean = new ObjectName(mbName); attr = new Attribute(attrName, null); } catch (Exception e) { log.warn(e.toString()); mName = "-unset-"; aName = "-unset-"; } } public Attribute getAttr() { return attr; } public ObjectName getMbean() { return mbean; } public String getOid() { return oid; } public int getNumDots() { return numDots; } /** * Compare two BindEntries. Ordering is defined at oid-level. * * @param other * The BindEntry to compare to. * @return 0 on equals, 1 if this is bigger than other */ public int compareTo(Object other) { if (other == null) throw new NullPointerException("Can't compare to NULL"); if (!(other instanceof BindEntry)) throw new ClassCastException("Parameter is no BindEntry"); // trivial case if (this.equals(other)) return 0; BindEntry obe = (BindEntry) other; if (getOid().equals(obe.getOid())) return 0; int res = compareOid(oid, obe.getOid()); return res; } /** * A string representation of this BindEntry */ public String toString() { StringBuffer buf = new StringBuffer(); buf.append("[oid="); buf.append(oid).append(", mbean="); buf.append(mName).append(", attr="); buf.append(aName).append(", rw="); buf.append(isReadWrite).append("]"); return buf.toString(); } } }