/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2010-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.mock.snmp; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.net.BindException; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.util.Iterator; import java.util.List; import org.snmp4j.MessageDispatcherImpl; import org.snmp4j.TransportMapping; import org.snmp4j.agent.BaseAgent; import org.snmp4j.agent.CommandProcessor; import org.snmp4j.agent.DuplicateRegistrationException; import org.snmp4j.agent.ManagedObject; import org.snmp4j.agent.io.ImportModes; import org.snmp4j.agent.mo.MOTableRow; import org.snmp4j.agent.mo.snmp.RowStatus; import org.snmp4j.agent.mo.snmp.SnmpCommunityMIB; import org.snmp4j.agent.mo.snmp.SnmpNotificationMIB; import org.snmp4j.agent.mo.snmp.SnmpTargetMIB; import org.snmp4j.agent.mo.snmp.StorageType; import org.snmp4j.agent.mo.snmp.TransportDomains; import org.snmp4j.agent.mo.snmp.VacmMIB; import org.snmp4j.log.ConsoleLogFactory; import org.snmp4j.log.Log4jLogFactory; import org.snmp4j.log.LogAdapter; import org.snmp4j.log.LogFactory; import org.snmp4j.mp.MPv1; import org.snmp4j.mp.MPv2c; import org.snmp4j.mp.MPv3; import org.snmp4j.mp.MessageProcessingModel; import org.snmp4j.security.AuthMD5; import org.snmp4j.security.AuthSHA; import org.snmp4j.security.PrivDES; import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; import org.snmp4j.security.UsmUser; import org.snmp4j.smi.Counter32; import org.snmp4j.smi.Counter64; import org.snmp4j.smi.Integer32; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.Variable; import org.snmp4j.transport.DefaultUdpTransportMapping; import org.snmp4j.util.ThreadPool; /* * The <code>MockSnmpAgent</code> class extends the SNMP4J BaseAgent * class to provide a mock SNMP agent for SNMP-based OpenNMS tests. * Large chunks of code were lifted from the org.snmp4j.agent.test.TestAgent * class. * * @author Jeff Gehlbach * @version 1.0 */ /** * <p>MockSnmpAgent class.</p> * * @author ranger * @version $Id: $ */ public class MockSnmpAgent extends BaseAgent implements Runnable { private static final String PROPERTY_SLEEP_ON_CREATE = "mockSnmpAgent.sleepOnCreate"; // initialize Log4J logging static { try { Class.forName("org.apache.log4j.Logger"); LogFactory.setLogFactory(new Log4jLogFactory()); } catch (Exception e) { LogFactory.setLogFactory(new ConsoleLogFactory()); } } private static final LogAdapter s_log = LogFactory.getLogger(MockSnmpAgent.class); private String m_address; private URL m_moFile; private boolean m_running; private boolean m_stopped; private List<ManagedObject> m_moList; private MockSnmpMOLoader m_moLoader; private IOException m_failure; private static File BOOT_COUNT_FILE; public static boolean allowSetOnMissingOid = false; static { File bootCountFile; try { bootCountFile = File.createTempFile("mockSnmpAgent", "boot"); bootCountFile.createNewFile(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(bootCountFile)); out.writeInt(0); out.flush(); out.close(); } catch (IOException e) { bootCountFile = new File("/dev/null"); } BOOT_COUNT_FILE = bootCountFile; } public MockSnmpAgent(final File confFile, final URL moFile) { super(BOOT_COUNT_FILE, confFile, new CommandProcessor(new OctetString(MPv3.createLocalEngineID(new OctetString("MOCKAGENT"))))); m_moLoader = new PropertiesBackedManagedObject(); m_moFile = moFile; agent.setWorkerPool(ThreadPool.create("RequestPool", 4)); } /* * Creates the mock agent with files to read and store the boot counter, * to read and store the agent configuration, and to read the mocked * managed objects (MOs), plus a string describing the address and port * to bind to. * * @param bootFile * a file containing the boot counter in serialized form (as expected by BaseAgent). * @param confFile * a configuration file with serialized management information. * @param moFile * a MIB dump file describing the managed objects to be mocked. The current implementation * expects a Java properties file, which can conveniently be generated using the Net-SNMP * utility <code>snmpwalk</code> with the <code>-One</code> option set. */ /** * <p>Constructor for MockSnmpAgent.</p> * * @param bootFile a {@link java.io.File} object. * @param confFile a {@link java.io.File} object. * @param moFile a {@link org.springframework.core.io.Resource} object. * @param bindAddress a {@link java.lang.String} object. * @throws IOException */ public MockSnmpAgent(final File confFile, final URL moFile, final String bindAddress) { this(confFile, moFile); m_address = bindAddress; } /** * <p>createAgentAndRun</p> * * @param moFile a {@link org.springframework.core.io.Resource} object. * @param bindAddress a {@link java.lang.String} object. * @return a {@link org.opennms.mock.snmp.MockSnmpAgent} object. * @throws java.lang.InterruptedException if any. */ public static MockSnmpAgent createAgentAndRun(URL moFile, String bindAddress) throws InterruptedException { setupLogging(); try { InputStream in = moFile.openStream(); if (in == null) { throw new IllegalArgumentException("could not get InputStream mock object resource; does it exist? Resource: " + moFile); } in.close(); } catch (IOException e) { throw new RuntimeException("Got IOException while checking for existence of mock object file: " + e, e); } final MockSnmpAgent agent = new MockSnmpAgent(new File("/dev/null"), moFile, bindAddress); Thread thread = new Thread(agent, agent.getClass().getSimpleName()); thread.start(); try { while (!agent.isRunning() && thread.isAlive()) { Thread.sleep(10); } } catch (final InterruptedException e) { agent.shutDownAndWait(); throw e; } if (!thread.isAlive()) { agent.m_running = false; agent.m_stopped = true; throw new IllegalStateException("agent failed to start", agent.m_failure); } if (System.getProperty(PROPERTY_SLEEP_ON_CREATE) != null) { long sleep = Long.parseLong(System.getProperty(PROPERTY_SLEEP_ON_CREATE)); Thread.sleep(sleep); } return agent; } private static void setupLogging() { if (LogFactory.getLogFactory() == null) { LogFactory.setLogFactory(new ConsoleLogFactory()); } } /** * <p>main</p> * * @param args an array of {@link java.lang.String} objects. * @throws MalformedURLException * @throws UnknownHostException */ public static void main(String[] args) throws UnknownHostException, MalformedURLException { LogFactory.setLogFactory(new ConsoleLogFactory()); AgentConfigData agentConfig = parseCli(args); if (agentConfig == null) { System.err.println("Could not parse configuration."); System.exit(1); } String listenSpec = agentConfig.getListenAddr().getHostAddress() + "/" + agentConfig.getListenPort(); try { MockSnmpAgent.createAgentAndRun(agentConfig.getMoFile(), listenSpec); } catch (InterruptedException e) { System.exit(0); } } /** * <p>parseCli</p> * * @param args an array of {@link java.lang.String} objects. * @return a {@link org.opennms.mock.snmp.AgentConfigData} object. * @throws MalformedURLException * @throws UnknownHostException */ public static AgentConfigData parseCli(String[] args) throws UnknownHostException, MalformedURLException { String dumpFile = null; String listenAddr = "127.0.0.1"; int listenPort = 1691; for(int i = 0; i < args.length; i++) { if ("-d".equals(args[i]) || "--dump-file".equals(args[i])) { if (i+1 >= args.length) { usage("You must specify at least a pathname or URL for the dump file."); } else { dumpFile = args[++i]; } } else if ("-l".equals(args[i]) || "--listen-addr".equals(args[i])) { if (i+1 >= args.length) { usage("You must pass an address argument when using " + args[i] + "."); } else { listenAddr = args[++i]; } } else if ("-p".equals(args[i]) || "--port".equals(args[i])) { if (i+1 >= args.length) { usage("You must pass a port number when using " + args[i] + "."); } else { listenPort = Integer.parseInt(args[++i]); } } } if (dumpFile == null) { usage("You must specify at least a pathname or URL for the dump file."); } return new AgentConfigData(dumpFile, listenAddr, listenPort); } private static void usage(String why) { System.err.println(why); System.err.println("java -jar mock-snmp-agent-jar-with-dependencies.jar -d dump-file [other options]"); System.err.println("-d, --dump-file {filename}\tPathname or URL of file containing MIB dump"); System.err.println("-l, --listen-addr {ip-address}\tIP address to bind to (default: all interfaces)"); System.err.println("-p, --port {udp-port}\tUDP port to listen on (default: 1691)"); System.exit(1); } /** {@inheritDoc} */ @Override protected void initMessageDispatcher() { dispatcher = new MessageDispatcherImpl(); usm = new USM(SecurityProtocols.getInstance(), agent.getContextEngineID(), updateEngineBoots()); mpv3 = new MPv3(usm); SecurityProtocols.getInstance().addDefaultProtocols(); dispatcher.addMessageProcessingModel(new MPv1()); dispatcher.addMessageProcessingModel(new MPv2c()); dispatcher.addMessageProcessingModel(mpv3); initSnmpSession(); } /** * <p>shutDownAndWait</p> * * @throws java.lang.InterruptedException if any. */ public void shutDownAndWait() throws InterruptedException { if (!isRunning()) { return; } shutDown(); while (!isStopped()) { Thread.sleep(10); } } /* * Starts the <code>MockSnmpAgent</code> running. Meant to be called from the * <code>start</code> method of class <code>Thread</code>, but could also be * used to bring up a standalone mock agent. * @see org.snmp4j.agent.BaseAgent#run() * * @author Jeff Gehlbach * @version 1.0 */ // XXX fix catch blocks /** * <p>run</p> */ public void run() { try { init(); loadConfig(ImportModes.UPDATE_CREATE); addShutdownHook(); finishInit(); super.run(); m_running = true; } catch (final BindException e) { s_log.error(String.format("Unable to bind to %s. You probably specified an invalid address or a port < 1024 and are not running as root.", m_address), e); } catch (final Throwable t) { s_log.error("An error occurred while initializing.", t); } while (m_running) { try { Thread.sleep(10); // fast, Fast, FAST, *FAST*!!! } catch (final InterruptedException e) { Thread.currentThread().interrupt(); break; } } for (final TransportMapping transportMapping : transportMappings) { try { if (transportMapping != null) transportMapping.close(); } catch (final Throwable t) { s_log.error("an error occurred while closing the transport mapping", t); } } m_stopped = true; } /* * */ /** * <p>shutDown</p> */ public void shutDown() { m_running = false; m_stopped = false; } /** * <p>isRunning</p> * * @return a boolean. */ public boolean isRunning() { return m_running; } /** * <p>isStopped</p> * * @return a boolean. */ public boolean isStopped() { return m_stopped; } /** {@inheritDoc} */ @Override protected void addCommunities(SnmpCommunityMIB communityMIB) { Variable[] com2sec = new Variable[] { new OctetString("public"), // community name new OctetString("public"), // security name getAgent().getContextEngineID(), // local engine ID new OctetString(), // default context name new OctetString(), // transport tag new Integer32(StorageType.nonVolatile), // storage type new Integer32(RowStatus.active) // row status }; MOTableRow row = communityMIB.getSnmpCommunityEntry().createRow( new OctetString("public2public").toSubIndex(true), com2sec); communityMIB.getSnmpCommunityEntry().addRow(row); } /** {@inheritDoc} */ @Override protected void addViews(VacmMIB vacm) { vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv1, new OctetString("public"), new OctetString("v1v2group"), StorageType.nonVolatile); vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c, new OctetString("public"), new OctetString("v1v2group"), StorageType.nonVolatile); vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, new OctetString("SHADES"), new OctetString("v3group"), StorageType.nonVolatile); vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, new OctetString("TEST"), new OctetString("v3test"), StorageType.nonVolatile); vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, new OctetString("opennmsUser"), new OctetString("v3group"), StorageType.nonVolatile); vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, new OctetString("SHA"), new OctetString("v3restricted"), StorageType.nonVolatile); vacm.addAccess(new OctetString("v1v2group"), new OctetString(), SecurityModel.SECURITY_MODEL_ANY, SecurityLevel.NOAUTH_NOPRIV, VacmMIB.vacmExactMatch, new OctetString("fullReadView"), new OctetString("fullWriteView"), new OctetString("fullNotifyView"), StorageType.nonVolatile); vacm.addAccess(new OctetString("v3group"), new OctetString(), SecurityModel.SECURITY_MODEL_USM, SecurityLevel.AUTH_PRIV, VacmMIB.vacmExactMatch, new OctetString("fullReadView"), new OctetString("fullWriteView"), new OctetString("fullNotifyView"), StorageType.nonVolatile); vacm.addAccess(new OctetString("v3restricted"), new OctetString(), SecurityModel.SECURITY_MODEL_USM, SecurityLevel.AUTH_NOPRIV, VacmMIB.vacmExactMatch, new OctetString("restrictedReadView"), new OctetString("restrictedWriteView"), new OctetString("restrictedNotifyView"), StorageType.nonVolatile); vacm.addAccess(new OctetString("v3test"), new OctetString(), SecurityModel.SECURITY_MODEL_USM, SecurityLevel.AUTH_PRIV, VacmMIB.vacmExactMatch, new OctetString("testReadView"), new OctetString("testWriteView"), new OctetString("testNotifyView"), StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("fullReadView"), new OID("1.3"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("fullWriteView"), new OID("1.3"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("fullNotifyView"), new OID("1.3"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("restrictedReadView"), new OID("1.3.6.1.2"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("restrictedWriteView"), new OID("1.3.6.1.2.1"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("restrictedNotifyView"), new OID("1.3.6.1.2"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("testReadView"), new OID("1.3.6.1.2"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("testReadView"), new OID("1.3.6.1.2.1.1"), new OctetString(), VacmMIB.vacmViewExcluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("testWriteView"), new OID("1.3.6.1.2.1"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); vacm.addViewTreeFamily(new OctetString("testNotifyView"), new OID("1.3.6.1.2"), new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile); } /** {@inheritDoc} */ @Override protected void addNotificationTargets(SnmpTargetMIB targetMIB, SnmpNotificationMIB notificationMIB) { targetMIB.addDefaultTDomains(); targetMIB.addTargetAddress(new OctetString("notification"), TransportDomains.transportDomainUdpIpv4, new OctetString(new UdpAddress("127.0.0.1/162").getValue()), 200, 1, new OctetString("notify"), new OctetString("v2c"), StorageType.permanent); targetMIB.addTargetParams(new OctetString("v2c"), MessageProcessingModel.MPv2c, SecurityModel.SECURITY_MODEL_SNMPv2c, new OctetString("public"), SecurityLevel.NOAUTH_NOPRIV, StorageType.permanent); notificationMIB.addNotifyEntry(new OctetString("default"), new OctetString("notify"), SnmpNotificationMIB.SnmpNotifyTypeEnum.trap, StorageType.permanent); } /** {@inheritDoc} */ @Override protected void addUsmUser(USM usm) { UsmUser user = new UsmUser(new OctetString("SHADES"), AuthSHA.ID, new OctetString("SHADESAuthPassword"), PrivDES.ID, new OctetString("SHADESPrivPassword")); usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); user = new UsmUser(new OctetString("TEST"), AuthSHA.ID, new OctetString("maplesyrup"), PrivDES.ID, new OctetString("maplesyrup")); usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); user = new UsmUser(new OctetString("opennmsUser"), AuthMD5.ID, new OctetString("0p3nNMSv3"), PrivDES.ID, new OctetString("0p3nNMSv3")); usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); user = new UsmUser(new OctetString("SHA"), AuthSHA.ID, new OctetString("SHAAuthPassword"), null, null); usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); } /** {@inheritDoc} */ @Override protected void initTransportMappings() throws IOException { try { transportMappings = new TransportMapping[1]; transportMappings[0] = new DefaultUdpTransportMapping(new UdpAddress(m_address)); } catch (final IOException e) { m_failure = e; throw e; } } // override the agent defaults since we are providing all the agent data /** {@inheritDoc} */ @Override protected void registerSnmpMIBs() { registerManagedObjects(); } /** {@inheritDoc} */ @Override protected void unregisterSnmpMIBs() { unregisterManagedObjects(); } /** {@inheritDoc} */ @Override protected void registerManagedObjects() { m_moList = createMockMOs(); Iterator<ManagedObject> moListIter = m_moList.iterator(); while (moListIter.hasNext()) { try { server.register(moListIter.next(), null); } catch (final DuplicateRegistrationException ex) { s_log.error("unable to register managed object", ex); } } } /** {@inheritDoc} */ @Override protected void unregisterManagedObjects() { Iterator<ManagedObject> moListIter = m_moList.iterator(); while (moListIter.hasNext()) { server.unregister(moListIter.next(), null); } } /** * <p>createMockMOs</p> * * @return a {@link java.util.List} object. */ protected List<ManagedObject> createMockMOs() { return m_moLoader.loadMOs(m_moFile); } private ManagedObject findMOForOid(OID oid) { for(ManagedObject mo : m_moList) { if (mo.getScope().covers(oid)) { return mo; } } return null; } /** * <p>updateValue</p> * * @param oid a {@link org.snmp4j.smi.OID} object. * @param value a {@link org.snmp4j.smi.Variable} object. */ public void updateValue(OID oid, Variable value) { ManagedObject mo = findMOForOid(oid); assertNotNull("Unable to find oid in mib for mockAgent: "+oid, mo); if (mo instanceof Updatable) { ((Updatable)mo).updateValue(oid, value); } } private void assertNotNull(final String string, final Object o) { if (!allowSetOnMissingOid && o == null) { throw new IllegalStateException(string); } } /** * <p>updateValue</p> * * @param oid a {@link java.lang.String} object. * @param value a {@link org.snmp4j.smi.Variable} object. */ public void updateValue(String oid, Variable value) { updateValue(new OID(oid), value); } /** * <p>updateIntValue</p> * * @param oid a {@link java.lang.String} object. * @param val a int. */ public void updateIntValue(String oid, int val) { updateValue(oid, new Integer32(val)); } /** * <p>updateStringValue</p> * * @param oid a {@link java.lang.String} object. * @param val a {@link java.lang.String} object. */ public void updateStringValue(String oid, String val) { updateValue(oid, new OctetString(val)); } /** * <p>updateCounter32Value</p> * * @param oid a {@link java.lang.String} object. * @param val a int. */ public void updateCounter32Value(String oid, int val) { updateValue(oid, new Counter32(val)); } /** * <p>updateCounter64Value</p> * * @param oid a {@link java.lang.String} object. * @param val a long. */ public void updateCounter64Value(String oid, long val) { updateValue(oid, new Counter64(val)); } /** * <p>updateValuesFromResource</p> * * @param moFile a {@link org.springframework.core.io.Resource} object. */ public void updateValuesFromResource(final URL moFile) { unregisterManagedObjects(); m_moFile = moFile; registerManagedObjects(); } /** * <p>toString</p> * * @return a {@link java.lang.String} object. */ public String toString() { return "MockSnmpAgent["+m_address+"]"; } }