Thứ Ba, 26 tháng 3, 2013

Introduction how to use snmp4j

SNMP is a widely accepted technology and is used in to monitor a wide variety of devices, but as it turns out very few people (at least among java programmers) seems to know anything about how to build snmp based solutions.

This post will not discuss anything at all about SNMP theory, instead I will focus on how to dig right into building a simple solution using snmp4j. So, if your boss just told you to implement any type of snmp technology in a java product and you have no idea where to start, this post is for you. Unfortunately, it is impossible to cover all aspects of snmp4j (and honestly I have a lot to learn) but hopefully this will get you up and running quickly.

We will create a simple client and a simple agent (with support for snmp version 2c) that we will use in an automated test.

SNMP crash course

SNMP was created to be very simple but it is actually quite hard to grasp at first glance, but there are other great sources that covers this. However, while writing I realized that there are really a few things that you must know before you can really understand this post.

- MIB (Management Information Base), if you have no idea what this is it might be a good idea to briefly read about it at wikipedia. To make it easy, think of a MIB as a tree.

- OID (Object identifier), represents the key of a tree node and is used to GET/SET values in that node. An example is .1.3.6.1.2.1.1.1.

- Scalar object, a node with a single object value.

- MIB table, allows for grouping of values in a node and it can contain multiple rows.

Other resources

Wikipedia | snmplink.org | Beginners guide (short!)

Getting started

This example project is built using maven 2.2.1 and java6. It might also be good to download the snmp4j sources from http://www.snmp4j.org.

Snmp4j is the leading open source technology for snmp based java solutions and has an API for both snmp clients (or managers) and agents and is licensed under Apache 2.

Download example sources

All example code published in this post can be downloaded in whole in the complete project zip file or on github https://github.com/jrask/snmp-blog.

Building your first snmp client

A client (or snmp manager) is an application that queries an agent for information. It may also receive traps from agents but this will not be discussed here. Building a client is very easy and this is well described in the javadoc for the org.snmp4j.Snmp class.

Below is some code fragments from my class SimpleSnmpClient which is included in the project.

1. Start the Snmp session.

If you forget the listen() method you will not get any answers because under the hood the communication is asynchronous and the listen() method listens for answers.

private void start() throws IOException {

        TransportMapping transport = new DefaultUdpTransportMapping();

snmp = new Snmp(transport);

// Do not forget this line!

transport.listen();

}

2. To make is as simple as possible in my client I have a simple method which takes a single OID and returns the response from the agent as a String.

public String getAsString(OID oid) throws IOException {

ResponseEvent event = get(new OID[]{oid});

return event.getResponse().get(0).getVariable().toString();

}

3. This method is more generic and is capable of handling multiple OIDs. In a real application with lots of agents you would probably implement this asynchronously with a ResponseListener instead to prevent your threadpool from being exhausted.

public ResponseEvent get(OID oids[]) throws IOException {

PDU pdu = new PDU();

         for (OID oid : oids) {

              pdu.add(new VariableBinding(oid));

         }

         pdu.setType(PDU.GET);

         ResponseEvent event = snmp.send(pdu, getTarget(), null);

if(event != null) {

             return event;

}

throw new RuntimeException("GET timed out");

}

4. This method returns a Target, which contains information about where the data should be fetched and how.

private Target getTarget() {

Address targetAddress = GenericAddress.parse(address);

CommunityTarget target = new CommunityTarget();

target.setCommunity(new OctetString("public"));

target.setAddress(targetAddress);

target.setRetries(2);

target.setTimeout(1500);

target.setVersion(SnmpConstants.version2c);

return target;

}

Invoking the client

Using the code below and aim for i.e. one of your routers (if snmp is enabled) you will get som basic information about what kind of router it is.

SimpleSnmpClient client = new SimpleSnmpClient("udp:10.0.0.50/161");

String sysDescr = client.getAsString(new OID(".1.3.6.1.2.1.1.1.0"));

System.out.println(sysDescr);

That was really easy, and it is basically the same code you can find in the org.snmp4j.Snmp class so we haven´t provided anything new yet. Ok, so what if we would like to test this client without accessing the physical router? Thats next.

Creating a super simple snmp agent

If creating an snmp client is easy, creating an snmp agent (and knowing what you are doing) is a much harder. To help you get started, snmp4j provides two classes; BaseAgent which can be subclassed to build your custom agent, and TestAgent which is an executable class that extends BaseAgent. The problem once you start looking at these classes is that it is very difficult to understand what they do unless you are an snmp expert, so instead I have created an agent implementation that only contains the minimum functionality to support snmp v2c. Basically what I have done is to rip out the parts from TestAgent that I really did not need for this demo.

   // package and imports removed

/**

 * This Agent contains mimimal functionality for running a version 2c snmp agent.

 */

