Java Dynamic Management Kit 5.1 Tutorial

Part V SNMP Interoperability

From the outset, the Java Dynamic Management Kit (Java DMK) 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 DMK 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 that access both SNMP agents and Java dynamic management agents.

The agent and manager tools of the Java DMK 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 part use the toolkit on both agent and manager sides, but this is only one possible configuration.

This part contains the following chapters:


Note –

The Java packaging of the SNMP classes for Java DMK 5.1 has changed. In Java DMK 5.0, the SNMP classes were included in the SUNWjsnmp package, and they required a separate Java archive (JAR) file, jsnmpapi.jar. In Java DMK 5.1, the SNMP classes are packaged in the SUNWjdmk-runtime package, and require the same jdmkrt.jar JAR file as the rest of the current Java DMK classes. This new arrangement avoids the issue of potentially conflicting versions of the SUNWjsnmp package encountered under Java DMK 5.0.

In addition, the SNMP API delivered with Java DMK 5.0 is now deprecated. The SNMP API in Java DMK 5.1 is effectively a completely new SNMP API, that introduces a more orthodox system of Java class naming.

To use existing SNMP implementations that you created using Java DMK 5.0 alongside SNMP implementations created using Java DMK 5.1, you must translate the class names of the 5.0 implementations into the new format. How to perform this translation is explained in the Release Notes.

To continue to use SNMP implementations you created using version 5.0 of Java DMK under version 5.1, a new JAR file called legacysnmp.jar is provided. You must add this new JAR to your classpath when running your Java DMK 5.0 SNMP implementations under Java DMK 5.1.

All the examples of SNMP code given in the /examples/current/Snmp directory have already been translated to implement the new class naming system.


Chapter 16 Creating an SNMP Agent

Using the Java Dynamic Management Kit (Java DMK), you can create an agent application that is both an SNMP agent and a normal Java Management Extensions (JMX) agent. SNMP MIBs can be represented as MBeans and the SNMP protocol adaptor exposes them to SNMP managers. Only MBeans derived from MIBs can be managed through the SNMP protocol adaptor, but other managers can 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 that expose the SNMP variables through their attributes. The mibgen tool creates skeleton getters and setters that only read or write internal variables. To expose the behavior of your host machine or device, you need to implement the code that accesses the host- or device-specific functionality.

There are two SNMP protocol adaptors in the Java DMK 5.1.

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 current/Snmp/Agent example directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

16.1 MIB Development Process

Here we describe the process for making MIBs manageable through the SNMP protocol adaptor of the Java DMK. 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 5.1 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 gives only a simple implementation of the MIB-II for demonstration purposes. However, this shows you the way to extend the generated classes to provide your own implementation.

The mibgen tool handles all three SNMP protocols identically, and the MIBs implemented are entirely protocol neutral. Consequently, code generated by mibgen for previous versions works perfectly in the SNMPv3 framework.

16.1.1 Generating MIB MBeans

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


$ mibgen -d . mib_II_subset.txt

This generates 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.

Because 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 that you must implement to do the checks that you require before the corresponding “Set” operation. You can add operations and expose them in the MBean interface, but the SNMP manager cannot access them. However, other managers can call these operations if they are connected through another protocol.

16.1.2 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 can reuse when you need to implement these common SNMP groups.

16.1.3 Compiling the MBeans and Agents

Compile all the classes in the examplesDir/current/Snmp/Agent directory. The classpath must contain the current directory (.):


$ javac -classpath classpath -d . *.java

We are now ready to look at the implementation of SNMPv1/v2 and SNMPv3 agents and run the example applications.

16.2 SNMP Protocol Adaptor

Once your MIBs are implemented as MBeans, your agent application needs an SNMP protocol adaptor to function as an SNMP agent. Because 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.

Since Java DMK 5.1, the performance of the SNMP protocol adaptor has been improved by the addition of multithread support.

The following SNMPv1/v2 Agent example launches the protocol adaptor for SNMPv1/v2 agents, through the code of the agent application.


Example 16–1 SNMPv1/v2 Agent Application

public class Agent {

    static SnmpAdaptorServer snmpAdaptor = null;

    private static int nbTraps = -1;

