Java Dynamic Management Kit 4.0 Tutorial

Chapter 9 Implementing an SNMP Proxy

It is easier to manage a large number of SNMP agents when they have a hierarchical structure of master agents and sub-agents. Master agents concentrate and relay the information in their sub-agents and can provide their own specific information as well. Managers only communicate with the master agents and access the sub-agents transparently, as if the information actually resided in the master agent.

In order to do this, agents must contain an SNMP proxy for each sub-agent that they manage. The proxy is a Java class that looks like a MIB MBean to the agent, but which actually accesses the sub-agent to provide the information that is requested of it. In order to access sub-agents, the proxy object relies on the SNMP manager API.

The SNMP proxy is used in this simple example to allow a manager to access two MIBs through one agent. You can reuse the proxy class in your management solutions to implement any hierarchy of managers, master agents and sub-agents.

The source code for the proxy object and the sample application is available in the Snmp/Proxy example directory located in the main examplesDir (see "Directories and Classpath" in the preface).

Contents:

The Proxy Roles

As we saw in "MIB Development Process", the MBeans that represent a MIB are generated from the MIB definition by the mibgen tool. This tool generates one main MBean representing the MIB and then one MBean for each group and each table entry. The main MBean extends the SnmpMib class whose implementation of the abstract SnmpMibAgent class processes all requests on a MIB.

For example, if an agent receives a get request for some variable, it will call the get method that the main MBean inherits from SnmpMibAgent. The implementation of this method relies on the MIB structure to find the MBean containing the corresponding attribute and then call its getter to read the value of the variable.

A proxy is another implementation of the SnmpMibAgent class which, instead of resolving variables in local MBeans, reformulates an SNMP request, sends it to a designated sub-agent and forwards the answer that it receives. Since only the main MBean of a MIB is bound to the SNMP adaptor, we bind the proxy instead, and the master agent transparently exposes the MIB which actually resides in the sub-agent.

The Master Agent

The master agent needs to instantiate one SNMP proxy object for each sub-agent containing MIBs that it wishes to serve. The remote MIBs can be on any number of sub-agents, and the master agent can have several proxy objects. Sub-agents themselves may contain proxies: it is up to the designer to define the complexity of the agent hierarchy.

The master agent may also contain a mix of proxies and MBeans for other MIBs. In the proxy example, the master agent exposes the DEMO MIB through local MBeans and a subset of the RFC1213 MIB through a proxy.


Example 9-1 The Master Agent of the Example

MBeanServerImpl server;
ObjectName snmpObjName;
ObjectName localMibObjName;
ObjectName remoteMibObjName;
int htmlPort = 8082;
int snmpPort = 8085;

// read sub-agent connection info from the command line
String host = argv[0];
String port = argv[1];

try {
    server = MBeanServerFactory.createMBeanServer();
    String domain = server.getDefaultDomain();

    // Create and start the HTML adaptor.
    [...]
      
    // Create and start the SNMP adaptor.
    [...]
  
    // Create, initialize, and bind the local MIB Demo.
    //
    localMibObjName = new ObjectName("snmp:class=DEMO_MIB");
    server.registerMBean(localMib, localMibObjName);
    localMib.setSnmpAdaptorName(snmpObjName);
  
    // Create and initialize the SNMP proxy.
    //
    remoteMibObjName = new ObjectName("snmp:class=proxy");
    SnmpMibAgentImpl remoteMib = new SnmpMibAgentImpl();
    server.registerMBean(remoteMib, remoteMibObjName);
    remoteMib.initializeProxy(host, Integer.parseInt(port), "1.3.6.1.2.1");

    // Bind the MIB proxy to the SNMP adaptor
    //
    ((SnmpMibAgent)remoteMib).setSnmpAdaptorName(snmpObjName);
  
} 
catch (Exception e) {
    e.printStackTrace();
    java.lang.System.exit(1);
}

