AgentXcpp  Version:0.3
Internals Documentation
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Pages
How to send notifications and traps
Note
This is a follow-up tutorial to How to implement a writeable variable.

This HOWTO explains how to send notifications (SNMP v2, v2c and v3) and traps (SNMP v1). Luckily, from a subagent's point of view, there is nearly no difference between traps and notifications. In the following text, the term "notification" is used for notifications and traps.

When a subagent detects some interesting condition, it may send a notification to the master agent. The master agent will then forward it to some SNMP managers, depending on its configuration. The destination of the SNMP notification (or trap) is determined solely by the master agent.

We start with the subagent developed in How to implement a writeable variable, which provides the SNMP variable simpleCounter. This counter is incremented each time it is read. Next we will enhance our implementation so that it sends a notification each second.

First of all, let's augment the SIMPLE-MIB:

SIMPLE-MIB DEFINITIONS ::= BEGIN

IMPORTS
    OBJECT-TYPE FROM SNMPv2-SMI
    enterprises FROM SNMPv2-SMI
    NOTIFICATION-TYPE FROM SNMPv2-SMI;

simpleCounter OBJECT-TYPE
    SYNTAX INTEGER
    ACCESS read-write
    STATUS current
    DESCRIPTION "A simple counter which is incremented each time
                 it is queried. Starts with 0."
    ::={ enterprises 42 1 }

simpleCounterStatus NOTIFICATION-TYPE
    OBJECTS { simpleCounter }
    STATUS current
    DESCRIPTION "A notification sent each second."
    ::={ enterprises 42 0 1 }

END

As can be seen, we added the simpleCounterStatus notification, which will include the simpleCounter value when sent. Note that the next-to-last subidentifier for notifications must be 0 according to RFC 1902, 8.5. "Mapping of the NOTIFICATION-TYPE value".

The Implementation

So, how do we implement a notification? Normally, a subagent is just sitting there, waiting for SNMP requests and handling them. Notifications typically arise outside of this mechanism. We simulate that by using a timer which fires every second and sends a notification. We will use a QTimer which can use the event loop provided by Qt. For this we need a class with a Qt slot to which the QTimer can be connected. We name that class NotificationSender and put it into its own file: NotificationSender.hpp (for simplicity we implement the whole class in the header, so we won't need an implementation file). Here is the NotificationSender.hpp file:

#ifndef _NOTIFICATIONSENDER_H_
#define _NOTIFICATIONSENDER_H_
#include <iostream>
#include <QObject>
#include <agentxcpp/Oid.hpp>
#include <agentxcpp/MasterProxy.hpp>
#include "SimpleCounter.hpp"
using namespace agentxcpp;
class NotificationSender : public QObject
{
Q_OBJECT
private:
MasterProxy* master;
Oid simpleCounter_oid;
QSharedPointer<SimpleCounter> counter;
public:
NotificationSender(MasterProxy* _master,
Oid _oid,
QSharedPointer<SimpleCounter> _counter)
: master(_master), simpleCounter_oid(_oid), counter(_counter)
{
}
public slots:
void sendNotification()
{
std::cout << "notification!" << std::endl;
std::vector<Varbind> objects;
IntegerVariable(counter->value());
objects.push_back(Varbind(simpleCounter_oid, counter));
master->send_notification(enterprises_oid + 42 + 0 + 1, objects);
}
};
#endif // _NOTIFICATIONSENDER_H_

The NotificationSender inherits QObject and uses the Q_OBJECT macro, which is both needed to make the class suitable for Qt's signal/slot mechanism. The constructor takes a pointer to the MasterProxy object (needed to send notifications), a pointer to the SimpleCounter object (will be included in the notifications) and the simpleCounter's OID (will also be included in the notifications). The sendNotification() slot will be connected to the QTimer object and sends a notification each time it is invoked.

The direct call to the MasterProxy::send_notification() slot works, because the NotificationSender and the MasterProxy will run in the same thread. If they were running in separate threads, QMetaObject::invokeMethod() would be used to invoke the slot.

Next, we augment the code within the main() function. But first, we need additional includes in simpleagent.cpp:

#include <QTimer>
#include "NotificationSender.hpp"

And this is the complete revised main() function:

int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
MasterProxy master;
Oid simpleagentOid = Oid(enterprises_oid, "42");
master.register_subtree(simpleagentOid);
QSharedPointer<SimpleCounter> counter(new SimpleCounter);
Oid simpleCounterOid = simpleagentOid + 1 + 0;
master.add_variable(simpleCounterOid, counter);
// New code for sending notifications ---------------
NotificationSender sender(&master, simpleCounterOid, counter);
QTimer timer;
QObject::connect(&timer, SIGNAL(timeout()),
&sender, SLOT(sendNotification()));
timer.setInterval(1000); // 1000 milliseconds
timer.start();
// --------------------------------------------------
app.exec();
}

The additional code creates a NotificationSender object. Then, it creates a QTimer object, connects its timeout() signal to the NotificationSender's sendNotification() slot, configures its interval to 1 second (the timer is repetitive by default), and starts it.

Compiling the Subagent

Compiling the subagent becomes a bit more complicated, because we need to invoke QT's meta-object compiler (moc) for the NotificationSender class, which we do first:

moc-qt4 `pkg-config --cflags QtCore QtNetwork` -o moc_NotificationSender.cc NotificationSender.hpp

This compiles the header NotificationSender.hpp into the file moc_NotificationSender.cc. Next, we compile this file into an object file:

g++ -c moc_NotificationSender.cc `pkg-config --cflags QtCore QtNetwork`

Finally we compile the simpleagent.cpp and link it against the generated object file:

g++ simpleagent.cpp -o simpleagent moc_NotificationSender.o `pkg-config --cflags --libs QtNetwork QtCore` -lagentxcpp

Now, the executable is ready to run.

Running the Subagent

Configuring the master agent

The subagent is run as described in How to write a Subagent, but to make notifications work, we need additional configuration for the master agent. For the NET-SNMP master agent, add the following line to its configuration file /etc/snmp/snmpd.conf:

informsink udp:127.0.0.1 rw

This line means that notifications are sent to the address 127.0.0.1, using the default port (which is 162) and the community string "rw" (which we also used in How to write a Subagent). 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 snmptrapd program

To receive notifications, we use the snmptrapd program from the NET-SNMP package. First, we have to configure it. Make sure that the following line is contained in /etc/snmp/snmptrapd.conf:

authCommunity log rw

That line enables logging for notifications with the community string "rw". Now start the program as root. The program must be run as root, because it needs to listen on the privileged port 162.

# As root:
snmptrapd -f -Le

The -f parameter forces the program to stay in the foreground, so we can see its output. The -Le parameter redirects the logging messages to stderr (i.e. to the console window).

Starting the Subagent

Now, the subagent is started:

./simpleagent

If all went right, we can see notifications arriving in the snmptrapd console, with each notification reporting the current counter value. Try some SNMP get and get requests:

snmpget -v1 -c rw localhost SIMPLE-MIB::simpleCounter.0
snmpset -v1 -c rw localhost SIMPLE-MIB::simpleCounter.0 i 42

This increases or sets the counter value, and the new values are reported within the notifications.

In next lesson you can learn how to implement SNMP tables!