    public static void main(String args[]) {
        
        final MBeanServer server;
        final ObjectName htmlObjName;
        final ObjectName snmpObjName;
        final ObjectName mibObjName;
        final ObjectName trapGeneratorObjName;
        int htmlPort = 8082;
        int snmpPort = 161;

        [...]    
        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
            snmpAdaptor.addMib(mib2);

            [...]

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

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

The following SNMPv3 AgentV3 example launches the protocol adaptor for SNMPv3 agents, in the same way as in the SNMPv1/v2 Agent example. This example creates a simple SNMPv3 agent, without encryption. See "Security Mechanisms in the SNMP Toolkit" for details of how to implement SNMPv3 agents with encryption.


Example 16–2 SNMPv3 AgentV3 Application

public class AgentV3 {

    static SnmpV3AdaptorServer snmpAdaptor = null;

    private static int nbTraps = -1;
    
    public static void main(String args[]) {
        
        final MBeanServer server;
        final ObjectName htmlObjName;
        final ObjectName snmpObjName;
        final ObjectName mibObjName;
        final ObjectName trapGeneratorObjName;
        int htmlPort = 8082;
        int snmpPort = 161;
[...]
       
        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 SnmpV3AdaptorServer(snmpPort);
            server.registerMBean(snmpAdaptor, snmpObjName);
	           snmpAdaptor.registerUsmMib(server, null);
            snmpAdaptor.start();

            // Send a coldStart SNMP Trap. 
            // Use port = snmpPort+1.
            //
            snmpAdaptor.setTrapPort(new Integer(snmpPort+1));
            snmpAdaptor.snmpV1Trap(0, 0, null);
            println("Done.");
      
            // 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_IMPL();
            server.registerMBean(mib2, mibObjName);
      
            // Bind the SNMP adaptor to the MIB 
         	   snmpAdaptor.addMib(mib2, "TEST-CONTEXT");

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

    public static SnmpAdaptorServer getSnmpAdaptor() {
       return snmpAdaptor;
    }

16.2.1 Starting the SNMP Adaptor

We start the SNMP adaptor in the same way that we start the HTML adaptor. First we create a meaningful object name for its MBean, then we instantiate the class with a constructor enabling 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. Because other applications might be using this port on a host, our simple agent, and all the other examples in this section, use port 8085. When we connect to this agent, our SNMP manager must specify this non-standard port number.


Note –

On certain platforms, applications also require superuser privileges to assign the default SNMP port 161. If your SNMP adaptor uses this port, its agent application must be started with superuser privileges.


16.2.2 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 was generated by the mibgen tool (see 16.1 MIB Development Process). We give it a meaningful object name and then we register it in the MBean server.

The registration process enables the MBean to 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 registers the new entry MBean into the MBean server as well.

If you do not want 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 creates all necessary MBean objects without registering them in the MBean server.

16.2.3 Binding the MIB MBeans

The SNMP adaptors do 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 add the MIB to the SNMP adaptor by calling the setSnmpAdaptor method.

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.

Even though the SNMP adaptors can 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.

16.2.4 MIB Scoping

In the SnmpV3AdaptorServer, it is possible to specify a scope, or context name, when registering a MIB. Conceptually, there is one instance of the global OID tree per scope. If you do not specify a context name when registering a MIB, the MIB is registered in the default scope. You can thus register SnmpProxy objects in any default or specific scope. Routing and MIB overlapping is handled in the same way in any scope.

When an incoming request is received, the SnmpV3AdaptorServer first determines the scope of that request. Only the SNMP agents that have been registered in that scope are triggered by that request.

If the incoming request is an SNMPv3 request, the scope is identified by the context name specified in the request protocol data unit (PDU). If no context name is specified, the scope of that request is the default scope. If the incoming request is an SNMPv1 or v2 request, the scope of the request is the default scope, unless mapping community strings to context names is enabled in the adaptor.

To register a MIB in a specific context, call the following method:

myMib.setSnmpAdaptor(myAdaptor, "myContext")

This method only works for adaptors that have been registered with an MBean server.

16.2.5 Accessing a MIB MBean

Once the MBean representing a MIB has been instantiated and bound to one of the two SNMP adaptors, it is accessible through that 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. One SNMP protocol adaptor is compatible with SNMPv1 and SNMPv2c, and the other is compatible with these two protocols as well as SNMPv3.

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. Because 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 were to include 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 can 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 cannot 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 are 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 examples. 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.

16.2.6 Managing the SNMP Adaptors

Non-SNMP managers can also control the SNMP agent through the MBean of the SNMP adaptors. 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 SNMPv1v/2 adaptor server implements the SnmpAdaptorServerMBean interface to define its operating information. The SNMPv3 adaptor server implements a similar interface called SnmpV3AdaptorServerMBean. The SNMP protocols define 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 adaptors also expose 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 enables you to 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 19.2.1 Enabling User-Based Access Control).

To Run the SNMPv1/v2 Agent Example
  1. After building the example as described in 16.1 MIB Development Process, start the simple SNMPv1/v2 agent with the following command:


    $ java -classpath classpath:. Agent nbTraps
    

    Set nbTraps to zero.

    You should see some initialization messages, including our notification listener giving information about the two table entries that are created. Access this agent's HTML adaptor by pointing a web browser to the following URL: http://localhost:8082/.

  2. Through the HTML adaptor, you can see the MBeans representing the MIB:

    • The class=RFC1213_MIB MBean in the snmp domain is the MBean representing the MIB; it contains a name and information about the SNMP adaptor to which the MIB is bound

    • The RFC1213_MIB domain contains the MBeans for each group; both name=Snmp and name=System contain variables with values provided by our customizations

    • The RFC1213_MIB/ifTable domain contains the entries of the Interfaces table

    • The trapGenerator domain contains the class that sends traps periodically, as part of our sample MIB implementation

  3. In any of these MBeans, you can write new values into the text fields of exposed attributes and click the Apply button.

    This sets the corresponding SNMP variable, and thereafter, SNMP managers see the new value. This is an example of managing a MIB through a protocol other than SNMP.

  4. Press Control C when you have finished viewing the agent.

16.2.7 Configuring SNMPv3 Security for Agents

Before you run the SNMPv3 agent examples, you require some information about how SNMPv3 security is configured. Below are brief descriptions of the SNMPv3 security files that provide you with the information you need to run the SNMPv3 examples in this chapter. Full descriptions of the SNMPv3 security mechanisms are given in 19.3 SNMPv3 User-Based Security Model.

The SNMPv3 security mechanisms are defined in two text files:

The files used by the SNMPv3 agent examples are provided in the examplesDir/current/Snmp/Agent directory. These files are used by the examples in the subsequent sections of this chapter.


Example 16–3 A jdmk.security File for an SNMPv3 Agent

The jdmk.security identifies the SNMP engine, authorized user and the security settings for the SNMPv3 session:

#Local engine ID
localEngineID=0x8000002a05819dcb6e00001f95
#Number of boots
localEngineBoots=0

#User and security configuration
userEntry=localEngineID,defaultUser,,usmHMACMD5AuthProtocol,mypasswd

The local engine ID and the number of times that engine will boot are read by the agent when it is created.

The authorized users and the security levels for the SNMP session are defined by the userEntry. This particular jdmk.security file defines a user that implements authentication, but not privacy. Consequently, the settings are as follows:

localEngineID

The identifier of the local engine, as specified earlier in the file

defaultUser

The name of the authorized user

usmHMACMD5AuthProtocol

The authentication algorithm; in this case, HMAC MD5

myPasswd

The authentication password


Note –

User-based access control is not used by the examples in this chapter, so we do not examine the jdmk.uacl file here. See Chapter 19, Security Mechanisms in the SNMP Toolkit to find out how to implement user-based access control.


To Run the SMNPv3 AgentV3 Example
  1. After building the example as described in 16.1 MIB Development Process, start the simple SNMPv3 agent with the following command:

    You have to direct the AgentV3 example to its security file to run it.


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    AgentV3 nbTraps
    

    Set nbTraps to zero.

    You should see some initialization messages, including our notification listener giving information about the two table entries that are created. Access this agent's HTML adaptor by pointing a web browser to the following URL: http://localhost:8082/.

  2. Through the HTML adaptor, you can see the MBeans representing the MIB:

    • The SNMP_USER_BASED_SM_MIB domain contains information pertaining to the user-based security model implemented; see "Security Mechanisms in the SNMP Toolkit" for details of how to implement SNMPv3 user-based security.

    • The class=RFC1213_MIB MBean in the snmp domain is the MBean representing the MIB; it contains a name and information about the SNMP adaptor to which the MIB is bound.

    • The RFC1213_MIB domain contains the MBeans for each group; both name=Snmp and name=System contain variables with values provided by our customizations.

    • The RFC1213_MIB/ifTable domain contains the entries of the Interfaces table.

    • The trapGenerator domain contains the class that sends traps periodically, as part of our sample MIB implementation.

  3. In any of these MBeans, you can write new values into the text fields of exposed attributes and click the “Apply” button.

    This sets the corresponding SNMP variable, and thereafter, SNMP managers see the new value. This is an example of managing a MIB through a protocol other than SNMP.

  4. Press Control C when you have finished viewing the agent.

16.3 Sending Traps

Agents can send unsolicited event reports to management applications by using traps. The SNMP protocol adaptor can send v1, v2 and v3 traps, the differences 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. However, under SNMPv2 and v3, acknowledgements can be sent in the form of informs.

Inform requests are acknowledged event reports, they are sent by entities acting in a manager role, according to RFC 1905. In the Java DMK, both the SNMP adaptor and the classes of the SNMP manager API can send inform requests. Manager-to-manager inform requests are described in 17.3 Inform Requests. Agent-to-manager inform requests are demonstrated by the applications in the current/Snmp/Inform/ example directory located in the main examplesDir directory.

In this example, we demonstrate how our simple SNMP agent application can send traps. The class IfEntryImpl in the example directory extends the IfEntry class generated by mibgen to provide a method that switches the IfOperStatus variable and sends a trap. Before sending the trap, the example establishes whether the agent is an SNMPv1/v2 agent or SNMPv3, and sends a trap accordingly. This is an example of customization of the generated code: an agent-side entity switches the operation status, the MIB variable is updated and a trap is sent to SNMP managers.


Example 16–4 Sending a Trap in the IfEntryImpl Class

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

    // Identify the adaptor created by one of the entry-point
    // classes for this example.

    private void initialize() {

			boolean encryption=false;
			SnmpAdaptorServer snmpAdaptor = null;
	
		// Determine wheter we are within the the scope of Agent.java, 
		// StandAloneSnmpAgent.java, AgentV3.java, AgentEncryptV3.java
	
		try {
	    snmpAdaptor = Agent.getSnmpAdaptor();
	    if (snmpAdaptor != null) return;

	    snmpAdaptor = StandAloneSnmpAgent.getSnmpAdaptor();
	    if (snmpAdaptor != null) return;

	    snmpAdaptor = AgentV3.getSnmpAdaptor();
	    if (snmpAdaptor != null) return;

	    snmpAdaptor = AgentEncryptV3.getSnmpAdaptor();
	    if (snmpAdaptor != null) {
		encryption=true;
		return;
	    }

	} finally {
	    // Set the value of this.snmpAdaptor and 
	    // this.securityLevel
	    // 
	    this.snmpAdaptor=snmpAdaptor;
	    this.securityLevel = SnmpDefinitions.noAuthNoPriv;
	    if (snmpAdaptor == null) return;
	    if (snmpAdaptor instanceof SnmpV3AdaptorServer) {
		this.securityLevel = (encryption?SnmpDefinitions.authPriv:
				      SnmpDefinitions.authNoPriv);
	    } 
	}
    }

    	// Send SNMP v1 traps with the generic number of the trap
    	// according to the "IfOperStatus" value.
     //
    	public void sendTrap(int generic) {

        if (snmpAdaptor == null) {
           java.lang.System.err.println("BUG: 
				IfEntryImpl.sendTrap(): 
				snmpAdaptor is null");
           return;
        }

        String generic_string= null;

        switch (generic) {
		case 3:
			generic_string = "linkUp";
			break;
		case 2:
			generic_string = "linkDown";
			break;
		default:
			java.lang.System.err.println("BUG: IfEntryImpl.sendTrap(): 
			bad generic: " + generic);
			return;
        }

        SnmpVarBindList varBindList = new SnmpVarBindList();

        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.addVarBind(varBind1);

        java.lang.System.out.print("NOTE: Sending a " + generic_string +
                                   " SNMP trap for the Interface " + 
				   IfIndex +
                                   " to each destination defined" +
				   " in the ACL file...");
        try {
	    	//Send SNMPv3 trap if v3 agent only.
	   		 if(snmpAdaptor instanceof SnmpV3AdaptorServer) {
				final SnmpV3AdaptorServer adaptorV3=
		   		 (SnmpV3AdaptorServer)snmpAdaptor;
				adaptorV3.snmpV3UsmTrap("defaultUser",
					securityLevel,
					"TEST-CONTEXT",
					new SnmpOid("1.2.3.4.5.6.7.8.9.0"), 
					varBindList);
	    }
	  	 	 // Send v1 trap in every case.
	    		snmpAdaptor.snmpV1Trap(generic, 0, varBindList);
			} catch (Exception e) {
            e.printStackTrace();
        }
        java.lang.System.out.println("Done.");
    }
}

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 different possible agent implementations. This code is specific to these agents and is only an example of how to retrieve this information.

As shown in the above example, SNMPv1 traps are always sent, regardless of the version of SNMP of the agent. An SNMPv3 trap is sent only when the agent is an SNMPv3 agent.

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


Example 16–5 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–6 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();

16.3.1 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 IP-based access control lists (InetAddressAcl), as described below.

In all cases, traps are sent to the port specified by the current value of the TrapPort attribute on the SnmpAdaptorServer or SnmpV3AdaptorServer 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.

Although SNMPv3 implements user-based access control for other types of requests, traps and informs are always sent using InetAddressAcl, in all versions of SNMP.

16.3.1.1 Using an InetAddressAcl Trap Group

The methods below were used in Example 16–4 to send SNMPv1 and v3 traps. They are presented here with their SNMPv2 equivalent (see the Javadoc API for a description of the parameters).

Using these methods, you must first define the trap group in an InetAddressAcl. See 19.1 IP-Based Access Control Lists for a formal definition of the trap group and instructions for defining the InetAddressAcl file when starting the agent. By default, these lists are file-based, but you can implement other mechanisms, as described in 19.1.3 Custom Access Control.

In this example, we provide the following template file.


Example 16–7 Trap Group of the jdmk.acl File

acl = {
  …
}

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

The trap group lists all the hosts to which the SNMP protocol adaptor sends every trap. A community definition associates a community name with a list of hosts specified either by one of the following identifiers:

Hostname

The name of the host

IP v4 and IPv6 address

For example, 123.456.789.12 for IPv4, and fe80::a00:20ff:fe9b:ea82 for IPv6

IPv4 and IPv6 netmask prefix notation

For example, 123.456.789.12/24 for IPv4, and fe80::a00:20ff:fe9b:ea82/64 for IPv6

All hosts in a community definition receive the trap in a PDU identified by the community name.


Note –

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


Given this definition, traps are 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 that will receive traps along with the community string for each host or group of hosts.


Note –

SNMPv3 does not use the community string to identify destinations. Only use the manager's IP address when creating an SNMPv3 trap group, or the contextName to define the scope of the requests sent.


If the InetAddressAcl 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 local host.

16.3.1.2 Specifying the Hostname Directly

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

In the first two cases, these methods take an address and a community string, in addition to the version-specific trap information. The address is an InetAddress object that is usually instantiated by its static methods getLocalHost or getByName. The second 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 is used as the community when sending the trap PDU.

The SNMPv3 method also takes an InetAddress, but does not use the community string. Only use the manager's IP address when creating an SNMPv3 trap group, or the contextName to define the scope of the requests sent.

Either one of these methods sends a trap to a single manager using a single community string. The InetAddressAcl trap group mechanism is better suited to sending traps to multiple managers, though it requires you to set up 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.

16.3.2 Traps in the Agent and AgentV3 Examples

Before starting 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 can start the example SNMPv3 agent by replacing Agent with AgentV3.

Start the agent, specifying the InetAddressAcl file as a property:


$ java -classpath classpath 
-Djdmk.acl.file=examplesDir/current/Snmp/Agent/jdmk.acl \ 
       -classpath classpath:. Agent nbTraps

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

If you do not have an SNMP manager or a second host, do not copy the InetAddressAcl file or specify it as a property. In the absence of the trap-community definitions, the traps are 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 17.1.4 SNMP Trap Handler for details about receiving traps.

To Interact 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 is to switch the value of the IfOperStatus variable. It is our implementation of the table entry that 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 nonzero 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 started.

  5. Press Control-C when you have 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 Java DMK.

16.4 Standalone SNMP Agents

The design of the SNMP protocol adaptors 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 standalone 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 needs, as it is impossible to create them through another manager. The advantage of a standalone agent is the reduced size of the application, in terms of memory usage.

This applies to all three versions of SNMP.


Example 16–8 StandAloneSnmpAgent Example

;
import com.sun.management.comm.SnmpAdaptorServer;

public class StandAloneSnmpAgent {

    static SnmpAdaptorServer snmpAdaptor = null;

    private static int nbTraps = -1;

    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 = 161;
            port = 8085;
            snmpAdaptor = new SnmpAdaptorServer(port);

            // Start the adaptor
            snmpAdaptor.start();

            // Send a coldStart SNMP Trap
            snmpAdaptor.setTrapPort(new Integer(port+1));
            snmpAdaptor.snmpV1Trap(0, 0, null);

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

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

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

            // Optional: create a LinkTrapGenerator
            int ifIndex = 1;
            LinkTrapGenerator trapGenerator = 
                 new LinkTrapGenerator(ifIndex, nbTraps);
            trapGenerator.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 standalone 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 standalone 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 standalone agent. If you use a standalone agent for memory considerations, you can remove the registration process from the generated MBean and only customize the init process.


Example 16–9 Customizations in the Generated RFC1213_MIB_Impl.java File

class RFC1213_MIB_IMPL extends RFC1213_MIB {

    public RFC1213_MIB_IMPL() {
	    super();
    }
    /**
     * Passing it a name in order to register the same mib in 2 MBeanServer.
     */
    public RFC1213_MIB_IMPL(String name) {
	    super();
	    mibName = name;
    }
        protected Object createSnmpMBean(String groupName, String groupOid, 
				     ObjectName groupObjname, 
				     MBeanServer server)  {

        // Note that when using standard metadata,
        // the returned object must implement the "InterfacesMBean"
        // interface.
        //
        if (server != null) 
            return new SnmpImpl(this,server);
        else 
            return new SnmpImpl(this);
    }

        protected Object createSystemMBean(String groupName, 
				       String groupOid, 
				       ObjectName groupObjname, 
				       MBeanServer server)  {

        // Note that when using standard metadata,
        // the returned object must implement the "InterfacesMBean"
        // interface.
        //
        if (server != null) 
            return new SystemImpl(this,server);
        else 
            return new SystemImpl(this);
    }

        protected Object createInterfacesMBean(String groupName, 
					   String groupOid, 
					   ObjectName groupObjname, 
					   MBeanServer server)  {

        // Note that when using standard metadata,
        // the returned object must implement the "InterfacesMBean"
        // interface.
        //
        if (server != null) 
            return new InterfacesImpl(this,server);
        else 
            return new InterfacesImpl(this);
    }

}

After the MIB is initialized, it only needs to be bound to the SNMP adaptor, as in the other agents; except that in the standalone 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 standalone SNMP agent.

You can subclass the MIB and override the group MBean factory method to instantiate your own customized MBean class, replacing for instance new Interfaces() with new InterfacesImpl(), as shown in the code example.

16.4.1 Running the Standalone Agent Example

To Run the Standalone Agent Example
  1. Start the standalone agent with the following command:


    $ java -classpath classpath StandAloneSnmpAgent nbTraps
    
  2. If you have not copied the jdmk.acl file to the configuration directory, add the following property to your command line:


    -Djdmk.acl.file=examplesDir/current/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 standalone 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 InetAddressAcl file for access control and traps, unless you have customized the InetAddressAcl mechanism, and you can implement other security schemes as described in 19.2.1 Enabling User-Based Access Control.

  3. Press Control-C when you have finished running the standalone agent.

16.5 Multiple Agents

It is possible to create multiple SNMP agents in the Java virtual machine (JVM). A multiple agent is an agent that instantiates more than one SNMP adaptor server. This enables you to create multiple SNMP MIBs using that agent, with each MIB having its own individual security configuration. The main advantage of this is to reduce the burden on the JVM.

You can create multiple agents using all three versions of SNMP. However, the multiple agent demonstrated in the following example is an SNMPv3 agent that creates and registers two SNMP adaptor servers in the MBean server. Registration in the MBean server enables the MIBs generated to be managed through the HTML adaptor.


Example 16–10 MultipleAgentV3 Example

public class MultipleAgentV3 {

    static SnmpV3AdaptorServer snmpAdaptor1 = null;
    static SnmpV3AdaptorServer snmpAdaptor2 = null;
    
	   //Set the number of traps
		//
    private static int nbTraps = -1;

	  public static void main(String args[]) {
        
        		MBeanServer server;
       		ObjectName htmlObjName;
        		ObjectName snmpObjName1;
				   ObjectName snmpObjName2;
        		ObjectName mibObjName1;
				   ObjectName mibObjName2;
        		ObjectName trapGeneratorObjName;
        		int htmlPort = 8082;
        
        		// Initialize trace property.
        		// 
        		[...]

	          // Create and start the HTML adaptor.
            //
            [...]                  
                 
            // Create and start the first SNMP adaptor.
            int snmpPort = 8085;
            snmpObjName1 = new ObjectName(domain + 
                  ":class=SnmpAdaptorServer,protocol=snmp,port=" 
						+ snmpPort);
            snmpAdaptor1 = new SnmpV3AdaptorServer(snmpPort);
            server.registerMBean(snmpAdaptor1, snmpObjName1);
	   			snmpAdaptor1.registerUsmMib(server, null);
            snmpAdaptor1.start();

            // Send a coldStart SNMP Trap. 
            // Use port = snmpPort+1.
            //
            print("NOTE: Sending a coldStart SNMP trap"+
		  		" to each destination defined in the ACL file...");
            snmpAdaptor1.setTrapPort(new Integer(snmpPort+1));
            snmpAdaptor1.snmpV1Trap(0, 0, null);
	   			snmpAdaptor1.enableSnmpV1V2SetRequest();
            println("Done.");
      
	    
	    		// Create and start the second SNMP adaptor.
           // 
				snmpPort = 8087;
          	snmpObjName2 = new ObjectName(domain + 
                 ":class=SnmpAdaptorServer,protocol=snmp,port=" + snmpPort);
            Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MISC, "Agent", "main", 
                       "Adding SNMP adaptor to MBean server with name \n\t"+
		       snmpObjName2);
            println("NOTE: SNMP Adaptor is bound on UDP port " + snmpPort);
	    

	    		// Instantiate second adaptor instantiation.
			   // 
				SnmpEngineParameters params = new SnmpEngineParameters();
	  			params.setSecurityFile("jdmk2.security");

	    		//Create the adaptor passing it the parameters.
            snmpAdaptor2 = new SnmpV3AdaptorServer(params,
						   null,
						   null,
						   snmpPort,
						   null);
            server.registerMBean(snmpAdaptor2, snmpObjName2);
	    		snmpAdaptor2.registerUsmMib(server, null);
            snmpAdaptor2.start();

            // Send a coldStart SNMP Trap.
            // Use port = snmpPort+1.
            //
       	    print("NOTE: Sending a coldStart SNMP trap"+
				 " to each destination defined in the ACL file...");
            snmpAdaptor2.setTrapPort(new Integer(snmpPort+1));
            snmpAdaptor2.snmpV1Trap(0, 0, null);
            println("Done.");


          	// Create the first and second instances of MIB II   
	  			// (RFC 1213) and add them  to the MBean server.
           	//
            mibObjName1 = new ObjectName("snmp:class=RFC1213_MIB");
            mibObjName2 = new ObjectName("snmp:class=RFC1213_MIB_2");
           
            // Create 2 instances of the customized MIB
            //
            RFC1213_MIB mib2 = new RFC1213_MIB_IMPL();

	    	    RFC1213_MIB mib2_2 = new RFC1213_MIB_IMPL("RFC1213_MIB_2");
            server.registerMBean(mib2, mibObjName1);
	    		server.registerMBean(mib2_2, mibObjName2);
	    
            // Bind the SNMP adaptor to the MIB in order to make the MIB 
            // 	    
	    		mib2.setSnmpAdaptor(snmpAdaptor, "TEST-CONTEXT");
	    		mib2_2.setSnmpAdaptor(snmpAdaptor2, "TEST-CONTEXT");
	    
	    //
	    //Multiple agent is ready to answer SNMP requests.
	    //

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

}

This example creates an MBean server as normal, and creates and registers an HTML adaptor in the MBean server, again in the same way as was done in the simple agent example. However, MultipleAgentV3 then creates and registers two SNMP adaptor servers in the MBean server. The two SNMP adaptors are bound to non-standard SNMP ports, in this case ports 8085 and 8087.

Each adaptor generates MIBs from its own security file, enabling you to have different, and remotely updatable, security configurations for each SNMP adaptor server. The first SNMP adaptor server, snmpAdaptor1, gets its security configuration from the security file jdmk.security when the agent is started. The second SNMP adaptor server, snmpAdaptor2, gets its security configuration from a second security file, jdmk2.security, using the params.setSecurityFile method. You can consult jdmk2.security in the examplesDir/current/Snmp/Agent directory. Both these security files implement authentication without privacy.


Caution – Caution –

The coherency of the jdmk.security file is not preserved if you use the same file for more than one adaptor server.


16.5.1 Running the SNMPv3 MultipleAgentV3 Example

In the same way as for the SNMPv3 agent example, AgentV3, you must point the multiple agent application to the security configuration file when you instantiate the multiple agent. This is only the case for the first of the SNMP adaptor servers created by your multiple agent application. All adaptor servers created subsequently by the multiple agent are directed to their corresponding security files in the code of the multiple agent application, when that adaptor server is instantiated.

You must call the MultipleAgentV3 application inside its directory and make sure that the two security configuration files are present in the same directory.

To Run the SNMPv3 MultipleAgentV3 Example
  1. Type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    MultipleAgentV3
    

    The Java DMK multiple agent is now running on your system.

  2. To manage the agent through a web browser, connect to the following URL:

    http://localhost:8082/

Chapter 17 Developing an SNMP Manager

The Java Management Extensions (JMX) specify the SNMP manager API for implementing an SNMP manager application in the Java programming language. This API is covered in the Javadoc API provided with the Java Dynamic Management Kit (Java DMK) (see “Related Books” in the Preface for more information). In this chapter, 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 DMK. It is compatible with SNMPv1, SNMPv2c and SNMPv3, and it includes mechanisms for handling traps. It lets you program both synchronous managers that block while waiting for responses and multi-threaded asynchronous managers that do not. Managers can also communicate with other managers using inform requests and responses.

The complete source code for these applications is available in the current/Snmp/Manager and current/Snmp/Inform example directories located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

17.1 Synchronous Managers

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 configuration 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 accesses in the SnmpOid object.

To refer to variable names, the manager needs to initialize this description with an OID table object. The OID table is instantiated from a subclass of the SnmpOidTableSupport class generated by the mibgen tool when compiling the MIB. Because this support class is regenerated whenever the MIB is recompiled, the new MIB definition is automatically loaded into the manager when it is started (see the Example 17–1).

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 objects SnmpUsmPeer and SnmpUsmParameters perform these roles under SNMPv3, and handle user-based security parameters. The SnmpSession is an object for sending requests. The session instance has an SnmpOptions field that 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 can be controlled remotely. However, you can 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 accesses, and any number of sessions, one for each type of behavior it implements. 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.

17.1.1 Synchronous SNMPv1 and SNMPv2 Managers

Example 17–1 shows the code of the main method of the SyncManager application for SNMPv1 and SNMPv2. It applies all the steps described in the previous section to execute a very simple management operation.


Example 17–1 SNMPv1 and SNMPv2 SyncManager Example

The SyncManager example is found in the examplesDir/current/Snmp/Manager directory.

// read the command line parameters
final String host = argv[0];
final String port = argv[1];

try {
// Specify the OidTable containing all the MIB II knowledge
// Use the OidTable generated by mibgen when compiling MIB II
//
final SnmpOidTableSupport oidTable = new RFC1213_MIBOidTable();
SnmpOid.setSnmpOidTable(oidTable);
       
final 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.
final SnmpParameters params = new SnmpParameters("public", "private");
agent.setSnmpParam(params);

final 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
final SnmpEventReportDispatcher trapAgent =
    new SnmpEventReportDispatcher(Integer.parseInt(port)+1,
					      null,taskServer,null);
// TrapListenerImpl will receive a callback
// when a valid trap PDU is received.
final Thread trapThread = new Thread(trapAgent);
	trapThread.setPriority(Thread.MAX_PRIORITY);
	trapThread.start();
// Build the list of variables you want to query.
// For debugging, you can associate a name to your list.
final SnmpVarbindList list= new SnmpVarbindList(
    "SyncManager varbind list");
     
// We want to read the "sysDescr" variable.
list.addVarBind("sysDescr.0");
    
// Make the SNMP get request and wait for the result.
SnmpRequest request = session.snmpGet(null, list);
final boolean completed = request.waitForCompletion(10000);

// Check for a timeout of the request.
if (completed == false) {
    java.lang.System.out.println(
"Request timed out. Check if agent can be reached");
    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.
final SnmpVarbindList result = request.getResponseVarBindList();
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.

17.1.2 Configuring SNMPv3 Security for Managers

Before you run the SNMPv3 manager examples, you require some information about how SNMPv3 user-based model (USM) security is configured. Below is a brief description of the SNMPv3 security mechanism that provides you with the information you need to run the SNMPv3 examples in this chapter. Full descriptions of the SNMPv3 security mechanisms are given in 19.3 SNMPv3 User-Based Security Model.

An SNMPv3 manager requires a security file, in the same way as an SNMPv3 agent does. The jdmk.security file for an SNMPv3 manager differs slightly from that of an SNMPv3 agent, as shown in the following example.


Example 17–2 A jdmk.security File for an SNMPv3 Manager

# User and security configuration
userEntry=0x8000002a05819dcb6e00001f95,defaultUser,,
    usmHMACMD5AuthProtocol,mypasswd
userEntry=0x8000002a05819dcb6e00001f96,defaultUser,,
    usmHMACMD5AuthProtocol,mypasswd

# Number of boots
localEngineBoots=5

# Local engine ID
localEngineID=0x8000002a05000000ec4c49ded9

In a manager's security file, there is more emphasis on the engine ID than in an agent's security file. The userEntry provides all the security information the manager needs to communicate with a particular authoritative agent, as follows:

0x8000002a05819dcb6e00001f95

This is the engine ID of the agent with which the manager will communicate

defaultUser

The authorized user for that agent

usmHMACMD5AuthProtocol

The authentication algorithm; in this case, HMAC MD5

mypasswd

The privacy password

In this example, the information in the userEntry corresponds to the security information provided in the AgentV3 example's jdmk.security file, in Example 16–3. Therefore, this manager can communicate with that agent.

The remaining information pertains to the manager itself:

localEngineBoots

Sets how many times the local engine will boot

localEngineID

Represents the ID of the engine associated to the SNMP session in which the manager is running

17.1.3 Synchronous SNMPv3 Managers

The example synchronous manager application created for SNMPv3 is similar to the SNMPv1/v2 manager, except that it implements SNMPv3 user-based USM mechanisms before making requests.


Example 17–3 SNMPv3 SyncManagerV3 Example

The SyncManagerV3 example is in the examplesDir/current/Snmp/Manager directory.

			//Read the command line parameters
        final String host = argv[0];
        final String port = argv[1];

        try {
            
     		 // Initialize the SNMP Manager API.
            final SnmpOidTableSupport oidTable = new RFC1213_MIBOidTable();
            SnmpOid.setSnmpOidTable(oidTable);
       
      		// Build the session.
            //
			    try {
						session= new SnmpSession("SyncManagerV3 session");
	   			 }catch(SnmpStatusException e) {
						println(e.getMessage());
						java.lang.System.exit(0);
	    		}
	    		catch(IllegalArgumentException e) {
				// If the engine configuration is faulty 
				println(e.getMessage());
				java.lang.System.exit(0);
	    		}
	
	    		// Access the SNMPv3 engine using getEngine
	    		//
	   			final SnmpEngine engine = session.getEngine();
	    
	    		// Create an SnmpUsmPeer object
 				//
			   final SnmpUsmPeer agent = 
				new SnmpUsmPeer(engine, host, Integer.parseInt(port));
	    
	    		// Create USM parameters
 				//
			   final SnmpUsmParameters p = 
				new SnmpUsmParameters(engine, "defaultUser");
	    
	    		// Set the security level 
				//
			 	p.setSecurityLevel(SnmpDefinitions.authNoPriv);

	    		// Contextualize the send request
	 		   //
	   			 p.setContextName("TEST-CONTEXT".getBytes());

	    		// Set the contextEngineId discovered by the peer upon 
				// creation
	   			 p.setContextEngineId(agent.getEngineId().getBytes());
	    
	    		// Associate the parameter with the agent.
	    		//
	    		agent.setParams(p);
	
	    
	    		// Discover time of creation and boot
			   //
			   agent.processUsmTimelinessDiscovery();
	    
	    		// Associate a default peer (agent) to an SnmpSession.
	    		// 
			   session.setDefaultPeer(agent);

	    		// Create a taskServer for processing traps (optional)
	    	    final DaemonTaskServer taskServer = new DaemonTaskServer();
	    		taskServer.start(Thread.NORM_PRIORITY);
	    
	    		// Create a listener and dispatcher for SNMP traps 
	    	   //
	    		final SnmpEventReportDispatcher trapAgent =
				new SnmpEventReportDispatcher(engine, 
					      Integer.parseInt(port) + 1, 
					      taskServer, null);
	    
	    		trapAgent.addTrapListener(new TrapListenerImpl());
           	final Thread trapThread = new Thread(trapAgent);
	    		trapThread.setPriority(Thread.MAX_PRIORITY);
			   	trapThread.start();	    
	    
	    		// Build the list of variables you want to query
	    	    //
	    		final SnmpVarBindList list = 
				new SnmpVarBindList("SyncManagerV3 varbind list");
	    
	    		// Read the "sysDescr" variable
			   //
           list.addVarBind("sysDescr.0");
	    
	    		// Make the SNMP get request and wait for the result
	    		//
	    		final SnmpRequest request = 
				session.snmpGetRequest(null, list);
	   			println("SyncManagerV3::main:" +
				    "Send get request to SNMP agent on " + host + 
				    " at port " + port);
			   final boolean completed = request.waitForCompletion(10000);
	    
	    		// Check for a timeout
	    		//
            if (completed == false) {
                println("SyncManagerV3::main:" + 
						" Request timed out. Check if agent  
						  can be reached");
		
                // Print request.
                //
                println("Request: " + request.toString());
                java.lang.System.exit(0);
            }
	    
            // Check the response for errors 
	    		//
            final int errorStatus = request.getErrorStatus();
            if (errorStatus != SnmpDefinitions.snmpRspNoError) {
                println("Error status = " + 
					 SnmpRequest.snmpErrorToString(errorStatus));
                println("Error index = " + 
					 request.getErrorIndex());
                java.lang.System.exit(0);
            }
       
            // Display the result.
            //
            final SnmpVarBindList result = request.getResponseVarBindList();
            println("Result: \n" + result);
       
            [...]

            // End the session
            //
            session.destroySession();
       
	    	    trapAgent.close();
	    		 taskServer.terminate();
            java.lang.System.exit(0);
     
				[...]
}

The first instantiated session creates an engine. This engine can be accessed using the getEngine method. To avoid excessive engine creation for each instantiated session, the first engine can be shared between SNMP session objects. While sharing is possible, it should be avoided. It represents an unnecessary increase in overhead and limits the security possibilities because only one security file can be associated with an engine.

The engine is used by all the other classes in the application programming interface (API) to access the USM configuration, contained in the jdmk.security file associated with that session. In Example 17–3, when the peer p is created, it discovers its engineId, and then uses it as the SNMPv3 ContextEngineId. When the request is sent, this engine ID is included as a parameter by setContextEngineId.

In this example, the level of security is set as authentication without privacy. Consequently, this level of security is applied to all the requests between this manager and the peers associated with it, via the security parameters. This level of security must match the level specified in the engine's jdmk.security file.

It is also possible to access MIBs that have been registered in the scope of a context (see 16.2.3 Binding the MIB MBeans for details of contextualized MIBs). In this example, the context TEST-CONTEXT is used, and is set as a parameter in the request by setContextName.

Finally, before sending any requests, if authentication is activated, the timeliness parameters of the request are discovered, using processUsmTimelinessDiscovery.

17.1.4 SNMP Trap Handler

A trap handler for the SNMP manager is an object that implements the SnmpTrapListener interface in the com.sun.management.snmp.manager package. When this object is bound as a listener of an SnmpEventReportDispatcher object, its methods are 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 work in the same manner, but they are given objects that represent traps, not instances of the Notification class.

The interface defines three methods, one for processing SNMPv1 traps, another for SNMPv2 traps and a third for SNMPv3 traps. Trap PDU packets have already been decoded by the dispatcher, and these methods handle an object representation of the trap: SnmpPduTrap objects for SNMPv1, SnmpPduRequest objects for SNMPv2 and SnmpScopedPduRequest for SNMPv3.


Example 17–4 SnmpTrapListener Implementation

public class TrapListenerImpl implements SnmpTrapListener {

		public void processSnmpTrapV1(SnmpPduTrap trap) {
        println("NOTE: TrapListenerImpl received trap :");
        println("\tGeneric " + trap.genericTrap);
        println("\tSpecific " + trap.specificTrap);
        println("\tTimeStamp " + trap.timeStamp);
        println("\tAgent adress " + trap.agentAddr.stringValue());
    }

    public void processSnmpTrapV2(SnmpPduRequest trap) {
        println("NOTE: Trap V2 not of interest !!!");
    }

    public void processSnmpTrapV3(SnmpScopedPduRequest trap) {
			println("NOTE: TrapListenerImpl received trap V3:");
			println("\tContextEngineId : " + 
				SnmpEngineId.createEngineId(trap.contextEngineId));
			println("\tContextName : " + new String(trap.contextName));
			println("\tVarBind list :");
			for(int i = 0; i < trap.varBindList.length; i++)
	    		println("oid : " + trap.varBindList[i].oid + " val : " + 
		    		trap.varBindList[i].value);
	
    }

 }

To Run the SyncManager Example
  1. In the examplesDir/current/Snmp/Manager directory, generate the OID table description of MIB-II that our manager will access.


    $ mibgen -mo mib_II.txt
    
  2. Compile the example classes.

    To set up your environment, see “Directories and Classpath” in the Preface.


    $ javac -classpath classpath -d . *.java
    
  3. Make sure that no other agent is running on port 8085, and start the simple SNMP agent in examplesDir/current/Snmp/Agent.

    See To Run the SNMPv1/v2 Agent Example if you have not already built and run this example.

    Type the following command to start the Agent example:


    $ cd examplesDir/current/Snmp/Agent
    $ java -classpath classpath Agent nbTraps
    

    If you are also running the asynchronous manager example with this agent, omit the nbTraps parameter. The agent then sends 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.

  4. Start the manager application in another window to connect to this agent.

    If you want to run the manager on a different host, replace localhost with the name of the host where you started the agent.


    $ cd examplesDir/current/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.

  5. 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 pressing Control-C.

To Run the SyncManagerV3 Example
  1. In the examplesDir/current/Snmp/Manager directory, generate the OID table description of MIB-II that our manager will access.


    $ mibgen -mo mib_II.txt
    
  2. Compile the example classes.

    To set up your environment, see “Directories and Classpath” in the Preface.


    $ javac -classpath classpath -d . *.java
    
  3. Make sure that no other agent is running on port 8085, and start the simple SNMPv3 agent, AgentV3, in examplesDir/current/Snmp/Agent.

    See To Run the SMNPv3 AgentV3 Example if you have not already built and run this example.

    Type the following commands to start the AgentV3 example:


    $ cd examplesDir/current/Snmp/Agent
    $ java -classpath classpath -Djdmk.security.file=jdmk.security AgentV3
    
  4. Start the manager application in another window to connect to this agent.

    If you want to run the manager on a different host, replace localhost with the name of the host where you started the agent.


    $ cd examplesDir/current/Snmp/Manager
    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    SyncManagerV3 localhost 8085
    

    Be sure to run the manager in its directory, otherwise it cannot find its jdmk.security file.

  5. Press Enter in the agent's window.

    You should see the manager receiving the traps it sends. Stop the manager by typing Control-C.

17.1.5 Synchronous Managers Accessing Several Agents

The SNMP API enables you to configure a synchronous SNMPv3 manager that can access several agents. The SyncManagerMultiV3 example demonstrates a simple SNMPv3 manager API that makes requests on two SNMPv3 agents. For the sake of simplicity, this manager does not listen for traps.


Example 17–5 SNMPv3 SyncManagerMultiV3 Example

	//Check host and port arguments
	//
        final String host1 = argv[0];
        final String port1 = argv[1];
			  final String host2 = argv[2];
        final String port2 = argv[3];

           // Initialize the SNMP Manager API.
            // 
            final SnmpOidTableSupport oidTable = new RFC1213_MIBOidTable();
            SnmpOid.setSnmpOidTable(oidTable);
       
            [...]
	  
			    // Build the session. 
				//
	  			  try {
				// When instantiating a session, a new SNMP V3 engine is 
				// instantiated.
				//
					session= new SnmpSession("SyncManagerMultiV3 session");
	    		}catch(SnmpStatusException e) {
					println(e.getMessage());
					java.lang.System.exit(0);
			   }
	    		catch(IllegalArgumentException e) {
				// If the engine configuration is faulty
				//
				println(e.getMessage());
				java.lang.System.exit(0);
	    }
	
	    		// Get the SnmpEngine. 
			    //
			    final SnmpEngine engine = session.getEngine();
	    
	   			 // Create a SnmpPeer object for representing the first  
	    		 // entity to communicate with. 
	    	    //
	    		 final SnmpUsmPeer agent1 = 
				 new SnmpUsmPeer(engine, host1, Integer.parseInt(port1));
	    
	    		 //Create the second peer.
	    		 //
	   			final SnmpUsmPeer agent2 = 
				new SnmpUsmPeer(engine, host2, Integer.parseInt(port2));
	    
	    		// Create parameters to associate to the entity to  
	    		// communicate with.
	     	   //
	    		final SnmpUsmParameters p = 
				new SnmpUsmParameters(engine, "defaultUser");
	    
			   // Set security level to authNoPriv. 
			   //
		    	p.setSecurityLevel(SnmpDefinitions.authNoPriv);

	    		// Contextualize the send request
	    	   //
	    		p.setContextName("TEST-CONTEXT".getBytes());

	    		// Specify a contextEngineId 
		   		//
	    		p.setContextEngineId(agent1.getEngineId().getBytes());
	    
	    		// Associate the newly created parameter to the agent
		    	//
		    	agent1.setParams(p);
	
	    
	    		// Discover timeliness and boot
	    		agent1.processUsmTimelinessDiscovery();
	    
	    
	    		// Build the list of variables you want to query
	    		//
	    		final SnmpVarBindList list = 
				new SnmpVarBindList("SyncManagerMultiV3 varbind list");
	    
	    		// Read the "sysDescr" variable
	    		//
			   list.addVarBind("sysDescr.0");
	    
	    		// Make the SNMP get request on the first agent agent1 
	    		// and wait for the result
	    		//
	    		SnmpRequest request = 
				session.snmpGetRequest(agent1, null, list);
	    		println("SyncManagerMultiV3::main:" +
		    		" Send get request to SNMP agent on " + 
		    		host1 + " at port " + port1);
	    		boolean completed = request.waitForCompletion(10000);
	    
	    		// Check for a timeout
	    		//
            if (completed == false) {
                println("SyncManagerMultiV3::main:" +
						" Request timed out. Check if agent can 
						be reached");
		
                // Print request
                //
                println("Request: " + request.toString());
                java.lang.System.exit(0);
            }
	    
            // Check if the response contains an error 
	    		 //
            int errorStatus = request.getErrorStatus();
            if (errorStatus != SnmpDefinitions.snmpRspNoError) {
                println("Error status = " + 
						SnmpRequest.snmpErrorToString(errorStatus));
                println("Error index = " + 
						request.getErrorIndex());
                java.lang.System.exit(0);
            }
       
            // Display the content of the result
            //
            SnmpVarBindList result = request.getResponseVarBindList();
            println("Result: \n" + result);
       
            println("\n>> Press Enter if you want to send the request" +
		    " on the second agent.\n");
            java.lang.System.in.read();
            

	      		
			   // Repeat the process for the second agent agent2.
			   //
	    		SnmpUsmParameters p2 = 
				new SnmpUsmParameters(engine, "defaultUser");
	    
	    		p2.setSecurityLevel(SnmpDefinitions.authNoPriv);

	   			p2.setContextName("TEST-CONTEXT".getBytes());
	    
			   p2.setContextEngineId(agent2.getEngineId().getBytes());
	    
			   // Associate the updated parameters with agent2.
			   //
		      agent2.setParams(p2);
	    	    
	    		// Discover timeliness and boot
	    	   //
	    		agent2.processUsmTimelinessDiscovery();
	    
			  	// Make the request with agent2
			   //
	    		request = session.snmpGetRequest(agent2, null, list);
			    println("SyncManagerMultiV3::main:" +
		   			 " Send get request to SNMP agent on " + 
				    host2 + " at port " + port2);
				  completed = request.waitForCompletion(10000);
	    
	    		// Check for a timeout
	    		//
            if (completed == false) {
                println("SyncManagerMultiV3::main:" +
					" Request timed out. Check  if agent can be 
					reached");
		
                // Print request.
                //
                println("Request: " + request.toString());
                java.lang.System.exit(0);
            }
	    
           // Check if the response contains an error
	    		// 
	    		errorStatus = request.getErrorStatus();
           		 if (errorStatus != SnmpDefinitions.snmpRspNoError) {
               	 println("Error status = " + 
					SnmpRequest.snmpErrorToString(errorStatus));
               	 println("Error index = " + 
				request.getErrorIndex());
                java.lang.System.exit(0);
            }
       
            // Display the content of the result
            //
	    		result = request.getResponseVarBindList();
            println("Result: \n" + result);
       
            println("\n>> Press Enter if you want to stop " +
		    		"this manager.\n");
            java.lang.System.in.read();
	    
            // End the session
            //
            session.destroySession();
       
            
}

The SyncManagerMultiV3 example essentially performs the same actions as the SyncManagerV3 example, except it creates two SNMP USM peers rather than just one. The two peer agents are each created in exactly the same way as in the single peer example.

Both the peer agents are accessed using a common set of parameters, which are kept in the jdmk.security file for the session. This jdmk.security file contains two rows, one for each SNMP USM peer agent, as shown in the following example.


Example 17–6 jdmk.security File for the SyncManagerMultiV3 Example

#Authentication only.
userEntry=0x8000002a05819dcb6e00001f95,defaultUser,,usmHMACMD5AuthProtocol,
mypasswd
userEntry=0x8000002a05819dcb6e00001f96,defaultUser,,usmHMACMD5AuthProtocol,
mypasswd

# #####APPENDED PROPERTY####
localEngineBoots=5

# #####APPENDED PROPERTY####
localEngineID=0x8000002a05000000ec4c49ded9

If you configure your manager to create a greater number of peers, then its associated jdmk.security file must contain a corresponding number of entries, one for each authoritative engine with which the manager will communicate.

In this example, the only difference between the two userEntry rows is between the engine ID numbers. These engine IDs correspond to the engines of the two SNMP adaptor servers created by the MultipleAgentV3 example in 16.5 Multiple Agents.

To Run the SyncManagerMultiV3 Example
  1. Before running the example, you must have run mibgen and compiled the Java classes in the examplesDir/current/Snmp/Manager directory.

    See To Run the SyncManager Example for instructions if you have not already done this.

  2. Make sure that no other agent is running, and start the multiple SNMPv3 agent, MultipleAgentV3, in examplesDir/current/Snmp/Agent.

    See 16.5.1 Running the SNMPv3 MultipleAgentV3 Example if you have not already built and run this example.

    Type the following commands to start the MultipleAgentV3 example:


    $ cd examplesDir/current/Snmp/Agent
    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    MultipleAgentV3
    

    The MultipleAgentV3 example simulates two SNMPv3 agents in one process. We shall make these agents peers of SyncManagerMultiV3.

    Be sure to run the multiple agent in its directory, otherwise it cannot find its jdmk.security and jdmk2.security files.

  3. Start the manager application in another window to connect to these two agents.

    If you want to run the manager on a different host from the one where the agents are running, replace localhost with the name of the host where you started the agents. Both the agents in the MultipleAgentV3 example run on the same host.


    $ cd examplesDir/current/Snmp/Manager
    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    SyncManagerMultiV3 localhost 8085 localhost 8087
    

    Be sure to run the manager in its directory, otherwise it cannot find its jdmk.security file.

  4. You should see sending a request to the first agent. Press Enter to send a request to the second agent.

    You will now see the manager sending a second request to the agent on port 8087.

  5. Press Enter to stop the manager

17.2 Asynchronous Managers

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 that 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. This applies to all three versions of SNMP.


Example 17–7 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.
final 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 
final SnmpPeer agent = new SnmpPeer(host, Integer.parseInt(port));
     
// Create parameters for communicating with the agent
final SnmpParameters params = new SnmpParameters("public", "private");
agent.setSnmpParam(params);
     
// Build the session and assign its default peer
final SnmpSession session = new SnmpSession("AsyncManager session");
session.setDefaultPeer(agent);

	final DaemonTaskServer taskServer = new DaemonTaskServer();
	taskServer.start(Thread.NORM_PRIORITY);

// Same dispatcher and trap listener as in SyncManager example
SnmpEventReportDispatcher trapAgent =
    		new SnmpEventReportDispatcher(Integer.parseInt(port)+1,
					      null,taskServer,null);
trapAgent.addTrapListener(new TrapListenerImpl()); 
final Thread trapThread = new Thread(trapAgent);
	trapThread.setPriority(Thread.MAX_PRIORITY);
	trapThread.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
final 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 receives traps and calls the corresponding method of our SnmpTrapListener class.

In this example, the manager performs an snmpWalkUntil request that gives a response for each variable that it gets. The response handler is called to process each of these responses.

17.2.1 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 want for processing the responses to a request. Typically, these methods 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–8 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) {

         // Check if a result is available.
        if (request.getRequestStatus() ==
              SnmpVarBindList result = request.getResponseVarBindList();
              println("Result = " + result.varBindListToString());

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

To Run the AsyncManager Example
  1. If you have not done so already, start the simple SNMP agent in examplesDir/current/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 To Run the SyncManager Example for instructions on how to do this.

  2. If you do not have an SNMP agent still running, make sure that no other agent is running on port 8085 and start one with the following command:


    $ cd examplesDir/current/Snmp/Agent
    $ java -classpath classpath Agent nbTraps
    
  3. 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.

  4. In another terminal window, start the manager application to connect to this agent.

    If you want to run the manager on a different host, replace localhost with the name of the host where you started the agent.


    $ cd examplesDir/current/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.

  5. Press Enter in the agent's window to send traps and see the trap reports as they are received in the manager.

    When you have finished with the agent, do not forget to stop it by typing Control-C in its terminal window.

17.3 Inform Requests

The inform request is specified in SNMPv2 and SNMPv3 as a mechanism for sending a report and receiving a response.

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

There are simple examples, one for SMNPv2 and one for SNMPv3, in examplesDir/current/Snmp/Inform. Each 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 these exchanges.

17.3.1 Sending an Inform Request (SNMPv2)

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 can 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 do not specify a peer when sending requests, they are automatically addressed to our manager peer. Because 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–9 Sending an SNMPv2 Inform Request in SimpleManager1

// When calling the program, you must specify the hostname
// of the SNMP manager you want to send the inform to.
//
final String host = argv[0];
        
// Initialize the port number to send inform PDUs on port 8085.
//
final int port = 8085;
   
try {
    // Create an SnmpPeer object that represents the entity to
    // communicate with. 
    //
    final 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.
    // 
    final 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.
    //
    final SnmpSession session = new SnmpSession("SimpleManager1 
                                                session");
    session.setDefaultPeer(peer);
     
    // Make the SNMP inform request and wait for the result.
    //
    final 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 if agent can be reached");
         
        // 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.
    //
    final 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.
    //
    final 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 that 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 calling process does not 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 17.2.1 Response Handler, and then use it to process responses, as shown in Example 17–7.

17.3.2 Sending an Inform Request (SNMPv3)

This example shows how to build an SNMPv3 manager that sends and/or receives SNMPv3 requests. It initializes an SNMPv3 USM peer and an SNMP session, and then sends an inform request to a second SNMP manager.


Example 17–10 Sending an SNMPv3 Inform Request in SimpleManager1V3

			//	When calling the program, specify the hostname of the  
			// SNMP manager to send the inform to
			//
			final String host = argv[0];
        
        // Initialize the port number to send inform PDUs on port 8085.
        //
        final int port = 8085;
   
        	[...]

	    	// Create an SnmpUSMPeer object for representing the entity 
	    	// to communicate with. 
        //
        final SnmpUsmPeer peer = new SnmpUsmPeer(session.getEngine(),
						    host, 
						    port);
     
        // Create parameters to associate to the entity to 
	      // communicate with.
       // When creating the parameter object, you specify the necessary 
	      // security related parameters.
        // 
        final SnmpUsmParameters params = 
			new SnmpUsmParameters(session.getEngine());

	    	// Set the parameters
		   //
	    
	   		// First a principal
	    	//
		   params.setPrincipal("defaultUser");
	    
	      // A security level. Authentication is applied.
	      // 
	      params.setSecurityLevel(SnmpDefinitions.authNoPriv);
	    
	      // Add a contextEngineId. The context engine Id is the 
	      // manager's engine ID and not the adaptor's.
	      //
	      params.setContextEngineId(peer.getEngineId().getBytes());

	      // Add a context name.
	      //
	      params.setContextName("TEST-CONTEXT".getBytes());
	    
       // The newly created parameter must be associated to the peer.
       //
       peer.setParams(params);
     

	      // The inform is authenticated, so the timeliness 
	      // discovery must be processed.
	      //
	      peer.processUsmTimelinessDiscovery();
	    


       // A default peer (listener manager) can be associated to an 
	      // SnmpSession. When invoking a service provided by the 
	      // SnmpSession, if the peer is not specified, the session 
	      // will perform the service using the default one as the 
	      // target of the service
       //
       session.setDefaultPeer(peer);
	    
       println("\nNOTE: SNMP V3 simple manager 1 initialized");
            
       // Make the SNMP inform request and wait for the result.
       //
       println("\n>> Press Enter to send the inform request on " + 
		  host + " at port " + port + "...");
        try {
                System.in.read();
            } catch (IOException e) {
                e.printStackTrace();
            }

            final SnmpRequest request = 
		 session.snmpInformRequest(null, new SnmpOid("1.2.3.4"), 
					  null);

            println("NOTE: Inform request sent to SNMP manager on " + 
		    host + " at port " + port);

            final boolean completed = request.waitForCompletion(10000);
       
         // Check for a timeout of the request.
         //
         if (completed == false) {
                println("\nSimpleManager1::main: Request timed out." +
			" Check reachability of agent");
	 
                // Print request.
                //
                println("Request: " + request.toString());
                java.lang.System.exit(0);
            }
     
         // Check if the response contains an error
         //
         final int errorStatus = request.getErrorStatus();
         if (errorStatus != SnmpDefinitions.snmpRspNoError) {
                println("Error status = " +
 			SnmpRequest.snmpErrorToString(errorStatus));
                println("Error index = " + request.getErrorIndex());
                java.lang.System.exit(0);
            }
       
            // Display the content of the result.
            //
            final SnmpVarBindList result = request.getResponseVarBindList();
            println("\nNOTE: Response received:\n" + result);
       
            // End the session
            //
            println("\nNOTE: SNMP V3 simple manager 1 stopped...");
            session.destroySession();
       

}

As you can see, once the SNMPv3 session has been instantiated and the SNMPv3 USM peer has been created and configured, the inform request is sent in exactly the same way as under SNMPv2. The only difference is the additional SNMPv3 security.


17.3.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 that, 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, because its only function is to create the dispatcher and the listener for inform requests. The receiving manager SimpleManager2 is the same for both the SimpleManager1 and SimpleManager1V3 examples.


Example 17–11 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.
    //

	    final DaemonTaskServer taskServer = new DaemonTaskServer();
	    taskServer.start(Thread.NORM_PRIORITY);

    final SnmpEventReportDispatcher informDispatcher =
        new SnmpEventReportDispatcher(port,taskServer,null,null);
    informDispatcher.addInformListener(new InformListenerImpl());
	   final Thread informThread = new Thread(informDispatcher);
	   informThread.setPriority(Thread.MAX_PRIORITY);    
    informThread.start();
		println("\nNOTE: SNMP simple manager 2 initialized");

    // 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 and processSnmpInformV3 methods handle the incoming inform requests.

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 extracts the variable bindings and takes 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–12 The InformListenerImpl Class

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

    }
			public void processSnmpInformV3(SnmpScopedPduRequest inform) {
				   println("\nNOTE: Inform request V3 received:\n");
       	 	println("\tType      = 
				" + inform.pduTypeToString(inform.type));
       		println("\tVersion   = " + inform.version);
		      	println("\tRequestId = " + inform.requestId);
     	  	   println("\tAddress   = " + inform.address);
        		println("\tPort      = " + inform.port);
        		println("\tContext = " + new String(inform.contextName));
        		println("\tVB list   = ");
        		for (int i = 0; i < inform.varBindList.length; i++) {
           		 println("\t\t" + inform.varBindList[i]);
        }
    }
    
     private final static void println(String msg) {
	       java.lang.System.out.println(msg);
     }

     private final static void print(String msg) {
	       java.lang.System.out.print(msg);    
     }
}

To Run the SNMPv2 Inform Request Example

The examplesDir/current/Snmp/Inform directory contains all of the files for the two manager applications, along with the InformListenerImpl class.

  1. 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/current/Snmp/Inform/
    $ javac -classpath classpath *.java
    
  2. To run the example, start the inform request receiver with the following command.


    $ java -classpath classpath SimpleManager2
    

    You can start the application in another terminal window or on another host.

  3. Wait for this manager to be initialized, then start the other manager with the following command.

    The hostname is the name of the host where you started the receiving manager, or localhost.


    $ java -classpath classpath SimpleManager1 hostname
    
  4. 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 afterwards, the sender receives the inform response containing the same variable bindings and displays them. Both manager applications then exit automatically.

To Run the SNMPv3 Inform Request Example
  1. Before running the example, you must compile the Java classes in the examplesDir/current/Snmp/Inform directory.

    Type the following commands in your working directory:


    $ javac -d . SimpleManager1V3.java SimpleManager2.java 
    InformListenerImpl.java
    
  2. Start the receiving manager, SimpleManager2:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    SimpleManager2
    

    This manager binds to port 8085.

  3. Wait for this manager to be initialized, then start the other manager with the following command, specifying the hostname and port number of the receiving manager.

    In this case, the host name is the localhost


    $ java -classpath classpath -Djdmk.security.file=sender.security 
    SimpleManager1V3 localhost
    
  4. 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 afterwards, the sender receives the inform response containing the same variable bindings and displays them. Both manager applications then exit automatically.

Chapter 18 Advanced MIB Implementations

This chapter covers the more advanced implementations of the Simple Network Management Protocol (SNMP) that you can create using Java Dynamic Management Kit (Java DMK). These advanced implementations focus on SNMP tables, and are presented in three examples of increasing complexity in the following sections.

18.1 Simple SNMP Tables

SNMP allows for the structuring of Management Information Base (MIB) variables into logical tables, in which the variables are organized into rows and columns. Java DMK allows for the dynamic addition of rows to the tables that conform to the RowStatus convention defined by SNMPv2. Java DMK also allows you to change the values of existing rows. An example of the use of tables in the Java DMK SNMP API is found in the directory examplesDir/current/Snmp/Rowstatus.

This example shows how to create rows remotely in an SNMP agent from an SNMP manager application, in tables that follow the RowStatus convention. It does this by enabling remote entry creation in the tables and by sending the appropriate requests to create or destroy rows in the table. The RowStatus example also shows how to replace the generated skeletons for SNMP groups, tables, and entries with your own customized classes, so that the customized entry class is instantiated when a remote SNMP manager requests the creation of a row in a table managed by a RowStatus variable.

This example is built around a small SNMP MIB called DEMO-MIB which contains one group, Demo, and one table, TableDemoTable. Before you can proceed with the example, you need to generate this MIB and its associated classes, by using the mibgen compiler supplied with Java DMK. For details of the mibgen compiler, see the Java Dynamic Management Kit 5.1 Tools Reference Guide.

To Generate the DEMO-MIB

The example MIB is contained in the configuration file mib_demo.txt, which is found in examplesDir/current/Snmp/Rowstatus. You should run the example from within this directory.

  1. Ensure that your PATH environment variable knows where to find mibgen.

    In a default installation, mibgen is found in installDir/bin.

  2. Run the mibgen compiler.


    $ mibgen -d . mib_demo.txt
    

    You see that mibgen has generated the following Java classes.

    • Demo, which implements the Demo group.

    • DemoEntry, which defines an MBean that provides variables to serve as entries in the table, and specifies the operations that can be performed on this MBean.

    • DemoEntryMBean, which represents the remote management interface for the DemoEntry MBean.

    • DemoEntryMeta, which constructs the SNMP metadata for the DemoEntry MBean.

    • DemoMBean, which represents the remote management interface for the Demo MBean.

    • DemoMeta, which constructs the SNMP metadata for the DemoEntry MBean.

    • DEMO_MIB, which defines the behavior of this SNMP MIB.

    • DEMO_MIBOidTable, which contains the metadata definitions for DEMO-MIB.

    • DemoTableMeta, which constructs the metadata definitions for the SNMP table.

    • EnumDemoTableRowStatus, which provides an enumerated integer value that informs you of the status of a table row.

    • TableDemoTable, which constructs the SNMP table, making each row from DemoEntry objects using SNMP SET operations, and then registers the rows in the Java DMK MBean server.

To be able to modify the classes dynamically, we must take the classes generated by the mibgen compiler and extend them accordingly in a series of custom classes. These customized classes are provided in the examplesDir/current/Snmp/Rowstatus directory.


Example 18–1 Adding Entries to Tables

public class TableDemoTableImpl extends TableDemoTable {
    public TableDemoTableImpl(SnmpMib myMib, DemoImpl myGroup) {
        super(myMib);
	       this.myGroup = myGroup;
    }
    public TableDemoTableImpl(SnmpMib myMib, MBeanServer server, 
			      DemoImpl myGroup) {
       super(myMib,server);
	       this.myGroup = myGroup;
    }

    public void removeEntryCb(int pos, SnmpOid row, ObjectName name,
                Object entry, SnmpMibTable meta)
            throws SnmpStatusException {
       super.removeEntryCb(pos,row,name,entry,meta);
	      myGroup.removeRowCb();
    }
    public void addEntryCb(int pos, SnmpOid row, ObjectName name, 
			         Object entry, SnmpMibTable meta) 
	          throws SnmpStatusException {
	      super.addEntryCb(pos,row,name,entry,meta);
	      myGroup.addRowCb();
    }
    public Object createDemoEntryMBean(SnmpMibSubRequest req,
                SnmpOid rowOid, int depth, ObjectName entryObjName,
                SnmpMibTable meta, Integer  aDemoTableIndex)
            throws SnmpStatusException  {

        DemoEntryImpl entry = new DemoEntryImpl(theMib);
        entry.DemoTableIndex = aDemoTableIndex;
        return entry;
    }
    private DemoImpl myGroup;

}

The TableDemoTableImpl class shown in Example 18–1 subclasses the TableDemoTable class generated by mibgen from the mib_demo.txt file. It customizes the SnmpTableSupport addEntryCb and removeEntryCb methods, adding new functionality not originally provided by TableDemoTable. The addEntryCb and removeEntryCb callback methods are called from the SNMP runtime when a row is added to or removed from the table. The removedRowCb and addRowCb methods are callback methods that maintain a local row counter, DemoInteger. However, the fact that they maintain a row counter is merely for the sake of this example. The addEntryCb and removeEntryCb callback methods could perform any operation the developer chooses to implement, such as emitting notifications, for example.

The most important method of the TableDemoTableImpl class is createDemoEntryMBean. The createDemoEntryMBean method inTableDemoTableImpl overrides the one defined by TableDemoTable to implement DemoEntryImpl instead of the skeletal DemoEntry.

The DemoImpl group, shown in Example 18–2, overrides the constructors defined by the Demo group, to allow remote creation of rows in a DemoTable.


Example 18–2 Permitting Remote Creation of Rows

public class DemoImpl extends Demo {
    public DemoImpl(SnmpMib myMib) {
	       super(myMib);
		    DemoTable = new TableDemoTableImpl(myMib, this);
       DemoTable.setCreationEnabled(true);
    }
    public DemoImpl(SnmpMib myMib, MBeanServer server) {
	        super(myMib,server);
        DemoTable = new TableDemoTableImpl(myMib, server, this);
	        DemoTable.setCreationEnabled(true);
    }
    public void addRowCb() {
	        DemoInteger = new Integer(DemoInteger.intValue() + 1);
    }
    public void removeRowCb() {
	        DemoInteger =  new Integer(DemoInteger.intValue() - 1);
    }
}

As you can see, as well as setting the SnmpTableSupport method setCreationEnabled value to true, DemoImpl defines the addRowCb and removeRowCb callback methods called by TableDemoTableImpl. For each row added or deleted, addRowCb and removeRowCb update the value of the DemoInteger counter accordingly. DemoInteger is defined by the Demo class, and counts the number of entries in the demoTable.


Example 18–3 Adding and Deleting Rows from a Remote SNMP Manager

public class Manager {

[...]
        try {
            SnmpOidTableSupport oidTable = new DEMO_MIBOidTable();
            SnmpOid.setSnmpOidTable(oidTable);
            [..]
            EnumRowStatus   createAndGo = new EnumRowStatus("createAndGo");
            EnumRowStatus   destroy     = new EnumRowStatus("destroy");
            
            SnmpVarBindList setList1 = 
                new SnmpVarBindList("Manager SET varbind list");
            SnmpVarBind stringRow1 = 
                new SnmpVarBind(new SnmpOid("demoTableString.1"),
                                new SnmpString("This is row 1"));
            SnmpVarBind statusRow1 = 
                new SnmpVarBind(new SnmpOid("demoTableRowStatus.1"),
                                createAndGo.toSnmpValue());

            setList1.addVarBind(stringRow1);
            setList1.addVarBind(statusRow1);
            
            request = session.snmpSetRequest(null, setList1);
            completed = request.waitForCompletion(10000);
            [...]
            result = request.getResponseVarBindList();
      
            SnmpVarBindList getList = 
                new SnmpVarBindList("Manager GET varbind list");
            getList.addVarBind("demoInteger.0");
            getList.addVarBind("demoTableString.1");
            getList.addVarBind("demoTableRowStatus.1");
            request = session.snmpGetRequest(null, getList);
            completed = request.waitForCompletion(10000);
            [...]
            result = request.getResponseVarBindList();

            SnmpVarBindList setList2 = 
                new SnmpVarBindList("Manager SET varbind list");
            SnmpVarBind statusRow2 = 
                new SnmpVarBind(new SnmpOid("demoTableRowStatus.1"),
                                destroy.toSnmpValue());

            setList2.addVarBind(statusRow2);
            request = session.snmpSetRequest(null, setList2);
            completed = request.waitForCompletion(10000);
            [...]
            result = request.getResponseVarBindList();
            request = session.snmpGetRequest(null, getList);
            completed = request.waitForCompletion(10000);
            result = request.getResponseVarBindList();
            [...]
            session.destroySession();
            java.lang.System.exit(0);
        }
        [...]
    }

The Manager begins by loading the DEMO_MIBOidTable table created by mibgen. After setting up the SNMP peer, session and parameters, the Manager defines the creation and deletion of rows. The table in this example supports the RowStatus variable. The values for columns can be changed by sending SNMP set requests to the index of the row that is to be added or removed. Setting the value of the column that contains the RowStatus variable to createAndGo creates and activates a row for which a value is provided by the set request. Setting the value of the RowStatus column of an existing row you want to delete to destroy, deletes that row.

To Run the SNMP Table RowStatus Example

As stated in To Generate the DEMO-MIB, you must have run mibgen on the mib_demo.txt file before proceeding with this example. Run the example inside the examplesDir/current/Snmp/Rowstatus directory.

  1. Compile the Java classes.


    $ javac -d . *.java
    
  2. Start the Agent.

    Make sure that no agents are already running before you start the Agent.


    $ java Agent
    

    You see confirmation of the creation of the HTML adaptor on port 8082 and the SNMP adaptor on port 8085, and the addition of the DEMO-MIB to the MBean server. You can perform management operations on the Agent using the HTML adaptor, by loading the URL http://localhost:8082/ in a browser.

  3. In another terminal, start the Manager.


    $ java Manager agent-hostname 8085
    

    Where agent-hostname is the name of the machine where the Agent is running. 8085 represents the port number on which the SNMP adaptor is running.

    When you start the Manager, you will be prompted to press Enter to perform the get and set operations to add and delete rows from the demoTable.

18.2 SNMP Table Instrumentation

The example shown in 18.1 Simple SNMP Tables showed simply how to use Java DMK to add and remove rows from an SNMP table. The example presented in this section goes further, and demonstrates how to add instrumentation to an SNMP table.

This example is based on the classes in the examplesDir/current/Snmp/MBeanTable directory. It defines a MIB called JMX-MBEAN-SERVER-MIB that exposes the content of an MBean server through SNMP. To achieve this, JMX-MBEAN-SERVER-MIB defines two SNMP tables:

18.2.1 Classes Generated by mibgen

Before you can proceed with this example, you need to generate the JMX-MBEAN-SERVER-MIB and it's associated classes, by using the mibgen compiler supplied with Java DMK. For details of the mibgen compiler, see the Java Dynamic Management Kit 5.1 Tools Reference Guide.

To Generate the JMX-MBEAN-SERVER-MIB

The example MIB is contained in the configuration file JMX-MBEAN-SERVER-MIB.mib, which is found in examplesDir/current/Snmp/MBeanTable. You should run the example from within this directory.

  1. Ensure that your PATH environment variable knows where to find mibgen.

    In a default installation, mibgen is found in installDir/bin.

  2. Create a new directory, generated, inside examplesDir/current/Snmp/MBeanTable.


    $ mkdir generated
    

    This directory is where mibgen generates the classes associated with JMX-MBEAN-SERVER-MIB.mib.

  3. Run the mibgen compiler.


    $ mibgen -X:use-display-hint -d generated JMX-MBEAN-SERVER-MIB.mib
    

    The advanced mibgen option use-display-hint instructs mibgen to generate an attribute of type String for any object using a textual convention whose DISPLAY-HINT is 255a. This option is used because JMX-MBEAN-SERVER-MIB defines textual conventions (for example, JmxMBeanObjectNameTC) which must be translated into java.lang.String attributes, rather than the default Byte[] attributes.

    The -d generated option sends the output of mibgen to the newly created generated directory

Within the generated directory, you see that mibgen has generated the following Java classes.

18.2.2 Customized Classes

As was the case for the simple SNMP table example, to be able to tie the code generated by mibgen to its proper instrumentation, you must extend the classes generated by mibgen in a series of custom classes. The customized classes are those provided in the examplesDir/current/Snmp/MBeanTable directory.

The customized extensions to the generated classes add the following functionality:

Aspects of the JmxMBeanServerImpl and JmxMBeanEntryImpl classes are examined in more detail in the following sections.

18.2.2.1 JmxMBeanServerImpl

As stated previously, JmxMBeanServerImpl extends the JmxMBeanServer empty skeleton class. JmxMBeanServerImpl also instantiates a subclass of TableJmxMBeanTable, to mirror the MBeans registered in an MBean server.


Example 18–4 Subclassing TableJmxMBeanTable

[...]
    private final void inittables() {

       JmxMBeanTable = new TableJmxMBeanTable(myMib,null) {
             public Object createJmxMBeanEntryMBean(SnmpMibSubRequest req,
                                                    SnmpOid rowOid, 
                                                    int depth, 
                                                    ObjectName entryObjName,
                                                    SnmpMibTable meta, 
                                                    Integer  aJmxMBeanIndex)
                 throws SnmpStatusException  {

                 return JmxMBeanServerImpl.this.
                    createJmxMBeanEntryMBean(req,rowOid,depth,entryObjName,
                                                 meta,aJmxMBeanIndex);
                }

                public void removeEntryCb(int pos, SnmpOid row, 
                                          ObjectName name,
                                          Object entry, SnmpMibTable meta)
                    throws SnmpStatusException {
                    super.removeEntryCb(pos,row,name,entry,meta);
                    final JmxMBeanEntryMBean e = 
                        (JmxMBeanEntryMBean) entry;
                    final EnumJmxMBeanRowStatus destroy =
                         new EnumJmxMBeanRowStatus(EnumRowStatus.destroy);
                    e.setJmxMBeanRowStatus(destroy);
                }
            };
	
        JmxMBeanTable.setCreationEnabled(true);
    }

[...]

In Example 18–4, JmxMBeanServerImpl defines a method, inittables, to instantiate and initialize its table objects. The inittables method creates a new instance of TableJmxMBeanTable, and overrides it so that its createJmxMBeanEntryMBean method returns a JmxMBeanEntryImpl object instead of the default JmxMBeanEntry object. This is done by defining a new createJmxMBeanEntryMBean method in the customized JmxMBeanEntryImpl class (see Example 18–5), and calling it on the parent JmxMBeanServerImpl object from the overridden TableJmxMBeanTable object, as shown in Example 18–4.

The removeEntryCb method is overridden, so that table entries representing MBeans also delete their associated entries in the jmxMBeanAtrrTable by calling setJmxMBeanRowStatus(destroy) when they are removed from the jmxMBeanTable.


Example 18–5 createJmxMBeanEntryMBean Method

[...]
    private Object createJmxMBeanEntryMBean(SnmpMibSubRequest req,
					                                SnmpOid rowOid, 
					                                int depth, 
					                                ObjectName entryObjName,
					                                SnmpMibTable meta, 
					                                Integer  aJmxMBeanIndex)
	         throws SnmpStatusException  {

		      SnmpVarBind rowStatusVb = req.getRowStatusVarBind();

	         if (rowStatusVb == null) 
	             throw new SnmpStatusException(
		                    SnmpStatusException.snmpRspNoCreation);

		      if (! isAvailable(aJmxMBeanIndex.intValue())) {
	             if (Boolean.getBoolean("info"))
                 System.out.println("Index is not suitable: " 
                     + aJmxMBeanIndex);
	             throw new SnmpStatusException(
		                    SnmpStatusException.snmpRspInconsistentValue);
	         }

		      JmxMBeanEntryImpl entry = 
	             new JmxMBeanEntryImpl(myMib,this);
	         entry.JmxMBeanIndex = aJmxMBeanIndex;

		      entry.createFromRemote(req,rowStatusVb);
	         return entry;
    }

[...]

As shown in Example 18–4, the createJmxMBeanEntryMBean method is called when a remote SNMP Manager creates a new row in a jmxMBeanTable. Example 18–5 shows how the createJmxMBeanEntryMBean method is overridden by the JmxMBeanServerImpl class, to add new entries in the jmxMBeanAttrTable as the rows are created in the table.

Of the parameters the customized version of createJmxMBeanEntryMBean takes when it is started, the following are the most significant..

The createJmxMBeanEntryMBean method checks first of all whether the VarBind list found when req calls getRowStatusVarBind() is valid. It then checks whether the row's OID index is available. Once it has established that both the VarBind and the index are viable, it proceeds to create a JmxMBeanEntryImpl instance, entry. Calling createFromRemote() at this point ensures that createMBean() will be called when the new entry is eventually activated.

As mentioned previously, JmxMBeanServerImpl is also configured to listen for notifications of the type MBeanServerNotifications coming from the MBean server.


Example 18–6 Listening for Notifications

[...]
public synchronized void start() {

	  started = true;
	  try {
	    	 final ObjectName delegate = 
		       new ObjectName("JMImplementation:type=MBeanServerDelegate");
	       myMib.getMibServer().
		       addNotificationListener(delegate,mbsListener,null,null);

	       try {
				   Set mbeans = myMib.getMibServer().queryNames(null,null);
            for (final Iterator it = mbeans.iterator(); 
                it.hasNext();) {
		             final ObjectName name = (ObjectName) it.next();
		             if (mustShow(name)) showMBean("start",name);
		         }
	        } catch (Exception x) {
		         try {
		             myMib.getMibServer().
			              removeNotificationListener(delegate,mbsListener);
		         } catch (Exception e) { /* OK */ }
		         throw x;
	        }
	    } catch (Exception x) {
	        started = false;	    
	        System.err.println("Failed to start MBean Table: " + x);
	        if (Boolean.getBoolean("debug")) x.printStackTrace();
	    }
}

[...]

The start() method shown in Example 18–5 starts a notification listener in the MIB server that listens out for notifications from the MBeanServerDelegate. It also populates the jmxMBeanTable and jmxMBeanAttrTable with the initial content of the MBean server. These tables are then updated as and when notifications are received, as shown in Example 18–7.


Example 18–7 Handling Notifications

[...]

private void handleMBSNotification(Notification notif, Object handback) {
	
	  synchronized(this) { if (!started) return; }

  	  if (notif instanceof MBeanServerNotification) {
	        final MBeanServerNotification n = 
		          (MBeanServerNotification) notif;
	        final String nt = n.getType();
	        final ObjectName mbeanName = n.getMBeanName();
	    
	        if (MBeanServerNotification.REGISTRATION_NOTIFICATION.
		         equals(nt)) {

             synchronized (this) {
		         if (mustShow(mbeanName)) { 
			          showMBean(nt,mbeanName);
		         }
   		  } 
	     } else if (MBeanServerNotification.UNREGISTRATION_NOTIFICATION.
   		       equals(nt)) {

     		synchronized (this) {
		        if (mustHide(mbeanName)) {
			         hideMBean(nt,mbeanName);
		        }
  		  }
	     }
	  }
}

[...]

The handleMBSNotification method that JmxMBeanServerImpl defines begins by checking that the correct type of notification has been received. If the notification is of the type MBeanServerNotification.REGISTRATION_NOTIFICATION, then a method named showMBean() is called. Otherwise, if MBeanServerNotification.UNREGISTRATION_NOTIFICATION is received, then the MBean is hidden with a call to hideMBean. The showMBean() method is shown in Example 18–8.


Example 18–8 Exposing MBeans through the SNMP Tables

[...]

private void showMBean(String operation,ObjectName name,int index) {
	   try {
	       JmxMBeanEntry entry = new JmxMBeanEntryImpl(myMib,this);
	       entry.JmxMBeanIndex = new Integer(index);
	       entry.setJmxMBeanObjectName(name.toString());
	       JmxMBeanTable.addEntry(entry);
	       names.put(name, entry);
	       entry.setJmxMBeanRowStatus(
		           new EnumJmxMBeanRowStatus(EnumRowStatus.active));
	       if (Boolean.getBoolean("info")) 
		        System.out.println("ADDED: JmxMBeanTable["+index+"]="+name);
	   } catch (Exception x) {
	       System.err.println("Failed to add MBean entry: " + name);
	       if (Boolean.getBoolean("debug")) x.printStackTrace();
	   }
}

[...]

The showMBean method adds the MBeans that handleMBSNotification learns about to the SNMP table JmxMBeanTable as JmxMBeanEntryImpl row entries. The status of the row is set to active. The MBean discovered by the notification listener is thus entered in the SNMP table as a row entry.

18.2.2.2 JmxMBeanEntryImpl

As mentioned previously, the JmxMBeanEntryImpl objects keep the jmxMBeanAttrTable updated. Example 18–9 and Example 18–10 show how JmxMBeanEntryImpl changes the status of rows, activates rows, and destroys them.


Example 18–9 Changing the Row Status

[...]

public synchronized void setJmxMBeanRowStatus(EnumJmxMBeanRowStatus x) 
	   throws SnmpStatusException {
	   switch (x.intValue()) {
	   case EnumRowStatus.active:
	       if (! (JmxMBeanRowStatus.intValue() == EnumRowStatus.active))
		        activate();
	       break;
	   case EnumRowStatus.notReady:
	       break;
	   case EnumRowStatus.notInService:
	       super.setJmxMBeanRowStatus(x);
	       break;
	   case EnumRowStatus.destroy:
	       destroy();
	       break;
	   default:
	       throw new SnmpStatusException(SnmpStatusException.
					                          snmpRspInconsistentValue);
	   }
}

[...]

The setJmxMBeanRowStatus method shown above is called by the SNMP runtime when the row is created remotely. It is also called explicitly by JmxMBeanEntryImpl when the row is created after receiving an MBean server notification.


Example 18–10 Activating and Destroying Rows

private void activate() throws SnmpStatusException {
	   if ((remoteCreation) && (mbean == null || name == null))
	       throw new SnmpStatusException(SnmpStatusException.snmpRspGenErr);
	   try {

	        if (remoteCreation) {
		         myGroup.registerMBean(mbean,name,this);
		         initJmxMBean(name);
		         remoteCreation = false;
		         mbean = null;
	        } 

	        exposedAttrCount = myGroup.addAttributes(name,this);
	        if (Boolean.getBoolean("info")) 
		         System.out.println(name.toString()+ ": added " + 
				                      exposedAttrCount
				                      + " attribute(s).");
	        JmxMBeanRowStatus = 
		         new EnumJmxMBeanRowStatus(EnumRowStatus.active);
	   } catch (Exception x) {
	        SnmpStatusException sn = 
		         new SnmpStatusException(SnmpStatusException.snmpRspGenErr);
	        sn.initCause(x);
	        throw sn;
	   }
}

private void destroy() throws SnmpStatusException {
  	try {
	       JmxMBeanRowStatus = 
		        new EnumJmxMBeanRowStatus(EnumRowStatus.notInService);
	       if (name != null && remoteDeletion)
		        myGroup.unregisterMBean(name,this);
	       remoteCreation = false;
	       mbean = null;	
	       if (name != null) 
		        myGroup.removeAttributes(name,this);
	       attrList = null;
	   } catch (Exception x) {
	       SnmpStatusException sn = 
		        new SnmpStatusException(SnmpStatusException.snmpRspGenErr);
	       sn.initCause(x);
	       throw sn;
	   }
}

Example 18–10 shows the activate and destroy methods defined by JmxMBeanEntryImpl. The activate method verifies first of all whether the row was created remotely or locally, and acts differently depending on the answer. If the row was created remotely by calling the createFromRemote method, the row entry registers the requested MBean in the MBean. If the row is created locally, this means that the request to create the row was made by an existing registered MBean, so there is no need to register it again in the MBean server.

The destroy method is created when the row is removed, either locally by handleMBeanServerNotification or remotely. The destroy method is called by removeEntryCb, which itself is called when the row is removed from the table, whether remotely or locally.

18.2.3 Point of Entry into the SNMP Table Instrumentation Example

In this example, the table instrumentation operations shown in the preceding sections are all demonstrated by a single class, the Agent. The fact that this Agent class creates MBeans, and registers them in an MBean server so that they can be mirrored by the SNMP MIB, is irrelevant to this example. The real purpose of the Agent is to demonstrate how to add instrumentation to the SNMP tables using Java DMK technology, not to demonstrate what the example actually does.


Example 18–11 Making an MBean Server Accessible by an SNMP Manager

public class Agent {
    public interface SimpleMBean {
	       public String getName();
	       public int    getCount();
	       public void   reset();
    }

    public static class Simple implements SimpleMBean {
	       public Simple() {
	           this(null);
	       }
    // Define MBean operations    
    [...]
    }

    public static MBeanServer getPlatformMBeanServer() {
	        final MBeanServer test = MBeanServerFactory.createMBeanServer();
	        final MBeanServer first = (MBeanServer)
	           MBeanServerFactory.findMBeanServer(null).get(0);
	        if (test != first) MBeanServerFactory.releaseMBeanServer(test);
	        return first;
    }

    public int populate(MBeanServer server, int mbeanCount) {
	      int count = 0;
	      for (int i=0; i<mbeanCount; i++) {
	          try {
		            final SimpleMBean simple = new Simple();
		            final ObjectName name = 
		               new ObjectName("Example:type=Simple,name="+
				                         simple.getName());
		            server.registerMBean(simple,name);
		            count ++;
		            System.out.println("Registered MBean: " + name);
	          } catch (Exception x) {
		            System.err.println("Failed to register MBean: " + x);
		            debug(x);
	          }
	      }
	      return count;
    }

    public int depopulate(MBeanServer server, int mbeanCount) {
	      int count = 0;
	      while (true) {
	          try { 
		           final ObjectName pattern = 
		              new ObjectName("Example:type=Simple,*");
		           Set mbeans = server.queryNames(pattern,null);
		           if (mbeans.isEmpty()) break;
		           for (Iterator it=mbeans.iterator(); it.hasNext() ; ) {
		                if (count == mbeanCount) return count;
		                final ObjectName next = (ObjectName) it.next();
		                try {
			                  server.unregisterMBean(next);
			                  count++;
                        System.out.println("Deregistered MBean: " 
                                            + next);
		                } catch (InstanceNotFoundException x) {
			               continue;
		                }
		           }
		           if (count >= mbeanCount) break;
	          } catch (Exception x) {
		           System.err.println("Unexpected exception: " + x);
		           debug(x);
		           break;
	          }
	      }
	      return count;
   }
[...]

The Agent class shown in Example 18–11 defines a basic MBean called SimpleMBean, that performs very basic MBean operations. The getPlatformMBeanServer method is used to obtain the first MBean server that has been created in this session by the MBeanServerFactory.


Example 18–12 Creating an SNMPv3 Adaptor Server and JMX-MBEAN-SERVER-MIB

[...]

    public void startmib(MBeanServer server,int port) throws IOException {
	      final SnmpV3AdaptorServer adaptor = 
	          new SnmpV3AdaptorServer((InetAddressAcl)null,port);
	      adaptor.enableSnmpV1V2SetRequest();
	      adaptor.start();
	      do {
	          adaptor.waitState(CommunicatorServer.ONLINE,1000);
	      } while (adaptor.getState() == CommunicatorServer.STARTING);

	      final int state = adaptor.getState();
	      if (state != CommunicatorServer.ONLINE) {
	          try { adaptor.stop(); } catch (Exception x) { /* OK */ }
	          throw new IOException("Can't start adaptor: " + 
				                        adaptor.getStateString());
	      }

       JMX_MBEAN_SERVER_MIB_Impl mib = 
              new JMX_MBEAN_SERVER_MIB_Impl(server);

	      try {
	           mib.init();
	           mib.setSnmpAdaptor(adaptor);
	           server.registerMBean(adaptor,null);
	           mib.start();
	      } catch (Exception x) {
	           System.err.println("Failed to register SnmpAdaptor: " + x);
	           try { adaptor.stop(); } catch (Exception e) { /* OK */ }
	           final IOException io = 
		            new IOException("Failed to register SnmpAdaptor");
	           io.initCause(x);
	           throw io;
	      }
       System.out.println("SnmpAdaptor ready at port: " 
                          + adaptor.getPort());
    }
[...]

The Agent class then defines a method, startmib, to instantiate an SNMPv3 adaptor server named adaptor, and creates an instance of the JMX_MBEAN_SERVER_MIB_Impl MIB, named mib. The mib is constructed around an MBean server named server, the content of which is mirrored in this MIB. The MIB is then initialized by a call to the init() method defined by JMX_MBEAN_SERVER_MIB_Impl. The reference to the SNMP protocol adaptor through which the MIB is accessible, in this case adaptor, is set by calling the SnmpMibAgent method setSnmpAdaptor, and this adaptor is then registered as an MBean in the MBean server server. The start() method defined by JMX_MBEAN_SERVER_MIB_Impl is then called. The start() method starts the mbeanServerGroup, which itself is an instance of JmxMBeanServerImpl, and which activates the mirroring of the MBean server in the MIB. See the JMX_MBEAN_SERVER_MIB_Impl.java file in the generated directory for the full implementation of the start() method.


Example 18–13 Starting the Agent

[...]
      public void start(int port, String[] args) throws IOException {
	       final MBeanServer server =  getPlatformMBeanServer();
	       final JMXServiceURL[] urls = new JMXServiceURL[args.length];
	       for (int i=0;i<args.length;i++) {
	            try {
		              urls[i] = new JMXServiceURL(args[i]);
	            } catch (MalformedURLException x) {
		       			throw x;
	            }
	       }
	       for (int i=0;i<urls.length;i++) {
	           try {
		            final JMXConnectorServer s = 
		                    JMXConnectorServerFactory. 
                       newJMXConnectorServer(urls[i],
								  null,null);
		            final ObjectName name = 
                  new ObjectName("Connector:type=
                                 "+s.getClass().getName()+
				                        ",instance="+i);
		            server.registerMBean(s,name);
		            s.start();
		    	   } catch (Exception x) {
		            final String msg = "Failed to start connector: 
                                  " + args[i];
		            System.err.println(msg);
		            final IOException io = new IOException(msg);
		            io.initCause(x);
		            throw io;
	    	      }
	       }

	       populate(server,5);

	       startmib(server,port);
	
	       final Timer timer = new Timer();
	       final long  period= 2000;
	       final long  now=0;
	       final int   max=4;
	       for (int i=0;i<max;i++) {
	           final TimerTask taskAdd = new TimerTask() {
		                public void run() {
                       final MBeanServer server = 
                                getPlatformMBeanServer();
			                 populate(server,1);
		                }
		             };
	           final TimerTask taskRem = new TimerTask() {
		                public void run() {
                       final MBeanServer server = 
                                getPlatformMBeanServer();
			                 depopulate(server,1);
		                }
		             };
	           timer.schedule(taskAdd,now+period*i,2*period*max);
	           timer.schedule(taskRem,now+period*max+period*i,2*period*max);
	       }
    }

    public static void initOidTable() {	
	       final SnmpOidTable orig = SnmpOid.getSnmpOidTable();
	       SnmpOidDatabaseSupport mibs = new SnmpOidDatabaseSupport();
	       mibs.add(new JMX_MBEAN_SERVER_MIBOidTable());
	       if (orig != null) mibs.add(orig);
	       SnmpOid.setSnmpOidTable(mibs);
    }
[...]

Agent now defines a start() method of its own. The start method firstly obtains an MBean server instance, server, by calling the getPlatformMBeanServer method defined earlier in the class. It also retrieves any JMX service URLs that might be passed to Agent as arguments when it is launched. If any JMX service URLs are provided at start-time, the Agent.start() method uses them to create JMXConnectorServer instances that it registers in the MBean server server as MBeans.

The MBean server instance server is populated with dummy MBeans by calling the populate() method. These MBeans are mirrored in the MIB, when the mirroring is activated.

The SNMP adaptor and the MIB are started by calling startmib, that was defined in Example 18–12.

Once the startmib method has been called, timer tasks are defined that periodically add and remove MBeans from the MBean server. These periodic tasks serve merely to test and demonstrate that the jmxMBeanTable and jmxMBeanAttrTable are updated accordingly in the MIB.

JMX_MBEAN_SERVER_MIBOidTable metadata definitions for the MIBs are loaded into the global SNMP OID table, to enable the use of symbolic OID names.


Example 18–14 Agent main() Method

[...]
    public static void main(String[] args) {

	       Agent agent = new Agent();
	       try {
	            initOidTable();
             final String portStr = System.getProperty("snmp.port",
                                                       "16161");
	            final int port;
	            try {
		              port = Integer.parseInt(portStr);
	            } catch (Exception x) {
                 System.err.println("Invalid value specified for 
                                    snmp.port: "
				                          + portStr);
		             System.err.println("Error is: " + x);
		             throw x;
	            }
	            System.out.println("Using SNMP port " + port);

	            agent.start(port,args);

	            // Wait forever...
	            Thread.sleep(Long.MAX_VALUE);
	       } catch (Exception x) {
	            System.err.println("Failed to start agent: " + x);
	            debug(x);
	            System.exit(1);
	       }

    }

}

Finally, an instance of Agent, agent, is started on the appropriate SNMP port by calling the start method defined in Example 18–13.

18.2.4 Running the SNMP Table Instrumentation Example

After you have run mibgen to generate JMX_MBEAN_SERVER_MIB and its associated classes, as explained in 18.2.1 Classes Generated by mibgen, you can run the example.

To Run the SNMP Table Instrumentation Example

Run the example from the examplesDir/current/Snmp/MBeanTable directory.

  1. Compile the Java classes in the generated directory.


    $ cd generated
    $ javac -d .. *.java
    

    This creates the compiled *.class files into the examplesDir/current/Snmp/MBeanTable directory, rather than in generated, so that they are accessible to the other classes used in the example.

  2. Compile the Java classes in the examplesDir/current/Snmp/MBeanTable directory.


    $ cd ..
    $ javac -d . *.java
    
  3. Start the Agent.

    You have several options when starting the Agent class.

    • To start the Agent on the default SNMP port, 16161:


      $ java  Agent service:jmx:jmxmp://
      
    • To start the Agent on a port of your choice:


      $ java  -Dsnmp.port=port_number Agent service:jmx:jmxmp://
      
    • To start the Agent with a connector of your choosing, you can specify a different service URL and connector port. For example:


      $ java  Agent JMX_service_URL
      

    In any of the above cases, you see confirmation of the SNMP port used, confirmation of the creation of the connector and then the alternating registration and deregistration of sets of five MBeans.

  4. You can now perform management operations on the Agent.

    Use a JMX management console of your choice to examine the content of the MBean server through a JMXMP connector.

    Use the SNMP management application, easymanager, that is supplied with Java DMK to examine the JMX-MBEAN-SERVER-MIB through SNMP. You can find the easymanager application in the installDir/contributions/easymanager directory.

To Examine JMX-MBEAN-SERVER-MIB Using easymanager
  1. Add the JMX-MBEAN-SERVER-MIBOidTable.class to your classpath.

    Type the following command if you are using a UNIX platform:


    $ export CLASSPATH=$CLASSPATH:examplesDir/current/Snmp/MBeanTable/

    Type the following command if you are using a Windows platform:


    set classpath=%classpath%;examplesDir/current/Snmp/MBeanTable/
  2. Open installDir/contributions/easymanager/bin

  3. Start easymanager.

    If the SNMP agent has been started with the default example port, type the following command.


    $ easymanager.sh -port 16161 

    You can now use easymanager to perform the following operations.

    • Pop up a MIB discovery window.


      discovermib -p JDMK_STD_V2_profile
      
    • Create a new MBean.


      set:2 -w private :<<jmxMBeanObjectName.998,4,Test:name=test1>,
      <jmxMBeanClassName.998,4,Agent$Simple>,
      <jmxMBeanRowStatus.998,2,4>>
      
    • Click on resynch in the MIB discovery window to observer the changes in the MIB.

    • Destroy the MBean you created.


      set:2 -w private :<<jmxMBeanRowStatus.998,2,6>>
      

18.3 Virtual SNMP Tables

This example shows how to implement virtual SNMP tables when instrumenting a MIB in Java DMK. It is based on the same MIB as the one used in the MBean Table Instrumentation example, so you should make sure you have run that example before running this one. However, the fundamental difference between this example and the SNMP Table Instrumentation example is that the tables implemented remain entirely virtual. The tables are calculated whenever they are needed by an SNMP request, rather than residing permanently in the SNMP session. The virtual table is also created with a limited period of validity. Once this period has expired, if the request for which the table was calculated has been executed, the table can be garbage-collected. There are thus two main advantages to operating with virtual tables rather than with tables that are permanently present.

The virtual SNMP table example is based on the classes in the examplesDir/current/Snmp/MBeanVirtualTable directory. It defines the MIB called JMX-MBEAN-SERVER-MIB that exposes the content of an MBean server through SNMP. This MIB is the same as the one defined in the previous example. To achieve this, JMX-MBEAN-SERVER-MIB defines the same two SNMP tables as before:

18.3.1 Classes Generated by mibgen

Before you can proceed with this example, you need to generate the JMX-MBEAN-SERVER-MIB and it's associated classes, by using the mibgen compiler supplied with Java DMK. For details of the mibgen compiler, see the Java Dynamic Management Kit 5.1 Tools Reference Guide.

To Generate the JMX-MBEAN-SERVER-MIB

The example MIB is contained in the configuration file JMX-MBEAN-SERVER-MIB.mib, which is found in examplesDir/current/Snmp/MBeanVirtualTable. You should run the example from within this directory.

  1. Ensure that your PATH environment variable knows where to find mibgen.

    In a default installation, mibgen is found in installDir/bin.

  2. Create a new directory, generated, inside examplesDir/current/Snmp/MBeanVirtualTable.


    $ mkdir generated
    

    This directory is where mibgen generates the classes associated with JMX-MBEAN-SERVER-MIB.mib.

  3. Run the mibgen compiler.


    $ mibgen -X:use-display-hint -X:no-table-access -X:abstract-mib \
               -d generated JMX-MBEAN-SERVER-MIB.mib
    

    The advanced mibgen option use-display-hint instructs mibgen to generate an attribute of type String for any object using a textual convention whose DISPLAY-HINT is 255a. This option is used because JMX-MBEAN-SERVER-MIB defines textual conventions (for example, JmxMBeanObjectNameTC) which must be translated into java.lang.String attributes, rather than the default Byte[] attributes.

    The -X:no-table-access option instructs mibgen not to generate a table accessor in the group MBean interfaces. The effect of this is that the accessors used to return the TableJmxMBeanTable and TableJmxMBeanAttrTable table objects are not created.

    The -X:abstract-mib option, as the name suggests, generates an abstract MIB. In this case, the MIB class is an abstract class, in which the MBean factory methods are also abstract.

    The -d generated option sends the output of mibgen to the newly created generated directory

    Using the first two options makes it possible to get rid of any references to the default JmxMBeanServer class, as well as to its associated Table* objects. These classes are still generated, but our customer implementation no longer references them.

The MIB used in this example is the same as the one used in the SNMP table instrumentation example. Consequently, mibgen generates the same files in the generated directory as were generated in the table instrumentation example. The differences between this example and the instrumentation example are found in the customized classes, that are used to extend the generated classes.

18.3.2 Customized Classes

The customized classes extend the functionality of the classes of the generated MIB. As was the case in the previous example, the JMX_MBEAN_SERVER_MIB_Impl extends the JMX-MBEAN-SERVER-MIB generated by mibgen, to instantiate the instrumented JmxMBeanServerImpl class. However, the extended behavior that the customized classes bring to the MIB in this example differs from that provided by the SNMP table instrumentation example. Most significantly, whereas the JmxMBeanServerImpl class was the principal author of the customized behavior of the MIB in the previous example, in this example the bulk of the work is done by JmxMBeanTableMetaImpl.

The following sections examine each of the customized classes, describing their purpose, and where necessary, highlighting their differences with the corresponding classes in the SNMP table instrumentation example.

18.3.2.1 JmxMBeanServerImpl

JmxMBeanServerImpl instruments the jmxMBeanServer group defined in the MIB. It implements the JmxMBeanServerMBean interface that is generated by mibgen, but unlike the previous example, it does not extend the generated JmxMBeanServer skeleton. As a result of not extending the JmxMBeanServer object, and also because the MIB was generated with the -X:no-table-access option, the generated TableJmxMBeanTable and TableJmxMBeanAttrTable objects are not instantiated. Although they are still generated by mibgen, these classes are not used in this example.

The jmxMBeanTable and jmxMBeanAttrTable tables are implemented as virtual tables. They are computed on the fly when an SNMP request needs to access them. Furthermore, the jmxMBeanTable and jmxMBeanAttrTable tables are managed by their respective customized JmxMBeanTableMetaImpl and JmxMBeanAttrTableMetaImpl meta objects. In the table instrumentation example, the tables were managed by the JmxMBeanServerImpl object.

18.3.2.2 JmxMBeanServerMetaImpl

JmxMBeanServerMetaImpl extends the generated JmxMBeanServerMeta class, that is used to represent SNMP metadata for the JmxMBeanServer group.

JmxMBeanServerMetaImpl overrides the table-meta factory methods in order to instantiate customized implementations of the JmxMBeanTableMeta and JmxMBeanAttrTableMeta meta classes. These meta classes are customized in the JmxMBeanTableMetaImpl and JmxMBeanAttrTableMetaImpl meta classes, that are shown in 18.3.2.3 JmxMBeanTableMetaImpl and 18.3.2.4 JmxMBeanAttrTableMetaImpl respectively.

18.3.2.3 JmxMBeanTableMetaImpl

JmxMBeanTableMetaImpl extends the generated JmxMBeanTableMeta class, and overrides the following JmxMBeanTableMeta methods.

In addition to overriding the above methods, it is the JmxMBeanTableMetaImpl class that implements the caching mechanism. An SnmpUserData factory is used to create a transient request-contextual cache, that will remain active for the duration of the incoming request. After the request has completed, the contextual-cache is garbage-collected.

JmxMBeanTableMetaImpl defines the following subclasses, that implement the caching mechanism.

18.3.2.4 JmxMBeanAttrTableMetaImpl

JmxMBeanAttrTableMetaImpl extends the generated JmxMBeanAttrTableMeta class, and overrides the following methods.

The methods createNewEntry(SnmpMibSubRequest req, SnmpOid rowOid, int depth), and removeTableRow(SnmpMibSubRequest req, SnmpOid rowOid, int depth) are not overriden. The table is read-only, so these methods will never be called.

This class does not use any specific caching mechanism. Since the jmxMBeanAttrTable is an extension of the jmxMBeanTable it simply relies on the cache established for the jmxMBeanTable. The list of attributes pertaining to a specific MBean is cached, if necessary, in the corresponding JmxMBeanEntryImpl object, which can be reclaimed at the end of the request. Note that attribute values are not cached, but rather they are obtained when they are needed.

18.3.2.5 JmxMBeanContextFactory

JmxMBeanContextFactory implements the com.sun.management.snmp.agent.SnmpUserDataFactory interface. The userData allocated by this factory is a java.util.Map that serves as a request-contextual cache. When the MIB instrumentation needs to access a piece of data, it proceeds as follows.

This mechanism ensures the coherency of the data returned in the response. Once a piece of data has been loaded into the request-contextual cache, it is consistently reused for the whole duration of the request. This makes sure that the same snapshot is always used during the request, even if the underlying managed object constantly changes its value.

18.3.2.6 JmxMBeanEntryImpl

JmxMBeanEntryImpl extends theJmxMBeanEntry skeleton that is generated by mibgen, and adds instrumentation to the entries of the jmxMBeanTable table defined in the MIB. Each JmxMBeanEntry represents an MBean registered in the exposed MBean server. The JmxMBeanEntry objects are created on the fly when needed. They are temporary objects which are created when an SNMP request needs access to the instrumented MBean. Once created, JmxMBeanEntry objects are put in the request-contextual cache, where they remain until the request completes.

18.3.2.7 JmxMBeanAttrEntryImpl

JmxMBeanAttrEntryImpl extends theJmxMBeanAttrEntry skeleton that is generated by mibgen, and adds instrumentation to the entries of the jmxMBeanAttrTable table defined in the MIB. Like JmxMBeanEntry objects, JmxMBeanAttrEntry objects are created on the fly when an SNMP request needs access to the instrumented MBean attributed.

JmxMBeanAttrEntry objects are stored in their corresponding JmxMBeanEntryImpl objects, which are themselves cached in the request-contextual cache and in the JmxMBeanTableCache.

18.3.3 Running the SNMP Virtual Tables Example

After you have run mibgen to generate JMX_MBEAN_SERVER_MIB and its associated classes, as explained in 18.2.1 Classes Generated by mibgen, you can run the example.

To Run the SNMP Virtual Tables Example

Run the example from the examplesDir/current/Snmp/MBeanVirtualTable directory.

  1. Compile the Java classes in the generated directory.


    $ cd generated
    $ javac -d .. *.java
    

    This creates the compiled *.class files into the examplesDir/current/Snmp/MBeanVirtualTable directory, rather than in generated, so that they are accessible to the other classes used in the example.

  2. Compile the Java classes in the examplesDir/current/Snmp/MBeanVirtualTable directory.


    $ cd ..
    $ javac -d . *.java
    
  3. Start the Agent.

    You have several options when starting the Agent class.

    • To start the Agent on the default SNMP port, 16161:


      $ java  Agent service:jmx:jmxmp://
      
    • To start the Agent on a port of your choice:


      $ java  -Dsnmp.port=port_number Agent service:jmx:jmxmp://
      
    • To start the Agent with a connector of your choosing, you can specify a different service URL and connector port. For example:


      $ java  Agent JMX_service_URL
      

    In any of the above cases, you see confirmation of the SNMP port used, confirmation of the creation of the connector and then the alternating registration and deregistration of sets of five MBeans.

  4. You can now perform management operations on the Agent.

    Use a JMX management console of your choice to examine the content of the MBean server through a JMXMP connector.

    Use the SNMP management application, easymanager, that is supplied with Java DMK to examine the JMX-MBEAN-SERVER-MIB through SNMP. You can find the easymanager application in the installDir/contributions/easymanager directory.

To Examine JMX-MBEAN-SERVER-MIB Using easymanager
  1. Add the JMX-MBEAN-SERVER-MIBOidTable.class to your classpath.

    Type the following command if you are using a UNIX platform:


    $ export CLASSPATH=$CLASSPATH:examplesDir/current/Snmp/MBeanVirtualTable/

    Type the following command if you are using a Windows platform:


    set classpath=%classpath%;examplesDir/current/Snmp/MBeanVirtualTable/
  2. Open installDir/contributions/easymanager/bin

  3. Start easymanager.

    If the SNMP agent has been started with the default example port, type the following command.


    $ easymanager.sh -port 16161 

    You can now use easymanager to perform the following operations.

    • Pop up a MIB discovery window.


      discovermib -p JDMK_STD_V2_profile
      
    • Create a new MBean.


      set:2 -w private :<<jmxMBeanObjectName.998,4,Test:name=test1>,
      <jmxMBeanClassName.998,4,Agent$Simple>,
      <jmxMBeanRowStatus.998,2,4>>
      
    • Click on resynch in the MIB discovery window to observer the changes in the MIB.

    • Destroy the MBean you created.


      set:2 -w private :<<jmxMBeanRowStatus.998,2,6>>
      

Chapter 19 Security Mechanisms in the SNMP Toolkit

Both the simple network management protocol (SNMP) protocol adaptor and the SNMP manager API provide mechanisms for ensuring the security of management operations. Under SNMPv1 and SNMPv2, agents act as information servers, and IP-based access control is used to protect this information from unauthorized access. The SNMPv3 protocol provides much more sophisticated security mechanisms, implementing a user-based security model (USM), allowing both authentication and encryption of the requests sent between agents and their managers, as well as user-based access control.

The complete source code for these examples is available in subdirectories of the examplesDir/current/Snmp/ directory (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

19.1 IP-Based Access Control Lists

For the SNMP adaptor, the Java Dynamic Management Kit (Java DMK) 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 (InetAddressAcl). The default implementation provided with the product uses an InetAddressAcl file, but you can provide your own implementation as described in 19.1.3 Custom Access Control.

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

The following code example gives the contents of the examplesDir/current/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 host running your SNMP manager application.


Example 19–1 jdmk.acl File

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

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

19.1.1 InetAddressAcl File Format

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

19.1.1.1 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 hosts specified in the hostList. There are two possible values: either read-write or read-only.

The hostList item gives the hosts 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:

The set of all access configurations defines the access policy of the SNMP agent. A manager whose host is specified in a hostList and that 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 can 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 protocol data units (PDU) from this manager will be dropped without being processed.

19.1.1.2 Format of the Trap Group

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

trap = {
   community1
   community2
   ...
   communityN
}

Each community definition 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. SNMPv3 does use the community string, so use IP addresses or the context name instead.

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

19.1.1.3 Format of the Inform Group

The inform group specifies the hosts to which your agent or manager will send informs if the InetAddressAcl mechanism is used. This group contains one or more inform community definitions.

inform = {
   community1
   community2
   ...
   communityN
}

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

{
   inform-community = informCommunityName
   hosts = informHostList
}

The informCommunityName item specifies a single SNMP community string. It will be included in the informs sent to the hosts specified in the hosts item. SNMPv3 does use the community string, so use IP addresses or the context name instead.

The informHostList 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 an inform using the InetAddressAcl mechanism, it will send an inform to every host listed in the inform community definitions. If a host is present in more than one list, it will receive more than one inform, each one identified by its corresponding inform community.

19.1.2 Enabling InetAddressAcl

The default InetAddressAcl mechanism provided with the Java DMK relies on an InetAddressAcl file to define the access rights and trap recipients. To enable access control with this mechanism, you must first write an InetAddressAcl 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 InetAddressAcl file exists when the SNMP protocol adaptor MBean is instantiated. To be automatically detected, the InetAddressAcl file must be named jdmk.acl and must be located in the configuration directory of the Java DMK installation. On UNIX systems with a standard installation of the product, the configuration directory is owned by root and requires superuser privileges to write or modify the InetAddressAcl file.

Operating Environment 

Configuration Directory 

Solaris/Linux/Windows 

installDir/SUNWjdmk/5.1/etc/conf/

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 InetAddressAcl file in the configuration directory. This property can be set programmatically, but it is usually done on the command line when starting your agent. For example, if the full pathname of your InetAddressAcl file is MyAclFile, use this command to start the agent with SNMP access control enabled:


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

If an InetAddressAcl 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 host: the InetAddressAcl groups must explicitly give permissions to localhost or the host's name or IP address for such managers. If the InetAddressAcl 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 local host.

The InetAddressAcl 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 InetAddressAcl file. This class provides the rereadTheFile method to reset the access control and trap lists with the contents of the InetAddressAcl file. This method will reload the same file that was used originally, regardless of any new property definitions. After you have updated the InetAddressAcl file, call the following methods to update the access control lists:

// assuming mySnmpAdaptor is declared as an SnmpAdaptorServer object
JdmkAcl theAcl = (JdmkAcl)(mySnmpAdaptor.getInetAddressAcl());
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 that 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 InetAddressAcl file. If you specify false for the useAcl parameter of this constructor, the SNMP adaptor will not even search for an InetAddressAcl file. In this case, no access control is performed, as if there were no InetAddressAcl file: all SNMP requests will be processed, and traps will be sent only to the local host. For security considerations, the use of access control cannot be toggled once the SNMP adaptor has been instantiated.

19.1.3 Custom Access Control

The JdmkAcl class that 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 that does not rely on the jdmk.acl file.

To instantiate an SNMP adaptor with your own access control, use one of the constructors that takes an acl parameter of the type InetAddressAcl. Note that if this parameter's value is null, or if you use a constructor that does not 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 InetAddressAcl 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 InetAddressAcl 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 InetAddressAcl interface, and it provides a few other methods, such as rereadTheFile, to control the InetAddressAcl mechanism.

19.2 SNMPv3 User-Based Access Control

The user-based access control implemented by SNMPv3 is based on contexts and user names, rather than on IP addresses and community strings. It is a partial implementation of the view-based access control model (VACM) defined in SNMP RFC 2575.

The users, contexts and associated security information controlling access to the agents in an SNMP session are defined in the jdmk.uacl file, as shown in the following example, taken from the examplesDir/current/Snmp/Agent directory.


Example 19–2 jdmk.uacl File

acl = {
{ 
context-names = TEST-CONTEXT
access = read-write
security-level=authNoPriv
users = defaultUser
}

} 

In the jdmk.uacl file, you define the following:

The user ACL file allows any request from defaultUser, in the scope of TEST-CONTEXT, with a minimum of authNoPrivacy to be authorized. Any other request will be rejected.

19.2.1 Enabling User-Based Access Control

To enable user-based access control, you must create a UserAcl file. You must then direct the agent applying the access control to look in this file.

The simplest way of enabling access control and traps is to ensure that a user-based access control file UserAcl file exists when the SNMP protocol adaptor MBean is instantiated. To be automatically detected, the UserAcl file must be named jdmk.uacl and must be located in the configuration directory of the Java DMK installation. On UNIX systems with a standard installation of the product, the configuration directory is owned by root and requires superuser privileges to write or modify the UserAcl file.

Operating Environment 

Configuration Directory 

Solaris/Linux/Windows 

installDir/SUNWjdmk/5.1/etc/conf/

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


$ java -classpath classpath -Djdmk.uacl.file=MyUaclFile MyAgent

If a UserAcl file exists, the access rights it defines apply to all management applications that access the agent through its SNMP adaptor. If the UserAcl 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 accepted.

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

Uacl.rereadTheFile

The following procedure demonstrates how to enable Uacl, using the example of the simple synchronous manager we saw in 17.1.3 Synchronous SNMPv3 Managers and the simple SNMPv3 agent from Example 16–2.

To Run a Simple Manager with Access Control
  1. If you have not already done so, build and compile the AgentV3 example in examplesDir/current/Snmp/Agent.

    Type the following commands:


    $ mibgen -mo -d . mib_II.txt
    $ javac -classpath classpath -d . *.java
    
  2. Start the AgentV3 example in its Agent directory, this time pointing it to its associated jdmk.uacl file, as well as to its jdmk.security file.


    $ java -classpath classpath 
    -Djdmk.security.file=jdmk.security -Djdmk.uacl.file=jdmk.uacl AgentV3
    
  3. If you have not already done so, in a separate window, compile the SyncManagerV3 example in examplesDir/current/Snmp/Manager.


    $ javac -classpath classpath -d . *.java
    
  4. Start the SyncManagerV3 SNMP manager in its Manager directory, specifying the agent's host and port.

    This is the manager we configured to communicate with AgentV3 in To Run the SyncManagerV3 Example. As before, we set the host to localhost and the port to 8085.


    $ java -classpath classpath 
    -Djdmk.security.file=jdmk.security SyncManagerV3 localhost 8085
    

    You should see the following error message:


    SyncManagerV3::main:Send get request to SNMP agent 
    on localhost at port 8085
    Error status = authorizationError
    Error index = -1
    

    The agent refuses the manager's request because the level of security for this agent in the manager's jdmk.security file does not match the level of security set in the agent's jdmk.uacl file.

  5. Press Control-C to stop the manager.

  6. Start the SyncManagerV3 SNMP manager again


    $ java -classpath classpath 
    -Djdmk.security.file=jdmk.security SyncManagerV3 localhost 8085
    

    You should now see the manager sending requests to the agent.


    $ SyncManagerV3::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.8]
    >> Press Enter if you want to stop this SNMP manager.
    

19.3 SNMPv3 User-Based Security Model

As stated in the earlier SNMP chapters in this manual, SNMPv3 implements a much more sophisticated set of security mechanisms than the previous versions of SNMP. The SNMPv3 USM enables you to implement authentication and privacy in the communication that takes place between your SNMP agents and managers. SNMPv3 also introduces the concept of the authoritative SNMP engine and enables you to create authorized users for specific SNMPv3 agents.

Chapter 16, Creating an SNMP Agent and Chapter 17, Developing an SNMP Manager provided just enough information about configuring SNMPv3 security to enable you to run the examples in those chapters. The following sections provide a more complete description of SNMPv3 security.

19.3.1 SNMPv3 Engines

SNMPv3 introduces the notion of the authoritative SNMP engine. The concept of authoritative is defined as follows:

Being authoritative means that entities have the ability to accept or deny requests from other entities, depending upon whether or not both sides of the exchange have been appropriately configured to communicate with each other, and whether the request itself arrives in a timely fashion. To check the timeliness of a request, the authoritative engine checks the time of sending included in the request against its own internal clock. If the difference between the time of sending and the time of receipt recorded by the authoritative engine exceeds 150 seconds, the request is not considered timely and is rejected.

The authoritative engine also checks timeliness by reading the localEngineBoots value recorded in the request, and comparing it to the number of reboots that the sending engine has undergone. It checks this by calling the SnmpEngine.getEngineBoots. If the value recorded in the request and the value returned by SnmpEngine.getEngineBoots do not correspond, the request is rejected.

In general, agents are authoritative, and managers are non-authoritative. However, when receiving informs, managers are authoritative, and can accept or deny the informs according to their timeliness.

Java DMK 5.1 associates an SNMP engine with every SnmpV3AdaptorServer that is instantiated. Engines can be shared between several SNMP sessions. SNMP engines are identified by their engine ID.

19.3.2 Generating SNMPv3 Engine IDs

SNMPv3 engine ID objects are generated in accordance with SNMP RFC 2571. An engine discovers its ID in one of the following ways, each of which is tried in the order shown:

  1. It is found in the engine's associated jdmk.security file

  2. It is found using SnmpEngineParameters

  3. It is computed by the SNMP adaptor, based on the host and port information, or it is computed by the SNMP session, based on time

In Java DMK 5.1, you can create new SNMP engine IDs either manually using a Java command line tool called EngineIdGenerator, or automatically in the code of your applications using the SnmpEngineId class. You can see the EngineIdGenerator tool in the examplesDir/current/Snmp/EngineId directory. You must provide either of EngineIdGenerator or SnmpEngineId with any of the following information:

The EngineIdGenerator uses the SnmpEngineId class when generating engine IDs (see the Javadoc entry for SnmpEngineId for details). The SnmpEngineId class simplifies the configuration of SNMP entities by enabling you to identify engines using information that is easily comprehensible to humans, such as host names and port numbers, which it then converts into system-oriented hexadecimal engine IDs for you. The SnmpEngineId class also generates SNMP object identifiers (OIDs) from the engine IDs it creates. This is particularly useful when computing USM MIB user table indexes, as seen in 19.3.6 Creating Users for SNMPv3 USM MIBs.

To Run the SNMP EngineIdGenerator Example
  1. Compile the EngineIdGenerator class in examplesDir/current/Snmp/EngineId.


    $ javac -classpath classpath EngineIdGenerator.java
    
  2. Start the EngineIdGenerator tool.


    $ java -classpath classpath EngineIdGenerator
    Start making your engine Id construct:(h for help)
    #:
    

    Typing h will provide examples of information you can provide.

  3. Provide the relevant information to create the engine ID.

    Declare your information, using the appropriate separators, as follows:

    address:port:IANA number

    All three inputs are used

    address:port

    The address and port you specify are used; the IANA number defaults to 42 (SUN Microsystems)

    address

    The address you specify is used; the port defaults to 161 and the IANA number defaults to 42 (SUN Microsystems)

    :port

    The port you specify is used; the host defaults to localhost and the IANA number defaults to 42 (SUN Microsystems)

    ::IANA number

    The IANA number you specify is used; the host defaults to localhost and the IANA number defaults to 42 (SUN Microsystems)

    :port:IANA number

    The port and IANA number you specify are used; the host defaults to localhost

    address::IANA number

    The address and IANA number you specify are used; the port defaults to 161

    ::

    The port defaults to 161, the address defaults to localhost and the IANA number defaults to 42 (SUN Microsystems)

    For example, to specify all three of the address, port and IANA number, when prompted you might type:


    Start making your engine Id construct:(h for help)
    #:localhost:8087:42
    Generated engine Id ******** [0x8000002a05819dcb6200001f97] ********
    #:
    
  4. Press Control-C when you have finished generating engine IDs

19.3.3 SNMPv3 USM Configuration

The SNMPv3 USM is configured in a Java DMK text file, called jdmk.security. Every SNMP engine has an associated security file.

In a traditional agent and manager SNMP architecture, you will have one security file associated with the agent and one associated with the manager. Both files will have a very similar configuration.

The authoritative agent's security file contains all the security information users need when requests are received from the manager. The non-authoritative manager's security file contains all the security information users need when making requests of authoritative agents.

The following examples show typical security files for an agent and a manager.


Example 19–3 A Typical Agent jdmk.security File

localEngineID=myHost:8085
localEngineBoots=7

#Typical authenticated entry. Accepts requests from a user called 
#aSecureUser
userEntry=localEngineID,aSecureUser,aSecureUser,
usmHMACMD5AuthProtocol, mypasswd

#Typical authenticated and encrypted entry. Accepts requests from 
#aSecureUser
#userEntry=localEngineID,aSecureUser,aSecureUser,
#usmHMACMD5AuthProtocol, #mypasswd,usmDESPrivProtocol,mypasswd

The example agent jdmk.security file identifies the agent's associated SNMP engine using its host name and port number, records the number of times that engine has rebooted, and sets two possible security configurations for a user called aSecureUser. One possible configuration applies authentication to requests from aSecureUser. The second configuration, which is currently commented out and is therefore inactive, applies both authentication and privacy to requests from the same user.


Example 19–4 A Typical Manager jdmk.security File

#Typical authenticated entry. Makes requests to authoritative engine 
#myHost:8085 with some parameters.
userEntry=myHost:8085,aSecureUser,aSecureUser,usmHMACMD5AuthProtocol, 
mypasswd

#Typical authenticated and encrypted entry. Makes requests to authoritative 
#engine myHost:8085 with some parameters.
#userEntry=myHost:8085,aSecureUser,aSecureUser,usmHMACMD5AuthProtocol, 
#mypasswd,
#usmDESPrivProtocol,mypasswd

# #####APPENDED PROPERTY####
localEngineBoots=5

# #####APPENDED PROPERTY####
localEngineID=myOtherHost:8087

The example manager jdmk.security file sets two possible configurations to send requests from the user aSecureUser to the above agent. Again, the first configuration applies authentication to requests from aSecureUser, and the second configuration, which is currently commented out and is therefore inactive, applies both authentication and privacy.


Note –

The localEngineID for each of the manager and the agent must be different. If two entities that communicate with each other have the same local engine ID, behavior is unpredictable.


19.3.3.1 Adding Users to the Security Files

As you can see in Example 19–3 and Example 19–4, every user that has access to an agent is represented by a userEntry row in each of the agent's and the manager's security files. The example manager jdmk.security file is configured to send requests from aSecureUser to the agent, either with authentication only, or with privacy activated. The agent is configured to receive those requests.

You configure userEntry as follows, with the parameters separated commas:

userEntry=engine ID,user name,security name,authentication algorithm,authentication key, privacy algorithm,privacy key,storage type,template

The only mandatory parameters are the engine ID and the user name. All the other parameters are optional.

The possible values for the parameters are as follows:

Engine ID

A local or remote SNMP engine, defined in one of the following ways:

  • The string localEngineID, to denote the local engine

  • A hexadecimal string, as generated by EngineIdGenerator; for example, 0x8000002a05819dcb6e00001f95

  • A human readable string used to generate an engine ID, providing any or all of the host name, port and IANA number, as shown in 19.3.2 Generating SNMPv3 Engine IDs

User name

Any human-readable string

Security name

Any human-readable string

Authentication algorithm

The following algorithms are permitted:

  • usmHMACMD5AuthProtocol

  • usmHMACSHAAuthProtocol

  • usmNoAuthProtocol

Authentication key

Any text password or any hexadecimal key starting with 0x; for example, 0x0098768905AB67EFAA855A453B665B12, of size:

  • 0 to 32 inclusive for HMACMD5

  • 0 to 40 inclusive for HMACSHA

Privacy algorithm

The following algorithms are permitted:

  • usmDESPrivProtocol

  • usmNoPrivProtocol

If no algorithm is specified, the default is usmNoPrivProtocol.

Any text password or any hexadecimal key starting with 0x; for example, 0x0098768905AB67EFAA855A453B665B12, of size 0 to 32 inclusive

If a hexadecimal string is provided, it must be a localized key

Storage type

A value of 3 denotes non-volatile, meaning that the user entry is flushed in the security file; any other value than 3 will be rejected, throwing an IllegalArgumentException

template

Can be either true or false:

If true, the row is a template, not seen from USM MIB. This kind of user is used when cloning users.

The default is false.

19.3.4 Enabling Privacy in SNMPv3 Agents

As shown in the example security files given in 19.3.3 SNMPv3 USM Configuration, you can protect the communication between your SNMPv3 entities by enabling encryption, otherwise known as privacy.

The privacy algorithms used by SNMPv3 are the data encryption standard (DES) protocol from the Java Cryptography Extension (JCE) from the Java 2 Platform, Standard Edition (J2SE) 1.4, as well as the secure hash algorithm (SHA) and message digest 5 (MD5) encryption protocols provided since J2SE 1.2.

To run an SNMP entity with privacy enabled, you must configure both the entity itself and its corresponding security file. The following example shows the code for an SNMPv3 agent with privacy enabled, called AgentEncryptV3. This example is found in the examplesDir/current/Snmp/Agent directory.


Example 19–5 AgentEncryptV3 Agent with Privacy Enabled

public class AgentEncryptV3 {

    static SnmpV3AdaptorServer snmpAdaptor = null;
    
    private static int nbTraps = -1;

    public static void main(String args[]) {
        
        final MBeanServer server;
        final ObjectName htmlObjName;
        final ObjectName snmpObjName;
        final ObjectName mibObjName;
        final ObjectName trapGeneratorObjName;
        int htmlPort = 8082;
        int snmpPort = 161;

        // Parse the number of traps to be sent.
       
			[...]   
         
      // SNMP specific code:
      
      [...]  

	    	// Set up encryption 
	  
		   //First create parameters.
	    	SnmpEngineParameters parameters = new SnmpEngineParameters();

	    	//Then activate encryption
	    	parameters.activateEncryption();

	    	//Create the SNMPv3 adaptor and pass it the parameters.
            snmpAdaptor = new SnmpV3AdaptorServer(parameters,
						  null,
						  null,
						  snmpPort,
						  null);
	    
	    	// Register the SNMP Adaptor in the MBean Server 
		   //
            server.registerMBean(snmpAdaptor, snmpObjName);

	    	// Register the USM MIB
		   snmpAdaptor.registerUsmMib(server, null);

	    	// Start the adaptor.
            snmpAdaptor.start();

            // Send a coldStart SNMP Trap. 
            // Use port = snmpPort+1.
            //
            print("NOTE: Sending a coldStart SNMP trap to each " +
		  		"destination defined in the ACL file...");

            snmpAdaptor.setTrapPort(new Integer(snmpPort+1));
            snmpAdaptor.snmpV1Trap(0, 0, null);
            println("Done.");
      
            // Create the MIB II (RFC 1213) and add it to the MBean server.
            //
            mibObjName= new ObjectName("snmp:class=RFC1213_MIB");
            Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MISC, "Agent", "main", 
                       "Adding RFC1213-MIB to MBean server with name \n\t" +
                       mibObjName);

            // Create an instance of the customized MIB
            //
            RFC1213_MIB mib2 = new RFC1213_MIB_IMPL();
            server.registerMBean(mib2, mibObjName);
      
            // Bind the SNMP adaptor to the MIB to make the MIB 
            // accessible through the SNMP protocol adaptor.
            //
			    snmpAdaptor.addMib(mib2, "TEST-CONTEXT");

            // Create a LinkTrapGenerator.
            // Specify the ifIndex to use in the object name.
            //
            String trapGeneratorClass = "LinkTrapGenerator";
            int ifIndex = 1;
            trapGeneratorObjName = new ObjectName("trapGenerator" + 
                            ":class=LinkTrapGenerator,ifIndex=" + ifIndex);
            Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MISC, "Agent", "main", 
                  "Adding LinkTrapGenerator to MBean server with name \n\t"+
		  		trapGeneratorObjName);
            LinkTrapGenerator trapGenerator = 
					new LinkTrapGenerator(nbTraps);
            server.registerMBean(trapGenerator, trapGeneratorObjName);

            println("\n>> Press Enter if you want to start sending traps."+
		    " SNMP V1 and SNMP V3 traps will be sent.");
            println("   -or-");
            println(">> Press Ctrl-C if you want to stop this agent.");
            java.lang.System.in.read();
            
            trapGenerator.start();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

   }

By default, a Java DMK 5.1 agent handles requests that are authenticated, but not encrypted. To activate encryption, you need to set certain parameters when you instantiate the SNMP engine. As shown in Example 19–5, these parameters are passed to the engine using the SnmpEngineParameters class, as follows:

The AgentEncryptV3 application then continues with the registration of the SNMP adaptor server in the MBean server, binding the MIBs and calling LinkTrapGenerator in the same way as any other agent.

As well as the agent itself, you must also configure the security file associated with that agent. Example 19–6 shows the security file associated with AgentEncryptV3.


Example 19–6 Agent jdmkencrypt.security File

#Local engine Id. 
localEngineID=0x8000002a05819dcb6e00001f95
#Number of boots.
localEngineBoots=0

#defaultUser configuration.
userEntry=localEngineID,defaultUser,null,usmHMACMD5AuthProtocol,mypasswd,
usmDESPrivProtocol,mypasswd,3,

In this file, you can see that the DES privacy protocol is specified.

To Run the AgentEncryptV3 Example
  1. If you have not already done so, build and compile the AgentEncryptV3 example in examplesDir/current/Snmp/Agent.

    Type the following commands:


    $ mibgen -d . mib_II.txt
    $ javac -classpath classpath -d . *.java
    
  2. Start the AgentEncryptV3 agent, passing it its associated security file, jdmkencrypt.security.


    $ java -classpath classpath 
    -Djdmk.security.file=jdmkencrypt.security AgentEncryptV3 [nb_traps]
    

    In the command above, nb_traps represents the number of traps that you want to send.

  3. Press Enter to start sending traps


    NOTE: Sending a linkDown SNMP trap for the Interface 1 to each 
    destination defined in the ACL file...Done.
    NOTE: Sending a linkDown SNMP trap for the Interface 1 to each 
    destination defined in the ACL file...Done.
    
  4. Press Control-C to stop the agent

19.3.5 Enabling Privacy in SNMPv3 Managers

If you enable privacy in your SNMPv3 agents, then you must also enable privacy in the corresponding manager. The following example shows the code for an SNMPv3 agent with privacy enabled, called SyncManagerEncryptV3. This example is found in the examplesDir/current/Snmp/Manager directory.


Example 19–7 SyncManagerEncryptV3 Manager with Privacy Enabled

/**
 public class SyncManagerEncryptV3 {
   
    	public static void main(String argv[]) {
		SnmpSession session = null;
	
        if (argv.length != 2) {
            usage();
            java.lang.System.exit(1);
        }
	
			//Check arguments first
			//host and port.
        final String host = argv[0];
        final String port = argv[1];
	
      	// Initialize the SNMP Manager API.
        //
	    	[...] 
	    

  		// Activate the encryption
	    
		   //

	   		// First create parameters.
	    	//
	    	final SnmpEngineParameters parameters = 
			new SnmpEngineParameters();

	    	// Then activate encryption
	    	parameters.activateEncryption();

	    	// Finaly create the session passing it the parameters.
	    	try {
			// When instantiating a session, a new SNMP V3 engine is 
			// instantiated.
			session= new SnmpSession(parameters,
					 null,
					 "SyncV3Manager session",
					 null);
	    	}catch(SnmpStatusException e) {
			println(e.getMessage());
			java.lang.System.exit(0);
	    }
	    	catch(IllegalArgumentException e) {
			//If the engine configuration is faulty
			println(e.getMessage());
			java.lang.System.exit(0);
	    }
	    
	   		final SnmpEngine engine = session.getEngine();
	    
	    	// Create a SnmpPeer object 
		   //
	    	final SnmpUsmPeer agent = 
			new SnmpUsmPeer(engine, host, Integer.parseInt(port));
	    
	    	// Create parameters to associate to the entity to 
	    	// communicate with.
	  		//
	    	final SnmpUsmParameters p = 
			new SnmpUsmParameters(engine, "defaultUser");
	    
	    	// Set Security level 
	    	//
	     	p.setSecurityLevel(SnmpDefinitions.authPriv);

	    	// Register MIBS under the scope of a context.
			//
	    	p.setContextName("TEST-CONTEXT".getBytes());

	    	// Specify a contextEngineId. This is 
	    	//
	    	p.setContextEngineId(agent.getEngineId().getBytes());
	    
	    	// The newly created parameter must be associated to the agent.
	    	//
	    	agent.setParams(p);
	
	    
	    	// Discovery timeliness
	    	//
	    	agent.processUsmTimelinessDiscovery();
	    
	    	// A default peer (agent) can be associated to a SnmpSession. 
	   	   //
	   		session.setDefaultPeer(agent);
	    
	 	 	// Create a listener and dispatcher for SNMP traps 
	    	final SnmpEventReportDispatcher trapAgent =
			new SnmpEventReportDispatcher(engine, 
					      Integer.parseInt(port) + 1, 
					      taskServer, null);
	    	trapAgent.addTrapListener(new TrapListenerImpl());
            final Thread trapThread = new Thread(trapAgent);
	    	trapThread.setPriority(Thread.MAX_PRIORITY);
	    	trapThread.start();
	    
	    
	    	// Build the list of variables you want to query.
	    	// For debug purposes, you can associate a name to your list.
	    	//
	    	final SnmpVarBindList list = 
			new SnmpVarBindList("SyncManagerEncryptV3 varbind list");
	    
	    	// We want to read the "sysDescr" variable.
	    	//
            // We will thus query "sysDescr.0", as sysDescr is a scalar
	    		// variable (see RFC 1157, section 3.2.6.3.  Identification 
           	// of Object Instances, or RFC 2578, section 7.  Mapping of 
	    		// the OBJECT-TYPE macro).
	    		//
	    		list.addVarBind("sysDescr.0");
	    
	    	// Make the SNMP get request and wait for the result.
	    	//
	    	final SnmpRequest request = session.snmpGetRequest(null, list);
	    	println("SyncManagerEncryptV3::main:" + 
				 " Send get request to SNMP agent on " + 
				 host + " at port " + port);
	    	final boolean completed = request.waitForCompletion(10000);
	    
	    	// Check for a timeout of the request.
	    	//
            if (completed == false) {
                println("SyncManagerEncryptV3::main:" +
			" Request timed out. Check reachability of agent");
		
                // Print request.
                //
                println("Request: " + request.toString());
                java.lang.System.exit(0);
            }
	    
            // Check if the response contains an 
	    		// error.
            //
            final int errorStatus = request.getErrorStatus();
            if (errorStatus != SnmpDefinitions.snmpRspNoError) {
                println("Error status = " + 
				SnmpRequest.snmpErrorToString(errorStatus));
                println("Error index = " + 
				request.getErrorIndex());
                java.lang.System.exit(0);
            }
       
            // Display the content of the result.
            //
            final SnmpVarBindList result = request.getResponseVarBindList();
            println("Result: \n" + result);
       
            println("\n>> Press Enter if you want to stop" +
		    " this SNMP manager.\n");
            java.lang.System.in.read();
            
            // Nicely stop the session
            //
            session.destroySession();
       
	    		// End the SnmpEventReportDispatcher.
	    		//
	    		trapAgent.close();
	    		taskServer.terminate();

            //
            // That's all !
            //
            java.lang.System.exit(0);
     
				} catch(Exception e) {
            java.lang.System.err.println("SyncManagerEncryptV3::main:" +
					 " Exception occurred:" + e );
            e.printStackTrace();
        }
    }

    
}

By default, a Java DMK 5.1 manager handles requests that are authenticated, but not encrypted. To activate encryption, you need to set certain parameters when you instantiate the SNMP session. As shown in Example 19–7, these parameters are passed to the engine using the SnmpEngineParameters class, as follows:

The SyncManagerEncryptV3 manager application then continues with the generation of a USM peer, defining the context and setting trap listeners in the same way as any other manager. Note, however, that in this manager, the security level is set to authPriv.

As well as the manager itself, you must also configure the security file associated with that manager. Example 19–8 shows the security file associated with SyncManagerEncryptV3.


Example 19–8 Manager jdmkencrypt.security File

#Authentication and encryption.
userEntry=0x8000002a05819dcb6e00001f95,defaultUser,,
usmHMACMD5AuthProtocol,mypasswd,usmDESPrivProtocol,mypasswd

# #####APPENDED PROPERTY####
localEngineBoots=2

# #####APPENDED PROPERTY####
localEngineID=0x8000002a05000000ebffd342ca

As was the case for the AgentEncryptV3 agent, in this file, you can see that the DES privacy protocol is specified.

To Run the SyncManagerEncryptV3 Example
  1. If you have not already done so, build and compile the AgentEncryptV3 example in examplesDir/current/Snmp/Agent.

    Type the following commands:


    $ mibgen -d . mib_II.txt
    $ javac -classpath classpath -d . *.java
    
  2. Start the AgentEncryptV3 agent, passing it its associated security file, jdmkencrypt.security.


    $ java -classpath classpath -Djdmk.security.file=jdmkencrypt.security 
    AgentEncryptV3
    

    Press Enter to start sending traps.

  3. Press Enter to start sending traps.

  4. In another window, if you have not already done so, build and compile the SyncManagerEncryptV3 example in examplesDir/current/Snmp/Manager.

    Type the following commands:


    $ mibgen -mo -d . mib_II.txt
    $ javac -classpath classpath -d . *.java
    
  5. Start the SyncManagerEncryptV3 manager, passing it its associated security file, jdmkencrypt.security, and specifying the host name and port number of the agent it is to communicate with.


    $ java -classpath classpath -Djdmk.security.file=jdmkencrypt.security 
    SyncManagerEncryptV3 localhost 8085
    

    You should see the manager start to receive encrypted traps from the agent.


    SyncManagerEncryptV3::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.8]
    
    >> Press Enter if you want to stop this SNMP manager.
    
    NOTE: TrapListenerImpl received trap V3:
            ContextEngineId : 0x8000002a05819dcb6e00001f95
            ContextName : TEST-CONTEXT
            VarBind list :
    oid : 1.3.6.1.2.1.1.3.0 val : 0:0:40
    oid : 1.3.6.1.6.3.1.1.4.1.0 val : 1.2.3.4.5.6.7.8.9.0
    oid : 1.3.6.1.2.1.2.2.1.1.1 val : 1
    
  6. Press Control-C in each window to stop both the agent and the manager

19.3.6 Creating Users for SNMPv3 USM MIBs

The SNMPv3 USM implemented in Java DMK 5.1 enables you to create users remotely in an SNMPv3 agent by accessing a MIB that has been registered in the SNMPv3 adaptor server. By default, the USM MIB is not registered in the adaptor server. You can register a MIB in the adaptor server by calling registerUsmMib.


$ snmpV3AdaptorServer.registerUsmMib()

Caution – Caution –

You can use registerUsmMib to register your MIB in the MBean server, making it available via the HTML server. This can be useful for debugging purposes, but this can also represent a security breach.


The CreateUsmMibUser example in the examplesDir/current/Snmp/UsmMib directory is a tool that uses the SNMPv3 manager API to instantiate a new user in an agent USM MIB. CreateUsmMibUser performs authenticated and encrypted communication with the agent Agent, which is found in the same directory.

The complete code for the CreateUsmMibUser example is too long to show here, but the process that it goes through to create new users remotely can be summarized as follows:

To Run the CreateUsmMibUser Example
  1. If you have not already done so, build and compile the examples in examplesDir/current/Snmp/UsmMib.

    Type the following commands:


    $ javac -classpath classpath -d . *.java
    
  2. Make sure that no other agents are running in examplesDir/current/Snmp/UsmMib, and start Agent.


    $ java -classpath classpath -Djdmk.security.file=jdmk.security Agent
    

    Note –

    The jdmk.security file must be writable if CreateUsmMibUser is to be able to add new user entries.


  3. In another window, start the CreateUsmMibUser example.

    When starting CreateUsmMibUser, you must point it to the manager.security configuration file, and specify the user name, the security level, the agent's host name and the port on which the agent is running. In this example, the security level is authentication and privacy enabled, and the agent is running on the local host.


    $ java -classpath classpath -Djdmk.security.file=manager.security 
    CreateUsmMibUser defaultUser noAuthNoPriv localhost 8085
    

    You will see the following output:


    Initializing creator.
    Ready for new user inputs.
    
  4. When prompted, provide the configuration information for your new user.

    The information you provide must correspond to users that you have already configured into your manager's security file. In this example, we are remotely adding the user myNewUser that is defined in manager.security to the agent Agent. You therefore provide the following information, all of which is found in the manager.security file. You can enter any value for the auth key random and the priv key random.


    Type the engine Id :0x000000000000000000000002
    Type the new user name :myNewUser
    Type the clone from user name :defaultUser
    Type the security level :authPriv
    Type the old priv password :maplesyrup
    Type the new priv password :newsyrup
    Type the priv key random :00000000000000000000000000000000
    Type the auth protocol :usmHMACMD5AuthProtocol
    Type the old auth password :maplesyrup
    Type the new auth password :newsyrup
    Type the auth key random :00000000000000000000000000000000
    

    You will see the following output:


    ********** Input summary ************ 
    
            * Engine Id : 0x000000000000000000000002
            * New user name : myNewUser
            * Clone from : defaultUser
            * Security level : authPriv
            * Old priv password : maplesyrup
            * New priv password : newsyrup
            * Priv key random : 00000000000000000000000000000000
            * Auth protocol : usmHMACMD5AuthProtocol
            * Old auth password : maplesyrup
            * New auth password : newsyrup
            * Auth key random : 00000000000000000000000000000000
    Do you agree (yes, no) [yes]:
    
  5. Press Enter to confirm your inputs.

    You should see the following confirmation:


    ***** New user [myNewUser] created.
    ***** Doing Priv key change
    ***** Priv key change DONE.
    ***** Doing Auth key change
    ***** Auth key change DONE.
    ***** Setting row status to active.
    ***** Setting row status to active DONE.
    
    ***** SUCCESSFULLY CREATED NEW ROW IN AGENT FOR USER : [myNewUser]*****
    
    
    Send sanity check? Your manager.security file MUST contain the currently 
    created user (press return to do it, "no" to skip):
    
  6. Press Enter to perform the sanity check.

    You should see the following confirmation:


    SANITY CHECK SUCCESSFUL, SPIN LOCK VALUE IS: 5
    Ready for new user inputs.
    
    Type the engine Id (return to accept) [0x000000000000000000000002]:
    

    You are then invited to provide configuration information for any other users you want to allow to access Agent.

  7. Check that the new user has been granted access to the agent by looking at the agent's jdmk.security file.

    You should see a new userEntry for the new user in the jdmk.security file.


    Example 19–11 jdmk.security for Agent File after Running CreateUsmMibUser

    localEngineID=0x000000000000000000000002
    localEngineBoots=7
    
    userEntry=0x000000000000000000000002,myNewUser,myNewUser,
    usmHMACMD5AuthProtocol,0x87021D7BD9D101BA05EA6E3BF9D9BD4A,
    usmDESPrivProtocol,0x87021D7BD9D101BA05EA6E3BF9D9BD4A,3,
    
    userEntry=localEngineID,defaultUser,,usmHMACMD5AuthProtocol,maplesyrup,
    usmDESPrivProtocol,maplesyrup,3,true

  8. When you have added all your new users, press Control C in both windows to stop both Agent and CreateUsmMibUser

19.4 Legacy SNMP Security

Because Java DMK 5.1 implements an SNMPv3 adaptor, and all SNMPv3 security aspects are handled completely by this adaptor, MIB instrumentation does not depend on the version of SNMP via which it is accessed. MIBs that were developed under previous releases of Java DMK can thus be directly registered into the new SnmpV3AdaptorServer, and benefit from all the SNMPv3 security mechanisms.

However, earlier versions of Java DMK provided a hook via the SnmpPduFactory, that enabled the implementation of authentication and encryption on top of the SNMPv1 and v2 protocol adaptor. This can be used to implement proprietary security over the regular SNMPv1 and v2 PDUs. This hook has been preserved in Java DMK 5.1, for reasons of backwards compatibility.


Note –

Although the SNMPv1 and v2 community-based security mechanism is still available in Java DMK 5.1, you should migrate applications that require better security to SNMPv3. When migrating your applications to SNMPv3, applications which have implemented their own PDU factory must be revised before they can be imported into the SnmpV3AdaptorServer, as the SnmpPduFactory class developed for SNMPv1/v2 PDUs is not compatible with SNMPv3 PDUs.


19.4.1 Decoding and Encoding SNMP Packets

The SnmpPduFactory hook provided by Java DMK 5.1 involves the following Java classes:

com.sun.management.snmp.SnmpPduFactory

Defines the interface of the object in charge of encoding and decoding SNMP packets.

com.sun.jdmk.snmp.SnmpPduFactoryBER

The default implementation of the SnmpPduFactory interface.

com.sun.management.snmp.SnmpPdu

The fully decoded representation of an SNMP packet.

com.sun.management.snmp.SnmpMsg

A partially decoded representation of an SNMP packet, containing the information stored in any SNMPv1, SNMPv2 or SNMPv3 message.

After receiving an SNMP packet, Java DMK 5.1 performs the following steps:

  1. The received bytes are translated into an SnmpMsg object by the message processing subsystem. If the SNMP protocol version of the original request was either v1 or v2, this step simply involves the BER decoding of the ASN.1 Message sequence as defined in RFC 1901. If the SNMP protocol version of the original request was v3, the message processing subsystem will in addition invoke the security subsystem to authenticate and decrypt the message.

  2. The SnmpMsg object is then translated into an SnmpPdu object.

  3. The SnmpPdu is analyzed and the corresponding operation is performed.

Before sending an SNMP packet, Java DMK 5.1 performs the following steps:

  1. An SnmpPdu object is initialized according to the requested operation. This could be either an SnmpPduPacketfor SNMPv1or v2, or an SnmpScopedPduPacket for SNMPv3.

  2. The SnmpPdu object is translated into an SnmpMsg.

  3. The SnmpMsg is then passed to the message processing subsystem, and translated into bytes. If SNMPv1 or SNMPv2 is being used, this step simply involves the BER encoding of the ASN.1 message sequence as defined in RFC 1901. If SNMPv3 is being used, the message processing subsystem also invokes the security subsystem to sign and encrypt the message.

The SnmpPdu object is the fully decoded description of the SNMP request. In particular, it includes the operation type (get, set, and so on), the list of variables to be operated upon, the request identifier, and the protocol version, as shown in Example 19–12.


Example 19–12 Using the SnmpPdu Class

abstract class SnmpPdu {
             ...
             public int version ;
             public int type ;
             public int requestId ;
             public SnmpVarBind[] varBindList ;
             ...
     }

The use of the SnmpMsg class is shown in Example 19–13. The SnmpMsg class is a partially decoded representation of the SNMP request. Only the protocol version and security parameters are decoded. All the other parameters remain encoded.

The SnmpMsg class is the base class derived from the message syntax from RFC 1157 and RFC 1901, and SNMPv3Message from RFC 2572. The SnmpMessage class that was present in releases of Java DMK before 5.0 derives from SnmpMsg and represents an SNMPv1 or SNMPv2 message. Because SNMPv3 introduces additional security parameters, the SnmpMessage class can only be used for SNMPv1 or SNMPv2 messages. SnmpPduFactory implementations that make direct use of SnmpMessage will therefore need to be updated if they are to be imported into a Java DMK 5.1 SNMPv3 protocol adaptor. However, they do not need to be changed as long if the old SnmpAdaptorServer is used instead of SnmpV3AdaptorServer.


Example 19–13 Using the SnmpMsg Class

abstract class SnmpMsg {
             ...
             public int version ;
             ...
     }

     class SnmpMessage extends SnmpMsg {
             ...
             public byte[] community ;
             public byte[] data ;
             ...
     }

19.4.2 SnmpPduFactory Interface

When Java DMK 5.1 needs to translate an SnmpMsg object into an SnmpPdu object, it delegates this task to an object which implements SnmpPduFactory, as shown in Example 19–14.


Example 19–14 Using the SnmpPduFactory Interface

interface SnmpPduFactory {

       // Makes an SnmpPdu from an SnmpMsg
       public SnmpPdu decodeSnmpPdu(SnmpMsg msg) 
       throws SnmpStatusException ;
       
       // Makes an SnmpMsg from an SnmpPdu
       public SnmpMsg encodeSnmpPdu(SnmpPdu pdu, int maxPktSize)
       throws SnmpStatusException, SnmpTooBigException ;
       
     }


Note –

SnmpPduFactory has two additional methods inherited from Java DMK 4.2, decodePdu and encodePdu, that are now deprecated but are kept for backwards compatibility.


Java DMK 5.1 provides a default implementation of theSnmpPduFactory, called SnmpPduFactoryBER. SnmpPduFactoryBER is used automatically unless stated otherwise. The SnmpPduFactoryBER methods control every incoming or outgoing SNMP PDU.

Therefore, it is possible to implement a security policy using an SnmpPduFactory class. However, it is recommended to rely rather on the standard SNMPv3 policy. Using the SnmpPduFactory to implement additional levels of security only makes sense on an SNMPv1 or SNMPv2 framework, when SNMPv3 is not an option.

19.4.3 Implementing a New SnmpPduFactory Class

Java DMK expects decodeSnmpPdu to behave as follows:

Java DMK expects encodeSnmpPdu to behave as follows:

Because SnmpPdu and SnmpMsg are abstract classes, you should delegate their creation and initialization to an instance of SnmpPduFactoryBER and work on the result returned.

You can change the SnmpPduFactory object used by the SNMP adaptor by using the setPduFactory method, shown in Example 19–15.


Example 19–15 Changing the SnmpPduFactory object Using setPduFactory

		  ...
       myAdaptor.setPduFactory(new MyFireWallPduFactory()) ;
       ...

In Java DMK 4.2, the SnmpPduFactory was attached to the SnmpPeer object. In Java DMK 5.1, the SnmpPduFactory is attached to the SnmpSession. Factories set via the deprecated SnmpPeer API are reused in Java DMK 5.0. They can be changed using the setPduFactory method, as shown in Example 19–16.


Example 19–16 Updating Deprecated SnmpPeer Factories Using setPduFactory

...
SnmpSession mySession = new SnmpSession() ;
mySession.setPduFactory(new MyFireWallPduFactory()) ;
mySession.snmpGet(myPeer, this, myVarBindList) ;
...


Caution – Caution –

Setting two different factories in the peer and in the session can lead to unpredictable behavior. Use the same factory at both levels.


Chapter 20 SNMP Master Agent

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

This chapter covers the following topics:

20.1 SNMP Master Agent and the SNMPv3 Proxy Forwarder

The Java DMK 5.1 SNMP master agent is clearly distinct from the SNMPv3 proxy forwarder application as described in RFC 2573 SNMPv3 Applications, and must not be confused with it. The distinction is as follows:

The SNMP master agent functionality is thus particularly dedicated to the integration of legacy SNMP agents, in cases where the manager must see only a single entity. An example of such an SNMP master agent is snmpdx on the Solaris operating environment. The proxy forwarder application is more generally used as a firewall, to route information from a public node to agents running in a private network, or to add an additional security layer (for example, SNMPv3 security) to unsecured agents.

Both applications, master agent and proxy forwarder, share a common set of functions, for example, transferring information from one version of the SNMP protocol to another. However, their scope of application is different, and the problems they address are distinct. Java DMK 5.1 supports only the master agent, and does not support the proxy forwarder.

20.2 Overview of the SNMP Master Agent

The SNMP master agent function is available on the SnmpV3AdaptorServer. As the SnmpV3AdaptorServer is trilingual, a master agent can answer requests in SNMPv1, SNMPv2 or SNMPv3. The master agent function is based on the functional blocks of the Java DMK 5.1 SNMP toolkit, particularly:

20.2.1 SnmpV3AdaptorServer

The SnmpV3AdaptorServer makes it possible to register an SnmpMibAgent that handles all varbinds whose OIDs are contained below a given node of the global OID tree. For each SNMP request, all varbinds that are relevant to the part of the OID tree handled by the SnmpMibAgent are grouped into an SnmpMibRequest and passed to the SnmpMibAgent. The concrete SnmpMibAgent implementation can be either a local MIB generated by the mibgen tool, or a remote MIB, configured with an SnmpProxy object.

The SnmpV3AdaptorServer enables you to register overlapping MIBs. The actual value returned to the manager is the value implemented by the deepest MIB registered for that value. This makes it possible to register, for example, a default SnmpMibAgent to which all unhandled OIDs are forwarded. An example of such a default subagent can be the Solaris subagent snmpdx, all the OIDs of which are neither handled locally nor handled by known subagents. This makes it possible to forward them to an SnmpProxy object that forwards to snmpdx and is registered at the root of the OID tree. This only requires registering such an SnmpProxy object at the root of the OID tree.

The SnmpV3AdaptorServer supports MIB scoping, namely, context names, and makes it possible to register a MIB within a given scope, that is, for a given context name. The SnmpV3AdaptorServer also makes it possible to extract context names from an SNMPv1 or SNMPv2 community string. The master agent function can thus be spawned over several context names. A specific context name can be dedicated to a specific subagent, enabling you to register a single SnmpProxy object at the root of the OID tree for a given context name. The context name can also be shared between several subagents, and possibly local MIBs, as described above.

20.2.2 SnmpProxy

The SnmpProxy object makes it possible to get the actual values requested by the manager from a MIB implemented in a remote SNMP subagent. The SnmpProxy objects are able to get values from SNMP agents implementing either of the SNMPv1 or SNMPv2 protocols. This makes it possible to get values from legacy SNMP agents in a way that is transparent to the manager.

The SnmpProxy object extends the SnmpMibAgent abstract class, and as such can be registered in the SnmpV3AdaptorServer just like a local MIB generated by the mibgen tool. In a master agent application, one SnmpProxy must be created per subagent. When creating the SnmpProxy object, you must provide it with the following parameters to enable it to access the underlying subagent:

Then you must register that proxy with the SnmpV3AdaptorServer, once for each MIB implemented by the subagent that you want to mirror in the master agent. This is demonstrated in Example 20–1.


Example 20–1 Registering SnmpProxy in the SnmpV3AdaptorServer

A subagent implements the following three MIBs:

If you want mib#1 and mib#3 to be accessible from the master agent managers, then you need to register your SnmpProxy object for both mib#1 and mib#3:

SnmpV3AdaptorServer snmpAdaptor = ....;
SnmpProxy mySubAgentProxy = new SnmpProxy(....);
SnmpOid oids[] = new SnmpOid(2);
oids[0] = new SnmpOid("x.y.z.1");
oids[1] = new SnmpOid("x.y.z.3");
snmpAdaptor.addMib(mySubAgentProxy,oids);

Once you have done this, all varbinds contained in an SNMP request whose OIDs appear to be below x.y.z.1 or x.y.z.3 are passed to the registered mySubAgentProxy. The mySubAgentProxy proxy gets the actual values from the remote agent it is proxying. OIDs that appear to be contained below x.y.z.2 are not forwarded, and where applicable, are handled locally, because the SnmpProxy is not registered for those OIDs.


20.2.3 SnmpUsmProxy

The SnmpUsmProxy object performs the same function as the SnmpProxy, except that it gets values from SNMP agents implementing the SNMPv3 protocol.

The SnmpUsmProxy object extends the SnmpUsmMibAgent abstract class, and as such can be registered in the SnmpV3AdaptorServer just like a local MIB generated by the mibgen tool. In a master agent application, one SnmpUsmProxy must be created per subagent. When creating the SnmpUsmProxy object, you must provide it with the following parameters to enable it to access the underlying subagent:

The SnmpUsmProxy receives requests in any version of SNMP, and always forwards them as SNMPv3 requests.

20.2.4 SnmpTrapForwarder

An SnmpTrapForwarder object makes it possible to listen to traps emitted by a subagent, convert them into the master agent's manager's protocol or protocols, and forward them to the manager or managers as if they originated from the master agent itself. One or more SnmpTrapForwarder objects must be instantiated, and the subagents must be configured to send their traps to these objects. This is the responsibility of the main application code. This is explained in detail in 20.5 Trap Forwarding.

20.3 Routing Overlapping MIBs

In a master agent application, routing is performed depending on the OIDs requested. SNMP MIB agents are registered for branches of the global OID tree, and varbinds are dispatched to subagents according to the requested OIDs. The SnmpV3AdaptorServer supports registration of overlapping MIBs. Overlapping MIBs are MIBs that are registered below the OID of a previously registered MIB. The actual value returned to the manager is the value implemented by the deepest MIB registered for that value. When a get or set request is received, for each varbind in the request, the SnmpV3AdaptorServer identifies the deepest MIB responsible for handling the OID contained in the varbind. All varbinds targeted to that MIB are grouped in an SnmpMibRequest, and that request is passed to the identified SnmpMibAgent.

20.3.1 Shadowing Overlapping MIBs

MIB overlapping enables you to shadow MIBs that are implemented in subagents. For instance, you might want to implement the system group and snmp from MIB-II locally in your master agent, and delegate all other requests concerning MIB-II to a legacy SNMP agent implementing MIB-II. You do this as described in To Shadow Overlapping MIBs.

To Shadow Overlapping MIBs
  1. Run the mibgen tool to generate an implementation of MIB-II

  2. Instrument the system group and snmp group as shown in the basic SNMP agent example in “Creating an SNMP Agent”

  3. Register your generated MIB with the OIDs of both the system group, 1.3.6.1.2.1, and snmp group, 1.3.6.1.2.1.1.1

  4. Create an SnmpProxy object proxying your legacy MIB-II agent, and register that proxy for the root OID of MIB-II

20.3.2 Delegation and Precedence of Overlapping MIBs

MIB overlapping also enables you to delegate all unhandled OIDs to a legacy master agent, such as the Solaris agent snmpdx. If you register an SnmpProxy at the root of the OID tree, then all varbinds for whose OIDs no SnmpMibAgent was registered are passed to that SnmpProxy.

In addition, MIB overlapping enables you to give precedence to a specific agent for a part of a MIB, by registering it deeper in the OID tree.


Note –

For a get-next or get-bulk request, the incoming OID does not make it possible to identify directly the subagent that will contain the next OID. Indeed, the next OID might not be a sub-OID of the requested OID, but might be in a different part of the global OID tree. The get-next and get-bulk requests are thus forwarded to all subagents, and sorted a posteriori to identify the deepest matching subagent.


20.4 MIB Scoping in Master Agents

MIB scoping is a special case in which the SNMP proxy forwarder application can be emulated by a master agent application. If a subagent is dedicated completely to handling a specific MIB scope, you register a single SnmpProxy for that subagent at the root of the OID tree for that scope. Because there can be only one context per request, then all or none of the varbinds in the incoming request are passed to the SnmpProxy. In this case, the master agent application behaves similarly to a proxy forwarder application. The only difference is that it identifies the target agent with the context name contained in the request.

20.5 Trap Forwarding

Another function of an SNMP master agent is to listen to SNMP traps and notifications emitted by its subagents, and to forward them to its managers. The Java DMK 5.1 SNMP toolkit provides an SnmpTrapForwarder for that purpose.

How to configure the trap forwarding mechanism is entirely the responsibility of your main application code. The following are three possibilities for ways to configure your trap forwarding mechanism, but the list is by no means exhaustive.

The SnmpTrapForwarder handles protocol conversion. It is able to convert any of an SNMPv1 trap, or an SNMPv2 or SNMPv3 notification into any of an SNMPv1 trap or an SNMPv2 or SNMPv3 notification following the protocol conversion rules specified in RFC 2576.

20.5.1 Configuration of Trap Targets

You can pass specific manager addresses when forwarding for a specific protocol. By default the InetAddressAcl is also parsed. So the set of actual manager addresses is the trap blocks located in the acl file and the set of added targets. You can disable the InetAddressAcl parsing by calling the method setInetAddressAclUsed(false).

To enable trap forwarding you must start the trap forwarder.


Caution – Caution –

If you have manager addresses in your set of targets that are also present in InetAddressAcl, or you have no InetAddressAcl activated but are targeting the local host, your manager will receive the trap twice. To prevent this, configure the SnmpTrapForwarder carefully. You can, for example, disable the setInetAddressAcl parsing by calling isInetAddressAclUsed(false).


20.5.2 Proxy Forwarding and Notification Originators

By default a trap is sent as a notification originator. The difference between acting as a notification originator or acting as a proxy when forwarding the trap is detailed in RFC 2576 “Coexistence Between SNMP versions”

20.5.2.1 Proxy Forwarding

Proxy forwarding is activated by calling the forwardAsProxy() method.

When translating SNMPv1 traps into SNMPv2 traps, the varbind received is reused. Three additional varbinds are appended, if these three additional varbinds do not already exist in the SNMPv1 varbinds:

The SNMPv1 agent-addr parameter is determined by the proxy extracting the original source of the notification from the varbinds, If the SNMPv2 varbinds contain a varbind whose name is snmpTrapAddress.0, the agent-addr parameter is set to the value of that varbind. Otherwise, the SNMPv1 agent-addr parameter is set to 0.0.0.0.

20.5.2.2 Notification Originators

Traps are forwarded as notification originators by calling the forwardAsNotificationOriginator() method.

When translating from SNMPv1 to SNMPv2, the SNMPv2 varbinds are the same as the SNMPv1 varbinds.

When translating from SNMPv2 to SNMPv1, the SNMPv1 agent-addr parameter is determined as follow:

20.6 Protocol Translation

As explained in the previous section, both the SnmpProxy and SnmpTrapForwarder are compliant with RFC 2576. However, RFC 2576 does not simply describe protocol conversion, but rather describes how protocol conversion is performed in the scope of a proxy forwarding application. In a master agent application, the question is more complex because we want the master agent to behave as a single agent responding with the same protocol as is used by the manager. We also want to hide the presence of subagents from the manager.

When looking from an end-to-end perspective, the master agent does not strictly behave as described in RFC 2576. This is because the protocol translation applied by the SnmpProxy is post-processed and aggregated in the master agent with results coming from other SnmpMibAgents, such as SnmpProxy objects and mibgen generated local MIBs, each possibly translating from different protocol versions.

The main differences, when looking from an end to end perspective, between protocol translation in the SNMP master agent and RFC 2576 are the following:

20.6.1 SNMP Proxy Translation

The SnmpProxy object implements the following translation:

20.6.2 SNMP USM Proxy Translation

The SnmpUsmProxy object inherits from SnmpProxy. When a request is received, the translateParameters() method is called.

If the request is an SNMPv1 or SNMPv2 request, the security level is set to noAuthNoPriv. If the received community string is of the form community@context, the context name is set to the received context value. If it is in any other form it is null.

If the request is an SNMPv3 request, the received context, security level, and other values are reused.

20.6.3 Atomicity and Error Handling

As stated in the previous section, the atomicity of set requests is no longer guaranteed when remote MIBs are involved. Although some strategies exist that try to offer a best-effort regarding the atomicity of set requests, there is no generic mechanism that is guaranteed to work in a master agent application. The best that can be done in a generic toolkit is to identify those cases where atomicity might have been broken, and to inform the manager of that situation. Java DMK 5.1 handles this by responding with undoFailed when an error occurs during the set() phase of a set request. In its default configuration, when an SNMPv2 set request is received, Java DMK guarantees that undoFailed is sent when atomicity might have been broken. This no longer holds if the application code configures the SnmpProxy object to send the remote SNMP set request during the check() phase of the set operation.

Some toolkits attempt to implement atomicity by:

  1. Getting the current values of all variables included in the set.

  2. Performing the set.

  3. Reverting to the old values by sending a second set with the values obtained in 1 above, or the values obtained in 2 if no error is returned.

Although this might seem more satisfactory it is not guaranteed to work. Depending on the semantics of the variables involved in the set, several things might happen:

Even if no generic mechanism is supported, the Java DMK can still be customized to implement any specific behavior. The SnmpUserDataFactory makes it possible to allocate and release contextual data about requests, which can be used to implement transactional behavior. By subclassing SnmpProxy, and mibgen–generated metadata classes, any kind of specific transactional behavior can be implemented. However, no generic solution exists, and if transactional behavior can be implemented, it is specific to the semantics of the objects contained in the application and subagent MIBs.

Another special case is when a subagent is entirely responsible for a given context scope. In that case, the atomicity of set requests can still be achieved by performing the remote SNMP set during the check() phase. See 20.4 MIB Scoping in Master Agents for details.

The following tables show the end-to-end error translation performed by a master agent application.

Table 20–1 Error Translation from SNMPv1 Subagents to SNMPv2/v3 Managers

PDU Type 

Error From SNMPv1 Subagents 

Error Sent to SNMPv2/v3 Managers 

get

genErr

genErr

get

noSuchName

noError => noSuchInstance in varbind

get

tooBig

handled by stack =>resend or tooBig

get

any other error 

genErr

set

any error 

undoFailed(**)

get-next/get-bulk

genErr(*)

genErr

get-next/get-bulk

noSuchName

noError => skip to next SnmpMibAgent or endOfMibView if none

get-next/get-bulk

tooBig

handled by stack resend or tooBig cated response (get-bulk)

get-next/get-bulk

any other error(*)

genErr

(*) The SnmpProxy objects can be configured to hide such errors. In this case the master agent skips to the next SnmpMibAgent. This behavior can be very useful when dealing with faulty legacy agents.

(**) See Table 20–5.

Table 20–2 Error Translation from SNMPv2/v3 Subagents to v1 Managers

PDU Type 

Error From SNMPv2/v3 Subagents 

Error Sent to SNMPv1 Managers 

get

genErr

genErr

get

noError => noSuchInstance in varbind

noSuchName

get

noError => noSuchInstance in varbind

noSuchName

get

tooBig

handled by stack => resend or tooBig

get

any other error 

genErr

set

any error 

genErr (**) - undoFailed is translated into genErr

get-next

genErr

genErr

get-next

noError => endOfMibView in varbind

noSuchName

get-next

tooBig

handled by stack => resend or tooBig

get-next

any other error 

genErr

(**) See Table 20–5.

Table 20–3 Error Translation From SNMPv1 Subagents to SNMPv1 Managers

PDU Type 

Error From SNMPv1 Subagents 

Error Sent to SNMPv1 Managers 

get

genErr

genErr

get

noSuchName

noSuchName

get

tooBig

handled by stack => resend or tooBig

get

any other error 

genErr 

set

any error 

genErr (**) - undoFailed is translated into genErr

get-next

genErr

genErr

get-next

noSuchName

noSuchName

get-next

tooBig

handled by stack => resend or tooBig

get-next

any other error 

genErr

(**) See Table 20–5.

Table 20–4 Error Translation from SNMPv2/v3 Subagents to SNMPv2/v3 Managers

PDU Type 

Error From SNMPv2/v3 Subagents 

Error Sent to SNMPv2/v3 Managers 

get

noError

noError

get

tooBig

handled by stack => resend or tooBig

get

any other error 

same error (if valid) or genErr

set

any error 

undoFailed (**)

get-next/get-bulk

noError

noError

get-next/get-bulk

tooBig

handled by stack => resend or tooBig or truncated response (GET-BULK)

get-next/get-bulk

any other error 

same error (if valid) or genErr

(**) By default SnmpProxy sends the remote set request during the set() phase of the set operation. When an error occurs during the set() phase, undoFailed is returned to the manager because the atomicity is no longer guaranteed. Note that in the special case where an SnmpProxy is configured to perform the remote set request during the check() phase of the set operation, the following translation is applied for errors returned by the remote set request, even if the atomicity of the set request is broken, as shown in the following table.

Table 20–5 Error Translation When SnmpProxy Performs Remote set

Before Translation 

After Translation 

v1 errorStatus

v2/v3 errorStatus

noError

noError

noSuchName

noSuchName

genErr

genErr

badValue

wrongValue

readOnly

wrongValue

v2/v3 errorStatus

v1 errorStatus

noError

noError

genErr

genErr

wrongValue, wrongEncoding, wrongLength, wrongType, inconsistentValue

badValue

noAccess, notWritable, noCreation, inconsistentName, authorizationError

noSuchName

resourceUnavailable, commitFailed, undoFailed, any other error

genErr

v1 errorStatus

v1 errorStatus

any error 

same error (if valid); genErr otherwise

v2/v3 errorStatus

v2/v3 errorStatus

any error 

same error (if valid); genErr otherwise

v1 errorStatus

v2/v3 errorStatus

noError

noError

noSuchName

noSuchName

genErr

genErr

badValue

wrongValue

readOnly

wrongValue

v2/v3 errorStatus

v1 errorStatus

noError

noError

genErr

genErr

wrongValue, wrongEncoding, wrongLength, wrongType, inconsistentValue

badValue

noAccess, notWritable, noCreation, inconsistentName, authorizationError

noSuchName

resourceUnavailable, commitFailed, undoFailed, any other error

genErr

v1 errorStatus

v1 errorStatus

any error 

same error (if valid); genErr otherwise

v2/v3 errorStatus

v2/v3 errorStatus

any error 

same error (if valid); genErr otherwise

20.7 SNMP Master Agent Examples

These examples show you how to register local and remote MIBs within the master agent SNMP adaptor. They demonstrate how to instantiate and use the SnmpProxy and SnmpUsmProxy classes to register the remote MIBs.

In these examples, we demonstrate two different types of SNMP master agent that delegate handling of SNMP requests to subagents:

These examples also contain a manager which is used to send requests to the master agent. This manager operates in synchronous mode. Manager requests are sent both in SNMPv2 and SNMPv3.

Before going through this example, you must be familiar with the agent and manager examples described in Chapter 16, Creating an SNMP Agent and Chapter 17, Developing an SNMP Manager.

The following applications are used in these examples:

SimpleManager

A manager used to query the master agent. This class is in the examplesDir/current/Snmp/MasterAgent/manager directory. The SimpleManager application makes SNMP requests both in SNMPv3 and in SNMPv2 on a Java DMK master agent, and listens to forwarded traps.

StandAloneAgent

A Java DMK SNMPv1/v2 standalone subagent. This class is in the examplesDir/current/Snmp/MasterAgent/standalone directory. The StandAloneAgent application is a multi-protocol SNMP agent implementing a simple MIB containing four OIDs:

  • sysDescr1 value "error1"

  • sysDescr2 value "error2"

  • sysDescr3 value "sysDescr3"

  • sysDescr4 value "sysDescr4"

This simple MIB contains a single fake system group registered in the OID space of MIB-II.

MasterAgent

A simple SNMPv1/v2 master agent, configured with a single remote MIB implemented by a single subagent. This class is in the examplesDir/current/Snmp/MasterAgent/master directory. The MasterAgent application registers an SnmpProxy to forward requests to the remote MIB implemented by the StandAloneAgent, and forwards SNMPv1 and v2 traps to SNMPv1 and v2 managers.

MasterAgentV3

A simple SNMPv3 master agent, configured with a single remote MIB implemented by a single subagent. This class is in the examplesDir/current/Snmp/MasterAgent/master directory. The MasterAgentV3 application registers an SnmpUsmProxy to forward requests to the remote MIB implemented by the StandAloneAgent and forwards SNMPv1 and SNMPv2 traps to SNMPv3 managers.

MasterAgent

A simple master agent that deals with MIBs whose OID spaces overlap. This class is in the examplesDir/current/Snmp/MasterAgent/overlap directory. The MasterAgent application registers an SnmpProxy to forward requests to the remote MIB implemented by the StandAloneAgent subagent. It also locally implements a MIB whose variables sysDescr1 and sysDescr2 with values "sysDescr1", "sysDescr2" overlap with the variables defined in the subagent's MIB.

The SimpleManager is identical to a standard Java DMK SNMP manager. The StandAloneAgent is also identical to a standard Java DMK SNMP standalone agent. It is unaware of the existence of the master agent, unless it is configured to send traps.

There are, however, differences in the way each of the SNMPv1/v2 master agent and the SNMPv3 master agent creates a proxy, as shown in Example 20–2 and Example 20–3. Example 20–4 shows how the MIB overlapping is programmed.

20.7.1 Proxy Creation in SNMPv1 and SNMPv2 Master Agents

In the SNMPv1/v2 master agent example, proxy registration is identical to standard MIB registration. The proxies are created as shown in the following example.


Example 20–2 Proxy Creation in the MasterAgent Example

		[...]

		 // First we need to create a peer to the distant sub agent.
	    //
	    final SnmpPeer peer = 
		 new SnmpPeer(host, Integer.parseInt(port));
	    final SnmpParameters p = new SnmpParameters();

	    // The sub agent is seen as a SNMPv2.
	    //
	    p.setProtocolVersion(SnmpDefinitions.snmpVersionTwo);

	    //Set the parameters to the peer.
	    //
	    peer.setParams(p);
	    
	    // SnmpProxy creation.
	    //
	    final SnmpProxy proxy = new SnmpProxy(snmpAdaptor.getEngine(),
						  peer,"1.3.6.1.2.1");

	    // A SnmpProxy is also an MBean. Register it in the 
	    // MBeanServer.
	    //
	    proxyObjName= new ObjectName("snmp:class=SnmpProxy");
	    server.registerMBean(proxy, proxyObjName);
	    proxy.setSnmpAdaptor(snmpAdaptor);

[...]

To instantiate and register the proxy, the MasterAgent example first of all creates a peer to the subagent using new SnmpPeer(), and creates security parameters p using the SnmpParameters() method. It then sets the subagent as an SMNPv2 agent using setProtocolVersion.

The MasterAgent passes the parameters p to the new peer using peer.setParams. Finally, MasterAgent creates the proxy, passing it the following information:

20.7.2 Proxy Creation in SNMPv3 Master Agents

In the SNMPv3 master agent example, proxy registration is identical to standard MIB registration. The proxies are created as shown in the following example.


Example 20–3 Proxy Creation in the MasterAgentV3 Example

		[...]

		 // First we need to create a peer to the distant SNMPv3 sub 
	    // agent
	    final SnmpUsmPeer peer = 
		 new SnmpUsmPeer(snmpAdaptor.getEngine(),
	    host, Integer.parseInt(port));

	    // Forward the requests.
	    //
	    final SnmpUsmParameters p = 
		new SnmpUsmParameters(snmpAdaptor.getEngine(), 
				      "defaultUser");
	    p.setContextEngineId(peer.getEngineId().getBytes());
	    p.setSecurityLevel(SnmpDefinitions.authNoPriv);
	    peer.setParams(p);
	    peer.processUsmTimelinessDiscovery();
	    
	    final SnmpUsmProxy proxy = 
		 new SnmpUsmProxy(snmpAdaptor.getEngine(),
				 peer, "1.3.6.1.2.1");

	    // A SnmpUsmProxy is also an MBean. Register it in the 
	    // MBeanServer.
	    //
	    proxyObjName= new ObjectName("snmp:class=SnmpUsmProxy");
	    server.registerMBean(proxy, proxyObjName);
	    proxy.setSnmpAdaptor(snmpAdaptor);

		[...]

To instantiate and register the proxy, the MasterAgentV3 example first of all creates a peer to the subagent using new SnmpUsmPeer(), and creates SNMPv3 security parameters p using the SnmpUsmParameters() method. These new security parameters are then passed to the peer using peer.setParams, thus translating all requests that come through the peer into SNMPv3 requests.

Finally, MasterAgent creates the proxy, passing it the following information:

20.7.3 MIB Overlapping in Master Agents

In the overlapping MIB master agent example, proxy registration is identical to standard MIB registration and the proxies are created in the same way as for SNMPv1 and v2 master agents.


Example 20–4 Overlapping MIBs MasterAgent Example

		 // Instantiate a Mib
	    //
	    final MIB1_MIB mib1 = new MIB1_MIB();
	    
	    // Register the local MIB1 implementation for sysDescr1 
	    // and sysDescr2.
	    //
	    final SnmpOid oid1 = new SnmpOid("1.3.6.1.2.1.1.1");
	    final SnmpOid oid2 = new SnmpOid("1.3.6.1.2.1.1.2");

	    // Make an array of these OIDs.
	    //
	    final SnmpOid[] local_oids = {oid1, oid2};

	    // Add the local MIB1 implementation for each redefined oid
	    //
	    snmpAdaptor.addMib(mib1, local_oids);
	    
	    // Initialize the mib
	    //
	    mib1.init();

The overlapping MIB MasterAgent example creates a new MIB using new MIB1_MIB. It then registers the local MIB1 implementation for sysDescr1 and sysDescr2, which are configured into the StandAloneAgent example as error1 and error2, respectively. The local MIB thus shadows the implementation of sysDescr1 and sysDescr2 instrumented in the remote MIB, because it is registered with deeper OIDs than the remote MIB.

The MIBs are then added to the SNMP adaptor server using snmpAdaptor.addMib and initialized using init().

20.7.4 Running the SNMP Master Agent Examples

Before running the example applications, compile all the Java classes in each of the four subdirectories inside examplesDir/current/Snmp/MasterAgent, by typing the following command in each directory.


$ javac -classpath classpath -d . *.java

The following procedures give instructions to run five different SNMP master agent scenarios:

Ensure that no agents are already running before you start the examples.

To Send Requests From a Manager to the SNMPv2 Master Agent

Because there are no MIBs overlapping in this first example, all the variables implemented by the subagent MIB can be seen from the manager. Thus, you can see that some of the OIDs queried return a DisplayString value of error#.

  1. Start the StandAloneAgent subagent.

    In examplesDir/current/Snmp/MasterAgent/standalone, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    StandAloneAgent 8085
    

    This binds StandAloneAgent to port 8085. Do not send traps when prompted.

  2. Start the SNMPv2 MasterAgent application.

    You need to provide MasterAgent with the following information:

    • The location of its security configuration file, jdmk.security

    • The port on which it should listen for incoming requests, in this case we choose 8087

    • The subagent's host, in this case the local host

    • The subagent's port number, in this case 8085

    In examplesDir/current/Snmp/MasterAgent/master, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    MasterAgent 8087 localhost 8085
    

    The following output is displayed:


    NOTE: HTML adaptor is bound on TCP port 8082
    NOTE: SNMP Adaptor is bound on UDP port 8087
    The master agent forward traps on port : 8088
    
    >> Press Enter if you want to stop.
    
  3. Start the SimpleManager application.

    You need to provide SimpleManager with the following information:

    • The location of its security configuration file, jdmk.security

    • The master agent's host, in this case the local host

    • The port on which the master agent is listening for request, in this case 8087

    In examplesDir/current/Snmp/MasterAgent/manager, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    SimpleManager localhost 8087
    >> Press Enter if you want to send a SNMPv3 request.
    
  4. Press Enter to send an SNMPv3 request

    The following output is displayed:


    SimpleManager::main: Send SNMPv3 get request to SNMPv3 agent  
    on localhost at port 8087
    Result: 
    [Object ID : 1.3.6.1.2.1.1.1.0  (Syntax : String)
    Value : error1, Object ID : 1.3.6.1.2.1.1.2.0  (Syntax : String)
    Value : error2, Object ID : 1.3.6.1.2.1.1.3.0  (Syntax : String)
    Value : sysDescr3, Object ID : 1.3.6.1.2.1.1.4.0  (Syntax : String)
    Value : sysDescr4]
    
    >> Press Enter if you want to send a SNMPv2 request.
    
  5. Press Enter to send an SNMPv2 request

    The following output is displayed:


    SimpleManager::main: Send SNMPv2 get request to SNMP agent 
    on localhost at port 8087
    Result: 
    [Object ID : 1.3.6.1.2.1.1.1.0  (Syntax : String)
    Value : error1, Object ID : 1.3.6.1.2.1.1.2.0  (Syntax : String)
    Value : error2, Object ID : 1.3.6.1.2.1.1.3.0  (Syntax : String)
    Value : sysDescr3, Object ID : 1.3.6.1.2.1.1.4.0  (Syntax : String)
    Value : sysDescr4]
    $
    

    The manager has successfully sent the two requests to the subagent, via the master agent.

To Send Requests From a Manager to the SNMPv3 Master Agent

Because there is no MIB overlapping in this second example, all the variables implemented by the subagent MIB can be seen from the manager. Thus, you see that some of the OIDs queried return a DisplayString value of error#.

  1. Start the StandAloneAgent subagent.

    In examplesDir/current/Snmp/MasterAgent/standalone, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    StandAloneAgent 8085
    

    This binds StandAloneAgent to port 8085. Do not send traps when prompted.

  2. Start the SNMPv3 MasterAgentV3 application.

    You need to provide MasterAgentV3 with the following information:

    • The location of its security configuration file, jdmk.security

    • The port on which it should listen for incoming requests, in this case we choose 8087

    • The subagent's host, in this case the local host

    • The subagent's port number, in this case 8085

    • The manager's host, in this case the local host

    In examplesDir/current/Snmp/MasterAgent/master, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    MasterAgentV3 8087 localhost 8085 localhost
    

    The following output is displayed:


    NOTE: HTML adaptor is bound on TCP port 8082
    NOTE: SNMP Adaptor is bound on UDP port 8087
    
    >> Press Enter if you want to stop.
    
  3. Start the SimpleManager application.

    You need to provide SimpleManager with the following information:

    • The location of its security configuration file, jdmk.security

    • The master agent's host, in this case the local host

    • The port on which the master agent is listening for requests, in this case 8087

    In examplesDir/current/Snmp/MasterAgent/manager, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    SimpleManager localhost 8087
    >> Press Enter if you want to send a SNMPv3 request.
    
  4. Press Enter to send an SNMPv3 request

    The following output is displayed:


    SimpleManager::main: Send SNMPv3 get request to SNMPv3 agent 
    on localhost at port 8087
    Result: 
    [Object ID : 1.3.6.1.2.1.1.1.0  (Syntax : String)
    Value : error1, Object ID : 1.3.6.1.2.1.1.2.0  (Syntax : String)
    Value : error2, Object ID : 1.3.6.1.2.1.1.3.0  (Syntax : String)
    Value : sysDescr3, Object ID : 1.3.6.1.2.1.1.4.0  (Syntax : String)
    Value : sysDescr4]
    
    >> Press Enter if you want to send a SNMPv2 request.
    
  5. Press Enter to send an SNMPv2 request

    The following output is displayed:


    SimpleManager::main: Send SNMPv2 get request to SNMP agent 
    on localhost at port 8087
    Result: 
    [Object ID : 1.3.6.1.2.1.1.1.0  (Syntax : String)
    Value : error1, Object ID : 1.3.6.1.2.1.1.2.0  (Syntax : String)
    Value : error2, Object ID : 1.3.6.1.2.1.1.3.0  (Syntax : String)
    Value : sysDescr3, Object ID : 1.3.6.1.2.1.1.4.0  (Syntax : String)
    Value : sysDescr4]
    $
    

    The manager has successfully sent the two requests to the subagent, via the SNMPv3 master agent.

To Send Requests From a Manager to a Master Agent With Overlapping MIBs

This example is very similar to To Send Requests From a Manager to the SNMPv2 Master Agent, except that the master agent now implements a local MIB which partly shadows the MIB implemented remotely by the agent.

Because the local MIB now shadows the variables for which the value was error#, these pseudo error messages no longer appear in the result displayed.

  1. Start the StandAloneAgent subagent.

    In examplesDir/current/Snmp/MasterAgent/standalone, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    StandAloneAgent 8085
    

    This binds StandAloneAgent to port 8085. Do not send traps when prompted.

  2. Start the overlap MasterAgent application.

    You need to provide MasterAgent with the following information:

    • The location of its security configuration file, jdmk.security

    • The port on which it should listen for incoming requests, in this case we choose 8087

    • The subagent's host, in this case the local host

    • The subagent's port number, in this case 8085

    In examplesDir/current/Snmp/MasterAgent/overlap, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    MasterAgent 8087 localhost 8085
    

    The following output is displayed:


    NOTE: HTML adaptor is bound on TCP port 8082
    NOTE: SNMP Adaptor is bound on UDP port 8087
    
    >> Press Enter if you want to stop.
    
  3. Start the SimpleManager application.

    You need to provide SimpleManager with the following information:

    • The location of its security configuration file, jdmk.security

    • The master agent's host, in this case the local host

    • The port on which the master agent is listening for requests, in this case 8087

    In examplesDir/current/Snmp/MasterAgent/manager, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    SimpleManager localhost 8087
    >> Press Enter if you want to send a SNMPv3 request.
    
  4. Press Enter to send an SNMPv3 request

    The following output is displayed:


    SimpleManager::main: Send SNMPv3 get request to SNMPv3 
    agent on localhost at port 8087
    Result: 
    [Object ID : 1.3.6.1.2.1.1.1.0  (Syntax : String)
    Value : sysDescr1, Object ID : 1.3.6.1.2.1.1.2.0  (Syntax : String)
    Value : sysDescr2, Object ID : 1.3.6.1.2.1.1.3.0  (Syntax : String)
    Value : sysDescr3, Object ID : 1.3.6.1.2.1.1.4.0  (Syntax : String)
    Value : sysDescr4]
    
    >> Press Enter if you want to send a SNMPv2 request.
    
  5. Press Enter to send an SNMPv2 request

    The following output is displayed:


    SimpleManager::main: Send SNMPv2 get request to SNMP agent 
    on localhost at port 8087
    Result: 
    [Object ID : 1.3.6.1.2.1.1.1.0  (Syntax : String)
    Value : sysDescr1, Object ID : 1.3.6.1.2.1.1.2.0  (Syntax : String)
    Value : sysDescr2, Object ID : 1.3.6.1.2.1.1.3.0  (Syntax : String)
    Value : sysDescr3, Object ID : 1.3.6.1.2.1.1.4.0  (Syntax : String)
    Value : sysDescr4]
    $
    

    The manager has successfully sent the two requests to the subagent, via the overlap master agent. As you can see, the error# values have been replaced by the variables sysDescr1 and sysDescr2, which were configured in the master agent to overlap with the variables in the subagent's MIB.

To Receive Forwarded SNMPv1/v2 Traps in the Manager

In this scenario, an SNMPv1 trap or a v2 trap is sent by the subagent. The master agent forwards the trap as is and sends a translated trap. The version translation from v1 to v2, or from v2 to v1 is performed by the master agent. The manager therefore receives two traps, namely, the initial trap and the translated trap.

  1. Start the StandAloneAgent subagent.

    In examplesDir/current/Snmp/MasterAgent/standalone, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    StandAloneAgent 8085
    

    The following output is displayed:


    NOTE: SNMP Adaptor is bound on UDP port 8085
    Press Return if you want to send trap
    
  2. Press Return to activate stand alone traps

    The following output is displayed:


    Press crtl-c in order to kill the agent
    
    Type 1 in order to launch snmp V1 traps, 2 for snmp V2.
    

    Do not send any traps yet, but leave the StandAloneAgent active.

  3. Start the SNMPv2 MasterAgent application.

    You need to provide MasterAgent with the following information:

    • The location of its security configuration file, jdmk.security

    • The port on which it should listen for incoming requests, in this case we choose 8087

    • The subagent's host, in this case the local host

    • The subagent's port number, in this case 8085

    In examplesDir/current/Snmp/MasterAgent/master, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    MasterAgent 8087 localhost 8085
    

    The following output is displayed:


    NOTE: HTML adaptor is bound on TCP port 8082
    NOTE: SNMP Adaptor is bound on UDP port 8087
    The master agent forward traps on port : 8088
    
    >> Press Enter if you want to stop.
    
  4. Start the SimpleManager application.

    You need to provide SimpleManager with the following information:

    • The location of its security configuration file, jdmk.security

    • The master agent's host, which in this example must be the local host

    • The port on which the master agent is listening for requests, in this case 8087

    In examplesDir/current/Snmp/MasterAgent/manager, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    SimpleManager localhost 8087
    >> Press Enter if you want to send a SNMPv3 request.
    

    Do not send a request this time.

  5. Go back to examplesDir/current/Snmp/MasterAgent/standalone

    The StandAloneAgent should still be waiting to send traps.

  6. Type either 1 or 2 to send SNMPv1 or SNMPv2 traps

    The following output is displayed:


    1
    V1 TRAP to send
    
    Trap V1 sent!
    
    Type 1 in order to launch snmp V1 traps, 2 for snmp V2.
    2
    V2 TRAP to send
    
    Trap V2 sent!
    
    Type 1 in order to launch snmp V1 traps, 2 for snmp V2.
    
  7. Go back to examplesDir/current/Snmp/MasterAgent/manager

    Notice that the SimpleManager application has received SNMPv1 and v2 traps from the StandAloneAgent. The following output from SimpleManager is displayed:


    NOTE: TrapListenerImpl received SNMPv1 trap :
            Generic 0
            Specific 0
            TimeStamp 104549
            Agent adress 129.157.203.98
    NOTE: TrapListenerImpl received SNMPv2 trap:
            CommunityString : 
            VarBind list :
    oid : 1.3.6.1.2.1.1.3.0 val : 0:17:25
    oid : 1.3.6.1.6.3.1.1.4.1.0 val : 1.3.6.1.6.3.1.1.5.1
    oid : 1.2.3.4.5.6.0.0 val : Test values
    oid : 1.2.3.4.5.6.1.0 val : NULL
    oid : 1.2.3.4.5.6.2.0 val : 43
    oid : 1.2.3.4.5.6.3.0 val : Test values
    oid : 1.3.6.1.6.3.18.1.3.0 val : 129.157.203.98
    oid : 1.3.6.1.6.3.18.1.3.0 val : 129.157.203.98
    oid : 1.3.6.1.6.3.18.1.4.0 val : 
    oid : 1.3.6.1.6.3.1.1.4.3.0 val : 1.3.6.1.4.1.42
    
  8. Stop the StandAloneAgent and the SimpleManager by pressing Control-C, and stop the MasterAgent by pressing Enter.

To Receive Forwarded SNMPv3 Traps in the Manager

In this scenario an SNMPv1 or SNMPv2 trap is sent by the subagent. The master agent translates the trap into an SNMP v3 trap and sends it to the manager.

  1. Start the StandAloneAgent subagent.

    In examplesDir/current/Snmp/MasterAgent/standalone, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    StandAloneAgent 8085
    

    The following output is displayed:


    NOTE: SNMP Adaptor is bound on UDP port 8085
    Press Return if you want to send trap
    
  2. Press Return to activate stand alone traps

    The following output is displayed:


    Press crtl-c in order to kill the agent
    
    Type 1 in order to launch snmp V1 traps, 2 for snmp V2.
    

    Do not send any traps yet, but leave the StandAloneAgent active.

  3. Start the SNMPv3 MasterAgentV3 application.

    You need to provide MasterAgentV3 with the following information:

    • The location of its security configuration file, jdmk.security

    • The port on which it should listen for incoming requests, in this case we choose 8087

    • The subagent's host, in this case the local host

    • The subagent's port number, in this case 8085

    • The manager's host, in this case the local host

    In examplesDir/current/Snmp/MasterAgent/master, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    MasterAgentV3 8087 localhost 8085 localhost
    

    The following output is displayed:


    NOTE: HTML adaptor is bound on TCP port 8082
    NOTE: SNMP Adaptor is bound on UDP port 8087
    The master agent forward traps on port : 8088
    
    >> Press Enter if you want to stop.
    
  4. Start the SimpleManager application.

    You need to provide SimpleManager with the following information:

    • The location of its security configuration file, jdmk.security

    • The master agent's host, in this case the local host

    • The port on which the master agent is listening for requests, in this case 8087

    In examplesDir/current/Snmp/MasterAgent/manager, type the following command:


    $ java -classpath classpath -Djdmk.security.file=jdmk.security 
    SimpleManager localhost 8087
    >> Press Enter if you want to send a SNMPv3 request.
    

    Do not send a request this time.

  5. Go back to examplesDir/current/Snmp/MasterAgent/standalone

    The StandAloneAgent should still be waiting to send traps.

  6. Type either 1 or 2 to send SNMPv1 or SNMPv2 traps

    The following output is displayed:


    1
    V1 TRAP to send
    
    Trap V1 sent!
    
    Type 1 in order to launch snmp V1 traps, 2 for snmp V2.
    2
    V2 TRAP to send
    
    Trap V2 sent!
    
    Type 1 in order to launch snmp V1 traps, 2 for snmp V2.
    
  7. Go back to examplesDir/current/Snmp/MasterAgent/manager

    Notice that the SimpleManager application has received SNMPv3 traps from the StandAloneAgent, via MasterAgentV3. The following output from SimpleManager is displayed:


    NOTE: TrapListenerImpl received trap V3:
            ContextEngineId : 0x8000002a05819dcb6e00001f95
            ContextName : TEST-CONTEXT
            VarBind list :
    oid : 1.3.6.1.2.1.1.3.0 val : 0:5:24
    oid : 1.3.6.1.6.3.1.1.4.1.0 val : 1.3.6.1.6.3.1.1.5.1
    oid : 1.2.3.4.5.6.0.0 val : Test values
    oid : 1.2.3.4.5.6.1.0 val : NULL
    oid : 1.2.3.4.5.6.2.0 val : 43
    oid : 1.2.3.4.5.6.3.0 val : Test values
    oid : 1.3.6.1.6.3.18.1.3.0 val : 129.157.203.98
    oid : 1.3.6.1.6.3.18.1.3.0 val : 129.157.203.98
    oid : 1.3.6.1.6.3.18.1.4.0 val : 
    oid : 1.3.6.1.6.3.1.1.4.3.0 val : 1.3.6.1.4.1.42
    
  8. Stop the StandAloneAgent and the SimpleManager by pressing Control-C, and stop the MasterAgentV3 by pressing Enter