MIB for Dummies
MIBs For Dummies
NOTE: this tutorial is based on the code for release 5.2. If you generate code from a release prior to 5.2 or with 5.3.x, your results may vary.
Power users or repeat readers, you may wish to jump to the list in the Getting Started node.
One of the questions that comes up on our mailing lists a lot is: I just ran mib2c on my MIB. What do I do now?. This tutorial, and the MIB for Dummies code approach, is designed to make your life as easy as possible. In this tutorial we'll walk you through the entire process of creating C code starting from only a MIB definition. This should answer this question for you, assuming you're willing to use the MIB for Dummies approach to coding.
One of the output code styles that mib2c supports is a style called "MIB for Dummies". This configuration file is called "local/mib2c.mfd.conf" and is what you would pass to the -c argument of mib2c to get it to produce the style of code this tutorial is going to discuss. It will generate mib-module code which is designed to plug into an agent when developing Net-SNMP agent extensions.
One of the primary motivations for writing the MIBs for Dummies configuration file was to reduce the amount of SNMP knowledge needed to implement a MIB. When using the MFD configuration file (mib2c.mfd.conf), mib2c will generate template code that hides much of the SNMP (or Net-SNMP) specific details for implementing a module, and allows the rest of the template code to use simple C data structures that you are probably more familiar with. Most of the template functions are short and simple, with a clearly defined purpose.
Another motivation was to separate the method used to locate the data for an incoming request to the SNMP agent from the methods that manipulate the data. The data lookup for MFD modules uses the netsnmp_container interface. Data ordering can be tricky in SNMP, since the lexicographical ordering required by SNMP specification isn't always intuitive, and the index specified in the MIB you have to implement might not match the order in which your data is currently stored. But the MFD configuration file can help you out with this problem too.
The templates generated by the MFD configuration file fall into several categories:
- data structures
- contains the data used to answer a request
- data lookup
- finding the right data for a request
- data manipulation
- either returning existing values or setting new values.
There are 4 important data structures used in an MFD module. They are the user context, the mib context, the data context and the the row request context.
The user context is a pointer provided by the user (ie, the programmer: You!) during module initialization. It is not used by the MFD code, other than to save it for the user. If your module needs access to some external data, you can use this pointer instead of a global variable.
The MIB context is a generated structure which is used to store the MIB indexes for a row.
The data context structure should contain all the data needed to get or set a value. The mib2c MFD configuration file will generate a data context structure with sufficient storage for all the objects defined in the MIB being implemented. (Later in the tutorial, we'll talk about using existing structures.)
Row Request Context
The row request context ties together all the other contexts. The table container will have a row request context for each row in the table.
There are lots of different ways to access data. Your data store may be a linked list, a text file, a database or an API to some device. Instead of trying to deal with all of the possiblities in the template code, the netsnmp_container interface is used.
There are currently two different interfaces between the MFD template code and the netsnmp_container used to locate data for a request.
The default method, which is generic enough to handle just about any situation, is the container-cached method. This combines two features of net-snmp: the cache handler and Containers (netsnmp_container). Quite simply, the first time a request is received for a table, a cache_load routine is called with a pointer to a netsnmp_container. That function accesses any datastore(s) that contain data, and adds all the rows to the container. The container is then used to find the rows for the incoming request, and (optionally) kept around a configurable number of seconds for future requests. While the basic principle is simple, the flexiblity makes this method the preferred method for most situations. More details on this cache helper can be found on the cache handler page.
The unsorted-external method is a wrapper around the netsnmp_continer container_iterator. This method is also very flexible, with a very simple interface. When a request is received for a table, a get_next routine is called repeatedly to loop over every item in the datastore(s). The agent will then select the appropriate row for the request. Due to the inefficency of this method, it is only recommended for small datastores, or when memory contraints are very tight and response times are not important.
For advanced users, a custom netsnmp_container can be used. This method lets you wrap a netsnmp_container around an existing datastore/access method. Be warned, however, that the implementation for the container's find-next method must handle SNMP's lexicographical ordering rules.
Once the appropriate data structure is retrieved from the data store, the data manipulation routines will be called. For each object defined in your mib module, a function will be generated to extract the data value from the data structure located during the data lookup phase.
For read-write objects, things get more complicated. SET requests are processed in multiple steps, so there are multiple functions generated for each object. These include functions for syntax checks, value checks, undo setup, value set and undo value set. Additionlly, several functions are generated per table, for tasks which only need to be performed once, even if multiple objects are set for a given index. Two examples are consistency checks and commit changes. All of these functions will be discussed in more detail later in the tutorial.
There are several example implementations. The process of implementing a module using the MFD configuration file can be broken down into several steps. You may wish to refer to this list in the future as it is a good high-level todo list for every module you implement.
- Generating the template code by running mib2c appropriately
- mib2c -c mib2c.mfd.conf
- Implementing a netsnmp_container for data access
- edit the MYTABLE.h file, adding column storage items to the MYTABLE_rowreq_ctx_s structure
- edit the MYTABLE_data_access.c file, filling out the MYTABLE_container_load function to load all the data into a container cache.
- (create multiple rowreq_ctx objects and insert them into the container)
- Implementing GET request functions
- In the MYTABLE_data_get.c file, fill out each COLUMN_get() to copy the data from your row_req cache object to the COLUMN_val_ptr objects.
- Implementing SET request functions
These examples are fairly basic, and should suffice for most simple MIB tables and data stores.
- MFD:IF-MIB Tables
These examples are sill fairly basic, but we are starting to add a few twists.
- TBD (set support; row creation?)
These examples show how to deal with complicated issues that sometimes come up while implementing MIB modules..
- TBD ...