public class Agent extends BaseAgent {

// not needed but very useful of course

static {

LogFactory.setLogFactory(new Log4jLogFactory());

}

private String address;

public Agent(String address) throws IOException {

// These files does not exist and are not used but has to be specified

// Read snmp4j docs for more info

super(new File("conf.agent"), new File("bootCounter.agent"),

new CommandProcessor(

new OctetString(MPv3.createLocalEngineID())));

this.address = address;

}

/**

 * We let clients of this agent register the MO they

 * need so this method does nothing

 */

@Override

protected void registerManagedObjects() {

}

/**

 * Clients can register the MO they need

 */

public void registerManagedObject(ManagedObject mo) {

try {

server.register(mo, null);

} catch (DuplicateRegistrationException ex) {

throw new RuntimeException(ex);

}

}

public void unregisterManagedObject(MOGroup moGroup) {

moGroup.unregisterMOs(server, getContext(moGroup));

}

/*

 * Empty implementation

 */

@Override

protected void addNotificationTargets(SnmpTargetMIB targetMIB,

SnmpNotificationMIB notificationMIB) {

}

/**

 * Minimal View based Access Control

 *

 * http://www.faqs.org/rfcs/rfc2575.html

 */

@Override

protected void addViews(VacmMIB vacm) {

vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c, new OctetString(

"cpublic"), new OctetString("v1v2group"),

StorageType.nonVolatile);

vacm.addAccess(new OctetString("v1v2group"), new OctetString("public"),

SecurityModel.SECURITY_MODEL_ANY, SecurityLevel.NOAUTH_NOPRIV,

MutableVACM.VACM_MATCH_EXACT, new OctetString("fullReadView"),

new OctetString("fullWriteView"), new OctetString(

"fullNotifyView"), StorageType.nonVolatile);

vacm.addViewTreeFamily(new OctetString("fullReadView"), new OID("1.3"),

new OctetString(), VacmMIB.vacmViewIncluded,

StorageType.nonVolatile);

}

/**

 * User based Security Model, only applicable to

 * SNMP v.3

 *

 */

protected void addUsmUser(USM usm) {

}

protected void initTransportMappings() throws IOException {

transportMappings = new TransportMapping[1];

Address addr = GenericAddress.parse(address);

TransportMapping tm = TransportMappings.getInstance()

.createTransportMapping(addr);

transportMappings[0] = tm;

}

/**

 * Start method invokes some initialization methods needed to

 * start the agent

 * @throws IOException

 */

public void start() throws IOException {

init();

// This method reads some old config from a file and causes

// unexpected behavior.

// loadConfig(ImportModes.REPLACE_CREATE);

addShutdownHook();

getServer().addContext(new OctetString("public"));

finishInit();

run();

sendColdStartNotification();

}

protected void unregisterManagedObjects() {

// here we should unregister those objects previously registered...

}

/**

 * The table of community strings configured in the SNMP

 * engine's Local Configuration Datastore (LCD).

 *

 * We only configure one, "public".

 */

protected void addCommunities(SnmpCommunityMIB communityMIB) {

Variable[] com2sec = new Variable[] {

new OctetString("public"), // community name

new OctetString("cpublic"), // security name

getAgent().getContextEngineID(), // local engine ID

new OctetString("public"), // 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);

}

}

Testing the client agains the agent

Next we will hook the agent and client into a JUnit test to verify that it works as expected.

// standard in RFC-1213

static final OID sysDescr = new OID(".1.3.6.1.2.1.1.1.0");

@BeforeClass

public static void setUp() throws Exception {

agent = new Agent("0.0.0.0/2001");

agent.start();

// Since BaseAgent registers some mibs by default we need to unregister

// one before we register our own sysDescr. Normally you would

// override that method and register the mibs that you need

agent.unregisterManagedObject(agent.getSnmpv2MIB());

// Register a system description, use one from you product environment

// to test with

agent.registerManagedObject(MOScalarFactory.createReadOnly(sysDescr,"MySystemDescr"));

        // Setup the client to use our newly started agent

client = new SimpleSnmpClient("udp:127.0.0.1/2001");

}

@AfterClass

public static void tearDown() throws Exception {

agent.stop();

        client.stop();

}

@Test

public void verifySysDescr() throws IOException {

     assertEquals("MySystemDescr", client.getAsString(sysDescr));

}

/**

  * Uses asynchronous fetch and test it with Awaitility.

  */

@Test

public void verifySysDescrAsynch() throws Exception {

    final StringResponseListener listener = new StringResponseListener();

    client.getAsString(sysDescr, listener);

    await().until(callTo(listener).getValue(),equalTo("MySystemDescr"));

}

Adding MIB tables to your agent

You will probably not get away from creating a table so the final thing I will show is how we create and test this. To show how this is done I will first create a table in the agent and then write a test that will pull the data from the table and verify the contents.

To create the table I have created a utility class that can be found in the downloadable project.

// standard in RFC-1213

static final OID interfacesTable = new OID(".1.3.6.1.2.1.2.2.1");

@BeforeClass

