This is a simple tutorial for implementing tables using NetSNMP. It is aimed at novices, but it does assume that you have read, understood and studied the basic tutorials especially the dynamically loadable object tutorial and the .
The tutorial comes with a very simple MIB (called SIMPLE-TABLE-MIB.my) that defines a table (indexed by an integer called devNumber) and 2 columns: blocksRead and blocksWritten. The structure of the table can be seen by running:
$ snmptranslate SIMPLE-TABLE-MIB::simpleTable -Tp
+--simpleTable(1)
|
+--simpleTableEntry(1)
| Index: devNumber
|
+-- ---- Unsigned devNumber(1)
+-- -R-- Unsigned blocksRead(2)
+-- -R-- Unsigned blocksWritten(3)
The first step is to either copy the SIMPLE-TABLE-MIB.my to a place where the agent can find it (/usr/local/share/snmp/mibs in my system) or tell the agent where to find the MIB by changing the mibDirs and mibs lines in your snmp.conf file. You can see where the agent expects to find MIBs by running:
net-snmp-config --default-mibdirs
(More information on this can be provided by the Using MIBs tutorial).
Let us make a first attempt of implementing this table using the MIBs-for-Dummies API (MfD). For more information on how this API works please visit MFD: IF-MIB page where the MfD data structures are explained.
Create a directory of your choice and cd into it.
Now create the template code by calling the mib2c program:
$ mib2c -c mib2c.mfd.conf SIMPLE-TABLE-MIB::simpleTable Defaults for simpleTable... writing to - There are no defaults for simpleTable. Would you like to 1) Accept hard-coded defaults 2) Set defaults now [DEFAULT] Select your choice :
Select 1 to accept hard-coded defaults and mib2c should generate the .c and header files for you. The next step is to decide on the build method: if you are writing a subagent then execute this command:
$ mib2c -c subagent.m2c SIMPLE-TABLE-MIB::simpleTable
The below will also generate a Makefile for you:
$ mib2c -c mfd-makefile.m2m SIMPLE-TABLE-MIB::simpleTable
In this tutorial we'll assume that you are writing a dynamically loadable object, so the above steps are optional. Since we are creating a dynamically loadable object, we need to create a Makefile that looks like the below (change paths where appropriate):
CC = gcc
CFLAGS = -I. `net-snmp-config --cflags` -fPIC
LDFLAGS= `net-snmp-config --libs` `net-snmp-config \
--agent-libs`
DL = simpleTable.so
OBJECTS = simpleTable_data_access.o simpleTable_data_get.o \
simpleTable_data_set.o simpleTable_interface.o \
simpleTable.o
all: $(DL)
$(DL): $(OBJECTS)
$(CC) $(CCFLAGS) $(LDFLAGS) -shared -o $@ $(OBJECTS)
$(OBJECTS): %.o :%.c
$(CC) -c $(CFLAGS) $< -o $@
clean:
rm *.o
Now we have a Makefile let's try and compile the code by typing:
$ make
I get the following output:
gcc -c -I. `net-snmp-config --cflags` -fPIC simpleTable_data_access.c -o simpleTable_data_access.o simpleTable_data_access.c: In function 'simpleTable_container_load': simpleTable_data_access.c:311: error: 'blocksRead' undeclared (first use in this function) simpleTable_data_access.c:311: error: (Each undeclared identifier is reported only once simpleTable_data_access.c:311: error: for each function it appears in.) simpleTable_data_access.c:318: error: 'blocksWritten' undeclared (first use in this function) make: *** [simpleTable_data_access.o] Error 1
So what's going on here? Shouldn't the code compile cleanly? The answer is no it shouldn't and you need to do some work to tell the code how and where to get the data from. The best starting point is to read the file simpleTable-README-FIRST.txt. If you are feeling really lazy and don't want to review all the changes (not recommended) you should at least implement the minimum, which is do all the mandatory changes (marked with 'M'):
:230:M: Implement simpleTable get routines. (simpleTable_data_get.c:18:) :240:M: Implement simpleTable mapping routines (if any). (simpleTable_data_get.c:19:) :350:M: Implement simpleTable data load (simpleTable_data_access.c:173:) :351:M: |-> Load/update data in the simpleTable container. (simpleTable_data_access.c:244:) :352:M: | |-> set indexes in new simpleTable rowreq context. (simpleTable_data_access.c:280:) :380:M: Free simpleTable container data. (simpleTable_data_access.c:364:)
In fact, since our table is so simple and only contains integers we can skip the implementations of the get and mapping routines - mib2c has already done the work for us. But we still need to load the data, so we need to visit simpleTable_data_access.c. A good advice is to actually spend some time reading the comments in the generated code. Mib2c includes a lot of comments to show you what the intended behaviour of the code is.
Let us move on and start editing simpleTable_data_get.c -- this is where you specify where your data comes from. It may come from a file, a database, the operating system, and so on. To keep things simple, we'll first generate some random data inside the program itself. Therefore, open the file and remove any references that are made to FILE. So you need to remove (or comment) these lines:
222 FILE *filep;
223 char line[MAX_LINE_SIZE];
227 /*
228 ***************************************************
229 *** START EXAMPLE CODE ***
230 ***---------------------------------------------***/
231 /*
232 * open our data file.
233 */
234 filep = fopen("/etc/dummy.conf", "r");
235 if(NULL == filep) {
236 return MFD_RESOURCE_UNAVAILABLE;
237 }
238
239 /*
240 ***---------------------------------------------***
241 *** END EXAMPLE CODE ***
242 ***************************************************/
233 while( 1 ) {
234 /*
235 ***************************************************
236 *** START EXAMPLE CODE ***
237 ***---------------------------------------------***/
238 /*
239 * get a line (skip blank lines)
240 */
241 do {
242 if (!fgets(line, sizeof(line), filep)) {
243 /* we're done */
244 fclose(filep);
245 filep = NULL;
246 }
247 } while (filep && (line[0] == '\n'));
248
249 /*
250 * check for end of data
251 */
252 if(NULL == filep)
253 break;
254
255 /*
256 * parse line into variables
257 */
258 /*
259 ***---------------------------------------------***
260 *** END EXAMPLE CODE ***
261 ***************************************************/
295 ***************************************************
296 *** START EXAMPLE CODE ***
297 ***---------------------------------------------***/
298 if(NULL != filep)
299 fclose(filep);
300 /*
301 ***---------------------------------------------***
302 *** END EXAMPLE CODE ***
303 ***************************************************/
304
Next you need to declare some variables that will hold your data. A variable for devNumber which is the table index has already been declared for you so you need to do the other two:
215 u_long devNumber; 216 u_long blocksRead; 217 u_long_blocksWritten;
Next you need to load the data -- to keep things simple we just generate some data.
/*
228 * TODO:351:M: |-> Load/update data in the simpleTable container.
229 * loop over your simpleTable data, allocate a rowreq context,
230 * set the index(es) [and data, optionally] and insert into
231 * the container.
232 */
233 int i;
234 // Let's insert 4 rows
235 for (i=0; i < 4; i++)
236 {
237 /* Table Index */
238 devNumber = i;
239 /* Blocks Read */
240 blocksRead = 2<
You also need to delete the continue statement from this block of code:
}
if(MFD_SUCCESS != simpleTable_indexes_set(rowreq_ctx
, devNumber
)) {
snmp_log(LOG_ERR,"error setting index while loading "
"simpleTable data.\n");
simpleTable_release_rowreq_ctx(rowreq_ctx);
/* Delete the below */
//continue;
}
Compile again with 'make' and you shouldn't get any errors:
$ make
gcc -c -I. `net-snmp-config --cflags` -fPIC simpleTable_data_access.c -o simpleTable_data_access.o
gcc -c -I. `net-snmp-config --cflags` -fPIC simpleTable_data_get.c -o simpleTable_data_get.o
gcc -c -I. `net-snmp-config --cflags` -fPIC simpleTable_data_set.c -o simpleTable_data_set.o
gcc -c -I. `net-snmp-config --cflags` -fPIC simpleTable_interface.c -o simpleTable_interface.o
gcc -c -I. `net-snmp-config --cflags` -fPIC simpleTable.c -o simpleTable.o
gcc `net-snmp-config --libs` `net-snmp-config --agent-libs` -shared -o simpleTable.so simpleTable_data_access.o simpleTable_data_get.o simpleTable_data_set.o simpleTable_interface.o simpleTable.o
Now it's time to test your work. First add a line that loads the shared object to your snmpd.conf. For example:
# Load simpleTable .so
dlmod simpleTable /path/to/so/simpleTable.so
Restart the agent in the foreground and with debugging turned on:
# /usr/local/sbin/snmpd -Dverbose:simpleTable -f
No log handling enabled - turning on stderr logging
registered debug token verbose:simpleTable, 1
Turning on AgentX master support.
verbose:simpleTable:init_simpleTable: called
verbose:simpleTable:initialize_table_simpleTable: called
verbose:simpleTable:simpleTable_init_data: called
verbose:simpleTable:simpleTable_container_init: called
NET-SNMP version 5.5
verbose:simpleTable:simpleTable_container_load: called
verbose:simpleTable:simpleTable_rowreq_ctx_init: called
verbose:simpleTable:simpleTable_indexes_set: called
verbose:simpleTable:simpleTable_indexes_set_tbl_idx: called
verbose:simpleTable:simpleTable_index_to_oid: called
verbose:simpleTable:simpleTable_rowreq_ctx_init: called
verbose:simpleTable:simpleTable_indexes_set: called
verbose:simpleTable:simpleTable_indexes_set_tbl_idx: called
verbose:simpleTable:simpleTable_index_to_oid: called
verbose:simpleTable:simpleTable_rowreq_ctx_init: called
verbose:simpleTable:simpleTable_indexes_set: called
verbose:simpleTable:simpleTable_indexes_set_tbl_idx: called
verbose:simpleTable:simpleTable_index_to_oid: called
verbose:simpleTable:simpleTable_rowreq_ctx_init: called
verbose:simpleTable:simpleTable_indexes_set: called
verbose:simpleTable:simpleTable_indexes_set_tbl_idx: called
verbose:simpleTable:simpleTable_index_to_oid: called
verbose:simpleTable:simpleTable_container_load: inserted 4 records
Test it:
$ snmptable -v 2c -c public localhost SIMPLE-TABLE-MIB::simpleTable
SNMP table: SIMPLE-TABLE-MIB::simpleTable
blocksRead blocksWritten
2 4294967293
4 4294967291
8 4294967287
16 4294967279
Now you got the basic functionality, let's try and read the numbers from the file. At this stage,
all you need to do is change the loop so the numbers are read from a file. If you want to create
some semi-realistic information per time instance instead of per device use this command:
iostat -d 5 10 | perl -wanle '/sda/ and print "$. @F[-2,-1]"'
If you are not too pedantic with contiguous index numbers, stick the output in a file for example
/tmp/iostat.txt. Essentially all you have to change is the file name in simpleTable_data_access.c:
236 filep = fopen("/tmp/iostat.txt", "r");
And add a line that gets the data (shown in lines 267-270 below). You also need to declare
variables for blocksRead, blocksWritten.
241 /*
242 ***---------------------------------------------***
243 *** END EXAMPLE CODE ***
244 ***************************************************/
245 /*
246 * TODO:351:M: |-> Load/update data in the simpleTable container.
247 * loop over your simpleTable data, allocate a rowreq context,
248 * set the index(es) [and data, optionally] and insert into
249 * the container.
250 */
251 while( 1 ) {
252 /*
253 ***************************************************
254 *** START EXAMPLE CODE ***
255 ***---------------------------------------------***/
256 /*
257 * get a line (skip blank lines)
258 */
259 do {
260 if (!fgets(line, sizeof(line), filep)) {
261 /* we're done */
262 fclose(filep);
263 filep = NULL;
264 }
265 } while (filep && (line[0] == '\n'));
266 // Get data from the file
267 sscanf(line, "%d %d %d\n",
268 &devNumber,
269 &blocksRead,
270 &blocksWritten);
271
272 /*
273 * check for end of data
274 */
275 if(NULL == filep)
276 break;
277
278 /*
279 * parse line into variables
280 */
281 /*
282 ***---------------------------------------------***
283 *** END EXAMPLE CODE ***
284 ***************************************************/
If you are implementing more complex tables with read-write columns and other types, more work is
required. Two good examples to look at are:
A little advice on other data types:
If you are using Counter64: You need to use the U64 type, which is struct containing low and high 32-bits of your 64-bit counter. Assumming you have your data stored in a 64-bit variable (uint64_t, long long, whatever) you can do the following to populate your Counter64:
U64 u_processedPackets;
u_processedPackets.low = processedPackets & 0xffffffff;;
u_processedPackets.high = processedPackets >> 32;
If you are using InetAddress: InetAddress must be exactly 4 bytes (if you are using IPv4), you have the option of using the inet_addr function (from <arpa/inet.h>) to give you a 4-byte in_addr_t containing the IP address in bytes. Once you get the address in the appropriate 4-byte format, you need to edit the file named xxxxxx_data_get.c, find the int xxxxx_get function that represent the extraction of the IP address and remove the return MFD_SKIP; line after you performed any necessary checks to ensure the address in the right format.
If you are using DateAndTime: DateAndTime must be either 8 or 11 bytes. So you have a choice of either setting manually year, month, day, hour, minutes, seconds, deci_seconds and calling netsnmp_dateandtime_set_buf_from_vars passing these values or using a char* variable that gets assigned from calling the date_n_time function. Of course this assumes that you have the date and time in UNIX time format.
time_t dm; // Get the dateandtime from some source that'll return in UNIX format
// Convert date to u_char
size_t len;
dateModified = date_n_time(&dm, &len);
As with IP addresses, once you get the DateAndTime in the appropriate format, you need to edit the file named xxxxxx_data_get.c, find the int xxxxx_get function that represent the extraction of the DateAndTime and remove the return MFD_SKIP; line after you performed any necessary checks to ensure the DateAndTime in the right format.
Disclaimer: I'm not affiliated with NetSNMP nor am I an NetSNMP expert. I'm just documenting the procedures that worked for me. If these procedures are incorrect please let me know by emailing perez dot angela7 at gmail dot com.