We register the proxy object as any other MBean and then initialize it. We call the initialize method of the proxy, giving the host and port of the sub-agent it must communicate with and the root OID of the MIB or subset that it represents.

A single proxy object can serve several MIBs on a sub-agent, and in this case, the root OID is the common prefix of all objects. However, the OIDs of all proxies and MIBs in an agent must be distinct, none may be a substring of another (see "Binding the MIB MBeans"). Finally, we bind the proxy to the SNMP adaptor just as we would a MIB MBean.

The Sub-Agent

The sub-agent in our example is a stand-alone agent which serves a subset of the RFC1213 MIB. Since it implements no proxies of its own, it is just a plain agent which responds to SNMP management requests that happen to originate from a proxy object. Any SNMP manager could also send requests to this agent.

Stand-alone agents are covered in "Stand-Alone SNMP Agents". Since this stand-alone agent contains no code that is specific to its role as a sub-agent, we will not repeat its program listing here. The StandAloneAgent.java file only contains some extra code for reading its assigned port from the command line. We will use this to launch the agent on a known port to which the proxy can connect.

The Manager Application

The manager application is not affected by proxies in the agents to which it sends requests. It sends the requests to the master agent, in the same way that it would send a request for a MIB that is not served by a proxy. In fact, the SNMP manager cannot even distinguish between a MIB served by an agent and another MIB served through a proxy in the agent, except perhaps by analyzing the values returned.

There is one consideration for proxies, and that is the timeout interval. Since the proxy issues another request and only answers at the end of its timeout, the manager must have a longer timeout. The manager should be designed with some knowledge of all sub-agents in a hierarchy, so that the timeout can take into account all proxy delays and the multiple communication times.

As with any manager application written with the SNMP manager API, the manager in the proxy example must have access to the OID table objects that represent the MIBs that it will manage. Here is the code to initialize the manager:


Example 9-2 Initialization of the SNMP Proxy Manager

String host = argv[0];
String port = argv[1];

// Initialize the SNMP Manager API.
// Specify the OidTables containing all the MIB Demo and MIB II knowledge.
// Use the OidTables generated by mibgen when compiling MIB Demo and MIB II.
//
SnmpOidDatabaseSupport oidDB = new SnmpOidDatabaseSupport();
SnmpOid.setSnmpOidTable(oidDB);
oidDB.add(new RFC1213_MIBOidTable());
oidDB.add(new DEMO_MIBOidTable());
  
SnmpPeer agent = new SnmpPeer(host, Integer.parseInt(port));
SnmpParameters params = new SnmpParameters("public", "private");
SnmpSession session = new SnmpSession("Manager session");
  
// We update the time out to let the agent enough time 
// to do his job before retry.
//
agent.setTimeout(100000);
agent.setSnmpParam(params);
session.setDefaultPeer(agent);

The rest of the manager application is the code for synchronous get and set requests, similar to the one shown in "The Synchronous Manager Example".

The SNMP Proxy Implementation

The SNMP proxy is an extension of the abstract SnmpMibAgent which implements all of its abstract methods and can be instantiated. The proxy implements a synchronous SNMP manager that forwards the requests to the sub-agent. It does some error fixing for SNMPv2 requests but doesn't claim to be extensive. In any case, the SNMP proxy implementation is provided as an example and Sun Microsystems makes no claim as to its suitability for any particular usage.


Note -

The implementation of the example proxy does not support the getBulk request or the check method that allows the SNMP Walk request.


Before it is used, the proxy must be initialized with the hostname and port of the sub-agent. It uses this information to create SNMP parameter and SNMP peer objects. It then creates three SNMP sessions:


Example 9-3 Internal Initialization of the SNMP Proxy

