So far we learned how to implement single variables and how to send notifications. This tutorial teaches us how an SNMP table can be implemented. We start with the code from the previous tutorials and add a table to 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 } counterChangeTable OBJECT-TYPE SYNTAX SEQUENCE OF counterChangeEntry ACCESS not-accessible STATUS current DESCRIPTION "This table lists all changes of the simpleCounter object." ::= { enterprises 42 2 } counterChangeEntry OBJECT-TYPE SYNTAX counterChangeEntry ACCESS not-accessible STATUS current DESCRIPTION "An entry within the counterChangeEntry table." INDEX { counterChangeEpoch, counterChangeMilliseconds } ::= { counterChangeTable 1 } counterChangeEntry ::= SEQUENCE { counterChangeEpoch INTEGER, counterChangeMilliseconds INTEGER, counterChangeTimeString DisplayString, counterChangeCounterValue INTEGER } counterChangeEpoch OBJECT-TYPE SYNTAX INTEGER ACCESS read-only STATUS current DESCRIPTION "The time at which the change occured.This variable contains the epoch, i.e. seconds since 1970-01-01, 00:00." ::={ counterChangeEntry 1 } counterChangeMilliseconds OBJECT-TYPE SYNTAX INTEGER ACCESS read-only STATUS current DESCRIPTION "The time at which counter changed. This variable contains the milliseconds since counterChangeEpoch, to increase precision." ::={ counterChangeEntry 2 } counterChangeTimeString OBJECT-TYPE SYNTAX OCTETSTRING ACCESS read-only STATUS current DESCRIPTION "The time (counterChangeEpoch + counterChangeMilliseconds) as a human-readable string." ::={ counterChangeEntry 3 } counterChangeValue OBJECT-TYPE SYNTAX INTEGER ACCESS read-only STATUS current DESCRIPTION "The counter value right after the change." ::={ counterChangeEntry 4 } END
Our table has an entry for each counter change which occurred in the past. The table starts empty and each time the counter changes (i.e. by an SNMP GET or SET request), an entry is added.
Each entry within the table has a timestamp, which consists of the epoch time and a milliseconds component, which is used as index of the entry. This is to show how multiple variables can be used to form the table's index. In addition, the time is reported in an human-readable format (e.g. "2011-09-11, 12:00:52.77"). Finally, each entry reports the new counter value.
Technically, our SNMP table is a sequence of counterChangeEntry, which in turn is a structure containing four "real" SNMP variables. The OID of these variables are assembled like this:
<tableOid>.<entrySubOid>.<variableSubOid>.<indexOid>
The <tableOid> is the OID given to the table. The counterChangeTable has the OID enterprises.42.2, which is .1.3.6.1.4.1.42.2.
The <entrySubOid> is a single number assigned to the table entry. The CounterChangeEntry has the OID counterChangeTable.1, which means that <entrySubOid> is 1.
The <variableSubOid> is a single number which can be thought to be the column of a specific variable. The counterChangeEpoch has <variableSubOid> 1, while counterChangeTimeString has <variableSubOid> 3.
The <indexOid> is the concatenation of the index variables (counterChangeEpoch and counterChangeMilliseconds), converted to OID. For example, if counterChangeEpoch was 123456789 and counterChangeMilliseconds was 512, the index would be "123456789.512". The index can be thought of as the identifier of the row in which the entry resides. In terms of a relational database it would be called the primary key of the record.
Thus, the full OID of the counterChangeTimeString in the entry 123456789.512, for example, would be ".1.3.6.1.4.1.42.2.1.3.123456789.512".
A table is implemented by creating a Table object and tell it its <tableOid> (enterprises.42.2). An entry is implemented by subclassing TableEntry, such that the subclass initializes its TableEntry::subid to <entrySubOid> (i.e. 1 for counterChangeEntry). For our table, we will implement the CounterChangeEntry class which contains the four variables counterChangeEpoch, counterChangeMilliseconds, counterChangeTimeString and counterChangeCounterValue. The CounterChangeEntry class must implement the TableEntry::indexVariables() and TableEntry::variables() methods.
Each time an entry is added to the table, the table will query the new entry for all its variables and add these to the MasterProxy object, so that they are served like any other variable. If an entry is removed from the table, the table will remove its variables from the MasterProxy object again. Neither the table nor the entry can actually be queried by SNMP requests; they are only used to organize the variables which can be queried. Therefore, neither the table nor the entries are added to the MasterProxy object. Only the variables are added.
First of all, we implement the CounterChangeEntry class. Here is the full code of CounterChangeEntry.hpp
:
The class inherits TableEntry and has four members which represent the four SNMP variables. Since in agentXcpp those variables are handled as QSharedPointer's, the constructor needs to create all of them using the new
operator. The constructor also sets TableEntry::subid to 1.
Our MIB states that counterChangeEpoch and counterChangeMilliseconds are used as index. The CounterChangeEntry::indexVariables() method implements this by returning these variables in a QVector, in the order in which they are used in the index. Note that the entry's OID changes if the index variables change their value. Our simpleagent will not change an entry after it was added to the table, though.
The CounterChangeEntry::variables() method return all variables as a QMap. The map key will be used by the table as <variableSubOid> part for the variable.
Next, we extent the SimpleCounter class so that it adds an entry to the table each time the counter is changed. First of all, we need additional includes:
One thing to do is to add a Table member, a member to store the entry just inserted into the table (needed for undoset(), a suitable constructor and the helper function addEntry():
Next we have to replace the perform_get()
, perform_commitset()
and perform_undoset()
functions with variants adding and removing an entries to/from the table. Here is how they look like now:
And finally we update the main() function, where a table is instantiated and given to the SimpleCounter constructor. Here is the complete revised main()
function:
And that's all we need for our table. Next, let's try it out.
We didn't add implementation files in this tutorial, so no additional steps are needed for compiling. However, we have to recompile the whole thing. We know the following commands already from the previous tutorials:
Now, the executable is ready to run.
The subagent is run like described in the previous tutorials, simply by executing it:
Now, let's add some entries to the table by querying the simpleCounter variable:
And finally we print the table using the snmptable command from the NET-SNMP package, which requires that the MIB is installed properly:
If the MIB is not properly installed, or in case of programming errors, the snmptable command might return nothing. The snmpwalk command can help out in those cases, because it prints the complete subtree of a given OID, regardless whether it is a table or not:
Now you know all you need to implement your own SNMP subagents. Use the API documentation to get more help on the individual classes.
Happy Hacking!