I've written an AgentX app (Linux, gcc, g++) which works well at sending back scalers. Here is what I'm doing now:
init_agent( "blah" );
netsnmp_register_read_only_scalar( netsnmp_create_handler_registration( "foo1", handle_foo1, oid, oid.size(), HANDLER_CAN_RONLY ) );
init_snmp( "blah" );
while ( true )
{
// internal stuff
agent_check_and_process(1); // where 1==block
}
The functions like handle_foo1(...)
call snmp_set_var_typed_value(...)
to return the values which are cached in a global C struct within the application.
What I'm trying to do now is modify this code to also support a SNMP table. The content of the table is stored/cached as a STL container within the application. This is a relatively simple SNMP table, with consecutive rows, and all columns are composed of types like Integer32, Gauge32, InetAddress, and TruthValue. The problem is I don't see great code examples on the net-snmp web site, just a lot of doxygen pages.
My question:
What API should I be looking at? Are these the right calls:
netsnmp_register_read_only_table_data();
netsnmp_create_table_data();
netsnmp_create_table_data_row();
netsnmp_table_data_add_row();
...or is there something simpler I should be using?
I think the biggest pain when it comes to net-snmp is all those Doxygen pages the Google indexes but which provides near-zero usable content. Reading the .h
files is probably already obvious to most developers, and the truth is that net-snmp provides many different layers of APIs with very little documentation I found useful. What we need isn't several dozen identical copies of web sites hosting Doxygen, but instead some good examples.
In the end, the mib2c tool is how I got enough example code to get the whole thing working. I think I tried running mib2c with every single net-snmp .conf
file, and spent a lot of time reading the code it generated to get a better understanding. Here are the ones I found gave me the best hints:
The .conf
files are here: /etc/snmp/mib2c.*
Also useful were the following pages:
From what I understand, there are many helpers/layers available in the net-snmp API. So this example pseudocode may not work for everyone, but this is how I personally got my tables to work using net-snmp v5.4:
Variable needed across several functions (make it global, or a member of a struct?)
netsnmp_tdata *table = NULL;
Structure to represent one row of the table (must match the MIB definition)
struct MyTable_entry
{
long myTableIndex;
...insert one line here for each column of the table...
int valid; // add this one to the end
}
Initialize the table with snmpd
std::string name( "name_of_the_table_from_mib" );
table = netsnmp_tdata_create_table( name.c_str(), 0 );
netsnmp_table_registration_info *table_info = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info );
netsnmp_table_helper_add_indexes( table_info, ASN_INTEGER, 0 ); // index: myTableIndex
// specify the number of columns in the table (exclude the index which was already added)
table_info->min_column = COLUMN_BLAH;
table_info->max_column = MAX_COLUMN_INDEX;
netsnmp_handler_registration *reg = netsnmp_create_handler_registration( name.c_str(), MyTable_handler, oid, oid.size(), HANDLER_CAN_RONLY );
netsnmp_tdata_register( reg, table, table_info );
Handler to process requests
int myTable_handler( netsnmp_mib_handler *handler, netsnmp_handler_registration *reginfo, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests )
{
if ( reqInfo->mode != MODE_GET ) return SNMP_ERR_NOERROR;
for ( netsnmp_request_info *request = requests; request; request = request->next )
{
MyTable_entry *table_entry = (MyTable_entry*)netsnmp_tdata_extract_entry( request );
netsnmp_table_request_info *table_info = netsnmp_extract_table_info( request );
if ( table_entry == NULL ) { netsnmp_set_request_error( reqinfo, request, SNMP_NOSUCHINSTANCE); continue; }
switch ( table_info->colnum )
{
// ...this is similar to non-table situations, eg:
case COLUMN_BLAH:
snmp_set_var_typed_integer( request->requestvb, ASN_INTEGER, table_entry->blah ); break;
// ...
default: netsnmp_set_request_error( reqinfo, request, SNMP_NOSUCHOBJECT );
}
}
return SNMP_ERR_NOERROR;
}
Building/adding rows to the table
if ( table == NULL ) return; // remember our "global" variable named "table"?
// start by deleting all of the existing rows
while ( netsnmp_tdata_row_count(table) > 0 )
{
netsnmp_tdata_row *row = netsnmp_tdata_row_first( table );
netsnmp_tdata_remove_and_delete_row( table, row );
}
for ( ...loop through all the data you want to add as rows into the table... )
{
MyTable_entry *entry = SNMP_MALLOC_TYPEDEF( MyTable_entry );
if ( entry == NULL ) ... return;
netsnmp_tdata_row *row = netsnmp_tdata_create_row();
if ( row == NULL ) SNMP_FREE( entry ); .... return;
entry->myTableIndex = 123; // the row index number
// populate the table the way you need
entry->blah = 456;
// ...
// add the data into the row, then add the row to the table
entry->valid = 1;
row->data = entry;
netsnmp_tdata_row_add_index( row, ASN_INTEGER, &(entry->myTableIndex), sizeof(entry->myTableIndex) );
netsnmp_tdata_add_row( table, row );
}
Putting it together
In my case, that last function that builds the rows is triggered periodically by some other events in the system. So every once in a while when new stats are available, the table is rebuilt, all old rows are removed, and the new ones are inserted. I didn't bother trying to modify existing rows. Instead, I found it was easier to just rebuild the table from scratch.