Java Dynamic Management Kit 4.0 Tutorial

Chapter 7 Creating an SNMP Agent

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

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

The SNMP protocol adaptor interacts with your customized MIB MBeans to implement a compatible SNMPv1 and SNMPv2 agent. It also provides the mechanisms for sending traps and implementing both community-based access control and message-level data security.

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

Contents:

MIB Development Process

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

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

Generating MIB MBeans

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


$ mibgen -d . mib_II_subset.txt

This will generate the following files in the current directory:

The MBean with the name of the MIB is just a central administrative class for managing the other MBeans which implement the MIB. 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 attributes do not do anything. You must implement the getters and setters of the attributes to read and write data with the correct semantic meaning of the SNMP variable.

Since SNMP does not support actions in MIBs, the only operations in these MBeans are checkers for implementing the SNMP set request in writeable variable. You may add operations and expose them in the MBean interface, but the SNMP manager will not be able to access them. However, other managers will be able to call these operations if they are connected through another protocol.

Implementing the MIB

Our example only implements a fraction of the attributes, those that are used in this tutorial. The others are just initialized with some plausible value. These implementations are contained in the classes with the Impl suffix. These implementation classes extend those that are generated by mibgen so that we can regenerate them without overwriting our customizations.

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

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

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

Compiling the MBeans and Agents

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


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

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

The SNMP Protocol Adaptor

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


Example 7-1 The SNMP Agent Application

public class Agent {

    static SnmpAdaptorServer snmpAdaptor = null;

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

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

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

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

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

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

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

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

            [...]

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

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

Launching the SNMP Adaptor

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

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


Note -

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


Creating MIB MBeans

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

The registration process lets the MBean instantiate and register other MBeans that represent the groups of the MIB and the entries of its tables. The set of all these MBeans at the end of registration make up the MBean representation of the MIB.

If you do not wish to expose a MIB through the MBean server, you do not have to register it. However, you still need to create all of its other constituent objects. The generated code provides the init method in the main MBean of the MIB. Calling this method will create all necessary MBean objects without registering them in the MBean server.

Binding the MIB MBeans

Because the SNMP data model relies on MIBs, only MBeans representing MIBs can be managed through SNMP. The main MBean of a MIB must be explicitly bound to the instance of the SNMP adaptor. The SNMP adaptor does not interact with any other MBeans, nor with the MBean server.

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

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

Even though the SNMP adaptor may be registered in the MBean server, the adaptor only makes MIBs visible to SNMP managers. Other MBeans in the agent cannot be accessed or even represented in the SNMP protocol. The SNMP manager is limited by its protocol: it cannot take full advantage of a Java Dynamic Management agent through the basic MIBs, and it does not have access to any other MBeans.

Accessing a MIB MBean

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

The advantage of having an SNMP agent "inside" a Java Dynamic Management agent is that you can use the other communications protocols to interact with MIBs and manage the SNMP adaptor. Since both the registered MIBs and the adaptor are MBeans, they are exposed for management. In our simple agent, the MIB was registered, and you can view its MBeans in a web browser through the HTML protocol adaptor. Furthermore, the MIB implementations in the SNMP agent could be easily upgraded using the m-let loader service (see Chapter 6, The M-Let Class Loader).

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

Non-SNMP managers may operate on the variables of a MIB, getting and setting values regardless of any SNMP manager that might also be accessing them through the SNMP adaptor. The designer of the agent and management applications is responsible for all coherence issues when accessing MIBs concurrently through different protocols.

Managing the SNMP Adaptor

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

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

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

Running the SNMP Agent Example

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


$ java -classpath classpath Agent

You should see some initialization messages, including our notification listener giving information about the two table entries which are created. Then, you should see the agent sending out a trap every two seconds. Traps are covered in "Sending Traps", and we can ignore these messages. Access this agent's HTML adaptor by pointing a web browser to the following URL: http://localhost:8082/. Through the HTML adaptor, you can see the MBeans representing the MIB:

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

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

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

Sending Traps

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

The IfEntryImpl.java file provided in the example directory contains the code for sending the trap through the SNMP adaptor.


Example 7-2 Sending a Trap in the IfEntryImpl Class

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

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

    SnmpAdaptorServer snmpAdaptor = null;

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

    Vector varBindList = new Vector();

    // IfIndex is the index of the current If table entry
    SnmpOid oid1 = new SnmpOid("1.3.6.1.2.1.2.2.1.1." + IfIndex);
    SnmpInt value1 = new SnmpInt(IfIndex);
    SnmpVarBind varBind1 = new SnmpVarBind(oid1, (SnmpValue) value1);

