Chapter 4. Developing an SNMP Agent


This chapter explains how to develop an SNMP agent or SMUX subagent to manage a system or network resource. It includes the following sections:

Overview of the Development Process

The Agent Development Kit lets you build an SNMP agent to support manageable features of a system or network resource defined in your own MIB (private or not private). The steps involved in developing an SNMP agent or SMUX subagent, using the Agent Development Kit, follow:

  1. Research and conceptualize, or form a model of, the system or network resource to be managed. You will want to select, or pinpoint, those features of the resource that are pertinent to system and network management goals.

  2. Define the MIB for the objects to be managed.

    This step involves assigning variable names to each distinct manageable item, and determining and assigning the ASN.1 data types. The MIB must be written in ASN.1 notation. Refer to RFC 1212 (Concise MIB Definitions) for information about the syntax of defining MIB groups. Before defining a MIB, you may wish to obtain your own enterprise object identifier (OID). Refer to Appendix A, "SNMP Information," in the Agent Integrator Reference Manual for instructions on how to obtain an enterprise OID.

    One approach to writing a MIB is to start with an existing MIB, selecting features similar to those you want, and modifying the entries appropriately. Accordingly, you might want to look at some examples, such as standard MIBs or the BEA MIBs in the bea.asn1 file. You must define your MIB in the file my.asn1. You can edit this file with any text editor, such as vi. The path name of the file is:

    $RELEASE_ROOT/agent_src/agentlib/my.asn1

    The Agent Development Kit code generator (imibgenall) checks for correct ASN.1 syntax and generates appropriate messages if it encounters syntax errors.

  3. The application or component being managed requires instrumentation that provides information in a form that can be accessed by the agent or subagent.

    The information must be provided in a way that matches the definitions in the MIB. For example, a queueing system might contain a counter that collects data on the number of current entries in a queue. An application programming interface (API) function call might provide this information in the form of an integer value.

  4. Define your environment (if you have not yet done so)

    The following is an example of how to define the working environment on a UNIX platform, using C shell commands:

    setenv RELEASE_ROOT install_dir
    set path = ($RELEASE_ROOT/bin $path)

    The following is an example of how to define the working environment on a Windows NT system:

    set RELEASE_ROOT = install_dir
    set path = %RELEASE_ROOT%\bin;%PATH%

    All other necessary environment settings are set automatically.

  5. Run the MIB compiler/code generator (imibgenall) on a selected table or MIB group in your MIB file.

    The MIB compiler generates function skeletons to help you build your SNMP agent. The compiler also creates other source files, which you do not need to modify, which provide "hooks" to the access functions. The compiler also updates the makefiles that are needed to build the agent. To do this you use the compiler command:

    imibgenall MIBRootName

    MIBRootName designates the target MIB component. (Refer to Chapter 3, "Tools and Functions," for the complete syntax of this command.)

    The target MIB component (MIBRootName) must not be a group of groups but either a table or, for scalar objects, their ancestor group (i.e., the group that contains the scalar objects). If you have multiple groups of scalar objects or both scalar objects and tables, you will need to run the compiler multiple times. You run the compiler once for each group that contains scalar objects and once for each table. The MIB compiler expects to find the MIB definitions in the file my.asn1.

    Figure 4-1 shows an example of a private MIB. In this example, the objects contained in the groups beaSecParam and beaSecStats (objects a through g) are scalar objects whereas beaSecMgrTable and beaSecUserTable are tabular objects in the group beaSec. Objects h through n are columnar objects (i.e., objects that can have multiple scalar-valued instances).

    Figure 4-1 Sample Private MIB Tree

    The imibgenall utility must be run four times to create four agent hook files, their corresponding access function files, and other files. This could be accomplished by entering the following four commands:

    imibgenall beaSecParam
    imibgenall beaSecStats
    imibgenall beaSecMgrTable
    imibgenall beaSecUserTable

  6. Flesh out the generated function skeletons by adding code to access the data in the component or application being managed.

    To do this you edit the source file MIBRootName.c generated by the MIB compiler. To flesh out the generated function skeletons, you use the instrumentation in the managed resource (referred to in step 3) to access the manageable features of the resource, to respond to SNMP GET and SET commands. Be sure that you create the file skeletons in the correct Agent Development Kit directory: $RELEASE_ROOT/agent_src/agentlib.

    The specific work required to carry out this step is described in "Adding Your Custom Code to Generated Files." The function skeletons and related components in the generated source files are described in Chapter 5, "Generated Access Function Templates."

  7. Do stand-alone testing of the access functions.

    Once you have written the access functions (step 5), you can test them in stand-alone fashion, before linking with the rest of the agent code. To do this, compile the source file with the following command:

    cd $RELEASE_ROOT/agent_src
    build_agent -r
    MIBRootName

    This builds an executable named csnmp_MIBRootName. This executable invokes the access functions and prints the values that are retrieved. To run this executable, enter the following command:

    cd $RELEASE_ROOT/agent_src/agentlib
    ./csnmp_
    MIBRootName

  8. Build the SNMP agent executable.

    Use the following command to build the agent:

    build_agent -n YourSNMPAgent

    The build_agent command is interactive and prompts you for the following information:

  9. Test the SNMP agent using the SNMP test utilities.

    The snmpwalk and snmptest utilities can be used to test the agent, as described in "Testing the Agent."

  10. Integrate your private MIB with an SNMP management platform and test the agent with the management system.

    You need to follow the vendor instructions of your SNMP manager to integrate a new MIB into the manager.

