Java Dynamic Management Kit 4.2 Tutorial

Part V SNMP Interoperability

From the start, the Java Dynamic Management Kit was designed to be compatible with existing management standards. The Simple Network Management Protocol (SNMP) is the most widely used of these, and the Java Dynamic Management Kit provides the tools to integrate Java technology-based solutions into the SNMP world.

Using the SNMP interoperability with Java Dynamic Management solutions, you can develop agents that can be accessed through SNMP and through other protocols. You can also develop managers in the Java programming language which access both SNMP agents and Java Dynamic Management agents.

The agent and manager tools of the Java Dynamic Management Kit are completely independent. SNMP agents developed with this toolkit can be accessed by any SNMP manager, and the SNMP manager API lets you connect to any SNMP agent. The sample applications in this lesson use the toolkit on both agent and manager sides, but this is only one possible configuration.

This lesson contains the following topics:

Chapter 16 Creating an SNMP Agent

Using the Java Dynamic Management Kit, you can create an agent application that is both an SNMP agent and a normal JMX agent. SNMP MIBs can be represented as MBeans and the SNMP protocol adaptor exposes them to SNMP managers. Only MBeans derived from MIBs may be managed through the SNMP protocol adaptor, but other managers may view and access them through other protocols.

The mibgen tool provided generates the code for MBeans that represent a MIB. Groups and table entries are represented as MBean instances which expose the SNMP variables through their attributes. The mibgen tool creates skeleton getters and setters which only read or write internal variables. In order to expose the behavior of your host machine or device, you need to implement the code that will access the host- or device-specific functionality.

The SNMP protocol adaptor interacts with your customized MIB MBeans to implement a compatible SNMPv1 and SNMPv2c agent. It also provides the mechanisms for sending traps and implementing both community-based access control and message-level data security (see "Security Mechanisms in the SNMP Toolkit").

The program listings in this tutorial show only functional code: comments and output statements have been modified or removed for space considerations. However, all management functionality has been retained for the various demonstrations. The complete source code is available in the Snmp/Agent example directory located in the main examplesDir (see "Directories and Classpath" in the preface).

Contents:

MIB Development Process

Here we describe the process for making MIBs manageable through the SNMP protocol adaptor of the Java Dynamic Management Kit. In our example, we demonstrate this process on a subset of the MIB-II defined by RFC 1213.

Once you have defined the MIB you want to manage in your SNMP agent you need to generate its MBean representation using the mibgen tool. This tool generates MBeans that represent the whole MIB, each of its groups and nested groups, and each of its table entries. This command-line tool and its output are fully described in the Java Dynamic Management Kit 4.2 Tools Reference guide.

The mibgen tool only generates the MBean structure to represent the MIB, it is up to the developer to implement the MIB functionality inside the generated classes. Our example will only give a simple implementation of the MIB-II for demonstration purposes. However, this will show you the way to extend the generated classes in order to provide your own implementation.

Generating MIB MBeans

To run the mibgen tool for our example, go to the examplesDir/Snmp/Agent directory and enter the following command:


$ mibgen -d . mib_II_subset.txt

This will generate the following files in the current directory:

The MBean with the name of the MIB is a central administrative class for managing the other MBeans that represent the MIB groups and table entries. All of the other MBeans contain the SNMP variables as attributes of their management interface. The mibgen tool generates standard MBeans for the MIB, so attributes are implemented with individual getter and setter methods.

These MBeans are just skeletons, meaning that the attribute implementations only return a default value. You must implement the getters and setters of the attributes to read and write data with the correct semantic meaning of the SNMP variable.

Since SNMP does not support actions in MIBs, the only operations in these MBeans are checkers associated with the SNMP "Set" request in writeable variables. Again, these are skeleton methods which you must implement to do the checks that you require before the corresponding "Set" operation. You may add operations and expose them in the MBean interface, but the SNMP manager will not be able to access them. However, other managers will be able to call these operations if they are connected through another protocol.

Implementing the MIB

Our example only implements a fraction of the attributes, those that are used in this tutorial. The others are simply initialized with a plausible value. Using DEFVAL statements in our MIB, we could force mibgen to generate MBeans with user-defined default values for attributes. As this is not done in our example, mibgen provides a plausible default value according to the variable type.

Our implementations of MIB behavior are contained in the classes with the Impl suffix. These implementation classes extend those that are generated by mibgen so that we can regenerate them without overwriting our customizations.

Here is a summary of the implementation shown in the agent example:

The SnmpImpl.java and SystemImpl.java files provide code that you may reuse when you need to implement these common SNMP groups.

The only class that we need to replace is RFC1213_MIB. Our implementation is located in patchfiles/RFC1213_MIB.java so that we can overwrite the one generated by mibgen. The main function of this MBean is to register the other MBeans of the MIB during its pre-registration phase. Our customization consists of specifying our *Impl classes when instantiating the group and table entry MBeans.

Compiling the MBeans and Agents

We replace the generated file with our implementation before compiling all of the classes in the examplesDir/Snmp/Agent directory. The classpath must contain the current directory (.):


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

We are now ready to look at the implementation of an SNMP agent and run the example applications.

The SNMP Protocol Adaptor

Once your MIBs are implemented as MBeans, your agent application needs an SNMP protocol adaptor in order to function as an SNMP agent. Since the SNMP adaptor is also an MBean, it can be created and started dynamically in your agent by a connected manager, or through the HTML adaptor. In our simple Agent example, we will launch it through the code of the agent application.


Example 16-1 The SNMP Agent Application

public class Agent {

    static SnmpAdaptorServer snmpAdaptor = null;

