Tnm4j
A simplified SNMP API for Java, based on Jürgen Schönwälder's Tnm extension for Tcl.
Install / Use
/learn @soulwing/Tnm4jREADME
tnm4j
A simplified SNMP API for Java, inspired by Jürgen Schönwälder's Tnm extension for Tcl.
The original Tnm made it easy to write network management applications using simple Tcl scripts. Tnm4j attempts to bring this same simplicity to the task of writing network management applications in Java or in Java-based scripting languages such as Groovy.
Running the Examples Using Docker
The src/examples/java subdirectory contains several examples.
Running the examples is easy if you have Maven and if you have or (or are willing to install) Docker Desktop and Docker Compose on your workstation.
The src/examples/docker subdirectory contains a Dockerfile that can be
used to create a Linux-based container image that runs Net-SNMP, with
configuration that matches up with the src/examples/java/ExampleTargets. At
the base directory for this project, there is a docker-compose.yml that
can build and run the container for you.
Steps for running the examples:
-
Install Docker Desktop
-
Install Docker Compose
-
Open a shell and navigate to the base directory for this project.
-
Run Docker Compose in the base directory.
docker-compose up --buildThe first time you start up, you'll see the container image being built. Subsequently, when you run
docker-compose up --buildit should reuse the cached image.After the image is built, it will run and you'll see the Net-SNMP console output.
-
In another shell, navigate to the base directory for this project. In this shell you'll run an example using Maven as follows.
mvn -Pexamples clean compile exec:java -Dexec.mainClass=Example01_GetAndGetNextMaven will build the project and run the example class you specified. You can run any of the examples in
src/examples/javain this same manner. -
After you're done playing with the examples, go back to the first shell, hit Ctrl-C on the keyboard and then
docker-compose down -
You can save a little disk space by getting rid of the container image, too.
docker image rm tnm4j-netsnmp
Architecture
Tnm4j provides a lightweight façade over an SNMP adapter and a MIB parser adapter. These adapters each implement a Tnm4j service provider interface to adapt a third-party library for use with Tnm4j. The JDK's ServiceLoader mechanism is used to locate adapters for SNMP and MIB parsing support.
The default SNMP adapter uses Snmp4j by Frank Fock and Jochen Katz. Snmp4j is an outstanding library providing comprehensive support for SNMP communications in Java.
The default MIB adapter uses Per Cederberg's excellent Mibble MIB parser.
Other SNMP providers or MIB parsers could be easily adapted for use with Tnm4j by implementing the necessary SPI.
Trivial Example
The following example illustrates just a few of the features of Tnm4j. This snippet retrieves and displays the name, description, and up time of an SNMP-enabled network device.
MIB mib = MIBFactory.getInstance().newMib();
mib.load("RFC1213-MIB");
SnmpV2cContext snmp = SnmpFactory.getInstance().newSnmpV2cContext(mib);
snmp.setAddress("10.0.0.1");
snmp.setCommunity("public");
VarbindCollection varbinds = snmp.getNext("sysName", "sysDescr", "sysUpTime").get();
for (Varbind varbind : varbinds) {
System.out.format("%s=%s\n", varbind.getName(), varbind.toString());
}
Notice how the MIB and SNMP context objects are designed to work together? This is arguably the most salient concept of Tnm4j. Tnm4j fully exploits the MIB to make it easy for the developer to access management objects in an SNMP agent. The developer can use MIB object names (instead of SNMP object identifiers) when getting or setting object values, reducing the time and effort required to get the desired management information.
The syntax and textual convention details from the MIB are used when
converting object values to strings. This is illustrated in the preceding
example -- converting a retrieved value to a string in the appropriate
format is as simple as calling Varbind.toString().
Getting Started: Targets, Contexts, and Operations
In using Tnm4j, there are three fundamental objects you will use to interact with SNMP agents: targets, contexts, and operations.
A target is a simple object that describes the relevant characteristics of a
remote agent necessary for communication. A target implements either the
SnmpV3Target, SnmpV2cTarget, or SnmpV1Target interface, depending on
whether the remote agent supports the SNMPv3, SNMPv2c, or SNMPv1 protocol,
respectively. These interfaces describe properties such as network address and
security characteristics of the remote agent. Tnm4j provides simple concrete
target implementations -- SimpleSnmpV3Target, SimpleSnmpV2cTarget,
and SimpleV1Target -- that your application can construct and configure
directly. Alternatively, your domain model objects representing network devices
could easily implement these interfaces, allowing your model objects to be used
directly as targets.
A context provides the ability to invoke SNMP operations on a given target.
You create a context using SnmpFactory and providing the target to the
factory method:
import org.soulwing.snmp4j.*;
SimpleSnmpV2cTarget target = new SimpleSnmpV2cTarget();
target.setAddress("10.0.0.1");
target.setCommunity("public");
SnmpContext context = SnmpFactory.getInstance().newContext(target);
try {
// perform some SNMP operations
}
finally {
context.close();
}
As shown in the preceding snippet, a context must (eventually) be closed, in order to avoid a resource leak. Contexts are, however, lightweight objects and it is quite reasonable to create and retain a context for as long as you need to continue communicating with the target SNMP agent. Depending on the needs of your application, this could be as short as the time needed to perform a few SNMP operations, or perhaps for as long as the remote agent exists and your application continues running. Tnm4j is used in applications that retain thousands of context objects on the heap.
If you neglect to close a context, it will eventually be closed when your code
no longer holds any references to it -- the underlying implementation implements
finalize to close the context if necessary. However, as it is difficult to
predict when a discarded context will be reclaimed by the JVM's garbage
collector, it is a best practice to close a context before you discard the last
reference to it.
Performing SNMP Operations
Once you have a context for a given target, you can use the context to perform SNMP operations on the targeted SNMP agent:
SnmpContext context = SnmpFactory.getInstance().newContext(target);
try {
VarbindCollection result = context.get("1.3.6.1.2.1.1.3.0").get();
System.out.println(result.get(0));
}
finally {
context.close();
}
The preceding snippet uses the context to invoke a GET operation on the
remote agent and stores a reference to the retrieved variable bindings --
varbinds in SNMP speak. The numeric object ID used here (in case you
didn't recognize it) is the SNMP sysUpTime object. When invoking an
operation, we can request an arbitrary number of SNMP objects; the operation
methods include variants which take a variable number of arguments or a
List.
If you're wondering why we're using numeric OIDs here instead of names, just hang on... we'll get there shortly!
In addition to the get, the context provides methods to support all of the
fundamental SNMP operations: GET, GETNEXT, GETBULK, and SET. Moreover, it
provides methods to support easy and efficient SNMP table walks, which we'll
cover later. See the javadoc
for the full details.
You might have noticed there are quite a few gets in those two lines of code inside of the try block. The snippet is written in the idiomatic style recommended for Tnm4j, which is lean on syntax, and hides a lot of the underlying details. Let's break it down a little more to help you understand what's going on. We could rewrite the snippet a little more verbosely, like this:
SnmpContext context = SnmpFactory.getInstance().newContext(target);
try {
SnmpResponse<VarbindCollection> response = context.get("1.3.6.1.2.1.1.3.0");
VarbindCollection result = response.get();
Varbind sysUpTime = result.get(0);
System.out.println(sysUpTime);
}
finally {
context.close();
}
Now we can see that when we use get to perform a GET operation, the
return value is an SnmpResponse. If you check out the javadoc for
SnmpResponse
you'll see that it has a single method (get) that retrieves the result of the
SNMP operation. The response object is patterned after the JDK's Future
object -- the get method will block until the result of the operation is
available. If the operation fails, the relevant exception will be thrown when
you try to get the result from the response object.
Assuming that the operation succeeds, the result we retrieve from the response
object is a VarbindCollection. This object
is not a subtype of the JDK's Collection type.