    varBindList.addElement(varBind1);

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

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

Since our example has no real operation status, we invent the LinkTrapGenerator class which will switch the status periodically. It is an MBean which contains a thread which loops endlessly. The interval period between traps and the index of the table entry can be modified through the MBean's attributes.


Example 7-3 The Thread of the Link Trap Generator

public void run() {
    while (true) {
        try {
            sleep(interval);
        } catch (Exception e) {
            e.printStackTrace();
        }
        sendTrap();
    }
}

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

To start the trap generator, the example application creates a LinkTrapGenerator MBean. During its registration, the MBean starts the thread, sending a trap every two seconds by default.


Example 7-4 Starting the Trap Generator Example

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

In order for an SNMP adaptor to send traps to remote managers, you must define the trap group in the access control list (ACL) file. This group of community definitions lists all of the hosts to which the agent will send every trap. A community definition associates a community name with a list of hosts specified either by their hostname or by their IP address. All hosts in a community will receive the trap in a packet identified by the community name.


Note -

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


See "Access Control Lists (ACL)" for a formal definition of the trap group and instructions for defining the ACL file when starting the agent. In this example we provide the following template:


Example 7-5 Sample jdmk.acl File

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

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

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

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

Traps in the Agent Example

Before launching the SNMP agent again, edit the jdmk.acl file to replace the occurrences of yourmanager with the name of a host running an SNMP manager. Then copy this file to the configuration directory:


$ cp jdmk.acl installDir/SUNWjdmk/jdmk4.0/JDKversion/etc/conf

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

Then launch the simple agent example again:


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

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

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

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

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

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

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

  5. Type "Control-C" when you are done interacting with the agent.

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

Access Control Lists (ACL)

For the SNMP adaptor, the Java Dynamic Management Kit provides access control based on the IP address and community of the manager's host machine. Information on the access rights for communities and host machines is stored in an ACL file.

The ACL file also defines the hosts of managers to which to agent will send traps. When a trap is sent, the agent will send it to all hosts listed in the trap definitions of the ACL file.

To enable access control and traps for the SNMP adaptor, ensure that an ACL file exists when any agents are started. The ACL file must be named jdmk.acl and must be located in the configuration directory.

Operating Environment 

Configuration Directory 

Solaris 

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

Windows NT 

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

Alternatively, you may specify a different file by setting the jdmk.acl.file property when launching your agent. For example, if the full pathname of your ACL file is MyAclFile, use this command to launch the agent with access control enabled:


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

If an ACL file exists, the access rights it defines apply to all managers or proxy servers that access the agent through its SNMP adaptor. If the ACL file does not exist when the agents are started, all managers are granted full access to the agent through the SNMP adaptor.

ACL File Format

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

Format of the acl Group

The acl group contains one or more lists of community configurations.

acl = {
   list1
   list2
     ...
   listN
}

Each list 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 running on the machines specified in the managers item. There are two possible values: either read-write or read-only.

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


Note -

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


Format of the trap Group

The trap group specifies the hosts to which the agent can send traps. This group contains a one or more trap community definitions.

trap = {
   community1
   community2
   ...
   communityN
}

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

{
   trap-community = trapCommunityString
   hosts = trapInterestHostList
}

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

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

Custom Access Control

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

Your access mechanism must be a class that implements the IPAcl interface. This interface specifies the methods that the SNMP adaptor uses to check permissions when processing a request. If you instantiate the SNMP adaptor with your access control class, the adaptor will call your implementation of the access control methods.

The JdmkAcl class implements the default access mechanism that uses an ACL file. It is also an implementation of the IPAcl interface. By default, the SNMP adaptor will use this implementation if no other is passed to its constructor.

Message-Level Security

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

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

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

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

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

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

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

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

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

Implementing the SnmpPduFactory Interface

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

In our example, the SnmpPduFactoryImpl class implements these methods and rejects messages if they originate from certain hosts. The list of hosts to refuse is passed to the class constructor at instantiation.


Example 7-6

public class SnmpPduFactoryImpl implements SnmpPduFactory {

    private String[] hostNames;

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

    public SnmpPduPacket decodePdu(SnmpMessage msg)
        throws SnmpStatusException {

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

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

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

Using a Custom PDU Factory

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

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

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

The secure agent does not use the trap generator and does not demonstrate traps. The LinkTrapGenerator class is not written to function with the SecureAgent class.

Running the Secure Agent Example

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

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


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

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

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

Stand-Alone SNMP Agents

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

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


Example 7-7 The StandAloneSnmpAgent Example

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

public class StandAloneSnmpAgent {

    static SnmpAdaptorServer snmpAdaptor = null;

    public static void main(String args[]) {

        // enable tracing
        [...]

        try {
            // The agent is started on a non standard SNMP port: 8085
            int port = 8085;
            snmpAdaptor = new SnmpAdaptorServer(port);
            java.lang.System.out.println(
                "NOTE: SNMP Adaptor is bound on port " + port);

            // Start the adaptor
            snmpAdaptor.start();

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

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

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

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

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

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

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

As this example demonstrates, the stand-alone agent uses exactly the same MIB MBeans, with the same customization, as our other agents. However, instead of registering them in 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.

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

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


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

public class RFC1213_MIB extends SnmpMib implements Serializable {

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

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

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

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

        isInitialized = true;
    }

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

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

        // Initialize MBeanServer information
        this.server = server;

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

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

        isInitialized = true;
        return name;
    }

    private boolean isInitialized = false;

}

After the MIB is initialized, it only needs to be bound to the SNMP adaptor, as in the other agents. That is all you need to do when programing a stand-alone SNMP agent.

Running the Stand-Alone Agent Example

Launch the stand-alone agent with the following command:


$ java -classpath classpath StandAloneSnmpAgent

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 its traps. See "Developing an SNMP Manager" for example applications you can use.

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

Type "Control-C" when you are done running the stand-alone agent.