    public static void main(String args[]) {
        
        MBeanServer server;
        ObjectName htmlObjName;
        ObjectName snmpObjName;
        ObjectName mibObjName;
        ObjectName trapGeneratorObjName;
        int htmlPort = 8082;
        int snmpPort = 8085; // non-standard

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

            // Create and start the HTML adaptor.
            //
            htmlObjName = new ObjectName( domain +
                ":class=HtmlAdaptorServer,protocol=html,port=" + htmlPort);
            HtmlAdaptorServer htmlAdaptor = new HtmlAdaptorServer(htmlPort);
            server.registerMBean(htmlAdaptor, htmlObjName);
            htmlAdaptor.start();

            // Create and start the SNMP adaptor.
            //
            snmpObjName = new ObjectName(domain +
                ":class=SnmpAdaptorServer,protocol=snmp,port=" + snmpPort);
            snmpAdaptor = new SnmpAdaptorServer(snmpPort);
            server.registerMBean(snmpAdaptor, snmpObjName);
            snmpAdaptor.start();

            // The rest of the code is specific to our SNMP agent

            // Send a coldStart SNMP Trap (use port = snmpPort+1)
            // Trap communities are defined in the ACL file
            //
            snmpAdaptor.setTrapPort(new Integer(snmpPort+1));
            snmpAdaptor.sendV1Trap(0, 0, null);

            // Create the MIB-II (RFC 1213) and add it to the MBean server.
            //
            mibObjName = new ObjectName("snmp:class=RFC1213_MIB");
            RFC1213_MIB mib2 = new RFC1213_MIB();
            // The MBean will register all group and table entry MBeans
            // during its pre-registration
            server.registerMBean(mib2, mibObjName);

            // Bind the SNMP adaptor to the MIB
            mib2.setSnmpAdaptorName(snmpObjName);

            [...]

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Needed to get a reference on the SNMP adaptor object
    static public SnmpAdaptorServer getSnmpAdaptor() {
        return snmpAdaptor;
    }
}

Launching the SNMP Adaptor

We launch the SNMP adaptor in the same way that we launch the HTML adaptor. First we create a meaningful object name for its MBean, then we instantiate the class with a constructor allowing us to specify a non-default port, we register the MBean with the MBean server, and we start the adaptor to make it active.

By default, the SNMP protocol adaptor uses the standard SNMP port 161. Since other applications may be using this port on a machine, our simple agent uses port 8085. When we connect to this agent, our SNMP manager will need to specify this non-standard port number.


Note -

On certain platforms, applications also require super-user privileges to assign the default SNMP port 161. If your SNMP adaptor uses this port, its agent application will have to be launched with super-user privileges.


Creating MIB MBeans

Our agent application creates and manages one MIB, our subset of MIB-II. To do so, it instantiates the corresponding RFC1213_MIB MBean that has been generated by the mibgen tool (see "MIB Development Process"). We give it a meaningful object name and then we register it in the MBean server.

The registration process lets the MBean instantiate and register other MBeans that represent the groups of the MIB and the entries of its tables. The set of all these MBeans at the end of registration makes up the MBean representation of the MIB. If an SNMP manager later adds entries to a table, the MIB implementation will register the new entry MBean into the MBean server as well.

If you do not wish to expose a MIB through the MBean server, you do not have to register it. However, you still need to create all of its other MBean objects so that the SNMP adaptor can access all of its groups and table entries. The generated code provides the init method in the main MBean of the MIB. Calling this method will create all necessary MBean objects without registering them in the MBean server.

Binding the MIB MBeans

The SNMP adaptor does not interact with MBeans in the same way as the other connectors and adaptors. Because the SNMP data model relies on MIBs, only MBeans representing MIBs can be managed through SNMP. The SNMP adaptor does not interact with MBeans of a MIB through the MBean server, they must be explicitly bound to the instance of the SNMP adaptor.

After a MIB is instantiated, you must set the SnmpAdaptorName attribute of its main MBean to bind it to the SNMP adaptor. You can either call its setSnmpAdaptorName method directly or, if the MIB's MBean was registered in the MBean server, another management application may set the attribute through the MBean's exposed interface.

In the binding process, the SNMP adaptor obtains the root OID of the MIB. The adaptor uses this OID to determine which variables are implemented in the MIB's corresponding MBeans. In order for the SNMP adaptor to resolve a request on a given OID, the root OID of all bound MIBs must not overlap. This implies that no root OID may be equal to another or be a substring of another.

Even though the SNMP adaptor may be registered in the MBean server, the adaptor only makes MIBs visible to SNMP managers. Other MBeans in the agent cannot be accessed or even represented in the SNMP protocol. The SNMP manager is limited by its protocol: it cannot take full advantage of a Java Dynamic Management agent through the basic MIBs, and it does not have access to any other MBeans. In an advanced management solution, you could write a special MIB and implement it so that operations on its variables actually interact with the MBean server. This is left as an exercise for the reader.

Accessing a MIB MBean

Once the MBean representing a MIB has been instantiated and bound to the SNMP adaptor, it is accessible through the SNMP adaptor. SNMP managers can send requests to operate on the contents of the MIB. The SNMP adaptor interprets the SNMP management requests, performs the operation on the corresponding MBean and returns the SNMP response to the manager. The SNMP protocol adaptor is compatible with SNMPv1 and SNMPv2c.

The advantage of having an SNMP agent "inside" a Java Dynamic Management agent is that you can use the other communications protocols to interact with MIBs and manage the SNMP adaptor. Since both the registered MIBs and the adaptor are MBeans, they are exposed for management. In our simple agent, the MIB was registered, and you can view its MBeans in a web browser through the HTML protocol adaptor.

If our agent included other connectors, management applications could connect to the agent and also manage the MIB and the SNMP adaptor. A non-SNMP manager could instantiate new MIB objects, bind them to the SNMP adaptor and operate on the exposed attributes and operations of the MIB.

Non-SNMP managers may operate on the variables of a MIB, getting and setting values, regardless of any SNMP manager that might also be accessing them through the SNMP adaptor. When dealing with a table, however, they may not create new table entry MBeans without adding them to the table. For example, in the InterfacesImpl.java class, we called the addEntry method of the IfTable object before registering the entry MBeans with the MBean server. This ensures that the new entries will be visible when an SNMP manager accesses the table.

In order for a non-SNMP manager to create a table entry, you must customize the table's group MBean to expose this functionality. Briefly, you would need to write a new method that instantiates and initializes the entry's MBean, adds the MBean to the table object, and registers the entry MBean in the MBean server. Advanced customization such as this is not covered in our example. In general, the designer of the agent and management applications is responsible for all coherency issues when accessing MIBs concurrently through different protocols and when adding table entries.

Managing the SNMP Adaptor

Non-SNMP managers can also control the SNMP agent through the MBean of the SNMP adaptor. Like the other communications MBeans, the port and other attributes can be modified when the SNMP adaptor is stopped. You can also get information about its state, and stop or restart it to control when it is online. These administrative attributes and operations are defined in the CommunicatorServerMBean interface.

The SNMP adaptor server also implements the SnmpAdaptorServerMBean interface to define its operating information. The SNMP protocol defines certain variables that SNMP agents must expose about their current state. For example, the SNMP adaptor provides methods for getSnmpInPkts and getSnmpOutBadValues. Non-SNMP managers can read these variables as attributes of the SNMP adaptor MBean.

The SNMP adaptor also exposes other operating information that is unavailable to SNMP managers. For example, the ActiveClientCount and ServedClientCount read-only attributes report on SNMP manager connections to this agent. The read-write BufferSize attribute lets you change the size of the message buffer, but only when the adaptor is not online. The adaptor MBean also exposes operations for sending traps or implementing your own security (see "Message-Level Security").

Running the SNMP Agent Example

After building the example as described in "MIB Development Process", launch the simple agent with the following command:


$ java -classpath classpath Agent nbTraps

For this run, set nbTraps to zero. You should see some initialization messages, including our notification listener giving information about the two table entries which are created. Access this agent's HTML adaptor by pointing a web browser to the following URL: http://localhost:8082/. Through the HTML adaptor, you can see the MBeans representing the MIB:

In any of these MBeans, you could write new values into the text fields of exposed attributes and click the "Apply" button. This will set the corresponding SNMP variable, and thereafter, SNMP managers will see the new value. This is an example of managing a MIB through a protocol other than SNMP.

For any SNMP agent application, you can turn on trace messages for the SNMP adaptor by specifying the -DINFO_ADAPTOR_SNMP property on the command line. The tracing mechanism is covered in the Java Dynamic Management Kit 4.2 Tools Reference guide and in the Javadoc API of the Trace class.

Type "Control-C" when you are finished viewing the agent.

Sending Traps

Agents can send unsolicited event reports to management application by using traps. The SNMP protocol adaptor can send both v1 and v2 traps, the difference being in the format of the corresponding PDU. Traps in the SNMP specification are not acknowledged by the management application, so agents do not know if traps are received.

Inform requests are acknowledged event reports, they are sent by entities acting in a manager role, according to RFC 1905. In the Java Dynamic Management Kit, both the SNMP adaptor and the classes of the SNMP manager API may send inform requests. Manager-to-manager inform requests are described in"The Inform Request Example". Agent-to-manger inform requests are demonstrated by the applications in the Snmp/Inform/ example directory located in the main examplesDir

In this example, we demonstrate how our simple SNMP agent application can send traps. The customized class IfEntryImpl in the example directory extends the IfEntry class generated by mibgen in order to provide a method that switches the IfOperStatus variable and sends a trap. This is an example of customization of the generated code: an agent-side entity will switch the operation status, the MIB variable will be updated and a trap will be sent to SNMP managers.


Example 16-2 Sending a Trap in the IfEntryImpl Class

public void switchifOperStatus() {
    // implements the switch and then calls sendTrap indirectly
    [...]
}

// Method called after the variable has been switched
// Should be called with generic==2 (up) or 3 (down or testing)
public void sendTrap(int generic) {

    SnmpAdaptorServer snmpAdaptor = null;

    // Retrieve the reference of the SNMP protocol adaptor through
    // the static method of the Agent or StandAloneSnmpAgent class
    snmpAdaptor = Agent.getSnmpAdaptor();
    if (snmpAdaptor == null) {
        snmpAdaptor = StandAloneSnmpAgent.getSnmpAdaptor();
    }
    if (snmpAdaptor == null) {
        return;
    }

    // Create the variable bindings to send in the trap
    Vector varBindList = new Vector();

    SnmpOid oid1 = new SnmpOid("1.3.6.1.2.1.2.2.1.1." + IfIndex);
    SnmpInt value1 = new SnmpInt(IfIndex);
    SnmpVarBind varBind1 = new SnmpVarBind(oid1, (SnmpValue) value1);

    varBindList.addElement(varBind1);

    try {
        snmpAdaptor.sendV1Trap(generic, 0, varBindList);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

As the sendTrap method runs in a different thread, it needs to get a reference to the SNMP adaptor instance. Here we call the static methods of our two possible agent implementations. This code is specific to these agents and is only an example of how to retrieve this information.

In order to simulate a live operation status, we invent the LinkTrapGenerator class which will switch the status periodically. It is an MBean which contains a thread which loops endlessly. The interval period between traps and the number of the table entry can be modified through the MBean's attributes.


Example 16-3 The Thread of the Link Trap Generator

public void run() {
    int remainingTraps = nbTraps;
    while ((nbTraps == -1) || (remainingTraps > 0)) {
        try {
            sleep(interval);
        } catch (Exception e) {
            e.printStackTrace();
        }
        triggerTrap();
        remainingTraps--;
    }
}

public void triggerTrap() {
    // get the entry whose status we will switch
    IfEntryImpl ifEntryImpl = InterfacesImpl.find(ifIndex);
    if (ifEntryImpl == null) {
        errors++;
        return;
    }
    ifEntryImpl.switchifOperStatus();
    successes++;
}

To run the trap generator, the example application instantiates and registers a LinkTrapGenerator MBean. During its registration, this MBean starts the thread, sending a trap every two seconds by default.


Example 16-4 Starting the Trap Generator Example

// Create a LinkTrapGenerator (specify the ifIndex in the object name)
//
String trapGeneratorClass = "LinkTrapGenerator";
int ifIndex = 1;
trapGeneratorObjName = new ObjectName("trapGenerator" +
    ":class=LinkTrapGenerator,ifIndex=" + ifIndex);
LinkTrapGenerator trapGenerator = new LinkTrapGenerator(nbTraps);
server.registerMBean(trapGenerator, trapGeneratorObjName);

[...] // Press <Enter> to start sending traps

trapGenerator.start();

Specifying the Trap Destination

There are several methods in the SNMP protocol adaptor for sending traps to remote managers. They differ in their method signatures, depending upon whether or not you need to specify the destination host. When no host is specified, the SNMP protocol adaptor relies on the trap group definition in access control lists (ACL), as described below.

In all cases, traps are sent to the port specified by the current value of the TrapPort attribute on the SnmpAdaptorServer MBean. In our simple agent, we set the trap port to 8086, but this can be changed at any time by a custom MIB implementation or a management application.

Using an ACL Trap Group

This is the method that was used in Example 16-2 to send traps, along with its v2 equivalent (see the Javadoc API for a description of the parameters):

Using these methods, you must first define the trap group in an access control list. See "Access Control Lists (ACL)" for a formal definition of the trap group and instructions for defining the ACL file when starting the agent. By default, these lists are file-based, but you may implement other mechanisms, as described in "Custom Access Control".

In this example we provide the following template file:


Example 16-5 Trap Group of the jdmk.acl File

acl = {
  ...
}

trap = {
  {
  trap-community = public
  hosts = yourmanager
  }
}

The trap group lists all of the hosts to which the SNMP protocol adaptor will send every trap. A community definition associates a community name with a list of hosts specified either by their hostname or by their IP address. All hosts in a community definition will receive the trap in a PDU identified by the community name.


Note -

Since access control and trap recipients share the same file, you must fully define the access control when you want to send traps using the ACL mechanism.


Given this definition, traps will be sent to a host called yourmanager, and the community string of the trap PDU would contain the value public. By adding community definitions to this file, you can specify all hosts which will receive traps along with the community string for each host or group of hosts.

If the ACL file is not defined, or if the trap group is empty, the default behavior of these methods is to send a trap only to the localhost.

Specifying the Hostname Directly

The other two methods of the SNMP protocol adaptor, one for each trap version, let you send a trap to a specified recipient:

In both cases, these methods take an address and a community string, in addition to the version-specific trap information. The address is an InetAddress object which is usually instantiated by its static methods getLocalHost or getByName. The latter method returns a valid InetAddress object when given a string representing a hostname or IP address.

The cs parameter is the community string, a name that the agent and manager exchange to help identify one another. The string given will be used as the community when sending the trap PDU.

Either one of these methods sends a trap to a single manager using a single community string. The ACL trap group mechanism is better suited to sending traps to multiple managers, though it requires the setup of a trap group. Note that even if a trap group is in use, the two methods above only send one trap to the specified host address.

Traps in the Agent Example

Before launching the SNMP agent again, edit the jdmk.acl file to replace the occurrences of yourmanager with the name of a host running an SNMP manager. You then have two options for launching the simple agent:

In these commands, nbTraps is the number of traps that the agent will send. Set it to a small integer to avoid too much output. If you omit this parameter, traps will be sent continuously.

If you don't have an SNMP manager or a second host, don't copy the ACL file or specify it as a property. In the absence of the trap-community definitions, the traps will be addressed to the trap port on the local host. And even if no manager is running, we can still see the agent sending the traps. See "SNMP Trap Handler" in the SNMP manager topic for details about receiving traps.

Interacting with the Trap Generator
  1. Access this agent's HTML adaptor by pointing a web browser to the following URL: http://localhost:8082/. Click on the class=LinkTrapGenerator,ifIndex=1 MBean in the trapGenerator domain.

    Through the HTML adaptor, you can see the MBean representing the trap generator object. You can modify its attributes to change the table entry that it operates on and to change the interval between traps.

  2. Change the trap interval to 10000 so that traps are sent every 10 seconds.

  3. Go back to the agent view and click on the ifEntry.ifIndex=1 MBean in the ifTable domain. Set the reload period to 10, and click the "Reload" button.

    You should see the effect of the trap generator which is to switch the value of the IfOperStatus variable. It is our implementation of the table entry which sends a trap when this status is changed.

  4. Go back to the agent view and click on the name=Snmp MBean in the RFC1213_MIB domain. Scroll down to see the SnmpOutPkts and SnmpOutTraps variables.

    These variables should be the only non zero values, if no manager has connected to the SNMP agent. The Snmp group shows information about the SNMP adaptor, and we can see how many traps have been sent since the agent was launched.

  5. Type <Control-C> when you are finished interacting with the agent.

The LinkTrapGenerator MBean is not manageable through the SNMP adaptor because it is not part of any MIB. It is an example of another MBean providing some control of the SNMP agent, and this control can be exercised by other managers connecting through other protocols. This shows that designing an SNMP agent application involves both the implementation of the MIB functionality and, if desired, the implementation of other dynamic controls afforded by the JMX architecture and the services of the Java Dynamic Management Kit.

Stand-Alone SNMP Agents

The design of the SNMP protocol adaptor and of the MBeans generated by mibgen give you the option of creating an SNMP agent that is not a Java Dynamic Management agent.

This stand-alone agent has no MBean server and thus no possibility of being managed other than through the SNMP protocol. The application must instantiate all MIBs that the SNMP agent will need, as it will be impossible to create them through another manager. The advantage of a stand-alone agent is the reduced size of the application, in terms of memory usage.


Example 16-6 The StandAloneSnmpAgent Example

import com.sun.jdmk.Trace;
import com.sun.jdmk.comm.SnmpAdaptorServer;

public class StandAloneSnmpAgent {

    static SnmpAdaptorServer snmpAdaptor = null;

    public static void main(String args[]) {

        // Parse command line and enable tracing
        [...]

        try {
            // The agent is started on a non standard SNMP port: 8085
            int port = 8085;
            snmpAdaptor = new SnmpAdaptorServer(port);

            // Start the adaptor
            snmpAdaptor.start();

            // Send a coldStart SNMP Trap
            snmpAdaptor.sendV1Trap(0, 0, null);

            // Create the MIB you want in the agent (ours is MIB-II subset)
            RFC1213_MIB mib2 = new RFC1213_MIB();

            // Initialize the MIB so it creates the associated MBeans
            mib2.init();

            // Bind the MIB to the SNMP adaptor
            mib2.setSnmpAdaptor(snmpAdaptor);

            // Optional: create a LinkTrapGenerator
            int ifIndex = 1;
            LinkTrapGenerator trapGen = new LinkTrapGenerator(ifIndex);
            trapGen.start();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Needed to get a reference on the SNMP adaptor object
    static public SnmpAdaptorServer getSnmpAdaptor() {
        return snmpAdaptor;
    }
}

As this example demonstrates, the stand-alone agent uses exactly the same MIB MBeans, with the same customization, as our other agents. However, instead of registering them in the MBean server, they are only instantiated. And whereas the registration process creates all subordinate MBeans of the MIB, now we must call its init method explicitly.

The init method performs the same function as the preRegister method, only it does not register the MBean with the MBean server. Each of the group MBeans then has two constructors, one with and one without a reference to the MBean server. When table entries are added dynamically, the corresponding object only registers the new entry's MBean if the MBean server reference is non-null; that is, only if the MBean is not instantiated in a stand-alone agent.

The mibgen tool automatically generates both the pre-registration methods and the init methods in the MIB MBeans. Therefore, no special action is necessary to use them in either a regular agent or a stand-alone agent. If you use a stand-alone agent for memory considerations, you can remove the registration process from the generated MBean and only customize the "init" process.

In our example, we have applied the customizations to both processes so that the MIB can be used by either agent. In the following code, customizations are noted with MODIF_ comments:


Example 16-7 Customizations in the Generated RFC1213_MIB.java File

public class RFC1213_MIB extends SnmpMib implements Serializable {

    // Default constructor. Initialize the Mib tree
    public RFC1213_MIB() {
        mibName = "RFC1213_MIB";
    }

    // Initialization of the MIB with no registration in the MBean server
    public void init() throws IllegalAccessException {
        // Allow only one initialization of the MIB
        if (isInitialized == true) {
            return ;
        }

        // Initialization of the "Snmp" group
        {
            SnmpMeta meta = new SnmpMeta((SnmpMib)this);
// MODIF_BEGIN
            //meta.setInstance(new Snmp((SnmpMib)this));
            meta.setInstance(new SnmpImpl((SnmpMib)this));
// MODIF_END
            root.registerNode("1.3.6.1.2.1.11", (SnmpMibNode)meta);
        }

        // Initialization of the other groups
        [...]

        isInitialized = true;
    }

    // Initialization of the MIB with AUTOMATIC REGISTRATION
    // in the MBean server
    public ObjectName preRegister(MBeanServer server, ObjectName name)
        throws Exception {

        // Allow only one initialization of the MIB
        if (isInitialized == true) {
            throw new InstanceAlreadyExistsException();
        }

        // Initialize MBeanServer information
        this.server = server;

        // Initialization of the "Snmp" group
        {
            SnmpMeta meta = new SnmpMeta((SnmpMib)this);
// MODIF_BEGIN
            //Snmp instance = new Snmp((SnmpMib)this, server);
            Snmp instance = new SnmpImpl((SnmpMib)this, server);
// MODIF_END
            meta.setInstance(instance);
            root.registerNode("1.3.6.1.2.1.11", (SnmpMibNode)meta);
            server.registerMBean(
                instance, new ObjectName(mibName + ":name=Snmp"));
        }

        // Initialization of the other groups
        [...]

        isInitialized = true;
        return name;
    }

    private boolean isInitialized = false;

}

After the MIB is initialized, it only needs to be bound to the SNMP adaptor, as in the other agents; except that in the stand-alone case, we use the setSnmpAdaptor method which takes a direct reference to the SNMP adaptor instead of an object name. That is all you need to do when programing a stand-alone SNMP agent.

Running the Stand-Alone Agent Example

Launch the stand-alone agent with the following command:


$ java -classpath classpath StandAloneSnmpAgent nbTraps

If you haven't copied the jdmk.acl file to the configuration directory, add the following property to your command line: -Djdmk.acl.file=examplesDir/Snmp/Agent/jdmk.acl

You should see the same initialization messages as with the simple agent. Then, you should see the agent sending out a trap every two seconds. If you have an SNMP manager application, you can send requests to the agent and receive the traps. See "Developing an SNMP Manager" for example applications you can use.

The only limitation of a stand-alone agent is that you cannot access or manage the SNMP adaptor and MIB MBeans in the dynamic management sense. However, the SNMP adaptor still relies on the ACL file for access control and traps, unless you have customized the ACL mechanism, and you can implement other security schemes as described in "Message-Level Security".

Type <Control-C> when you are finished running the stand-alone agent.

Chapter 17 Developing an SNMP Manager

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

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:

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.


Note -

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.


Example 17-1 The SyncManager Example

// 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.

SNMP Trap Handler

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.


Example 17-2 The SnmpTrapListener Implementation

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");
    }
}

Running the SyncManager Example

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 Manager Example

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.


Example 17-3 The AsyncManager Example

// 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.

The Response Handler

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.


Example 17-4 The SnmpHandler Implementation

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);
    }
}

