Let's build a simple application to illustrate the use of the SNMP Package. We'll choose the snmpget application provided with the package. This application allows you to run a command line request to a managed device to get an attribute of a managed object.
There are two class files, or .java files, for this application. One called ParseOptions.java is a generic class file to help with parsing options. We won't spend too much time with it. The class file we need to look at is snmpget.java.
First let's take care of imports. We'll need the Advent SNMP package and the ParseOptions class in addition to the JDK packages listed.
import java.lang.*;
import java.util.*;
import java.net.*;
import Snmp.*;
import ParseOptions;
Lets declare the snmpget class and the main method, which must be static.
public class snmpget {
public static void main(String args[]) {
Next we need to parse the options using the ParseOptions class. This class accepts an array of options, e.g. "-d", and returns an array of strings (values) associated with the option array. The strings are null if the particular option was not specified. The "None" string needs to be set for any option that does not have an associated string. The "remArgs" array in ParseOptions contains the remaining arguments.
// Take care of getting options
String usage = "snmpget [-d] [-c community] [-m MIB_file] [-m2 Second_MIB_file] [-p port] [-t timeout] [-r retries] host OID";
String options[] = { "-d", "-c", "-m", "-m2", "-p", "-r", "-t" };
String values[] = { "None", null,null, null, null, null, null};
ParseOptions opt = new ParseOptions(args,options,values,usage);
Now we come to the SNMP package stuff. Any application or applet that uses the SNMP Package needs to instantiate and start the SnmpAPI class. Here we also check if the -d flag was set on the command line and set DEBUG to true if it was.
// Start SNMP API
SnmpAPI api = new SnmpAPI();
api.start();
if (values[0].equals("Set")) api.DEBUG = true;
If MIB files were specified (with the -m, and -m2 options) we need to load the MIB modules. The package permits only one module per MIB file, and you need -m2 if you have to load a second. You'd have to change the code to have it allow more than two MIB modules at a time. MIB loading and parsing is not snappy, and can make this snmpget slow.
A MIB is loaded and parsed by instantiating the MibModule class. Here we specify the MIB file as an argument to the constructor, along with the SnmpAPI instance, and whether debugging is turned on. Debugging output while loading a MIB is quite verbose, and is recommended only if parsing fails for any reason.
The MibModule constructor throws a bunch of exceptions, and all we're doing here is catching and printing them. There are two new exception classes with the Advent SNMP package. These are the SnmpException and the MibException for MIB related exceptions.
// Next deal with MIBs
MibModule m1, m2;
if ( (values[2] !=null)||(values[3] !=null) ) try { // Open the MIB module
System.out.println("Loading MIB files: " + values[2] +" "+ values[3]);
if (values[2] != null) m1 = new MibModule(values[2], api,api.DEBUG);
if (values[3] != null) m2 = new MibModule(values[3], api,api.DEBUG);
} catch(Exception e) { System.err.println(e); }
The minimum number of arguments is two, as shown in the usage string above. So we look at the remArgs array and make sure the user has specified the SNMP agent host and at least one OID to get from the agent.
if (opt.remArgs.length<2) opt.usage_error();
The SnmpSession class is used to communicate with an SNMP peer. All communication using the SNMP Package needs to be via an SnmpSession instance. You can have as many of these as you like, but be aware that each session is a thread, and you may not want to keep opening sessions unless you need them. You can use one session to communicate with multiple SNMP peers if you like. It makes sense to use sessions dedicated to a single peer if you are sending many PDUs to that peer.
We now instantiate the SnmpSession. The first remaining argument is the hostname of the SNMP peer we're talking to and we set the peername on the session. This peername will be used on all PDUs sent on this session where the PDU address is not exlicitly set. If the user has specified non-default community string, or the remote port, or the timeout, or retries, we need to set these for the session we'll be using. We'll have to catch any problems with converting the string arguments to integers.
// Open session and set remote host & port if needed,
SnmpSession session = new SnmpSession(api);
session.peername = opt.remArgs[0];
if (values[1] != null) session.community = values[1];
try {
if (values[4] != null) session.remote_port = Integer.parseInt(values[4]);
if (values[5] != null) session.retries = Integer.parseInt(values[5]);
if (values[6] != null) session.timeout = Integer.parseInt(values[6]);
}
catch (NumberFormatException ex) { System.err.println("Invalid Integer Arg"); }
We now move to the SNMP PDUs we'll use to send a request. An SnmpPDU instance needs to be created to send any request to an SNMP peer. So we create a PDU and set the command to an SNMP get request. The command constants are defined in the SnmpAPI class, where you'll find most of the constants.
// Build get request PDU
SnmpPDU pdu = new SnmpPDU(api);
pdu.command = api.GET_REQ_MSG;
The user has specified one or more OIDs on the command line, and these need to be added to the PDU. The SnmpOID class provides a constructor that takes a string argument and the SnmpAPI instance. The string argument can be of the form X.X...X, or .X.X...X, where in the latter case the OID is assumed to be fully qualified, and not fully qualified in the first case. When not fully qualified, the SnmpAPI.Standard_Prefix is added to the OID. This prefix (is static) can be changed by the user, but has to apply across the entire application or applet.
We need to support users specifying names instead of numbers, provided they've loaded the right MIB modules. Therefore, we will need to search for a MIB node with the specified name. For this reason, the SnmpAPI instance is specified in this constructor. The constructor will use it to search all MIB modules in that SnmpAPI instance for the node matching the OID string. When the SnmpAPI instance is specified, X in the above OID can be the label of a MIB node, e.g. iso, sysDescr, etc. However, if string labels are used, a valid SnmpOID instance is created only if the MIB node is found in the modules of the SnmpAPI instance specified.
If the SnmpAPI instance is not specified, or there is no node matching the OID string in the modules of the SnmpAPI instance, X must be a number in the above oid string. Else the OID value would be null.
In the code fragment below, we add each OID specified on the command line to the PDU. We check to make sure a valid OID has been created by the constructor before adding the OID to the PDU. The addNull method in the SnmpPDU class adds a variable binding with the OID specified and a null variable value.
for (int i=1;i<opt.remArgs.length;i++) { // add OIDs
SnmpOID oid = new SnmpOID(opt.remArgs[i], api);
if (oid.toValue() == null) System.err.println("Invalid OID argument: " + opt.remArgs[i]);
else pdu.addNull(oid);
}
The session needs to be opened before it can be used. This results in a datagram socket being created for our use. If you are running a daemon (e.g. to receive traps), you would need to specify the local port before opening the session.
We will also send the PDU now, and use the synchronous method to send the PDU, since we don't mind waiting. An exception is thrown if there are any problems on send.
try {
session.open();
// Send PDU
pdu = session.syncSend(pdu);
} catch (SnmpException e) { System.err.println("Sending PDU"+e.getMessage()); }
If the call returns and the returned PDU is null, the request has timed out.
if (pdu == null) { // timeout
System.out.println("Request timed out to: " + opt.remArgs[0] );
System.exit(1);
}
Otherwise, we got a response PDU and we'll let the user know.
// print and exit
System.out.println("Response received from " +pdu.address+
", community: " + pdu.community);
Check for errors on the PDU from the agent.
if (pdu.errstat != 0)
System.out.println("Error Indication in response: " +
SnmpException.exceptionString((byte)pdu.errstat) +
"\nErrindex: " + pdu.errindex);
Print the variable bindings and we're done.
System.out.println(pdu.printVarBinds());
System.exit(0);
}
}
You can build similar applications quite easily, for example it's easy to extend this to poll the variables and show the deltas continuously, etc. All you need is a little JAVA which can provide plenty of energy to meet your application needs.