AgentXcpp  Revision:0.1.1
API Documentation
 All Classes Functions Variables Friends Pages
How to write a Subagent
Note:
It is assumed that agentXcpp is installed properly; see Building and Installing AgentXcpp for how to do that.

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!

Implementing the Subagent

We implement the subagent within a single file - simpleagent.cpp.

The Headers

First of all, we need to include some header files for our subagent:

// Make the class agentxcpp::Integer available:
#include <agentxcpp/Integer.hpp>
// Make the class agentxcpp::master_proxy available:
#include <agentxcpp/master_proxy.hpp>
// We want to output error messages:
#include <iostream>

All agentXcpp classes are in the namespace 'agentxcpp'. To make the code more readable, we make the namespace globally available:

using namespace agentxcpp;

Implementing the simpleCounter Variable

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):

class simpleCounter : public Integer
{

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:

private:
int32_t counter;
public:
virtual int32_t get()
{
return counter++;
}

Finally, we add a constructor which initializes the 'counter' member to zero:

simpleCounter()
: counter(0)
{
}
};

The main() Function

In the main() function, we create a master_proxy object:

int main(void)
{
master_proxy master;

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:

oid simpleagent_oid = oid(enterprises_oid, "42.1");
master.register_subtree(simpleagent_oid);

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:

oid simpleCounter_oid(simpleagent_oid, "0");
shared_ptr<simpleCounter> sc(new simpleCounter);
master.add_variable( simpleCounter_oid, sc);

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:

while( master.is_connected() )
{
master.get_io_service()->run();
}
std::cerr << "I lost the connection to the master agent." << std::endl;
}

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!

Compiling the Subagent

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:

g++ simpleagent.cpp -o simpleagent -lboost_system -lpthread -lagentxcpp

This assumes that the agentxcpp library is installed properly, so that the compiler can find the headers and the shared object file.

Running the Subagent

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.

Starting the Net-SNMP master agent

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.

Starting the Subagent

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.

Querying the Subagent

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!