Running the AsyncManager Example

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 Example

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.

Sending an Inform Request

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.


Example 17-5 Sending an Inform Request in SimpleManager1

// 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 17-3.

Receiving Inform Requests

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.


Example 17-6 Receiving Inform Requests in SimpleManager2

// 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.


Example 17-7 The InformListenerImpl Class

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);
    }
}

Running the Inform Request Example

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.

Chapter 18 Security Mechanisms in the SNMP Toolkit

Both the SNMP protocol adaptor and the SNMP manager API provide mechanisms for ensuring the security of management operations. Agents act as information servers, and access control is used to protect this information from unauthorized access. This topic covers the different ways that the SNMP protocol adaptor can limit access to the data in the agent.

Security in a manager involves positively identifying the source of management responses, to ensure that the expected agent answered the request. This amounts to securing the communication against falsification of data and usurpation of identity, which must both be performed at the communication level. Both the agent and the manager handle the communication packets and both provide hooks for implementing your own security scheme.

The complete source code for these examples is available in the Snmp/Agent directory located in the main examplesDir (see "Directories and Classpath" in the preface).

Contents:

Access Control Lists (ACL)

For the SNMP adaptor, the Java Dynamic Management Kit provides access control based on the IP address and community of the manager's host machine. Information on the access rights for communities and host machines is stored in access control lists (ACL). The default implementation provided with the product uses an ACL file, but you may provide your own implementation as described in "Custom Access Control".

The ACL mechanism can also be used to define the communities and managers to which the agent will send traps. When you rely on the ACL trap group, the agent will send traps to all hosts listed in the ACL file. See "Specifying the Trap Destination" for the different ways that an agent application may sends traps.

The following code example gives the contents of the examplesDir/Snmp/Agent/jdmk.acl file used when running the SNMP example applications. When using it in the security examples, you should replace yourmanager with the complete IP address or hostname of the machine running your SNMP manager application.


Example 18-1 A Sample ACL File

acl = {
 {
 communities = public
 access = read-only
 managers = yourmanager
 }
 {
 communities = private
 access = read-write
 managers = yourmanager
 } 
} 

trap = {
  {
  trap-community = public
  hosts = yourmanager
  }
}

ACL File Format

An ACL file contains an acl group defining community and manager access rights and a trap group defining the community and hosts for sending traps.

Format of the acl Group

The acl group contains one or more access configurations.

acl = {
   access1
   access2
     ...
   accessN
}

Each access configuration has the following format:

{
   communities = communityList
   access = accessRights
   managers = hostList
}

The communityList is a list of SNMP community names to which this access control applies. The community names in this list are separated by commas.

The accessRights specifies the rights to be granted to all managers connecting from the machines specified in the hostList. There are two possible values: either read-write or read-only.

