Java Dynamic Management Kit 5.0 Tutorial

Chapter 18 Creating an SNMP Agent

Using the Java Dynamic Management Kit (DMK), you can create an agent application that is both an SNMP agent and a normal JMX agent. SNMP MIBs can be represented as MBeans and the SNMP protocol adaptor exposes them to SNMP managers. Only MBeans derived from MIBs 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.0.

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

This chapter covers the following topics:

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.0 Tools Reference.

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.

Generating MIB MBeans

To run the mibgen tool for our example, go to the examplesDir/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.

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.

Compiling the MBeans and Agents

Compile all the classes in the examplesDir/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.

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.0, 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 18–1 SNMPv1/v2 Agent Application

public class Agent {

    static SnmpAdaptorServer snmpAdaptor = null;

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

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

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

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

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

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

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

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

            [...]

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

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

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 18–2 SNMPv3 AgentV3 Application

public class AgentV3 {

    static SnmpV3AdaptorServer snmpAdaptor = null;
    
    /**
     * This variable defines the number of traps this agent have to send.
     * If not specified in the command line arguments, the traps will be 
     * sent continuously.
     */
    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);
            Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MISC, "Agent", "main", 
                       "Adding HTML adaptor to MBean server with name \n\t"+
		       htmlObjName);
            println("NOTE: HTML adaptor is bound on TCP port " + htmlPort);
            HtmlAdaptorServer htmlAdaptor = new HtmlAdaptorServer(htmlPort);
            server.registerMBean(htmlAdaptor, htmlObjName);
            htmlAdaptor.start();
                  
            // Create and start the SNMP adaptor.
            //     snmpPort = 8085;
            //
            snmpPort = 8085;
            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 
         	mib2.setSnmpAdaptorName(snmpObjName, "TEST-CONTEXT");

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

    public static SnmpAdaptorServer getSnmpAdaptor() {
       return snmpAdaptor;
    }

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


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

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 set the SnmpAdaptorName attribute of its main MBean to bind it to the SNMP adaptor. You can either call its setSnmpAdaptorName method directly or, if the MIB's MBean was registered in the MBean server, another management application can set the attribute through the MBean's exposed interface.

In the binding process, the SNMP adaptor obtains the root OID of the MIB. The adaptor uses this OID to determine which variables are implemented in the MIB's corresponding MBeans.

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.

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 SNM v3 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 SNM v1 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.setSnmpAdaptorName(myAdaptorName, "myContext")

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

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.

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 Enabling User-Based Access Control).

To Run the SNMPv1/v2 Agent Example
  1. After building the example as described in 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 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.

    For any SNMP agent application, you can turn on trace messages for the SNMP adaptor by specifying the -DINFO_ADAPTOR_SNMP property on the command line. The tracing mechanism is described in the Java Dynamic Management Kit 5.0 Tools Reference and in the Javadoc API of the com.sun.jdmk.TraceManager class (for receiving traces) and the com.sun.jdmk.trace.Trace class (for producing traces).

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

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 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/Snmp/Agent directory. These files are used by the examples in the subsequent sections of this chapter.


Example 18–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 20, 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 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 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.

    For any SNMP agent application, you can turn on trace messages for the SNMP adaptor by specifying the -DINFO_ADAPTOR_SNMP property on the command line. The tracing mechanism is covered in the Java Dynamic Management Kit 5.0 Tools Reference guide and in the Javadoc API of the com.sun.jdmk.TraceManager class (for receiving traces) and the com.sun.jdmk.trace.Trace class (for producing traces).

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

Sending Traps

Agents can send unsolicited event reports to management application 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 Inform Requests. Agent-to-manager inform requests are demonstrated by the applications in the 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 18–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 18–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 18–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();

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.

Using an InetAddressAcl Trap Group

The methods below were used in Example 18–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 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 Custom Access Control.

In this example, we provide the following template file:


Example 18–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.

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

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 have two options for starting agents, as shown in the following commands. You can start the example SNMPv3 agent by replacing Agent with AgentV3.

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 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 that 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 the Java DMK.

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 18–8 StandAloneSnmpAgent Example

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

public class StandAloneSnmpAgent {

    static SnmpAdaptorServer snmpAdaptor = null;

    public static void main(String args[]) {

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

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

            // Start the adaptor
            snmpAdaptor.start();

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

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

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

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

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

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

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

As this example demonstrates, the 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 18–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.

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/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 Enabling User-Based Access Control.

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

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 18–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);
            Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MISC, "Agent", "main", 
                       "Adding SNMP adaptor to MBean server with name \n\t"+
		       snmpObjName1);
            println("NOTE: SNMP Adaptor is bound on UDP 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");
            Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MISC, "Agent", "main", 
                       "Adding RFC1213-MIB to MBean server with name \n\t" +
                       mibObjName1);
	    
	    		 mibObjName2 = new ObjectName("snmp:class=RFC1213_MIB_2");
            Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MISC, "Agent", "main", 
                       "Adding RFC1213-MIB to MBean server with name \n\t" +
                       mibObjName2);

            // 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.setSnmpAdaptorName(snmpObjName1, "TEST-CONTEXT");
	    		mib2_2.setSnmpAdaptorName(snmpObjName2, "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/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.


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: