Chapter 9 Implementing an SNMP Proxy
It is easier to manage a large number of SNMP agents when they have
a hierarchical structure of master agents and sub-agents. Master agents concentrate
and relay the information in their sub-agents and can provide their own specific
information as well. Managers only communicate with the master agents and
access the sub-agents transparently, as if the information actually resided
in the master agent.
In order to do this, agents must contain an SNMP proxy for each sub-agent
that they manage. The proxy is a Java class that looks like a MIB MBean to
the agent, but which actually accesses the sub-agent to provide the information
that is requested of it. In order to access sub-agents, the proxy object relies
on the SNMP manager API.
The SNMP proxy is used in this simple example to allow a manager to
access two MIBs through one agent. You can reuse the proxy class in your management
solutions to implement any hierarchy of managers, master agents and sub-agents.
The source code for the proxy object and the sample application is available
in the Snmp/Proxy example directory located in the main examplesDir (see "Directories
and Classpath" in the preface).
Contents:
The Proxy Roles
As we saw in "MIB Development Process", the MBeans that represent
a MIB are generated from the MIB definition by the mibgen
tool. This tool generates one main MBean representing the MIB and then one
MBean for each group and each table entry. The main MBean extends the SnmpMib class whose implementation of the abstract SnmpMibAgent class processes all requests on a MIB.
For example, if an agent receives a get request for
some variable, it will call the get method that the
main MBean inherits from SnmpMibAgent. The implementation
of this method relies on the MIB structure to find the MBean containing the
corresponding attribute and then call its getter to read the value of the
variable.
A proxy is another implementation of the SnmpMibAgent
class which, instead of resolving variables in local MBeans, reformulates
an SNMP request, sends it to a designated sub-agent and forwards the answer
that it receives. Since only the main MBean of a MIB is bound to the SNMP
adaptor, we bind the proxy instead, and the master agent transparently exposes
the MIB which actually resides in the sub-agent.
The Master Agent
The master agent needs to instantiate one SNMP proxy object for each
sub-agent containing MIBs that it wishes to serve. The remote MIBs can be
on any number of sub-agents, and the master agent can have several proxy objects.
Sub-agents themselves may contain proxies: it is up to the designer to define
the complexity of the agent hierarchy.
The master agent may also contain a mix of proxies and MBeans for other
MIBs. In the proxy example, the master agent exposes the DEMO
MIB through local MBeans and a subset of the RFC1213 MIB
through a proxy.
Example 9-1 The Master Agent of the Example
MBeanServerImpl server;
ObjectName snmpObjName;
ObjectName localMibObjName;
ObjectName remoteMibObjName;
int htmlPort = 8082;
int snmpPort = 8085;
// read sub-agent connection info from the command line
String host = argv[0];
String port = argv[1];
try {
server = MBeanServerFactory.createMBeanServer();
String domain = server.getDefaultDomain();
// Create and start the HTML adaptor.
[...]
// Create and start the SNMP adaptor.
[...]
// Create, initialize, and bind the local MIB Demo.
//
localMibObjName = new ObjectName("snmp:class=DEMO_MIB");
server.registerMBean(localMib, localMibObjName);
localMib.setSnmpAdaptorName(snmpObjName);
// Create and initialize the SNMP proxy.
//
remoteMibObjName = new ObjectName("snmp:class=proxy");
SnmpMibAgentImpl remoteMib = new SnmpMibAgentImpl();
server.registerMBean(remoteMib, remoteMibObjName);
remoteMib.initializeProxy(host, Integer.parseInt(port), "1.3.6.1.2.1");
// Bind the MIB proxy to the SNMP adaptor
//
((SnmpMibAgent)remoteMib).setSnmpAdaptorName(snmpObjName);
}
catch (Exception e) {
e.printStackTrace();
java.lang.System.exit(1);
}
|
We register the proxy object as any other MBean and then initialize
it. We call the initialize method of the proxy, giving
the host and port of the sub-agent it must communicate with and the root OID
of the MIB or subset that it represents.
A single proxy object can serve several MIBs on a sub-agent, and in
this case, the root OID is the common prefix of all objects. However, the
OIDs of all proxies and MIBs in an agent must be distinct, none may be a substring
of another (see "Binding the MIB MBeans"). Finally, we bind the proxy
to the SNMP adaptor just as we would a MIB MBean.
The Sub-Agent
The sub-agent in our example is a stand-alone agent which serves a subset
of the RFC1213 MIB. Since it implements no proxies of its
own, it is just a plain agent which responds to SNMP management requests that
happen to originate from a proxy object. Any SNMP manager could also send
requests to this agent.
Stand-alone agents are covered in "Stand-Alone SNMP Agents". Since
this stand-alone agent contains no code that is specific to its role as a
sub-agent, we will not repeat its program listing here. The StandAloneAgent.java file only contains some extra code for reading its assigned port
from the command line. We will use this to launch the agent on a known port
to which the proxy can connect.
The Manager Application
The manager application is not affected by proxies in the agents to
which it sends requests. It sends the requests to the master agent, in the
same way that it would send a request for a MIB that is not served by a proxy.
In fact, the SNMP manager cannot even distinguish between a MIB served by
an agent and another MIB served through a proxy in the agent, except perhaps
by analyzing the values returned.
There is one consideration for proxies, and that is the timeout interval.
Since the proxy issues another request and only answers at the end of its
timeout, the manager must have a longer timeout. The manager should be designed
with some knowledge of all sub-agents in a hierarchy, so that the timeout
can take into account all proxy delays and the multiple communication times.
As with any manager application written with the SNMP manager API, the
manager in the proxy example must have access to the OID table objects that
represent the MIBs that it will manage. Here is the code to initialize the
manager:
Example 9-2 Initialization of the SNMP Proxy Manager
String host = argv[0];
String port = argv[1];
// Initialize the SNMP Manager API.
// Specify the OidTables containing all the MIB Demo and MIB II knowledge.
// Use the OidTables generated by mibgen when compiling MIB Demo and MIB II.
//
SnmpOidDatabaseSupport oidDB = new SnmpOidDatabaseSupport();
SnmpOid.setSnmpOidTable(oidDB);
oidDB.add(new RFC1213_MIBOidTable());
oidDB.add(new DEMO_MIBOidTable());
SnmpPeer agent = new SnmpPeer(host, Integer.parseInt(port));
SnmpParameters params = new SnmpParameters("public", "private");
SnmpSession session = new SnmpSession("Manager session");
// We update the time out to let the agent enough time
// to do his job before retry.
//
agent.setTimeout(100000);
agent.setSnmpParam(params);
session.setDefaultPeer(agent);
|
The rest of the manager application is the code for synchronous get and set requests, similar to the one shown
in "The Synchronous Manager Example".
The SNMP Proxy Implementation
The SNMP proxy is an extension of the abstract SnmpMibAgent which implements all of its abstract methods and can be instantiated.
The proxy implements a synchronous SNMP manager that forwards the requests
to the sub-agent. It does some error fixing for SNMPv2 requests but doesn't
claim to be extensive. In any case, the SNMP proxy implementation is provided
as an example and Sun Microsystems makes no claim as to its suitability for
any particular usage.
Note -
The implementation of the example proxy does not support the getBulk request or the check method that
allows the SNMP Walk request.
Before it is used, the proxy must be initialized with the hostname and
port of the sub-agent. It uses this information to create SNMP parameter and
SNMP peer objects. It then creates three SNMP sessions:
Example 9-3 Internal Initialization of the SNMP Proxy
public void initializeProxy(String h, int p,
String strOid, String name)
throws UnknownHostException, SnmpStatusException {
host = h;
port = p;
oid = strOid;
mibName = name;
// Initialization for SNMP v1 protocol.
//
SnmpParameters paramsV1 = new SnmpParameters("public", "private");
paramsV1.setProtocolVersion(SnmpDefinitions.snmpVersionOne);
SnmpPeer peerV1 = new SnmpPeer(host, port);
peerV1.setSnmpParam(paramsV1);
sessionV1 = new SnmpSession("SnmpMibAgentImpl session V1");
sessionV1.setDefaultPeer(peerV1);
// Using SNMP v1 protocol, errors are not fixed and
// forwarded to the manager
sessionV1.snmpOptions.setPduFixedOnError(false);
// Initialization for SNMP v2 protocol.
//
SnmpParameters paramsV2 = new SnmpParameters("public", "private");
paramsV2.setProtocolVersion(SnmpDefinitions.snmpVersionTwo);
SnmpPeer peerV2 = new SnmpPeer(host, port);
peerV2.setSnmpParam(paramsV2);
// If we get an error, we don't retry the request using SNMP v2,
// but we try the request using SNMP v1
peerV2.setMaxRetries(0);
sessionV2 = new SnmpSession("SnmpMibAgentImpl session V2");
sessionV2.setDefaultPeer(peerV2);
// Using SNMP v2 protocol, the error is fixed
//
sessionV2.snmpOptions.setPduFixedOnError(true);
// Initialization for SNMP v2 protocol simulated
// using SNMP v1 protocol
//
sessionV2WithV1 = new SnmpSession("SnmpMibAgentImpl session V2 with V1");
sessionV2WithV1.setDefaultPeer(peerV1);
// Simulating SNMP v2 with SNMP v1 protocol, the error is fixed.
//
sessionV2WithV1.snmpOptions.setPduFixedOnError(true);
}
|
The proxy exposes the public methods for handling requests, and then
this algorithm for reducing errors is implemented by internal
methods. Roughly, the proxy must determine the version of the incoming request
and handle it as promised. Version 1 requests that timeout or fail are dropped,
and v2 requests that timeout or fail are retried as v1 requests. Here we only
show the code for implementing the get method.
Example 9-4 Implementing a get
Request in the Proxy
// the exposed method
public void get(Vector list, int version) throws SnmpStatusException {
java.lang.System.out.println(
"Proxy: Sending get request to SNMP sub-agent on " +
host + " using port " + port);
// Request using SNMP v1 protocol
if (version == SnmpDefinitions.snmpVersionOne) {
get(list, version, sessionV1);
}
// Request using SNMP v2 protocol.
if (version == SnmpDefinitions.snmpVersionTwo) {
get(list, version, sessionV2);
}
}
// the internal implementation
private void get(Vector list, int version, SnmpSession session)
throws SnmpStatusException {
SnmpVarbindList varbindlist = setVarBindList(list);
try {
request = session.snmpGet(null, varbindlist);
}
catch (SnmpStatusException e) {
throw new SnmpStatusException(SnmpDefinitions.snmpRspGenErr, 0);
}
java.lang.System.out.println("\nRequest:\n" + request.toString());
boolean completed = request.waitForCompletion(10000);
if (completed == false) {
// If the completion failed using SNMP v1, we give up
if (version == SnmpDefinitions.snmpVersionOne) {
java.lang.System.out.println(
"\nRequest timed out: check reachability of sub-agent.");
return;
}
// If the completion failed using SNMP v2, we try again using v1
if (version == SnmpDefinitions.snmpVersionTwo) {
get(list, SnmpDefinitions.snmpVersionOne, sessionV2WithV1);
return;
}
}
int errorStatus = request.getErrorStatus();
int errorIndex = request.getErrorIndex() + 1;
if (errorStatus != SnmpDefinitions.snmpRspNoError) {
// If there is an error status using v1, we throw an exception
if (version == SnmpDefinitions.snmpVersionOne) {
throw new SnmpStatusException(errorStatus, errorIndex);
}
// If there is an error status using v2, we try again using v1
if (version == SnmpDefinitions.snmpVersionTwo) {
get(list, SnmpDefinitions.snmpVersionOne, sessionV2WithV1);
return;
}
}
result = request.getResponseVbList();
// Update the list parameter with the result
Enumeration l = list.elements();
for (Enumeration e = result.elements(); e.hasMoreElements();) {
SnmpVarBind varres = (SnmpVarBind) e.nextElement();
SnmpVarBind varbind = (SnmpVarBind) l.nextElement();
varbind.value = varres.value;
}
}
|
The complete list of methods that a proxy must implement are the same
as for MIB MBeans represented by instances of the SnmpMib
class:
-
long[] getRootOid() - Gets the root object
identifier of the MIB
-
void get(java.util.Vector list, int version) - Processes a get operation.
-
void getNext(java.util.Vector list, int
version) - Processes a getNext operation.
-
void getBulk(java.util.Vector list, int
nonRepeat, int maxRepeat, int version) - Processes a getBulk operation.
-
void check(java.util.Vector list)
- Prepares a walk operation.
-
void set(java.util.Vector list, int version) - Processes a set operation.
Running the SNMP Proxy Example
First, we generate the MBeans for our MIBs using the mibgen tool:
$ cd examplesDir/Snmp/Proxy/
$ mibgen -a -d . mib_II_subset.txt mib_demo.txt
|
Since the proxy acts as an SNMP manager, the master agent application
now needs the SNMP manager API in its classpath, for both compilation and
execution. Similarly, the proxy object also needs the class representing the
SNMP OID table of the MIB that it accesses on the sub-agent.
These issues are resolved in our case by having all classes in the example
directory and using dot (.) in our usual classpath. Replace
the two MIB files with those from the patchfiles directory
and then compile all of the classes in the example directory:
$ cp -i patchfiles/*_MIB.java .
cp: overwrite ./DEMO_MIB.java (yes/no)? y
cp: overwrite ./RFC1213_MIB.java (yes/no)? y
$ javac -classpath classpath -d . *.java
|
We can run all three applications on the same machine as long as we
choose different port numbers. Here we give commands for launching the applications
from the same terminal window running the Korn shell. On the Windows NT platform,
you will have to launch each application in a separate window, in which case
you will not see the sequence of the merged output.
Running the SNMP Proxy Example
-
First we launch the sub-agent; by default it will reply to port 8086,
or we can specify a different one on the command line:
$ java -classpath classpath StandAloneAgent 8090 &
Adding SNMP adaptor using port 8090
Initializing the MIB RFC1213_MIB
|
-
Then we launch the master agent, giving it the sub-agent's hostname
and port number:
$ java -classpath classpath Agent localhost 8090 &
NOTE: HTML adaptor is bound on TCP port 8082
NOTE: SNMP Adaptor is bound on UDP port 8085
Initializing the MIB snmp:class=DEMO_MIB
Initializing the SNMP proxy snmp:class=proxy to query host localhost using
port 8090
|
-
Now you can view the master agent through its HTML adaptor. In your
web browser, go to the following URL: http://localhost:8082/
The class=proxy MBean in the snmp
domain is the proxy object, but we cannot see the MBean that it represents.
In order to manage the actual MIB, we would have to connect to the sub-agent,
but in our example it is a stand-alone and therefore unreachable.
The name=Demo MBean in the DEMO_MIB
domain is the MIB that is served locally. If we click on its name, we can
see its initial values.
-
Finally, we launch the manager application, giving it the master agent's
hostname and port:
$ java -classpath classpath Manager localhost 8085
|
The manager will run through its requests to the master agent. If the
output is in the correct order, there are messages from the manager issuing
a request, and then the proxy which relays a request for two of the variable
to the sub-agent. The proxy then prints the result it received for the two
variables, then the manager receives the final response with all of the variables,
including the same two that the proxy just forwarded.
-
In the name=Demo MBean on the web browser, you should
see the new values that were set in the DEMO_MIB as part of the manager's
set operation.
-
Don't forget to stop the agent applications with the following commands
(just use "Control-C" if they are in separate terminal windows):
$ fg
java [...] Agent localhost 8090 <Control-C>
^C$ fg
java [...] StandAloneAgent 8090 <Control-C>
^C$
|