The hostList item gives the host machines of the managers to be granted the access rights. The hostList is a comma-separated list of hosts, each of which can be expressed as any one of the following:


Note -

To distinguish between IP addresses and subnet masks in an ACL file, each integer in a subnet mask is separated by an exclamation mark (!) instead of a dot (.).


The set of all access configurations defines the access policy of the SNMP agent. A manager whose host is specified in a hostList and which identifies itself in one of the communities of the same configuration will be granted the permissions defined by the corresponding accessRights. A manager's host may appear in several access configurations provided it is associated with a different community list. This will define different access communities with different rights from the same manager.

A manager whose host-community identification pair does not appear in any of the access configurations will be denied all access. This means that PDUs from this manager will be dropped without being processed.

Format of the trap Group

The trap group specifies the hosts to which the agent will send traps if the ACL mechanism is used. This group contains one or more trap community definitions.

trap = {
   community1
   community2
   ...
   communityN
}

Each defines the association between a set of hosts and the SNMP community string in the traps to be sent to them. Each trap definition has the following format:

{
   trap-community = trapCommunityName
   hosts = trapHostList
}

The trapCommunityName item specifies a single SNMP community string. It will be included in the traps sent to the hosts specified in the hosts item.

The trapHostList item specifies a comma-separated list of hosts. Each host must be identified by its name or complete IP address.

When the SNMP protocol adaptor is instructed to send a trap using the ACL mechanism, it will send a trap to every host listed in the trap community definitions. If a host is present in more than one list, it will receive more than one trap, each one identified by its corresponding trap community.

Enabling Access Control

The default ACL mechanism provided with the Java Dynamic Management Kit relies on an ACL file to define the access rights and trap recipients. To enable access control with this mechanism, you must first write an ACL file to reflect the access and trap policy of your SNMP agent. Then, there are two ways to enable file-based access control, one way to modify the file in use and one way to disable access control.

The simplest way of enabling access control and traps is to ensure that an ACL file exists when the SNMP protocol adaptor MBean is instantiated. In order to be automatically detected, the ACL file must be named jdmk.acl and must be located in the configuration directory of the Java Dynamic Management Kit installation. On Unix systems with a standard installation of the product, the configuration directory is owned by root and requires super-user privileges in order to write or modify the ACL file.

Operating Environment 

Configuration Directory 

Solaris 

installDir/SUNWjdmk/jdmk4.2/JDKversion/etc/conf/

Windows NT 

installDir\SUNWjdmk\jdmk4.2\JDKversion\etc\conf\

In order for the application to locate the configuration directory, the classpath of the Java virtual machine running the agent must include the full path of the jdmkrt.jar file.

The other way of enabling file-based access control is to specify a different file through the jdmk.acl.file system property. The filename associated with the property will override any ACL file in the configuration directory. This property may be set programmatically, but it is usually done on the command line when launching your agent. For example, if the full pathname of your ACL file is MyAclFile, use this command to launch the agent with SNMP access control enabled:


$ java -classpath classpath -Djdmk.acl.file=MyAclFile MyAgent

If an ACL file exists, the access rights it defines apply to all management applications that access the agent through its SNMP adaptor. This includes managers on the agent's local machine: the ACL groups must explicitly give permissions to localhost or the host's machine name or IP address for such managers. If the ACL file does not exist when the SNMP adaptor is instantiated, either in the configuration directory or defined as a property, all SNMP requests will be processed, and traps will be sent only to the localhost.

