Looking for example code to implement a SNMP table using AgentX
Asked Answered
K

1

8

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?

Ked answered 23/3, 2012 at 23:43 Comment(1)
Are you writing your agent from scratch or using mib2c? mib2c can generate the whole skeleton for you. After that, converting it to a subagent is easy. Have you arleady looked at "net-snmp.sourceforge.net/wiki/index.php/TUT:Writing_a_Subagent"?Mudlark
K
20

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:

  • mib2c -c mib2c.create-dataset.conf MyMib
  • mib2c -c mib2c.table_data.conf MyMib

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.

Ked answered 6/4, 2012 at 2:44 Comment(2)
If you would share a working example of a simple-to-the-bone table in a GitHub repository, you would be our hero, especially the last part where "[...] that last function that builds the rows is triggered periodically by some other events in the system".Serriform
I posted this almost 10 years ago! I don't work at that company any more and don't have the source code. Nor would they allow it to be posted publicly. But if you want to hire me, feel free to get in touch with me.Melanosis

© 2022 - 2024 — McMap. All rights reserved.