This HOWTO explains how to implement, compile and run a trivial subagent. Our trivial subagent has only one variable called 'simpleCounter' which is described within the minimal MIB called 'SIMPLE-MIB':
SIMPLE-MIB DEFINITIONS ::= BEGIN IMPORTS OBJECT-TYPE FROM SNMPv2-SMI simpleCounter OBJECT-TYPE SYNTAX INTEGER ACCESS read-only STATUS mandatory DESCRIPTION "A simple counter which is incremented each time it is queried. Starts with 0." ::={ enterprises 42 1 } END
Now let's see how easy this can be implemented with agentXcpp!
We implement the subagent within a single file - simpleagent.cpp
.
First of all, we need to include some header files for our subagent:
All agentXcpp classes are in the namespace 'agentxcpp'. To make the code more readable, we make the namespace globally available:
The simpleCounter thing described above is an OBJECT-TYPE. OBJECT-TYPE is a description of the object, not the object itself. The OID of simpleCounter is "enterprises.42.1", while the real object will be accessible with OID "enterprises.42.1.0".
In C++, objects are described by their classes (note the difference between classes and objects). The class-object-relation in C++ is similar to the "OBJECT-TYPE"-"object"-relation in SNMP. Therefore, OBJEC-TYPEs are represented by classes in agentXcpp, while SNMP objects are represented by C++ objects. Therefore, to implement simpleCounter, we start with a class (later we will make an object of the class):
The simpleCounter has a type which is given in the SYNTAX clause in the MIB. AgentXcpp provides various classes to match standard SYNTAX types, all of which inherit from the variable class. Our simpleCounter class inherits from Integer, which makes it an INTEGER.
Next, the get() method from Integer must be overridden. This method is called on SNMP GET requests and returns the current value of the object. In our case, it returns the value of a member variable and increments this variable afterwards:
Finally, we add a constructor which initializes the 'counter' member to zero:
In the main() function, we create a master_proxy object:
The class master_proxy is used to communicate with the master agent. A master_proxy can be thought of a local C++ object representing the remote master agent. The master_proxy can be in disconnected state at any time, or become disconnected, which means that most operations could possibly fail with an agentxcpp::disconnected exception.
The default constructor of master_proxy tries to connect to the master agent via the unix domain socket /var/agentx/master
. Of course, another unix domain socket may be chosen, but it cannot be changed once the master_proxy object is created.
Now, as we have the master_proxy object, we register a subtree. This means that we provide an OID to the master to indicate that our subagent wishes to serve all requests to OIDs starting with that OID. For our subagent we use "enterprises.42.1", where 'enterprises' is the standard OID "1.3.6.1.4.1". The master will then send all SNMP requests within that subtree to our subagent (e.g. "enterprises.42.1.0" or "enterprises.42.1.1.2.0"), regardless of whether the subagent actually can serve them. If a request cannot be served, the master_proxy will send an appropriate error message to the master agent.
And here is the code for subtree registration:
As you see, an oid object can be created by giving it the "enterprises_oid" object (which is provided by agentXcpp for convenience) and an additional string with further subids.
Next, we create a simpleCounter variable and register it with agentXcpp:
This is the place where we create the real SNMP variable 'sc'. This variable is then registered with the master agent by providing its OID (the one with .0) and the object itself. Note that the object is given in form of a shared pointer, which means that is must be created using the 'new' operator.
Finally we give control to the agentXcpp library in order to receive requests from the master agent:
The master_proxy uses boost::asio for networking, therefore a boost::asio::io_service object is utilized to give control to boost::asio, which in turn informs agentXcpp about incoming data. If you plan to use boost::asio in your real subagent, you can integrate with agentXcpp. For example, you can use the same boost::asio::io_service object as the master_proxy, or tell the master_proxy to use a boost::asio::io_service object that you provide.
The main loop is left if the 'master' object ever gets disconnected. In this case, an error message is output, and the subagent exits. A real subagent might try to reconnect to the master agent with master.reconnect()
.
If the connection fails earlier (e.g. upon creation of the 'master' object), exceptions are thrown by the master.register_subtree()
or master.add_variable()
calls, which will abort the subagent. This should be fine for the first time; a more realistic subagent should of course handle exceptions properly.
Now, let's store the program as simpleagent.cpp
and compile it!
When compiling the subagent, we need to link against the boost library 'system_error', 'pthread' and, of course, against the agentXcpp library. In linux, you can type the following command:
This assumes that the agentxcpp library is installed properly, so that the compiler can find the headers and the shared object file.
This step is difficult to describe, because it depends on your setup how to get the subagent running. This howto describes the procedure using the NET-SNMP package on linux. That package contains a daemon which can be used as master agent, and some command line tools, which can be used to query the SNMP agent.
We will configure the NET-SNMP agent to act as AgentX master agent. To keep this guide simple, we will use the SNMPv1 protocol using "rw" as read-write community name, but our subagent can of course be queried using SNMPv2c or SNMPv3, as long as the master is configured accordingly. The subagent implementation is independent of the used SNMP version.
First, we add the following lines to its config file /etc/snmp/snmpd.conf
:
rwcommunity rw # use "rw" as read-write-community name master agentx # snmpd shall act as agentX master agent agentXPerms 0666 777 # make socket world-readable
The community name is needed later, when issuing a SNMP GET request. We set it to "rw" in the example, but you can use anything you wish.
The agentXPerms are not strictly required, but making the socket world-readable saves us some trouble regarding access restrictions. Next, we (re)start the master agent using its init scripts:
/etc/init.d/snmpd stop /etc/init.d/snmpd start
Note that the path depends on your system. For example in ArchLinux, it is /etc/rc.d/snmpd.
Now the subagent can be started:
./simpleagent
If the subagent can't connect to the master, or another error is detected, it will terminate with an exception (since our code doesn't catch any exceptions). Or, if connection is lost, it will leave the main loop and exit with the error message we implemented.
Otherwise, the subagent is now connected, and we can try if it works.
Our little subagent exposes an INTERGER object with the OID "1.3.6.1.4.1.42.1.0". We can query this object with an SNMP GET request. We use the NET-SNMP utility snmpget (but you can use any tool you wish, of course):
snmpget -v1 -c rw localhost 1.3.6.1.4.1.42.1.0
Or, if the MIB above is put into a file in the right directory, we can also do:
snmpget -v1 -c rw localhost enterprises.42.1.0
Each time the command is executed, the simpleCounter object reports the incremented number. The trivial subagent is working now, your installation have proven to be usable, thus you can go and implement your own subagent!