The ACL file-based mechanism relies on the JdmkAcl class to provide the access control functionality. This is the class that is initialized with the contents of the ACL file. This class provides the rereadTheFile method to reset the access control and trap lists with the contents of the ACL file. This method will reload the same file that was used originally, regardless of any new property definitions. After you have updated the ACL file, call the following methods to update the access control lists:

// assuming mySnmpAdaptor is declared as an SnmpAdaptorServer object
JdmkAcl theAcl = (JdmkAcl)(mySnmpAdaptor.getIPAcl());
theAcl.rereadTheFile();

The JdmkAcl class that is used by default might not be suitable for all environments. For example, it relies on the java.security.acl package which is not available in the PersonalJavaTM runtime environment. Therefore, one constructor of the SnmpAdaptorServer class lets you override this default, forcing the adaptor not to use access control, regardless of any existing ACL file. If you specify false for the useAcl parameter of this constructor, the SNMP adaptor won't even search for an ACL file. In this case, no access control is performed, as if there were no ACL file: all SNMP requests will be processed, and traps will be sent only to the localhost. For security considerations, the use of access control cannot be toggled once the SNMP adaptor has been instantiated.

Custom Access Control

The JdmkAcl class which relies on an ACL file is the default access control mechanism in the SNMP adaptor. For greater adaptability, the SnmpAdaptorServer class has constructors that let you specify your own access control mechanism. For example, if your agent runs on a device with no file system, you could implement a mechanism which doesn't rely on the jdmk.acl file.

In order to instantiate an SNMP adaptor with your own access control, use one of the constructors which takes an acl parameter of the type IPAcl. Note that if this parameter's value is null, or if you use a constructor that doesn't specify an acl parameter, the SNMP adaptor will use the JdmkAcl class by default. If you want to instantiate an SNMP adaptor without access control, call the constructor with the useAcl parameter set to false.

Your access control mechanism must be a class that implements the IPAcl interface. This interface specifies the methods that the SNMP adaptor uses to check permissions when processing a request. If you instantiate the SNMP adaptor with your access control class, the adaptor will call your implementation of the access control methods. Again, for security reasons, the IPAcl implementation in use cannot be changed once the SNMP adaptor has been instantiated.

The JdmkAcl class implements the default access mechanism that uses the jdmk.acl file. It is also an implementation of the IPAcl interface, and it provides a few other methods, such as rereadTheFile, to control the ACL mechanism.

Message-Level Security

The SecureAgent example shows another level of security at the SNMP message level. Whereas the access control mechanism handles access rights to all MIBs for communities of manager hosts, message-level security lets you control how PDUs (Protocol Data Units) representing requests are encoded and decoded by the SNMP protocol adaptor.

The data in an SNMP message is stored in its raw form as a byte array. When receiving a message, the SNMP protocol adaptor must decode the data to obtain the corresponding request, and before sending a request, the adaptor must encode it as a message. By default, the basic encoding rules (BER) are used to translate back and forth between message and decoded PDU. The SNMP protocol adaptor provides a hook to let you implement your own encoding and decoding mechanism.

Message-level security relies on the following classes in the javax.management.snmp package:

An SnmpPduPacket object represents the fully decoded description of an SNMP request. In particular, it includes the operation type (get, set, ...), the list of variables to which the operation applies, the request identifier, and the protocol version.

The SnmpMessage object is a partially decoded representation of the SNMP request. The type of the request, the variables and their values all remain encoded as a byte array. This object also contains the default BER encoding and decoding methods. The SnmpMessage class is derived from the Message syntax in RFC 1157 and RFC 1902.

The SnmpPduFactory interface defines the method signatures for encoding PDUs into messages and decoding messages into PDUs. By providing an implementation of this class, you can fully control the contents of messages and see the contents of packets before they are processed.

Both the packet and message classes also contain port and address information of the SNMP manager. When implementing your own security mechanism, you also have access to the contents of the PDU or message you are handling. This lets you implement security based on several factors:

Because message-based security gives you access to all these different factors, you can perform elaborate filtering of incoming requests. For example, you could limit the access of certain SNMP managers to certain variables, or you could filter variable bindings, such as untrusted IP addresses, before they are assigned.

If your security is based on encryption of the message data, your manager must of course be using the same encryption. Because the SNMP classes are also part of the SNMP manager API, you can reuse the same encryption code in your manager if it is developed in the Java programming language. Security in the SNMP manager API is implemented in the SnmpPeer and SnmpEventReportDispatcher objects, see "SNMP Manager Security" for more information.

If your security scheme is based only on the sender of the message or contents of the PDU, it can be applied unilaterally by the agent, without requiring any coordination with the manager application. This is what is demonstrated in the SecureAgent example.

Implementing the SnmpPduFactory Interface

In the SNMP protocol adaptor, the task of translating an SnmpMessage object into an SnmpPduPacket object is delegated to an object which implements the SnmpPduFactory interface. This interface defines two methods, one for decoding messages into PDUs and one for encoding PDUs into messages:

In our example, the SnmpPduFactoryImpl class implements the decodePdu method to reject messages if they originate from certain hosts. The list of hosts to refuse is passed to the class constructor at instantiation. The encodePdu method only does the standard BER encoding of outgoing messages.


Example 18-2 Implementing the SnmpPduFactory Interface

public class SnmpPduFactoryImpl implements SnmpPduFactory {

    private String[] hostNames;

    // HostNames is the array of the host names whose requests will be
    // refused by the agent
    public SnmpPduFactoryImpl(String[] hostNames) {
        this.hostNames = hostNames;
    }

    public SnmpPduPacket decodePdu(SnmpMessage msg)
        throws SnmpStatusException {

        // Get the sender's hostname
        String from = msg.address.getHostName();
        for (int i = 0; i < hostNames.length; i++) {
            if (from.equals(hostNames[i])) {
                java.lang.System.out.println("Pdu rejected from " + from);
                return null;
            }
        }
        // If the host is accepted, we return the standard BER decoding
        return msg.decodePdu();    
    }

    // No security when sending, just do the standard BER encoding
    public SnmpMessage encodePdu(SnmpPduPacket pdu, int maxPktSize)
        throws SnmpStatusException, SnmpTooBigException {
        
        SnmpMessage result = new SnmpMessage();
        result.encodePdu(pdu, maxPktSize);
        return result;    
    }
}

Beyond our simple check of the sender's hostname, our example relies on the standard BER encoding and decoding of the SnmpMessage class. Even if you choose to implement encryption, it can still be implemented on top of the standard BER for simplicity. In this case, you only need to encrypt the message's byte array after the standard encoding and decrypt it before the standard decoding.

When you implement the encodePdu, you must ensure that it also handles trap PDUs, by encoding them as they will be expected by the manager application (see "SNMP Manager Security"). For example, trap messages are decoded separately from response messages in applications based on the SNMP manager API.

Using a Custom PDU Factory

To use your custom PDU factory in your SNMP agent, you need to call the usePduFactory method of your SnmpAdaptorServer instance. First instantiate your PDU factory implementation and then pass it to this method. Your encoding and decoding scheme will then replace the standard one used by the SNMP protocol adaptor.

The SecureAgent.java file contains a simple agent like the one presented in "The SNMP Protocol Adaptor". It only adds the call to force the SNMP adaptor to use the new PDU factory that we specify.

// Use SnmpPduFactoryImpl class for SnmpPduFactory to filter requests.
// Enter your list of refused hosts as arguments when launching this agent.
// The agent will reject requests coming from the specified hosts.
//
String[] refusedHosts = new String[args.length];
refusedHosts = args;
snmpAdaptor.usePduFactory(new SnmpPduFactoryImpl(refusedHosts));

The SNMP adaptor will then use this PDU factory to filter incoming requests based on their originating host. It will also encode all outgoing messages, including any traps that are sent, though it does nothing more than standard BER encoding. The secure agent example does not demonstrate traps, and the LinkTrapGenerator class is not written to function with the SecureAgent class. However, the SnmpPduFactoryImpl could be used as it is shown above in the SNMP Agent example.

Running the Secure Agent Example

You can only demonstrate the output of our custom PDU factory if you have an SNMP manager application which can connect to the secure agent. See "Developing an SNMP Manager" for example applications you can use.

The SecureAgent class takes command line arguments to create the list of hosts from which it will refuse SNMP requests. Use the following command to launch the secure agent example:


$ java -classpath classpath SecureAgent [host1..hostN]

Whenever one of the refused hosts sends a request, you should see the message displayed by our custom PDU factory implementation. Type "Control-C" when you are finished running the secure agent.

You can combine message-level security and access control defined by the presence of an ACL file. The ACL file indicates trusted hosts and communities from which messages are accepted. After they are accepted, they are decoded with the message-level security you provide. This lets you provide more precise security based on types of requests or the target variable, as well as any encryption.

SNMP Manager Security

Since the role of SNMP managers is that of a client, their security needs revolve around sending and receiving data safely. To do this, the SNMP manager API also relies on implementations of the SnmpPduFactory interface in order to control message-level encoding and decoding.

By default, the SNMP manager API relies on the standard BER encoding implemented by the SnmpPduFactoryBER class, which is the encoding used by default by all SNMP agents, not just those developed with the Java Dynamic Management Kit. You may change the encoding to use your own implementation of the SnmpPduFactory interface through the following two hooks:

Using these hooks, message-level security can be implemented regardless of whether the managers are synchronous or asynchronous. If you are using a symmetrical encryption of messages between your agent and manager, you may also reuse the same classes for your PDU factory implementation on both agent and manager sides, assuming they are both using the Java Dynamic Management Kit SNMP toolkit.

Otherwise, if your encryption is not symmetrical or if your agents do not use the SNMP protocol adaptor, your PDU factory implementation will necessarily be specific to the security scheme you choose to implement in your manager application.

The example applications do not cover the security features in the SNMP manager API. Please refer to the Java Management Extensions SNMP Manager API document and the Javadoc API for more details about using these hooks to implement manager security.

Chapter 19 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 in order 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 a request on a MIB.

For example, if an agent receives a get request for a 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 first find the MBean containing the corresponding attribute and to 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 19-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 for 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 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". As 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 session of the master agent, in the same way that it would 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 potentially answers only 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 interval 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 may use the OID table object to refer to the MIB variables by name. Here is the code to initialize the manager:


Example 19-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. The SNMP proxy implementation is only 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 nor the check method.


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


Example 19-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 implements this algorithm for reducing errors 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 19-4 Implementing a get Request in the Proxy

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

    // Get the protocol version
    final int version = inRequest.getVersion();

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

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

// the internal implementation
private void get(SnmpMibRequest inRequest, int version, SnmpSession session)
    throws SnmpStatusException {
	
    // Construction of the SnmpVarBindList for the request.
    final SnmpVarbindList varbindlist = 
        new SnmpVarbindList("SnmpMibAgentImpl varbind list",
                             inRequest.getSubList() );

    SnmpRequest request = null;
    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(inRequest, SnmpDefinitions.snmpVersionOne, sessionV2WithV1);
            return;
        }
    }

    // Check the request result
    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(inRequest, SnmpDefinitions.snmpVersionOne, sessionV2WithV1);
            return;
        }
    }
    // Get and display the returned values
    final SnmpVarbindList result = request.getResponseVarBindList();
    java.lang.System.out.println("\nResult: \n" + 
                                 result.varBindListToString());

    // Update the list parameter with the result
    // The varbinds in the result list are expected to be in the same
    // order as in the request, so we can safely loop sequentially
    // over both lists.
    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 uses the SNMP manager API, the master agent application needs the SNMP manager classes in its classpath, for both compilation and execution. In addition, if the proxy object uses the SNMP OID tables that are generated for its MIBs by the mibgen tool, these classes must also be found in the classpath.

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$