AgentXcpp  Revision:0.2
API Documentation
 All Classes 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 every 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 5 seconds."
    ::={ enterprises 42 0 1 }

END

As can be seen, we added the simpleCounterChange 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 ist the NotificationSender.hpp file:

#ifndef _NOTIFICATIONSENDER_H_
#define _NOTIFICATIONSENDER_H_
#include <iostream>
#include <QObject>
#include <agentxcpp/OidValue.hpp>
#include <agentxcpp/MasterProxy.hpp>
#include "SimpleCounter.hpp"
using namespace agentxcpp;
class NotificationSender : public QObject
{
Q_OBJECT
private:
MasterProxy* master;
OidValue simpleCounter_oid;
shared_ptr<SimpleCounter> counter;
public:
NotificationSender(MasterProxy* _master,
OidValue _oid,
shared_ptr<SimpleCounter> _counter)
: master(_master), simpleCounter_oid(_oid), counter(_counter)
{
}
public slots:
void sendNotification()
{
std::cout << "notification!" << std::endl;
std::vector<varbind> objects;
shared_ptr<AbstractValue> counter_value(new IntegerValue(counter->value()));
objects.push_back(varbind(simpleCounter_oid, counter_value));
QMetaObject::invokeMethod(master, "send_notification",
Q_ARG(const OidValue&, simpleCounter_oid),
Q_ARG(const std::vector<varbind>&, 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), the simpleCounter's oid (will be included in the notifications) and a pointer to the counter (will be queried to include the value in the notifications). The sendNotification() slot will be connected to the QTimer object and sends a notification each time it is invoked. Also, it performs an output to indicate that a notification is sent, which we will possibly miss as long as the master agent is not yet configured properly.

We use QMetaObject::invokeMethod() here to invoke the MasterProxy::send_notification() slot. This would even work if the NotificationSender were running in a separate thread. In the example, were the NotificationSender and the MasterProxy run in the same thread, a simple call would also work:

// Could be used instead of QMetaObject::invokeMethod(),
// because NotificationSender and MasterProxy run in
// the same thread:
master->send_notification(simpleCounter_oid, objects);

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 main() function:

int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
MasterProxy master;
OidValue simpleagent_oid = OidValue(enterprises_oid, "42.1");
master.register_subtree(simpleagent_oid);
boost::shared_ptr<SimpleCounter> counter(new SimpleCounter);
OidValue simpleCounter_oid(simpleagent_oid, "0");
master.add_variable(simpleCounter_oid, counter);
// New code: ----------------------------------------
NotificationSender sender(&master, simpleCounter_oid, counter);
QTimer timer;
QObject::connect(&timer, SIGNAL(timeout()),
&sender, SLOT(sendNotification()));
timer.setInterval(1000); // 1000 milliseconds
timer.start();
// --------------------------------------------------
app.exec();
}

First, we create a NotificationSender object. Then, we create a QTimer object, connect its timeout() signal to the NotificationSender's sendNotification() slot, configure its interval to 1 second (the timer is repetitive by default), and start 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

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.

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

The subagent is started as last times:

./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.