public static void setUp() throws Exception {

agent = new Agent("0.0.0.0/2001");

agent.start();

// Build a table. This example is taken from TestAgent and sets up

// two physical interfaces

MOTableBuilder builder = new MOTableBuilder(interfacesTable)

.addColumnType(SMIConstants.SYNTAX_INTEGER,MOAccessImpl.ACCESS_READ_ONLY)

.addColumnType(SMIConstants.SYNTAX_OCTET_STRING,MOAccessImpl.ACCESS_READ_ONLY)

.addColumnType(SMIConstants.SYNTAX_INTEGER,MOAccessImpl.ACCESS_READ_ONLY)

.addColumnType(SMIConstants.SYNTAX_INTEGER,MOAccessImpl.ACCESS_READ_ONLY)

.addColumnType(SMIConstants.SYNTAX_GAUGE32,MOAccessImpl.ACCESS_READ_ONLY)

.addColumnType(SMIConstants.SYNTAX_OCTET_STRING,MOAccessImpl.ACCESS_READ_ONLY)

.addColumnType(SMIConstants.SYNTAX_INTEGER,MOAccessImpl.ACCESS_READ_ONLY)

.addColumnType(SMIConstants.SYNTAX_INTEGER,MOAccessImpl.ACCESS_READ_ONLY)

// Normally you would begin loop over you two domain objects here

.addRowValue(new Integer32(1))

.addRowValue(new OctetString("loopback"))

.addRowValue(new Integer32(24))

.addRowValue(new Integer32(1500))

.addRowValue(new Gauge32(10000000))

.addRowValue(new OctetString("00:00:00:00:01"))

.addRowValue(new Integer32(1500))

.addRowValue(new Integer32(1500))

//next row

.addRowValue(new Integer32(2))

.addRowValue(new OctetString("eth0"))

.addRowValue(new Integer32(24))

.addRowValue(new Integer32(1500))

.addRowValue(new Gauge32(10000000))

.addRowValue(new OctetString("00:00:00:00:02"))

.addRowValue(new Integer32(1500))

.addRowValue(new Integer32(1500));

agent.registerManagedObject(builder.build());

// Setup the client to use our newly started agent

client = new SimpleSnmpClient("udp:127.0.0.1/2001");

}

To test that we receive expected table data I have created a method getTableAsString() method in the SimpleSnmpClient class. This method uses org.snmp4j.util.TableUtils getTable() method.

/**

* Normally this would return domain objects or something else than this...

 */

public List<list<string>> getTableAsStrings(OID[] oids) {

TableUtils tUtils = new TableUtils(snmp, new DefaultPDUFactory());

@SuppressWarnings("unchecked")

List<tableEvent> events = tUtils.getTable(getTarget(), oids, null, null);

List<list<string>> list = new ArrayList<list<string>>();

for (TableEvent event : events) {

if(event.isError()) {

throw new RuntimeException(event.getErrorMessage());

}

List<string> strList = new ArrayList<string>();

list.add(strList);

for(VariableBinding vb: event.getColumns()) {

strList.add(vb.getVariable().toString());

}

}

return list;

}

To get table contents we supply which table columns that we want to fetch. In my test I only supply three columns and this is done by adding a column number(starts with 1) after the table OID.

@Test

public void verifyTableContents() {

// You retreive a table by suppling the columns of the table that

// you need, here we use column 2,6 and 8 so we do not verify the complete

// table

List<list<string>> tableContents = client.getTableAsStrings(new OID[]{

                   new OID(interfacesTable.toString() + ".2"),

                    new OID(interfacesTable.toString() + ".6"),

                    new OID(interfacesTable.toString() + ".8")});

//and validate here

assertEquals(2, tableContents.size());

assertEquals(3, tableContents.get(0).size());

assertEquals(3, tableContents.get(1).size());

// Row 1

assertEquals("loopback", tableContents.get(0).get(0));

assertEquals("00:00:00:00:01", tableContents.get(0).get(1));

assertEquals("1500", tableContents.get(0).get(2));

// Row 2

assertEquals("eth0", tableContents.get(1).get(0));

assertEquals("00:00:00:00:02", tableContents.get(1).get(1));

assertEquals("1500", tableContents.get(1).get(2));

}

That´s it, please have a look at the complete source if anything is unclear. The javadoc at http://www.snmp4j.org/ is also very useful.

Good luck!

8 nhận xét:

  1. Please let me know if you're looking for a article writer for your weblog. You have some really great articles and I believe I would be a good asset. If you ever want to take some of the load off, I'd love to write some articles for your blog in exchange for a link back to mine.
    Please send me an email if interested. Cheers!

    Trả lờiXóa
  2. i'm very glad when we are friend.That's wonderful if you if you are ready to become a article writer for your weblog.If you have any questions you can contact to my email: vusonbk@gmail.com .Thanks.

    Trả lờiXóa
  3. Does your site have a contact page? I'm having a tough time locating it but, I'd like to shoot you
    an e-mail. I've got some recommendations for your blog you might be interested in hearing. Either way, great site and I look forward to seeing it grow over time.

    Trả lờiXóa
  4. Thanks you very much,I'll try to improve it better over time

    Trả lờiXóa
  5. Title...

    [...]check beneath, are some completely unrelated websites to ours, nevertheless, they may be most trustworthy sources that we use[...]...

    Trả lờiXóa
  6. Most online content cannot measure up to the work you have written here. Your insight has convinced me of many of the points you expressed.

    Trả lờiXóa