Figure 4-2 illustrates the three basic phases in the development process.

Figure 4-2 Writing, Building and Testing an Agent

Figure 4-3 illustrates the detailed steps of the agent development process.

Figure 4-3 Agent Creation Flowchart

Adding Your Custom Code to Generated Files

You create an access functions skeleton file and header file for each MIB group or table by running the MIB compiler/code generator (imibgenall). You need to run the compiler once for each group or table, by entering the command:

imibgenall MIBRootName

where MIBRootName is the name of a table or a group that contains scalar objects. The compiler expects to find your MIB definitions in the file my.asn1.

Be sure to generate the source files in the correct Agent Kit directory: $RELEASE_ROOT/agent_src/agentlib.

The generated files that need to be modified are:

MIBRootName.c - generated access functions file

MIBRootName.h - generated header file

The access function skeletons and other components of these generated files are described in Chapter 5, "Generated Access Function Templates."

Once you have generated these source files for a MIB group, you need to edit the files to carry out the following tasks:

  1. Modify the constant definitions in the header file, if necessary.

    The header file generated for each MIB group or table defines a constant that determines the maximum length for read-only and read/write string objects in the agent cache - MAX_ReadOnlyStringObjectName. By default these values are set to 256. You may want to modify these values.

    One of the constant values that gets defined for tabular MIB groups is INIT_MIBRootName_ENTRIES. By default, the value of this constant is 10. This constant represents the number of the entries in the cache for a table, space for which is allocated at initialization time. Another constant, DELTA_MIBRootName_ENTRIES, represents the number of new row entries for which space will be allocated if the currently allocated space for the cache table runs short.

    You should set the INIT_MIBRootName_ENTRIES constant to the typical value of the number of entries in the table. For example, if you are writing a MIB for a process table, a good value of INIT_MIBRootName_ENTRIES can be 200 and if you are writing a MIB for disks, a good value of INIT_MIBRootName_ENTRIES would be 10. If you choose a big number for this constant, you will end up wasting memory. If you choose a small number, the SNMP agent may end up doing multiple allocations, which may fragment memory.

  2. Use resource instrumentation in the refresh function to update the agent cache.

    When the agent receives an SNMP GET or GETNEXT request from a management station, it calls the generated get_ObjectName functions to send back the current value of the object. The generated access functions file contains one such get_ObjectName function for each object in the MIB group or table. These get_ObjectName functions do not access the managed resource but retrieve the current value from the agent cache. Normally, you do not need to modify the get_ObjectName functions.

    The agent tracks the age of the cache. Before calling the get_ObjectName functions, the agent checks to determine whether the cache is older than the refresh rate. If it is older, the agent calls the function refresh_MIBRootName before calling the get_ObjectName functions for that MIB group. This refresh function updates the agent cache by directly accessing the managed resource - for example, using an API or system calls - to obtain current values of the attributes of the managed resource. Accordingly, you must edit the refresh_MIBRootName function to add the code that directly accesses the managed resource and updates the object values in the agent cache.

    For example, if your MIB group has a scalar object myQEntryCount under the myQ MIB group, there will be a line in the generated code such as the following:

    v_myQ.val_myQEntryCount = dummy_value;

    The object values for a given MIB group are cached in the record v_MIBRootName - in this example, v_myQ. This record is an instance of a structure of type s_MIBRootName, defined in the header file. You will need to replace dummy_value with the code that provides the actual value accessed from the managed resource - in this example, this might involve a call to the queue system's API.

    For tabular objects, there can be zero or more rows (entries) in the table. Thus, you need to use some sort of loop control to fill out the values corresponding to each row in the table. This loop control requires some index that indicates the number of rows in the table. The dummy loop control code generated in the access functions file for tables looks something like this:

    int row_num;
    for (i=0; i<2; i++)
    {/* This dummy code assumes 2 rows in the table */
    /* The following statement increments the size of the table, if required */
    if (!create_MIBRootName_entry(&row_num)) return SNMP_FAILURE;

    A function create_MIBRootName_entry() is generated for each tabular object. This function creates a row in a table. The function returns an index of the newly created row. You should use this index to fill out the values of the objects in this row in the cache table. You must call create_MIBRootName_entry() each time in the control loop to create a row. This function should not be called from anywhere else in the agent program. Otherwise, the results are undefined. Before refresh_MIBRootName() is called by the SNMP agent, earlier rows are discarded. You can fill in the rows in the cache in any order. The SNMP agent automatically arranges them in lexicographic order in replying to the management station, as required by the SNMP standard.

  3. Modify the refresh rate, if necessary.

    The agent maintains an internal cache of the values of the managed objects for each of its MIB groups. The agent tracks the time since the last refresh of the cache. When the agent receives an SNMP GET or GETNEXT request from a management station, it takes the value from the cache if the cache is not older than the refresh rate, otherwise it invokes the refresh function to update the cache. The refresh function uses the instrumentation in the managed resource to update the agent cache that maintains current values for the managed objects accessed by the agent. The refresh rate is specified by the constant MIBRootName_refresh_rate. By default this value is set to 10 seconds. This is set by the following line in the generated access functions file:

    int MIBRootName_refresh_rate = 10 /* seconds */;

    You may want to increase this value if the object values in the managed resource do not change very quickly. If the value of MIBRootName_refresh_rate is set to zero, the refresh function is called every time an object is queried by a management station. The refresh function is not invoked if no requests are received by the agent for the MIB group objects.

  4. Flesh out the initialization function for the MIB group.

    An initialization function skeleton init_MIBGroupName is generated for each MIB group or table. This function is called once by the agent the first time any object in the MIB group is polled. You should fill in this function with any required initialization code. This function returns SNMP_SUCCESS if successful, and SNMP_FAILURE otherwise. If this function returns a failure, the agent or subagent always returns "NO SUCH NAME" for any object in this MIB group. It is recommended that your code print a reason for failure when failures occur.

  5. Edit the test functions to check values in set requests to ensure that the value is valid.

    If an SNMP agent receives an SNMP SET request containing more than one variable, either all objects are set or none are set. This behavior is known as atomic set and is one of the requirements of the SNMP standard. Atomic sets are supported using a two-phase commit. In order to implement this, for each read-write object a test and a set function is generated as part of the access functions file. On receiving a SET request from the management station, the SNMP agent first invokes the test function corresponding to each object involved in the SET request. If all test functions return success, only then does the agent invoke the set functions corresponding to these objects.

    For each scalar or columnar object that is defined as read-write in the MIB, a test_ObjectName function skeleton is generated in the access functions file. You must flesh out each of these functions with code that checks to determine whether the value in the SET request is valid for the target attribute in the managed resource. The test function should return success only if the requested set value is valid for the target.

  6. Use appropriate access methods (e.g., APIs or system calls) in set functions to modify attributes of the managed resource.

    To support SNMP SET requests, a set_ObjectName function is generated in the access functions file for each MIB object that is defined as read-write. You need to add the necessary code to directly access the managed resource, such as API function calls. See "Row Deletion" and "Row Addition" in "Additional Programming Guidelines" for supporting SET requests for addition or deletion of rows in tables.

Additional Programming Guidelines

The following points may be useful for writing the access functions code. In the following sections, MIBRootName refers to the root name of the MIB tree for which the C code is generated using imibgenall.

Community

If the agent is working as an SNMP agent, access functions can access the SNMP community used by the management station (if needed). The community is made available as an extern character array sid. The integer sidlen contains the length in bytes.

Defining Keywords in beamgr.conf

The SNMP agent uses a configuration file, beamgr.conf. Each record in beamgr.conf is separated by a newline character. Each record represents a keyword-value pair. The SNMP agent uses this file for maintaining certain configuration parameters and MIB object values. As the implementor of a MIB, you can use the same file for defining your own keywords.

To facilitate the use of this file, two APIs, csam_get_keyword() and csam_set_keyword(), are provided. csam_get_word() fetches the value corresponding to a given keyword and csam_set_keyword() sets the value corresponding to a keyword in beamgr.conf.

For more details about these two APIs, please refer to Chapter 3, "Tools and Functions."

Sample Traps Program

The Agent Development Kit includes APIs for generating traps from your SNMP agent. The trap APIs are described in Chapter 3, "Tools and Functions." The directory $RELEASE_ROOT/agentsrc/agentlib/ contains a sample program, snmp_appl.c, illustrating the use of the SNMP trap APIs. A sample makefile, sample.mk, is also provided in the same directory to compile the sample program.

Row Deletion

In order to support row deletion capability, you must define an integer-valued read-write columnar object, which represents the status of the row. This object minimally has two valid values representing valid and invalid status. When the management station sets the value of this object to invalid, the effect should be of invalidating the corresponding row from the table.

To help the implementation of row deletion effects, an API delete_MIBRootName_entry() is provided. This function deletes a row from cache maintained by SNMP agent for the MIBRootName MIB group.

It should be noted that delete_MIBRootName_entry() only deletes the row from cache and has no effect on the source of the MIB information (e.g., kernel memory). In order to remove a row from the actual place of MIB information, you should take specific action inside the SET function corresponding to this MIB object, which is generated as part of the access functions file.

The function delete_MIBRootName_entry() expects the row index to be deleted, as an argument. This row index is the one that is passed to the set routine. delete_MIBRootName_entry() returns SNMP_SUCCESS, if the row deletion is successful, else it returns SNMP_FAILURE.

Note: This function must be called only from a set routine generated as part of the access functions file. If it is called from any other place, results are undefined.

Row Addition

In order to create a new row, the management station issues a SNMP set request containing one or more variables that refers to an object instance not currently available in the SNMP agent.

In order to support row creation, two functions, test_MIBRootName_create() and set_MIBRootName_row(), are created as part of the access functions file.

These two functions are called when a SET request is received for a non-existent row. First, all values corresponding to different columnar objects are stored in the cache, then test_MIBRootName_create() is called to check if the values are acceptable. If it returns success, set_MIBRootName_row() is called. set_MIBRootName_row() should always return success for atomic sets to work correctly.

Both of these functions are passed the index of the newly created row in the cache.

If test_MIBRootName_create() returns failure, row creation is assumed to have failed and the newly created row is removed from the cache.

It should be noted that a set request from the management station may not contain all columnar objects. It is the responsibility of the test_MIBRootName_create() function to figure out if this set of columnar objects is sufficient to create a row. The objects that are received in the set request are indicated by an SNMP_VALID value of their corresponding status. In the process, programmers may assign some suitable default values to other columnar objects in test_MIBRootName_create() and set_MIBRootName_row_create().

Testing Your Custom Code

To test the access function file MIBRootName.c, compile it as stand-alone code to bypass the SNMP requests. To do this, enter the following command:

cd $RELEASE_ROOT/agent_src
build_agent -r MIBRootName

This generates the file:

$RELEASE_ROOT/agent_src/agentlib/csnmp_MIBRootName

To run the executable, enter:

cd $RELEASE_ROOT/agent_src/agentlib/
./
csnmp_MIBRootName

This executes the get functions and prints the retrieved values. This ensures that the access functions work as a stand-alone utility before linking it with the rest of the SNMP agent code.

Building the Agent Executable

Follow these steps to build the agent source code:

  1. Change to the directory containing the agent source code:

    cd $RELEASE_ROOT/agent_src

  2. Execute the build_agent command:

    build_agent -n SNMPAgentName

    The build_agent command prompts you for additional information:

    The build_agent command creates the agent executable in the file SNMPAgentName in the following directory:

    $RELEASE_ROOT/agent_src/agentlib

  3. This step applies to UNIX platforms only: if you want to run the agent executable as a stand-alone SNMP agent (-s option) after building the agent executable, do the following:

  4. Add the following services to your /etc/services files or to the services file on your yp host, if you are running yellow pages.

    snmp

    161/udp

    snmp-trap

    162/udp

  5. Run the agent:

    ./SNMPAgentName -s

    The above command starts SNMPAgentName as an SNMP agent. If you want to run SNMPAgentName as a SMUX subagent, do not enter the -s command-line option. But in that case, a SMUX master agent or Agent Integrator should already be running.

    The beamgr.conf file can be placed in the /etc directory, or you can set the BEA_SM_BEAMGR_CONF environment variable to point to this file so the agent can send a coldstart trap when it starts up. The default host is the local host.

    For more details about command-line options of newly created agents, please refer to Chapter 5, "Starting the Subagents," in the Agent Integrator Reference Manual.

Object Identifier Lists

The sorted_oid_list and ascii_oid_list files are automatically generated whenever an SNMP agent is built using the build_agent utility. These files contain a sorted cross-reference mapping managed object MIB names to OIDs.

Sample sorted_oid_list

Listing 4-1 is an example of the file sorted_oid_list, containing the beaEx example MIB objects.

Listing 4-1 sorted_oid_list
..
beaTrapHost: 1.3.6.1.4.1.140.1.8
beaTrapCommunity: 1.3.6.1.4.1.140.1.9
beaEx: 1.3.6.1.4.1.140.100
beaExIntRO: 1.3.6.1.4.1.140.100.1
beaExIntRW: 1.3.6.1.4.1.140.100.2
beaExStrRO: 1.3.6.1.4.1.140.100.3
beaExStrRW: 1.3.6.1.4.1.140.100.4
beaUnix: 1.3.6.1.4.1.140.2
beaPsTable: 1.3.6.1.4.1.140.2.1
..

Sample ascii_oid_list

Listing 4-2 is an example of the file ascii_oid_list, containing the beaEx example objects. In this example, the file contents look the same because object names and object identifiers are alphabetically and numerically in the same order.

Listing 4-2 ascii_oid_list
..
beaEmProcsEnvtVar: 1.3.6.1.4.1.140.4.3
beaEmShmAllocated: 1.3.6.1.4.1.140.4.4
beaEx: 1.3.6.1.4.1.140.100
beaExIntRO: 1.3.6.1.4.1.140.100.1
beaExIntRW: 1.3.6.1.4.1.140.100.2
beaExStrRO: 1.3.6.1.4.1.140.100.3
beaExStrRW: 1.3.6.1.4.1.140.100.4
..

Testing the Agent

To test the SNMP agent executable set the environment variable BEA_SM_SNMP_MIBFILE to:

$RELEASE_ROOT/agent_src/agentlib/mib.txt

This ensures that the SNMP utilities can properly access the objects you added to the MIB file as the mib.txt file contains your new managed object definitions.

Test the agent by using either the snmpwalk or snmptest utilities. To use the snmpwalk utility, enter (all on one line):

snmpwalk hostname public private.enterprises.bea.beaEx

where hostname is the name or Internet address (in dot-dot notation) of the node where the agent is running.

The following data is retrieved from the snmpwalk command:

Name: private.enterprises.bea.beaEx.beaExIntRO.0
INTEGER: 1
Name: private.enterprises.bea.beaEx.beaExIntRW.0
INTEGER: 2
Name: private.enterprises.bea.beaEx.beaExStrRO.0
OCTET STRING- (ascii): get_beaExStrRO
Name: private.enterprises.bea.beaEx.beaExStrRW.0
OCTET STRING- (ascii): get_beaExStrRW
End of MIB.

Note: snmpwalk and snmptest print the full English name of objects because they have access to the mib.txt file.

Refer to the Chapter 3, "Tools and Functions," for complete instructions.

Installing the New Agent

To install the new SNMP agent, copy the SNMP agent executable to the host where you want to use it. Set its s-uid bit and ownership to root.

Install the beamgr.conf and bea_snmpd.conf files and the following network services:

snmp

161/udp

snmp-trap

162/udp

The default location for the agent executable is /usr/sbin on SVR4 systems. The default location for beamgr.conf and bea_snmpd.conf is /etc on UNIX platforms. On Windows NT systems, the configuration files are located in C:\etc.

Troubleshooting

There are several error messages you may encounter during the installation process:

This message informs you that an SNMP agent is already running on that host. To correct this problem, terminate the conflicting agent.

This message informs you that the user-id and the group-id of the agent are incorrect. To correct this problem, change the effective user-id of the agent to root.

This message informs you that the SNMP services are not configured correctly. To correct this problem, make sure the following services are available:

snmp

161/udp

snmp-trap

162/udp

MIB Modification

If you realize that you made a mistake in defining a new MIB after you have created the C code using the imibgenall utility, you can undo it with the following command:

imibgenall -u MIBRootName

For example:

imibgenall -u beaEx

will undo any beaEx MIB related sources/modifications (previously created using imibgenall beaEx).

You might want to do this for one of the following reasons:

Perhaps you have written code in the access functions file already which you do not want to lose. In such a situation, follow these steps:

  1. Save a copy of the access function file. For example, on UNIX platforms, enter the following commands:

    cp MIBRootName.c MIBRootName.c.sav
    cp MIBRootName.h MIBRootName.h.sav

    The following would be the Windows NT commands:

    copy MIBRootName.c MIBRootName.c.sav
    copy MIBRootName.h MIBRootName.h.sav

  2. Undo the earlier created sources corresponding to this MIB. To do so, type:

    imibgenall -u MIBRootName

  3. Make any desired modifications in the MIBRootName MIB group definitions in the my.asn1 file.

  4. Create the C code for this MIB group again. To do so, execute this command:

    imibgenall MIBRootName

    Most of your changes made to MIBRootName.c.sav should be usable as is in MIBRootName.c

  5. Delete MIBRootName.c.sav.

Using Multiple SNMP Agents

If a host already has an SNMP agent (for example, an SNMP agent supplied by the hardware manufacturer), there are two strategies for using an agent generated by the Agent Development Kit: