The Java Management extensions specify the SNMP manager API for implementing an SNMP manager application in the Java programming language. This API is covered in the JMX specification and in the Javadoc API provided with the Java Dynamic Management Kit (see "Related Books" in the preface for more information). Here we explain the example applications that use this API.
The SNMP manager API can be used to access any SNMP agent, not just those developed with the Java Dynamic Management Kit. It is compatible with both SNMP v1 and v2, and it includes mechanisms for handling traps. It lets you program both synchronous managers which block while waiting for responses and multi-threaded asynchronous managers that don't. Managers may also communicate with other managers using inform requests and responses.
The complete source code for these applications is available in the Snmp/Manager and Snmp/Inform example directories located in the main examplesDir (see "Directories and Classpath" in the preface).
Contents:
"The Synchronous Manager Example" shows the simplest way to program an SNMP manager in the Java programming language.
"The Asynchronous Manager Example" demonstrates the advantages of a manager that doesn't block when sending request.
"The Inform Request Example" shows how two managers can exchange management information.
The synchronous SNMP manager is the simplest to program: the manager sends a request to an agent (peer) and waits for the answer. During the wait, the manager is blocked until either a response is received or the timeout period expires.
The SNMP manager API allows two ways of referring to variables when issuing requests:
By OID (for example, "1.3.6.1.2.1.11.29")
Or by name ("SnmpOutTraps" in this case)
Referring directly to OIDs requires no setup but makes code less flexible. The advantages of using variable names are simplified coding and the independence of manager code when custom MIBs are modified. The SNMP manager API supports variable names by storing a description of the MIB it will access in the static SnmpOid object.
In order to refer to variable names, the manager needs to initialize this description with an OID table object. The OID table is instantiated from the SnmpOidTableSupport class generated by the mibgen tool when "compiling" the MIB. Since this support class is regenerated whenever the MIB is recompiled, the new MIB definition will be automatically loaded into the manager when it is launched (see the code example below).
The SNMP manager API specifies the SnmpPeer object for describing an agent, and the SnmpParameters object for describing its read-write communities and its protocol version (SNMPv1 or SNMPv2). The SnmpSession is an object for sending requests and we can associate a default peer to it. The session instance has an SnmpOptions field which we can use to set multiplexing and error fixing behavior.
The objects specified by the SNMP manager API are not MBeans and cannot be registered in an MBean server to create a manager that could be controlled remotely. However, you could write an MBean that uses these classes to retrieve and expose information from SNMP agents.
A manager can contain any number of peers, one for each agent it wishes to access, and any number of sessions, one for each type of behavior it wishes to implement. Once the peers and the sessions are initialized, the manager can build lists of variables and send session requests to operate on them. The session returns a request object, and the manager calls its waitForCompletion method with the desired timeout delay.
Finally, the manager analyzes the result of the request, first to see if there were any errors, then to extract the data returned by the request.
Here is the code of the main method of the SyncManager application. It applies all of the above steps to execute a very simple management operation.
// read the command line parameters String host = argv[0]; String port = argv[1]; // Specify the OidTable containing all the MIB II knowledge // Use the OidTable generated by mibgen when compiling MIB II // SnmpOidTableSupport oidTable = new RFC1213_MIBOidTable(); SnmpOid.setSnmpOidTable(oidTable); SnmpPeer agent = new SnmpPeer(host, Integer.parseInt(port)); // When creating the parameter object, you can specify the // read and write community to be used when querying the agent. SnmpParameters params = new SnmpParameters("public", "private"); agent.setSnmpParam(params); SnmpSession session = new SnmpSession("SyncManager session"); // When invoking a service provided by the SnmpSession, it // will use the default peer if none is specified explicitly session.setDefaultPeer(agent); // Create a listener and dispatcher for SNMP traps: // SnmpEventReportDispatcher will run as a thread and // listens for traps in UDP port = agent port + 1 SnmpEventReportDispatcher trapAgent = new SnmpEventReportDispatcher(Integer.parseInt(port)+1); // TrapListenerImpl will receive a callback // when a valid trap PDU is received. trapAgent.addTrapListener(new TrapListenerImpl()); new Thread(trapAgent).start(); // Build the list of variables you want to query. // For debugging, you can associate a name to your list. SnmpVarbindList list= new SnmpVarbindList( "SyncManager varbind list"); // We want to read the "sysDescr" variable. list.addVariable("sysDescr.0"); // Make the SNMP get request and wait for the result. SnmpRequest request = session.snmpGet(null, list); boolean completed = request.waitForCompletion(10000); // Check for a timeout of the request. if (completed == false) { java.lang.System.out.println( "Request timed out. Check reachability of agent"); java.lang.System.exit(0); } // Check if the response contains an error. int errorStatus = request.getErrorStatus(); if (errorStatus != SnmpDefinitions.snmpRspNoError) { java.lang.System.out.println("Error status = " + SnmpRequest.snmpErrorToString(errorStatus)); java.lang.System.out.println("Error index = " + request.getErrorIndex()); java.lang.System.exit(0); } // Now we can extract the content of the result. SnmpVarbindList result = request.getResponseVbList(); java.lang.System.out.println("Result: \n" + result); [...] // Wait for user to type enter. Traps will be handled. // End the session properly and we're done session.destroySession(); java.lang.System.exit(0); |
In this SNMP manager application, we demonstrate how to implement and enable a trap listener for the traps sent by the agent. First we need to instantiate an SnmpEventReportDispatcher object. Then we add our listener implementation through its addTrapListener method, and finally we start its thread. Trap listeners can be implemented in any manager using the SNMP manager API, not only synchronous managers.
A trap handler for the SNMP manager is an object that implements the SnmpTrapListener interface in the javax.management.snmp.manager package. When this object is bound as a listener of an SnmpEventReportDispatcher object, its methods will be called to handle trap PDUs.
A trap listener is not a notification listener because the dispatcher is not a notification broadcaster. The listener has callback methods that the work in the same manner, but they are given objects that represent traps, not instances of the Notification class.
The interface defines two methods, one for processing SNMPv1 traps and the other for SNMPv2 traps. Trap PDU packets have already been decoded by the dispatcher, and these methods handle an object representation of the trap: SnmpPduTrap objects for v1 and SnmpPduRequest objects for v2. In our implementation, we are only interested in v1 traps, and we just print out the trap information fields.
public class TrapListenerImpl implements SnmpTrapListener { public void processSnmpTrapV1(SnmpPduTrap trap) { java.lang.System.out.println( "NOTE: TrapListenerImpl received trap :"); java.lang.System.out.println( "\tGeneric " + trap.genericTrap); java.lang.System.out.println( "\tSpecific " + trap.specificTrap); java.lang.System.out.println( "\tTimeStamp " + trap.timeStamp); java.lang.System.out.println( "\tAgent address " + trap.agentAddr.stringValue()); } public void processSnmpTrapV2(SnmpPduRequest trap) { java.lang.System.out.println("NOTE: Trap V2 ignored"); } } |
In the examplesDir/Snmp/Manager directory, we first need to generate the OID table description of MIB-II that our manager will access. Then we compile the example classes. To set up your environment, see "Directories and Classpath" in the preface.
$ mibgen -mo mib_II.txt [output omitted] $ javac -classpath classpath -d . *.java |
Make sure that no other agent is running on port 8085, and launch the simple SNMP agent in examplesDir/Snmp/Agent. See "MIB Development Process" if you have not already built and run this example.
Here we give commands for launching the applications from different Unix terminal windows running the Korn shell. In the first window, enter the following commands:
$ cd examplesDir/Snmp/Agent $ java -classpath classpath Agent nbTraps |
If you will also be running the asynchronous manager example with this agent, omit the nbTraps parameter. The agent will then send traps continuously and they can be seen in both managers. Otherwise, specify the number of traps to be sent to the manager. Wait until the manager is started to send the traps.
Now we can launch the manager application in another window to connect to this agent. If you wish to run the manager on a different host, replace localhost with the name of the machine where you launched the agent.
$ cd examplesDir/Snmp/Manager $ java -classpath classpath SyncManager localhost 8085 SyncManager::main: Send get request to SNMP agent on localhost at port 8085 Result: [Object ID : 1.3.6.1.2.1.1.1.0 (Syntax : String) Value : SunOS sparc 5.7] |
Here we see the output of the SNMP request, it is the value of the sysDescr variable on the agent.
Now press <Enter> in the agent's window: you should see the manager receiving the traps it sends. Leave the agent running if you are going on to the next example, otherwise remember to stop it by typing <Control-C>.
The asynchronous SNMP manager lets you handle more requests in the same amount of time because the manager is not blocked waiting for responses. Instead, it creates a request handler object which runs as a separate thread and processes several responses concurrently. Otherwise, the initialization of peers, parameters, sessions, options, and dispatcher is identical to that of a synchronous manager.
// read the command line parameters String host = argv[0]; String port = argv[1]; // Use the OidTable generated by mibgen when compiling MIB-II. SnmpOidTableSupport oidTable = new RFC1213_MIBOidTable(); // Sample use of the OidTable. SnmpOidRecord record = oidTable.resolveVarName("udpLocalPort"); java.lang.System.out.println( "AsyncManager::main: variable = " + record.getName() + " oid = " + record.getOid() + " type = " + record.getType()); // Initialize the SNMP Manager API. SnmpOid.setSnmpOidTable(oidTable); // Create an SnmpPeer object for representing the agent SnmpPeer agent = new SnmpPeer(host, Integer.parseInt(port)); // Create parameters for communicating with the agent SnmpParameters params = new SnmpParameters("public", "private"); agent.setSnmpParam(params); // Build the session and assign its default peer SnmpSession session = new SnmpSession("AsyncManager session"); session.setDefaultPeer(agent); // Same dispatcher and trap listener as in SyncManager example SnmpEventReportDispatcher trapAgent = new SnmpEventReportDispatcher(Integer.parseInt(port)+1); trapAgent.addTrapListener(new TrapListenerImpl()); new Thread(trapAgent).start(); // Build the list of variables to query SnmpVarbindList list = new SnmpVarbindList("AsyncManager varbind list"); list.addVariable("sysDescr.0"); // Create a simple implementation of an SnmpHandler. AsyncRspHandler handler = new AsyncRspHandler(); // Make the SNMP walk request with our handler SnmpRequest request = session.snmpWalkUntil( handler, list, new SnmpOid("sysServices")); // Here you could do whatever processing you need. // In the context of the example, we are just going to wait // 4 seconds while the response handler displays the result. Thread.sleep(4000); [...] // Wait for user to type enter. Traps will be handled. // End the session properly and we're done. // session.destroySession(); java.lang.System.exit(0); |
The trap mechanism in this application is identical to the one presented in the SyncManager example. The event report dispatcher will receive traps and call the corresponding method of our SnmpTrapListener class.
In this example, the manager performs an snmpWalkUntil request which will give a response for each variable that it gets. The response handler will be called to process each of these responses.
A response handler for an asynchronous manager is an implementation of the SnmpHandler interface. When a handler object is associated with a request, its methods are called when the agent returns an answer or fails to return an answer. In these methods, you implement whatever actions you wish for processing the responses to a request. Typically, these methods will extract the result of each request or the reason for its failure.
The timeout used by the request handler is the one specified by the SnmpPeer object representing the agent. The handler is also called to process any errors caused by the request in the session. This ensures that the manager application is never interrupted after issuing a request.
public class AsyncRspHandler implements SnmpHandler { // Empty constructor public AsyncRspHandler() { } // Called when the agent responds to a request public void processSnmpPollData( SnmpRequest request, int errStatus, int errIndex, SnmpVarbindList vblist) { java.lang.System.out.println( "Processing response: " + request.toString()); java.lang.System.out.println( "errStatus = " + SnmpRequest.snmpErrorToString(errStatus) + " errIndex = " + errIndex); // Check if a result is available. if (request.getRequestStatus() == SnmpRequest.stResultsAvailable) { // Extract the result for display SnmpVarbindList result = request.getResponseVbList(); java.lang.System.out.println( "Result = " + result.vbListToString()); } } // Called when the agent fails to respond to a request public void processSnmpPollTimeout(SnmpRequest request) { java.lang.System.out.println( "Request timed out: " + request.toString()); if (request.getRequestStatus() == SnmpRequest.stResultsAvailable) { // The result is empty and will display an error message SnmpVarbindList result = request.getResponseVbList(); java.lang.System.out.println( "Result = " + result.vbListToString()); } } // Called when there is an error in the session public void processSnmpInternalError(SnmpRequest request, String errmsg) { java.lang.System.out.println( "Session error: " + request.toString()); java.lang.System.out.println("Error is: " + errmsg); } } |
If you have not done so already, launch the simple SNMP agent in examplesDir/Snmp/Agent, after making sure that no other agent is running on port 8085. This manager also uses the OID table description (the SnmpOidTableSupport class) that we generated from the MIB for the synchronous manager. If you have not already done so, see "Running the SyncManager Example" for instructions on how to do this.
If you do not have an SNMP agent still running, make sure that no other agent is running on port 8085 and launch one with the following command:
$ cd examplesDir/Snmp/Agent $ java -classpath classpath Agent nbTraps |
Specify the number of traps to be sent to the manager in the nbTraps parameter. Wait until the manager is started to send the traps.
In another terminal window, launch the manager application to connect to this agent. If you wish to run the manager on a different host, replace localhost with the name of the machine where you launched the agent.
$ cd examplesDir/Snmp/Manager $ java -classpath classpath AsyncManager localhost 8085 |
You should then see the output of the SnmpWalkUntil request: the response handler method is called for each variable that is returned.
Press <Enter> in the agent's window to send traps and see the trap reports as they are received in the manager. When you are finished with the agent, don't forget to stop it by typing <Control-C> in its terminal window.
The inform request is specified in SNMP v2 as a mechanism for sending a report and receiving a response. This functionality is implemented in the JMX SNMP manager API for transmitting management information from one SNMP manager to another.
Since SNMP managers both send and receive inform requests, the SNMP manager API includes the mechanisms for doing both. Roughly, inform requests are sent in the same way as other requests, and they are received in the same way as traps. Both of these mechanisms are explained in the following sections.
This simple example has two manager applications, one of which sends an inform request, and the other which listens for and replies to this request. No SNMP agents are involved in this exchange.
Like the other types of requests, the manager sends an inform request through a session. The only difference is that the peer object associated with the request should be an SNMP manager able to receive and reply to InformRequest PDUs.
You may associate a peer with a session by making it the default peer object. This is how we do it in this example. This means that if we don't specify a peer when sending requests, they are automatically addressed to our manager peer. Since sessions often have agent peers as a default, you can specify the manager peer as a parameter to the snmpInform method of the session object.
// When calling the program, you must specify the hostname // of the SNMP manager you want to send the inform to. // String host = argv[0]; // Initialize the port number to send inform PDUs on port 8085. // int port = 8085; try { // Create an SnmpPeer object that represents the entity to // communicate with. // SnmpPeer peer = new SnmpPeer(host, port); // Create parameters to associate to the peer. // When creating the parameter object, you can specify the // read and write community to be used when sending an // inform request. // SnmpParameters params = new SnmpParameters( "public", "private", "public"); // The newly created parameter must be associated to the peer. // peer.setSnmpParam(params); // Build the session. A session creates, controls and manages // one or more requests. // SnmpSession session = new SnmpSession("SimpleManager1 session"); session.setDefaultPeer(peer); // Make the SNMP inform request and wait for the result. // SnmpRequest request = session.snmpInform( null, new SnmpOid("1.2.3.4"), null); java.lang.System.out.println( "NOTE: Inform request sent to SNMP manager on " + host + " at port " + port); boolean completed = request.waitForCompletion(10000); // Check for a timeout of the request. // if (completed == false) { java.lang.System.out.println( "\nSimpleManager1::main: Request timed out. " + "Check reachability of agent"); // Print request. // java.lang.System.out.println("Request: " + request.toString()); java.lang.System.exit(0); } // Now we have a response. Check if the response contains an error. // int errorStatus = request.getErrorStatus(); if (errorStatus != SnmpDefinitions.snmpRspNoError) { java.lang.System.out.println("Error status = " + SnmpRequest.snmpErrorToString(errorStatus)); java.lang.System.out.println("Error index = " + request.getErrorIndex()); java.lang.System.exit(0); } // Now we shall display the content of the result. // SnmpVarbindList result = request.getResponseVbList(); java.lang.System.out.println("\nNOTE: Response received:\n" + result); // Stop the session properly before exiting session.destroySession(); java.lang.System.exit(0); } catch(Exception e) { java.lang.System.err.println( "SimpleManager1::main: Exception occurred:" + e ); e.printStackTrace(); } |
Before sending the request, the snmpInform method automatically adds two variables to the head of the varbind list which is passed in as the last parameter. These are sysUpTime.0 and snmpTrapOid.0, in the order they appear in the list. These variables are mandated by RFC 1905 and added systematically so that the caller doesn't need to add them.
Like all other requests in a session, inform requests can be handled either synchronously or asynchronously in the sender. In our example, we process the inform request synchronously: the manager blocks the session while waiting for the completion of the request. In an asynchronous manager, you would need to implement a response handler as explained in "The Response Handler", and then use it to process responses, as shown in Example 13-3.
Managers receive inform requests as they do traps: they are unsolicited events that must be received by a dispatcher object. Unlike traps, an inform request requires a response PDU which, according to the SNMP specification, must contain the same variable bindings. Therefore, immediately after an inform request is successfully received and decoded, the SnmpEventReportDispatcher class automatically constructs and sends the inform response back to the originating host.
The manager application then retrieves the data in the inform request through a listener on the dispatcher. Inform request listeners are registered with the dispatcher object in the same way as trap listeners. The receiving manager in our example is very simple, since its only function is to create the dispatcher and the listener for inform requests.
// Initialize the port number to listen for incoming inform PDUs on port 8085. // int port = 8085; try { // Create a dispatcher for SNMP event reports (SnmpEventReportDispatcher). // SnmpEventReportDispatcher is run as a thread and listens for informs // on the specified port. // Add our InformListenerImpl class as an SnmpInformListener. // InformListenerImpl will receive a callback when a valid trap // PDU is received. // SnmpEventReportDispatcher informDispatcher = new SnmpEventReportDispatcher(port); informDispatcher.addInformListener(new InformListenerImpl()); new Thread(informDispatcher).start(); // Note that you can use the same SnmpEventReportDispatcher object // for both incoming traps and informs. // Just add your trap listener to the same dispatcher, for example: // informDispatcher.addTrapListener(new TrapListenerImpl()); // Here we are just going to wait for inform PDUs. // java.lang.System.out.println("\nNOTE: Event report listener initialized"); java.lang.System.out.println( " and listening for incoming inform PDUs on port " + port + "..."); } catch(Exception e) { java.lang.System.err.println( "SimpleManager2::main: Exception occurred:" + e ); e.printStackTrace(); } |
The remaining step is to program the behavior we want upon receiving an inform request. To do this, we must write the InformListenerImpl class that we registered as an inform request listener in the previous code sample. This class implements the SnmpInformListener interface and its processSnmpInform method handles the incoming inform request.
Because the dispatcher automatically sends the inform response back to the originating host, the SnmpInformListener implementation does not need to do this. Usually this method will extract the variable bindings and take whatever action is necessary upon receiving an inform request. In our example, we simply print out the source and the contents of the inform request.
import java.io.IOException; import javax.management.snmp.SnmpPduRequest; import javax.management.snmp.manager.SnmpInformListener; /** * This class implements the SnmpInformListener interface. * The callback method processSnmpInform is called when a * valid inform PDU is received. */ public class InformListenerImpl implements SnmpInformListener { public void processSnmpInform(SnmpPduRequest inform) { // Display the received PDU. // java.lang.System.out.println("\nNOTE: Inform request received:\n"); java.lang.System.out.println("\tType = " + inform.pduTypeToString(inform.type)); java.lang.System.out.println("\tVersion = " + inform.version); java.lang.System.out.println("\tRequestId = " + inform.requestId); java.lang.System.out.println("\tAddress = " + inform.address); java.lang.System.out.println("\tPort = " + inform.port); java.lang.System.out.println("\tCommunity = " + new String(inform.community)); java.lang.System.out.println("\tVB list = "); for (int i = 0; i < inform.varBindList.length; i++) { java.lang.System.out.println("\t\t" + inform.varBindList[i]); } // Our listener stop the manager after receiving its first // inform request java.lang.System.out.println( "\nNOTE: SNMP simple manager 2 stopped..."); java.lang.System.exit(0); } } |
The examplesDir/Snmp/Inform directory contains all of the files for the two manager applications, along with the InformListenerImpl class.
Compile all files in this directory with the javac command. For example, on the Solaris platform with the Korn shell, you would type:
$ cd examplesDir/Snmp/Inform/ $ javac -classpath classpath *.java |
To run the example, launch the inform request receiver with the following command. You may launch the application in another terminal window or on another machine.
$ java -classpath classpath SimpleManager2 |
Wait for this manager to be initialized, then launch the other manager with the following command. The hostname is the name of the machine where you launched the receiving manager, or localhost.
$ java -classpath classpath SimpleManager1 hostname |
When the sender is ready, press <Enter> to send the inform request. You should see the contents of the request displayed by the receiving manager. Immediately afterwords, the sender receives the inform response containing the same variable bindings and displays them. Both manager applications then exit automatically.