Introduction
This article presents an example of how to build a C++ wrapper over a Windows API, in this case the WinSNMP library, in order to hide the interface complexity from the user. The wrapper, referred to as library in this article, intends to ease development of WinSNMP managers (clients) and makes use of the STL. Please note that it is not the intent of the article to present any help on SNMP or the WinSNMP API itself, nor to provide a full featured library. For that other sources are available, namely other CodeProject articles or open source projects.
Background and Motivation
The contents of this article are the result of some cleaning up around some code I had stashed away from some experiments on the STL and the WinSNMP library I performed some time ago. The whole idea of producing the article is just to share the work with the community.
Within the Example Files
The files include the complete source code (text format) and UML model (Visio format) under the GPL license, together with VS 2005 Beta 2 project files. Also included are compiled and running versions of the example tools: SNMPGET, SNMPSET, SNMPWALK, SNMPTRAP and SNMPSCAN.
Understanding the Model
The Big Picture
If you understand UML, you can build the big picture in your mind by examining the class diagram of figure 1.
Figure 1 - UML Class Diagram.
In this diagram all the library classes are represented, including main relationships and public methods.
The SNMPManager Class
This class wraps the WinSNMP startup and shutdown procedures. To use the library, you need to invoke the startup
method on the manager before any other operation. A pointer to the single instance of the manager can be obtained via the getManager
method (by the way, this is a spot to see a working implementation of the singleton pattern). The WinSNMP and library clean up code is called automatically in the class destructor that is called just before your application terminates. The getDisplayInformation
method will return a string with various information items on the installed WinSNMP library such as version, vendor and options.
The SNMPTracer Class
The SNMPTracer
is a singleton class which is used to control the tracing of protocol messaging which may be used for testing/debugging purposes (to see it working, run any of the examples with the -T option). Should trace be enabled (via the enable
method), a string representation of all exchanged SNMP protocol data units is sent to the console during library operation, including time tagging. You can browse the code to see how to handle the console via the Win32 API.
The SNMPObject Class
This is the most interesting and useful class in the library. An instance of this class represents an item (or variable) of SNMP data including addressing (OID) and/or value (to read or write).
OID
The object identifier of an SNMPObject
can be accessed via getOID
and setOID
methods in string format. All of the parsing and conversion to binary format is handled internally. Helper methods (compareOID
) for comparing OIDs with the OID of the object are also available. These map to the lexicographical OID comparison function of the WinSNMP library.
Syntax
The syntax methods (getSyntax
, setSyntax
and getSyntaxAsString
) will return or set the internal type of the object in binary or string form. Available types are: SNMP_INT
, SNMP_OCTETS
, SNMP_OPAQUE
, SNMP_BITS
, SNMP_CNTR64
, SNMP_CNTR32
, SNMP_GAUGE32
, SNMP_TIMETICKS
, SNMP_UINT32
, SNMP_OID
and SNMP_IPADDR
. Note that these constants are not necessarily the same as the corresponding constants of the WinSNMP library. The idea here is to build a wrapper library that does not enforce the user to be dependent on the WinSNMP library itself. This pattern may be rather useful if you plan on replacing the WinSNMP library with another implementation in the future and would like to avoid rewriting the user code.
Value
The value of the object can be set or read using the getValueXXX
and setValueXXX
methods. The string based methods are particularly helpful since they provide all the code to parse and produce values of any type. Check the internal code to see how this is achieved.
Display Information
As with any other class in the library, the getDisplayInformation
returns a string with human-readable information on the object, in this case the OID, the value and the syntax.
The SNMPSession and SNMPRemoteAgent Classes
An object of the SNMPSession
class holds information on one or more SNMP remote agents. This class is not that important at this point unless you understand the WinSNMP library. You don't even need to use it as you can see in the examples or the implementation of the SNMPRemoteAgent
class. Objects of the SNMPRemoteAgent
class hold information on SNMP remote agents. This includes name (host name or IP address of the manager machine), SNMP community name, and server remote port. These attributes are set in the class constructor. For each remote agent you need to exchange information with, an object of the SNMPRemoteAgent
class must be instantiated.
SNMP Requests
All SNMP requests are performed using instances of the SNMPRequest
abstract class. For each SNMP exchange type (Get, Set or Get Next), a concrete subclass exists that should be used according to the SNMP service you wish to access. To issue an SNMP request you must create an SNMPRequest
object and call its execute method. Then you can wait on the object until the transaction terminates (via wait
method) or periodically check if the operation is completed (via isTerminated
method). The implementation is inherently asynchronous and performs no retries. Canceling and obtaining result information on the status of the last execution request is possible via the cancel
and timedOut
/succeeded
/getErrorXXX
methods, respectively. The resulting SNMP objects (which may be more than one) can be accessed via the getResultCount
and getResult
methods.
Get Requests
Get requests can be created by instantiating the SNMPRemoteAgentGetRequest
class. The constructor receives the OIDs to retrieve from and the remote agent to request the objects from. To learn more, check the SNMPGET example.
Set Requests
Set requests can be created by instantiating the SNMPRemoteAgentSetRequest
class. In this case, the input SNMPObject
s include both the OID and the value to set in the service request. To learn more, check the SNMPSET example.
Get Next Requests
Similar to the Get requests, the Get Next requests (SNMPRemoteAgentGetNextRequest
) return the objects with adjacent OIDs. To learn more, check the SNMPWALK example.
The SNMPTrap Class
The SNMPTrap
class handling is very similar to the request classes but allows you to receive indication of SNMP traps. To use it, create an instance of the class passing the OID you wish to listen to and the agent that provides it. Then call the enable
method to start receiving indications. You can also disable the indications via the disable
method. Processing indications takes a little bit more work because you need to derive SNMPSession
and override the processTrap
method. A usage example, taken from the SNMPTRAP example, is shown below:
class MySession : public SNMPSession {
public:
virtual void processTrap(const SNMPObject & obj) {
std::cout << obj.getDisplayInformation() << "\n";
}
};
MySession session;
SNMPRemoteAgent ragent(host, community, 0, &session);
SNMPTrap trap(oid, &ragent);
trap.enable();
Please note that the trap handling has not been tested, so it is probably broken. Feel free to check if it works!
Other Classes
Exceptions
Most library methods you can call (virtually all of them) may throw an exception of class SNMPException
or SNMPErrorException
(actually only SNMPErrorException
is thrown). Each exception will give you detailed information on the error, including a message regarding the cause and an error ID. To print this information use toString
(or toStringStack
if you need the entire exception stack). To help with the coding of the library a small exception framework was set up using macros (see SNMPException.h).
Sets
The set classes (SNMPRequestSet
and SNMPRemoteAgent
) are pretty straightforward and allow both storage and set operations.
Using the Code
For those of you that believe code and commenting is the most accurate documentation, the full code of the SNMPGET example, with additional comments, is shown below:
int main(int argc, char* argv[]) {
try {
SNMPManager::getManager()->startup();
std::string host = "172.18.200.90";
std::string community = "public";
int tmo = 1000;
unsigned int port = 0;
int oidcount = 0;
std::string oidarray[512];
SNMPRemoteAgent ragent(host, community, port);
SNMPObject * oids = new SNMPObject[oidcount];
for(int i=0; i<oidcount; i++) {
oids[i].setOID(oidarray[i]);
}
SNMPRemoteAgentGetRequest getReq(oids, oidcount);
getReq.execute(&ragent);
getReq.wait(tmo);
delete [] oids;
if(!getReq.succeeded()) {
if(!getReq.timedOut()) {
std::cout << getReq.getErrorAsString();
} else {
std::cout << "timeout";
}
} else {
for(int i=0; i<getReq.getResultCount(); i++) {
std::cout << getReq.getResult(i)->getDisplayInformation() << "\n";
}
}
std::cout << "\n";
return 0;
} catch (SNMPException * pe) {
std::cout << "\n\n";
std::cout << pe->toStringStack();
std::cout << "\n";
delete pe;
}
return 0;
}
Exploring the Code and the Examples
All of the code is included in a set of *.h and *.cpp files. Finding the class you want is pretty straightforward given the file names. The examples are included in the main.cpp file. To compile each example, you need to define the appropriate macro as explained in the beginning of the file.
All of the examples are command line tools that accept a set of arguments to perform various operations. Common to each one are some options such as: printing help (-h or -?), enabling tracing (-T), setting the timeout (t:<n>) or setting the remote port of the agent (p:<n>).
- SNMPGET: Reads a set of object values given their OIDs.
- SNMPSET: Writes one SNMP object given its OID, value, and optionally, its syntax.
- SNMPWALK: Displays all objects in the agent from given OID.
- SNMPSCAN: Scans your network for the availability of SNMP agents.
- SNMPTRAP: If it works, processes traps from the agent.
Last Things Last
I hope this is a useful article, please comment if you think it is. There is, of course, a lot of room for improvement, mainly regarding design. Although I have no plans to do any updates please come forward with any comments as well as design or coding alternatives.