public void initializeProxy(String h, int p,
                            String strOid, String name)
    throws UnknownHostException, SnmpStatusException {
	
    host = h;
    port = p;
    oid = strOid;
    mibName = name;
	
    // Initialization for SNMP v1 protocol.
    //
    SnmpParameters paramsV1 = new SnmpParameters("public", "private");
    paramsV1.setProtocolVersion(SnmpDefinitions.snmpVersionOne);
    SnmpPeer peerV1 = new SnmpPeer(host, port);
    peerV1.setSnmpParam(paramsV1);
    sessionV1 = new SnmpSession("SnmpMibAgentImpl session V1");
    sessionV1.setDefaultPeer(peerV1);

    // Using SNMP v1 protocol, errors are not fixed and
    // forwarded to the manager
    sessionV1.snmpOptions.setPduFixedOnError(false);

    // Initialization for SNMP v2 protocol.
    //
    SnmpParameters paramsV2 = new SnmpParameters("public", "private");
    paramsV2.setProtocolVersion(SnmpDefinitions.snmpVersionTwo);
    SnmpPeer peerV2 = new SnmpPeer(host, port);
    peerV2.setSnmpParam(paramsV2);
    // If we get an error, we don't retry the request using SNMP v2,
    // but we try the request using SNMP v1
    peerV2.setMaxRetries(0);
    sessionV2 = new SnmpSession("SnmpMibAgentImpl session V2");
    sessionV2.setDefaultPeer(peerV2);
    // Using SNMP v2 protocol, the error is fixed
    //
    sessionV2.snmpOptions.setPduFixedOnError(true);

    // Initialization for SNMP v2 protocol simulated
    // using SNMP v1 protocol
    //
    sessionV2WithV1 = new SnmpSession("SnmpMibAgentImpl session V2 with V1");
    sessionV2WithV1.setDefaultPeer(peerV1);
    // Simulating SNMP v2 with SNMP v1 protocol, the error is fixed.
    //
    sessionV2WithV1.snmpOptions.setPduFixedOnError(true);
}

The proxy exposes the public methods for handling requests, and then this algorithm for reducing errors is implemented by internal methods. Roughly, the proxy must determine the version of the incoming request and handle it as promised. Version 1 requests that timeout or fail are dropped, and v2 requests that timeout or fail are retried as v1 requests. Here we only show the code for implementing the get method.


Example 9-4 Implementing a get Request in the Proxy

// the exposed method
public void get(Vector list, int version) throws SnmpStatusException {
			  	  	  
    java.lang.System.out.println(
        "Proxy: Sending get request to SNMP sub-agent on " + 
            host + " using port " + port);

    // Request using SNMP v1 protocol
    if (version == SnmpDefinitions.snmpVersionOne) {
        get(list, version, sessionV1);
    }

    // Request using SNMP v2 protocol.
    if (version == SnmpDefinitions.snmpVersionTwo) {
        get(list, version, sessionV2);
    }
}

// the internal implementation
private void get(Vector list, int version, SnmpSession session)
    throws SnmpStatusException {
			  	  	  
    SnmpVarbindList varbindlist = setVarBindList(list);

    try {
        request = session.snmpGet(null, varbindlist);
    } 
    catch (SnmpStatusException e) {
        throw new SnmpStatusException(SnmpDefinitions.snmpRspGenErr, 0);
    }
    java.lang.System.out.println("\nRequest:\n" + request.toString());
	
    boolean completed = request.waitForCompletion(10000);
    if (completed == false) {

        // If the completion failed using SNMP v1, we give up
        if (version == SnmpDefinitions.snmpVersionOne) {
            java.lang.System.out.println(
                "\nRequest timed out: check reachability of sub-agent.");
            return;
        }

        // If the completion failed using SNMP v2, we try again using v1

        if (version == SnmpDefinitions.snmpVersionTwo) {
            get(list, SnmpDefinitions.snmpVersionOne, sessionV2WithV1);
            return;
        }
    }

    int errorStatus = request.getErrorStatus();
    int errorIndex = request.getErrorIndex() + 1;
    if (errorStatus != SnmpDefinitions.snmpRspNoError) {

        // If there is an error status using v1, we throw an exception
        if (version == SnmpDefinitions.snmpVersionOne) {
            throw new SnmpStatusException(errorStatus, errorIndex);
        }
        // If there is an error status using v2, we try again using v1
        if (version == SnmpDefinitions.snmpVersionTwo) {
            get(list, SnmpDefinitions.snmpVersionOne, sessionV2WithV1);
            return;
        }
    }
    result = request.getResponseVbList();

    // Update the list parameter with the result
    Enumeration l = list.elements();
    for (Enumeration e = result.elements(); e.hasMoreElements();) {
        SnmpVarBind varres = (SnmpVarBind) e.nextElement();
        SnmpVarBind varbind = (SnmpVarBind) l.nextElement();
        varbind.value = varres.value;
    }
}

