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:
Overview of the Development Process
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 The Agent Development Kit code generator (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
imibgenall
) checks for correct ASN.1 syntax and generates appropriate messages if it encounters syntax errors.
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.
The following is an example of how to define the working environment on a UNIX platform, using C shell commands:
The following is an example of how to define the working environment on a Windows NT system:
All other necessary environment settings are set automatically.
setenv RELEASE_ROOT
install_dir
set path = ($RELEASE_ROOT/bin $path)set RELEASE_ROOT =
install_dir
set path = %RELEASE_ROOT%\bin;%PATH%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:
The target MIB component ( Figure 4-1 shows an example of a private MIB. In this example, the objects contained in the groups
The imibgenall
MIBRootName
MIBRootName
designates the target MIB component. (Refer to Chapter 3, "Tools and Functions," for the complete syntax of this command.)
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
.
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
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
To do this you edit the source file 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."
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
.
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:
This builds an executable named cd $RELEASE_ROOT/agent_src
build_agent -r MIBRootName
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
Use the following command to build the agent:
The build_agent -n
YourSNMPAgent
build_agent
command is interactive and prompts you for the following information:
Note:
Any C files that you list here must be located in the following directory:
install_directory
/agent_src/agentlib
Note:
You must use the absolute path to any libraries specified here.
The snmpwalk
and snmptest
utilities can be used to test the agent, as described in "Testing the Agent."
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
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:
imibgenallMIBRootName
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:
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 - One of the constant values that gets defined for tabular MIB groups is You should set the MAX_
ReadOnlyStringObjectName
. By default these values are set to 256. You may want to modify these values.
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.
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.
When the agent receives an SNMP GET or GETNEXT request from a management station, it calls the generated The agent tracks the age of the cache. Before calling the For example, if your MIB group has a scalar object The object values for a given MIB group are cached in the record 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:
A function 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.
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.
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
;
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.
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;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.
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 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
. 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 */;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.
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.
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 For each scalar or columnar object that is defined as read-write in the MIB, 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.
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.
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
.
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.
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."
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.
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.
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()
.
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 -rMIBRootName
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.
Follow these steps to build the agent source code:
cd $RELEASE_ROOT/agent_src
build_agent
command:
The build_agent -n
SNMPAgentName
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
-s
option) after building the agent executable, do
the following:
root
.
For example:
cd $RELEASE_ROOT/agent_src/agentlib
chown root SNMPAgentName
chmod u+s SNMPAgentName
ls -l SNMPAgentName
-rwsrwxr-x 1 root 499712 Jun 20 10:58 SNMPAgentName*
/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
The above command starts The 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.
./
SNMPAgentName
-sSNMPAgentName
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.
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.
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.
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
..
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
..
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):
snmpwalkhostname
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.
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.
There are several error messages you may encounter during the installation process:
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
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 -uMIBRootName
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:
The following would be the Windows NT commands:
cp
MIBRootName
.c MIBRootName
.c.sav
cp MIBRootName
.h MIBRootName
.h.savcopy
MIBRootName
.c MIBRootName
.c.sav
copy MIBRootName
.h MIBRootName
.h.sav
imibgenall -u
MIBRootName
MIBRootName
MIB group definitions in
the my.asn1
file.
Most of your changes made to imibgenall
MIBRootName
MIBRootName
.c.sav
should be usable as is in MIBRootName
.c
MIBRootName
.c.sav
.
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:
where SNMPAgentName
-s -p
port_number
port_number
is the alternate port. Configure the SNMP manager to communicate with this agent on port_number
according to the manufacturer's instructions.