SNMPD connector library basics

The following example code, taken from the SNMPD-SMARTCTL extension for harddisk SMART Data, demonstrates the basic use of the snmpd-connector-lib library.

Including the library

The first step toward using the snmpd-connector-lib library is to source it into your script.

Listing 1
  1. #!/bin/bash -u
  2.  
  3. # Include snmpd-connector-lib.sh or die.
  4. [[ ! -r ${SNMPD_CONNECTOR_LIB:=/usr/lib/snmpd-connector-lib.sh} ]] && echo "Unable to find ${SNMPD_CONNECTOR_LIB}" && exit 1
  5. source ${SNMPD_CONNECTOR_LIB}
Example code demonstrating loading the snmpd-connector-lib library

As you can see the recommended method of including the snmpd-connector-lib library allows the location of the script file to be specified at run-time using the SNMPD_CONNECTOR_LIB variable. This may be useful if you wish to modify or extend the library yourself or if the library is installed in a non-standard location.

Defining the MIB structure

The second step is to define the MIB structure as a series of one or more Bash arrays and declare the BASE_MIB variable, as shown in the example below.

Listing 2
  1. # Configure base address
  2. BASE_MIB="SMARTCTL-MIB::smartCtl"
  3.  
  4. # Declare the tables
  5. RTABLE[2]="#ETABLE"
  6.     ETABLE[1]="#FTABLE"
  7.         FTABLE_INDEX="get_next_index"                               # Internal function to get next index
  8.         FTABLE[1]="send_index"                                      # Index request.
  9.         FTABLE[2]="send_device_path"                                # Mapped device path.
  10.         FTABLE[3]="send_device_info 'Model Family:'"                # Device model-family.
  11.         FTABLE[4]="send_device_info 'Device Model:'"                # Device device-model.
  12.         FTABLE[5]="send_device_info 'Serial Number:'"               # Device serial number.
  13.         FTABLE[6]="send_device_info 'User Capacity:'"               # Device user capacity.
  14.         FTABLE[7]="send_device_info 'ATA Version is:'"              # Device ATA version.
  15.         FTABLE[8]="send_device_hlth"                                # Overall SMART health state.
  16.         FTABLE[9]="send_device_attr 'Temperature_Celsius' R"        # Device temperature.
  17.         FTABLE[10]="send_device_attr 'Reallocated_Sector_Ct' R"     # Reallocated Sector count.
  18.         FTABLE[11]="send_device_attr 'Current_Pending_Sector' R"    # Current Pending Sector count.
  19.         FTABLE[12]="send_device_attr 'Offline_Uncorrectable' R"     # Off-line Uncorrectable count.
  20.         FTABLE[13]="send_device_attr 'UDMA_CRC_Error_Count' R"      # UDMA CRC Error count.
  21.         FTABLE[14]="send_device_attr 'Read_Error_Rate' L"           # Read Error Rate (lifetime).
  22.         FTABLE[15]="send_device_attr 'Seek_Error_Rate' L"           # Seek Error Rate (lifetime).
  23.         FTABLE[16]="send_device_attr 'Hardware_ECC_Recovered' L"    # Hardware ECC recovered (lifetime).
Example code demonstrating definition of MIB structure as arrays

As the above example demonstrates the BASE_MIB variable should be set to the root element to which this extension agent should be bound. Each entry in the RTABLE (root table) array will appear directly beneath the element specified in BASE_MIB.

To declare another level of elements beneath the root entries (in this case corresponding to the smartCtlEntry node of the smartCtlTable table) another array may declared and entered into the parent table by prefixing the array name with a # symbol.

Finally, the FTABLE array in the above example provides an index function (declared by appending _INDEX to the table name) and a list of functions which may be called (with an index as a parameter) by the extension agent to obtain the data values represented by this MIB from the system.

Define an index function

When declaring a table, as opposed to a list of objects, an index function is required to enable the snmpd-connector-lib library firstly to detect that this is in fact a table and secondly so that the correct index values may be automatically returned when presented with an appropriate GETNEXT request. As you can see from the example below the function will usually be fairly trivial.

Listing 3
  1. # Function to get the next index value
  2. #
  3. #   @in_param   $1 - The (optional) starting index value
  4. #   @echo          - The new index, or nothing if out of range
  5. #
  6. function get_next_index
  7. {
  8.     # If we were passed a starting index...
  9.     if (( $# > 0 )); then
  10.         # If the passed index is less than the number of devices then return it +1,
  11.         # otherwise return nothing to indicate that the index would be out of range.
  12.         if (( ${1} < ${#DEVICES[@]} )); then
  13.             echo $(( ${1} + 1 ))
  14.         fi
  15.         return
  16.     fi 
  17.    
  18.     # If we got this far then we were not passed an index so return the first
  19.     # available index.
  20.     echo "1"
  21. }
Example code demonstrating the use of a table index function

The index function may be called by the snmpd-connector-lib library in two distinct modes. In the first mode no index will be supplied indicating that the first index is requested. In the second mode an index value will be supplied indicating that the next index value should be returned. If the next index would be out of range then nothing should be returned. Any return value should be sent to stdout for capture.

Information:
The Hacking Networked Solutions library for Bash provides a range of useful functionality which can often ease the development of Bash scripts. Of particular interest to developers of SNMPD extension agents will be the array manipulation functions, including the get_next_array_index fucntion.
 

Implement MIB specific functions

The fourth, and most complex step, is to implement the MIB specific functions which we have named when we declared the MIB objects in step two above. These functions will be called automatically by the snmpd-connector-lib library (with the appropriate index parameter if they are part of a table) and are responsible for retrieving the data from the system and sending it to the requesting agent (usually using one of the helper functions provided in the snmpd-connector-lib library for this purpose).

Listing 4
  1. # Function to send the index value
  2. #
  3. #   @in_param   $1 - The OID to send before this data
  4. #   @in_param   $2 - The index value
  5. #
  6. function send_index
  7. {
  8.     # If we were passed an index...
  9.     if (( $# > 1 )); then
  10.         # ...and the index is in range...
  11.         if (( ${2} <= ${#DEVICES[@]} )); then
  12.             # Send the OID and the index number.
  13.             send_integer ${1} ${2}
  14.             return
  15.         fi
  16.     fi
  17.  
  18.     # Send the OID and NONE.
  19.     send_none ${1}
  20. }
Example code demonstrating the implementation of a MIB specific function

The above example implements a simple function to return the index of the queried entry in a table. As you can see the function simply checks that the supplied index value is in range and, if it is, sends it back to the requesting agent using the send_integer function. Should the supplied index not be in range then this is indicated to the requesting agent using the send_none function.

Caution:
It is considered best practice to check the supplied index value for validity in every MIB specific function, or to handle errors in some other way, to ensure that the send_none helper function is called should the supplied index be out of range. If a MIB specific function returns without sending any data to the requesting agent a deadlock will occur and no further requests will be processed.
 

Start the loop

Finally, the last step is to call the main entry point in the snmpd-connector-lib library.

Listing 5
  1. # Start the loop
  2. the_loop
Example code demonstrating execution of the functional loop

As you can see from the above example this is easily accomplished with a single call to the_loop which will start the extension agent listening for requests.