The complete list of methods that a proxy must implement are the same as for MIB MBeans represented by instances of the SnmpMib class:

Running the SNMP Proxy Example

First, we generate the MBeans for our MIBs using the mibgen tool:


$ cd examplesDir/Snmp/Proxy/
$ mibgen -a -d . mib_II_subset.txt mib_demo.txt

Since the proxy acts as an SNMP manager, the master agent application now needs the SNMP manager API in its classpath, for both compilation and execution. Similarly, the proxy object also needs the class representing the SNMP OID table of the MIB that it accesses on the sub-agent.

These issues are resolved in our case by having all classes in the example directory and using dot (.) in our usual classpath. Replace the two MIB files with those from the patchfiles directory and then compile all of the classes in the example directory:


$ cp -i patchfiles/*_MIB.java .
cp: overwrite ./DEMO_MIB.java (yes/no)? y
cp: overwrite ./RFC1213_MIB.java (yes/no)? y
$ javac -classpath classpath -d . *.java

We can run all three applications on the same machine as long as we choose different port numbers. Here we give commands for launching the applications from the same terminal window running the Korn shell. On the Windows NT platform, you will have to launch each application in a separate window, in which case you will not see the sequence of the merged output.

Running the SNMP Proxy Example
  1. First we launch the sub-agent; by default it will reply to port 8086, or we can specify a different one on the command line:


    $ java -classpath classpath StandAloneAgent 8090 &
    Adding SNMP adaptor using port 8090
    
    Initializing the MIB RFC1213_MIB
  2. Then we launch the master agent, giving it the sub-agent's hostname and port number:


    $ java -classpath classpath Agent localhost 8090 &
    NOTE: HTML adaptor is bound on TCP port 8082
    NOTE: SNMP Adaptor is bound on UDP port 8085
    
    Initializing the MIB snmp:class=DEMO_MIB
    
    Initializing the SNMP proxy snmp:class=proxy to query host localhost using
    port 8090
  3. Now you can view the master agent through its HTML adaptor. In your web browser, go to the following URL: http://localhost:8082/

    The class=proxy MBean in the snmp domain is the proxy object, but we cannot see the MBean that it represents. In order to manage the actual MIB, we would have to connect to the sub-agent, but in our example it is a stand-alone and therefore unreachable.

    The name=Demo MBean in the DEMO_MIB domain is the MIB that is served locally. If we click on its name, we can see its initial values.

  4. Finally, we launch the manager application, giving it the master agent's hostname and port:


    $ java -classpath classpath Manager localhost 8085
    

    The manager will run through its requests to the master agent. If the output is in the correct order, there are messages from the manager issuing a request, and then the proxy which relays a request for two of the variable to the sub-agent. The proxy then prints the result it received for the two variables, then the manager receives the final response with all of the variables, including the same two that the proxy just forwarded.

  5. In the name=Demo MBean on the web browser, you should see the new values that were set in the DEMO_MIB as part of the manager's set operation.

  6. Don't forget to stop the agent applications with the following commands (just use "Control-C" if they are in separate terminal windows):


    $ fg
    java [...] Agent localhost 8090 <Control-C>
    ^C$ fg
    java [...] StandAloneAgent 8090 <Control-C>
    ^C$