The Simple Network Management Protocol (SNMP) is an application layer protocol that allows remote management and data collection from networked devices. A networked device can be anything that is connected to the network, such as a router, a bridge, or a host.
A managed networked device contains software that acts as the SNMP agent for the device. It handles the application layer protocol for SNMP and carries out the management commands. These commands consist of getting information and setting of operational parameters.
There are also network management application programs (usually running on a host somewhere on the network) that send SNMP commands to the various managed devices on the network to perform the management tasks. These tasks can consist of configuration management, network traffic monitoring and network trouble shooting.
The Extensible Simple Network Management Protocol (eSNMP) is the SNMP agent architecture for a host machine on the network running Digital UNIX Version 4.0 (or higher). It includes a master-agent process and multiple related processes containing eSNMP subagents. The master-agent performs the SNMP protocol handling and the subagents perform the requested management commands. This section assumes you are familiar with the following:
This chapter provides the following information:
This section describes the components and architecture the eSNMP agent for Digital UNIX. It contains information on the following:
The eSNMP components are as follows:
/usr/sbin/snmpd
- The master-agent daemon.
/usr/sbin/os_mibs
- The subagent daemon provided by Digital UNIX.
/usr/sbin/mosy
- The MIB compiler.
/usr/sbin/snmpi
- The object table code generator.
/usr/shlib/libesnmp.so
- The eSNMP Library.
/usr/include/esnmp.h
- eSNMP definitions.
/usr/examples/esnmp/*
- Example code.
The Management Information Base (MIB) defines a set of data elements that relate to network management. Many of these are standardized in the RFCs which are produced as a result of the Internet Engineering Task Force (IETF) working group standardization effort of the Internet Society.
The data elements defined in the RFCs are identified using a naming scheme with a hierarchical structure. Each name at each level of the hierarchy has a number associated with it. You can refer to the data elements in the MIB definitions by name or by its corresponding sequence of numbers. This is called the Object Identifier (OID). You can extend an OID for an specific data element further by adding more numbers to identify a specific instance of the data element. The entire collection of managed data elements is called the MIB tree.
Each SNMP agent implements those MIB elements that pertain to the device being managed, plus a few common MIB elements. These are the supported MIB tree elements. An extensible SNMP agent is one that permits its supported MIB tree to be distributed among various processes and change dynamically.
For eSNMP there is a single master-agent and there may be any number of
subagents. The master-agent itself does not support (implement)
any MIBs, it handles the SNMP protocol and maintains a registry of
subagents and the MIBs they support. The master-agent for eSNMP
is the daemon process
/usr/sbin/snmpd
.
The eSNMP protocol contains one standard subagent that implements the common
MIB elements contained under the
mib-2
OID name. This is the daemon process
/usr/sbin/os_mibs
.
Another eSNMP subagent is built into the
gated
daemon process
(/usr/sbin/gated
).
Additional subagents will
be added by Digital and third parties. These subagents communicate
with the master-agent and work together to appear to the
management application programs as a single SNMP agent for the host.
The master-agent listens on the preassigned User Datagram Protocol (UDP)
port for an incoming SNMP request. When the master-agent receives an
SNMP request, it authenticates it against the local security database
and handles any authentication or protocol errors. If the request is
valid, the
snmpd
daemon consults its MIB registry. (See the
snmpd
(8)
reference page for more information.) For each MIB object
contained in the request it determines which registered MIB could contain
that object and which subagent has registered that MIB. The master-agent
then builds a series of messages; one for each subagent that will be involved
in this SNMP request. These messages do not carry SNMP, but use the more
efficient eSNMP protocol[Footnote 2] for communication between the master-agent and the
subagents.
Each subagent program is linked with the shareable library
libesnmp.so
.
This library contains the protocol implementation that enables
communication between the master-agent and the subagent.
This code parses the master-agent's message and consults its local object table.
The object table is a data structure that is defined and initialized in code emitted by the MIB compiler tools. It contains an entry for each MIB object that is contained in the MIBs implemented in that subagent. One part of an object table entry is the address of a function which services requests for this MIB object. These functions are called method routines.
The eSNMP library code calls into the indicated method routine for each of the MIB variables in the master-agent's message. The eSNMP library code creates a response packet based on the function return values and sends it back to the master-agent.
The master-agent starts a timer and marshals the response packets from
all involved subagents.
The master-agent may rebuild and resend a new set of subagent
messages, depending on the specific request; for example, a
GetNext
request. When the master-agent has all required data or error responses
or has timed out waiting for a response from a subagent, it builds an
SNMP response message and sends it to the originating SNMP application.
The interaction between the master-agent and subagent is invisible to
the requesting SNMP management application.
Subagent programs are linked against
libesnmp.so
shareable library, which performs all the protocol handling and
dispatching.
Subagent developers need to code the method routines for their MIB
objects.
The IETF working group is readdressing SNMPv2 and RFCs have not been published, at the time of this writing.
Extensible SNMP support for SNMPv2 does exist in the following areas. This is based on the original SNMPv2 RFCs that were submitted and withdrawn:
mosy
and
snmpi
programs) support SNMPv2 SMI and textual conventions.
GetBulk
)
are latent.
The subagent's function is to establish communications with the master-agent, register the MIBs that it is going to handle, and process requests from the master-agent. It must also be able to send SNMP traps on behalf of the host application.
The subagent consist of the following:
mosy
and
snmpi
programs
The subagent is usually embedded within a host application, such as a router daemon. Here the subagent processing is only a small part of the work performed by the process. The main routine of the host application contains the calls to the eSNMP library to perform the eSNMP protocol. In other cases, the subagent is a standalone daemon process that has its own main routine.
The eSNMP library calls the method routines while
processing a packet from the master-agent. Each MIB variable in
the object table has a pointer to the method routine that is to
handle that variable. Since the object tables are generated by the
mosy
and
snmpi
programs, the method routine names are static.
The eSNMP developer's kit provided with Digital UNIX consists of the following:
/usr/sbin/mosy
- MIB compiler utility
/usr/sbin/snmpi
- Object table code generator utility
/usr/examples/esnmp/mib-converter.sh
- MIB text extraction tool
/usr/shlib/libesnmp.so
- eSNMP library
/usr/include/esnmp.h
- eSNMP definitions file
/usr/examples/esnmp/*
- Subagent example code
The eSNMP library
(libesnmp.so
)
contains the following:
These routines implement communication with the master-agent on behalf of the subagent; they are:
esnmp_init
- Initializes the protocol (performs a handshake with the master-agent)
esnmp_register
- Registers a MIB with the master-agent
esnmp_poll
- Processes a packet from the master-agent
esnmp_trap
- Requests the master-agent to generate an SNMP trap
esnmp_are_you_there
- Pings the master-agent
esnmp_unregister
- Unregisters a MIB
esnmp_term
- Ends communication with the master-agent and terminate extensible SNMP
esnmp_sysuptime
- Time handling and synchronization
These are also resolved in libesnmp.so, and are optional routines for convenience in developing method routines. These include, but are not limited to, the following:
str2oid
- Converts an ASCII dot-format string into internal
OID
format; see
Section 6.3.3.5
for more information.
cmp_oid
- Compares the value of two
OID
structures; see
Section 6.3.3.10
for a complete list.
The
esnmp.h
header file is associated with the eSNMP library. This file
defines all data structures, constants, and function prototyes
required to implement subagents to this API.
Understanding subtrees is crucial to understanding the eSNMP API and how your subagent will work.
Note
This section assumes that you understand the OID naming structure used in SNMP. If not, refer to RFC1442 Structure of Management Information.
The information in SNMP is structured hierarchically like an inverted tree. Data can be associated with any leaf node in this hierarchy. Each node has a name and a number. Each node can also be identified by an OID, which is an accumulation of the numbers that make up a path from the root down to that node in the tree.
For example, the
chess
MIB used in the sample code has an element with the name
chess
.
The OID for the element
chess
is
1.3.6.1.4.1.36.2.15.2.99
,
which is derived from its position in the hierarchy:
(The
chess
MIB appears in the
/usr/examples/esnmp
directory.)
iso(1) org(3) dod(6) internet(1) private(4) enterprise(1) digital(36) ema(2) sysobjects(15) decosf(2) chess(99)
Any node in the MIB hierarchy can define a subtree. All elements
within the subtree have an OID that starts with OID of the subtree base.
For example, if we define
chess
to be a subtree base, the elements with the same prefix as the
chess
OID are all within the subtree:
chess 1.3.6.1.4.1.36.2.15.2.99 chessProductID 1.3.6.1.4.1.36.2.15.2.99.1 ObjectID chessMaxGames 1.3.6.1.4.1.36.2.15.2.99.2 Integer32 chessNumGames 1.3.6.1.4.1.36.2.15.2.99.3 Integer32 gameTable 1.3.6.1.4.1.36.2.15.2.99.4 gameEntry 1.3.6.1.4.1.36.2.15.2.99.4.1 gameIndex 1.3.6.1.4.1.36.2.15.2.99.4.1.1 Integer32 gameDescr 1.3.6.1.4.1.36.2.15.2.99.4.1.2 DisplayString gameNumMoves 1.3.6.1.4.1.36.2.15.2.99.4.1.3 Integer32 gameStatus 1.3.6.1.4.1.36.2.15.2.99.4.1.4 INTEGER moveTable 1.3.6.1.4.1.36.2.15.2.99.5 moveEntry 1.3.6.1.4.1.36.2.15.2.99.5.1 moveIndex 1.3.6.1.4.1.36.2.15.2.99.5.1.1 moveByWhite 1.3.6.1.4.1.36.2.15.2.99.5.1.2 DisplayString moveByBlack 1.3.6.1.4.1.36.2.15.2.99.5.1.3 DisplayString moveStatus 1.3.6.1.4.1.36.2.15.2.99.5.1.4 INTEGER chessTraps 1.3.6.1.4.1.36.2.15.2.99.6 moveTrap 1.3.6.1.4.1.36.2.15.2.99.6.1
It is this subtree base that is registered with the master-agent to tell it that this subagent handles all requests related to the elements within the subtree.
The master-agent expects a subagent to handle all objects subordinate to the registered subtree. This principle guides your choice of subtrees.
For example, registering a subtree of
chess
is reasonable because it is realistic to assume that the subagent could
handle all requests for elements in this subtree.
Registering an entire application-specific MIB usually makes sense
because the particular application expects to handle all objects defined
in the MIB.
Registering a subtree of
transmission
(under MIB-2) would be
a mistake, because it is unlikely that the subagent is prepared to
handle every MIB subordinate to transmission (FDDI, Token Ring, and so on).
A subagent may register as many subtrees as it wants. It can register OIDs that overlap with other registrations by itself or other subagents; however, it cannot register the same OID more than once. The subagents can register and unregister subtrees at any time after it has established communication with the master-agent.
Normally it is the nonterminal nodes that are registered as a subtree with the master-agent. However, terminal nodes (those of one object type), or even specific instances, can be registered as a subtree.
The master-agent distributes requests to the subagent that has the subtree with the highest priority (largest priority number) or the most recent (if priority is equal), matching the OID on the variable bindings of the request.
The
mosy
and
snmpi
utilities are used to generate the C language code
that defines the object tables from the MIBs. The object tables are
defined in the emitted files
subtree_tbl.h
and
subtree_tbl.c
,
files that are compiled into your subagent.
These modules are created by the utilities and it is not recommended that they be edited. If the MIBs change or a future version of the eSNMP development utilities require your object tables to be rebuilt, it is easy to rebuild the files and recompile them if you did not edit the files.
The
subtree_tbl.h
file contains the following information:
The first section is a declaration of the subtree structure.
The subtree is automatically initialized by code in the
subtree_tbl.c
file. A pointer to this structure is passed to the
esnmp_register
routine to register a subtree with the master-agent.
All access to the object table for
this subtree is through this pointer.
The declaration has the following form:
extern SUBTREE
subtree_subtree;
The next section contains index definitions for each MIB variable in
the
SUBTREE
of the form:
#define I_mib-variable nnnn
These values are unique for each MIB variable within a subtree and are the index into the object table for this MIB variable. These values are also generally used to differentiate between variables that are implemented in the same method routine so they can be used in a switch operation.
The next section contains enumeration definitions for those integer MIB variables that are defined with enumerated values, as follows:
#define D_
mib-variable_enumeration-name value
These are useful since they describe the architected value that enumerated integer MIB variables may take on; for example:
/* enumerations for gameEntry group */ #define D_gameStatus_complete 1 #define D_gameStatus_underway 2 #define D_gameStatus_delete 3
The next section contains the MIB group data structure definitions of the form:
typedef struct xxx
{
type
mib-variable;
.
.
.
char mib-variable
_mark;
.
.
.
}
mib-group_type
One of these data structures is emitted for each MIB group within the subtree.
Each structure definition contains a field representing each MIB variable
within the group. If the MIB variable name is not unique within the
pool of MIBs presented to the
snmpi
program at the time the
subtree_tbl.h
file is built, the
snmpi
program does not qualify the name with the name of its parent variable
(group name) to make it unique.
In addition to the MIB variable fields,
the structure includes a 1-byte
mib-variable_mark
field for each variable.
You can use these for maintaining status of a MIB variable; for example,
the following is the group structure for the
chess
MIB:
typedef struct _chess_type { OID chessProductID; int chessMaxGames; int chessNumGames;char chessProductID_mark; char chessMaxGames_mark; char chessNumGames_mark; } chess_type;
These MIB group structures are provided for convenience, but are not mandatory. You can use whatever structure is easiest for you in your method routine.
The next section is the method routine function prototypes. Each MIB group within the subtree has a method routine prototype defined. A MIB group is a collection of MIB variables that are leaf nodes and share a common parent node.
There is always a function prototype for the method
routine that handles the
Get
,
GetNext
,
and
GetBulk
operations. If the group contains any writable variables, there is also
a function prototype for the method routine that
handles
Set
operations. Pointers to these routines appear
in the subtree's object table which is initialized in the
subtree_tbl.c
module. You must write method routines for each prototype that is defined,
as follows:
extern int mib-group
_get(METHOD *method
)
extern int mib-group
_set(METHOD *method
)
For example:
extern int chess_get(METHOD *method); extern int chess_set(METHOD *method);
Method routines are discussed in more detail in Section 6.3.2.3.
The
subtree_tbl.c
file contains the following information:
OBJECT
structures. (See
esnmp.h
.)
SUBTREE
structure
The first section is the array of integers used for the OIDs of each MIB variable in the subtree, as follows:
static unsigned int elems[] = { ...
The next section is an array of
OBJECT
structures. There is one
OBJECT
for each MIB variable within the subtree. (See
esnmp.h
.)
An
OBJECT
represents a MIB variable and has the following fields:
object_index
- The constant
I_mib-variable
from the
subtree_tbl.h
file.
oid
- The this is the variable's OID (points to a part of
elems[]
).
type
- The variable's data type.
getfunc
- The address of method routine to call for
Get
operations.
setfunc
- The address of method routine to call for
Set
operations.
The master-agent has no knowledge of object tables or MIB variables.
It only maintains a registry of subtrees. When a request for a
particular MIB variable arrives, it is processed as follows. In the
following procedure, the MIB variable is
mib_var
and the subtree is
subtree_1
:
subtree_1
which contains (for
Get
or
Set
requests) or might contain (for
GetNext
or
GetBulk
requests)
mib_var
.
subtree_1
.
subtree_1
.
It searches the object table of
subtree_1
and locates the following:
mib_var
(for
Get
and
Set
requests)
mib_var
(for
Next
or
Bulk
requests)
Get
or
Set
,
an error is returned.
For
Next
or
Bulk
,
the
libsnmp
code keeps trying subsequent objects in the object table of
subtree_1
until a method routine returns success or
the table is exhausted; in either case a response is returned.
subtree_1
could not return data on a
Next
or
Bulk
routine, it recursively tries the subtree lexicographically after
subtree_1
.
The next section is the
SUBTREE
structure itself. It is a pointer to
this structure that is passed to the
esnmp_register
eSNMP library routine to register the subtree. It is through this pointer that
the library routines find the object structures. The following is
an example of the
chess
subtree structure:
SUBTREE chess_subtree = { "chess", "1.3.6.1.4.1.36.2.15.2.99", { 11, &elems[0] }, objects, I_moveStatus};
The
SUBTREE
structure has the following elements:
name
- This is the name of the base node of the subtree.
dots
- The ASCII string representation of the subtree's
OID; it is what actually gets registered.
oid
- The OID of the base node of the subtree; it points
back to the array of integers.
object_tbl
- A pointer to the array of objects in the object
table. It is indexed by the
I_xxxx
definitions found in the
subtree_tbl.h
file.
last
- This is the index of the last object in the
object_tbl
file. It is used to determine when the end of the table has been
reached.
The final section of the
subtree_tbl.c
contains short routines for allocating and freeing the
mib-group_type
structures. These are provided as a convenience
and are not a required part of the API.
As a subagent developer, you are usually presented with a UNIX application,
daemon, or driver (such as the
gated
daemon or ATM drivers) and have to implement an SNMP
interface. The following steps explain how you do this:
MIB development starts with a MIB specification. Usually these are RFCs, written in concise MIB format according to RFC 1212. Designing and specifying a MIB is beyond the scope of this document; it is assumed you have a MIB specification.
The standard RFCs can be obtained from the the InterNIC directory at the following URL:
http://ds.internic.net/ds/dspg1intdoc.html
If you have to build your own MIB specification, you can look at a similar MIBs written by another vendor. One source for a listing of these is in the archives section of the Network Management page at the following URL:
http://smurfland.cit.buffalo.edu/NetMan/index.html
You need MIBs for all of the elements you are implementing in the subagent and
for any elements referenced by these MIBs (such that all element names
resolve to the OID numbers). As a minimum you will need the SMI MIB
rfc1442.my
and the textual conventions
v2-tc.my
.
These are in the
/usr/examples/esnmp
directory.
Once you obtain MIB definitions, use them to generate the object tables for your new subagent. The objective is to take the MIB specification text for each of the MIBs, remove the ASN.1 specifications, and compile them into C language modules that contain the local object tables.
Compile your MIBs using the following tools:
mib-converter.sh
The
mib-converter.sh
is a
gawk
shell script that extracts the MIB ASN.1 definitions
from the RFC text. This step removes the text before
and after the MIB definition and removes page headings and
footings.
The
mib-converter.sh
script may not remove everything that needs to be removed; therefore,
you may need to remove some things
manually, using a text editor.
The following is an example of how to use the
mib-converter.sh
script:
#
/usr/examples/esnmp/mib-converter.sh
mib-def.txt > \
mib-def.my
Be careful; some RFCs contain more than one MIB definition.
You can only use the
mib-converter.sh
script shell on RFCs that contain a single MIB definition.
The
mosy
compiler may not handle it either. If you use an RFC that contains
more than one MIB definition, make each one into a separate file.
The resulting files containing the MIBs should be in the following form:
mib-def.my
mosy
The Managed Object Syntax
(mosy
)
compiler parses
.my
files created by the
mib-converter.sh
script and compiles them into
.defs
files. The
.defs
files describe the object hierarchy within the MIB. The
.defs
files are front-ends to several tools. The following is an example of
how to use the
mosy
compiler:
#
mosy
mib-def.my
The
mosy
compiler produces
mib-def.defs
files.
The
mosy
program is taken from ISODE 8.0 (distributed with the
4BSD/ISODE SNMPv2 package).
snmpi
The MIB data initializer creation program
(snmpi
)
reads a concatenation of the
.def
files compiled by the
mosy
compiler and generates the C code to define
the static structures of the object table for a specified
MIB subtree.
Note
The
snmpi
program supplied with Digital UNIX is different from thesnmpi
program in 4BSD/ISODE SMUX.
Concatenate the
.def
files the
mosy
compiler compiles into the
objects.defs
file. Be sure to include the compiled versions of
rfc1442.my
and
v2-tc.my
.
The
objects.defs
file must contain enough MIBs to resolve all MIB
names, even if they are not
used by your subtrees.
Then generate the object table files using the following
command:
#
/usr/sbin/snmpi objects.defs
subtree
The
snmpi
program has a print option that allows you
to dump the contents of the entire tree generated as a result
of the objects it finds into the
objects.defs
file. If you are
having trouble with the subtrees you may find this to be helpful.
Use the following command to generate a listing:
#
/usr/sbin/snmpi -p objects.defs > objects.txt
The
snmpi
program outputs the
subtree_tbl.c
and
subtree_tbl.h
.
The
subtree
is the name of the base MIB variable name for a
MIB subtree. These two files are C code used to initialize the
MIB object table for the specified subtree. (This is the
local object table referred to above.) Repeat this process
for each MIB subtree being implemented in your subagent.
Note that the
snmpi
program defaults to using MIB groups as the level of granularity
for method routines; that is, the assumption is made that all MIB variables
within a group should be serviced by the same method routine.
(It also provides the
mib-group_type
data structure to help do this.)
The
mib-group_type
structure is not part of the API; it is
provided as a convenience. It is helpful to use the
mib-group
organization of the object table. This is because,
generally, those objects are logically related and usually accessed
as a group; for example,
ipRoutes
are returned more or less complete from the kernel routing tables.
Write the code that calls the eSNMP library API to
initialize communications with the master-agent
(snmpd
),
and register your MIBs. (See
Section 6.2.4.)
Write the code for the required method routines. (See
Section 6.3.)
Usually you need one
Get
method routine and one
Set
method routine
for each MIB group within your registered MIB subtree.
The
subtree_tbl.h
files generated in the previous step define
the names and function prototype for each method routine you
need.
An example
Makefile
is provided in the
/usr/examples/esnmp
directory.
Run your subagent like any other program or daemon. There
are trace facilities built into the eSNMP library routines
to assist in the debugging process. Use the
set_debug_level
routine in the
main
section to enable the trace.
Once the subagent has initialized and successfully registered
a MIB subtree, you can send SNMP requests using standard
applications. For example, POLYCENTER Netview, HP OPenview, or any
MIB browser. If you do not have access to SNMP applications, you can
use the
snmp_request
and
snmp_traprcv
programs to help debug subagents.
Note that if you interactively debug, your subagent will probably cause SNMP requests to timeout.
Normally all error and warning messages are recorded in
the system's daemon log. When running the sample
chess
subagent and the
os_mibs
subagent, you specify a trace runtime argument, as follows:
os_mibs -trace
With the trace option active, the program does not daemonize
and all trace output goes to
stdout
;
it displays each message that is processed.
You can use this feature in your own subagents by calling the
set_debug_level
routine and pass it the
TRACE
parameter.
Anything passed in the debug macro is sent to
stdout
,
as follows:
ESNMP_LOG ((TRACE, (
"message_text \n"
));
To send everything to the daemon log, call the
set_debug_level
routine and pass it the
WARNING || DAEMON_LOG
parameter or the
set_debug_level
routine and pass it the
ERROR || DAEMON_LOG
parameter to suppress warning messages.
The eSNMP API provides for autonomous subagents that are not closely
tied to the master agent
(snmpd
).
Subagents can be part of
other subsystems or products and have primary functions not
related to SNMP. For instance, the
gated
daemon is primarily
concerned with Internet routing; however it also functions as a subagent.
In particular, the
snmpd
daemon does not start or stop any subagent daemons
during its startup or shutdown procedures. It also does not maintain
any on-disk configuration information about subagents. Whenever the
snmpd
daemon starts, it has no knowledge of previously registered subagents or subtrees.
Typically all daemons on a Digital UNIX system are started or
stopped together, as the system changes run levels. But subagents
should correctly handle situations where they start before the
snmpd
daemon, or are running while the
snmpd
daemon is restarted to reload information from its configuration file.
In these situations subagents need to restart the eSNMP protocol
as described in the following sections.
Subagent protocol operations follow the following sequence:
esnmp_init
)
esnmp_register [esnmp_register ...]
)
The following loop happens continuously:
{ determine sockets with data pending
if the eSNMP socket has data pending esnmp_poll
periodically call esnmp_are_you_there as required during periods of inactivity }
esnmp_term
)
Note that is very important that subagents call the
esnmp_term
function when they are stopping.
This enables eSNMP to free system resources being used by the subagent.
The example subagent in the
/usr/examples/esnmp
directory shows how to code subagent protocol operations.
The eSNMP API function return values indicate to a subagent both the success or failure of the requested operation and the state of the master agent. The following list provides a description of each return value and the indicated subagent actions:
ESNMP_LIB_OK
The operation was successful.
ESNMP_LIB_NO_CONNECTION
The connection between the subagent and the master agent
could not be initiated. This value is returned by the
esnmp_init
function.
esnmp_init
function again after a suitable delay.
ESNMP_LIB_DUPLICATE
A duplicate subagent identifier has been
received by the master agent. This means that another
process with the same subagent identifier is connected
to the master agent and that this process should terminate.
This value is returned by the
esnmp_poll
function.
ESNMP_LIB_LOST_CONNECTION
Lost communications with the master-agent.
This value is returned by the
esnmp_register
,
esnmp_poll
,
esnmp_are_you_there
,
esnmp_unregister
,
and
esnmp_trap
functions.
esnmp_init
function after a suitable delay.
ESNMP_LIB_BAD_REG
The attempt to send a registration failed. This value is returned by
the
esnmp_register
,
esnmp_unregister
,
and
esnmp_poll.
functions.
esnmp_init
function has not been successfully called prior to calling the
esnmp_register
function.
timeout
parameter in the
esnmp_register
function is invalid.
esnmp_register
function has already been queued for registration or has been
registered by this subagent.
esnmp_poll
function). See the log file to
determine the details regarding why it failed and which
subtree was at fault.
esnmp_unregister
).
esnmp_register
function in the proper sequence and with correct arguments.
ESNMP_LIB_CLOSE
The master-agent is stopping. This value is returned by the
esnmp_poll
function.
esnmp_init
function as suited by the subagent.
ESNMP_LIB_NOTOK
An eSNMP protocol error occurred and the packet was discarded.
This value is returned by the
esnmp_poll
,
and
esnmp_trap
functions.
This section provides detailed information on the SNMP Application Programming Interface, which consists of the following:
libesnmp
support routines
The calling interface contains the following routines:
esnmp_init
esnmp_register
esnmp_unregister
esnmp_poll
esnmp_are_you_there
esnmp_trap
esnmp_term
esnmp_sysuptime
The
esnmp_init
routine locally initializes the extensible SNMP subagent,
and initiates communication with the master-agent.
This call does not block waiting for a response from the master-agent. After
calling the
esnmp_init
routine, call the
esnmp_register
routine for each subtree that is to be handled by this subagent.
Call this routine during program initialization or to restart
the eSNMP protocol. If you are restarting, the
esnmp_init
routine clears all registrations so each subtree must be reregistered.
You should attempt to create a unique
subagent_identifier
,
perhaps using the program name
(argv[0]
)
and additional descriptive
text. The master-agent does not open communications with a subagent
whose subagent-identifier is already in use.
The syntax for the
esnmp_init
routine is as follows:
int esnmp_init
(
int
*socket,
char
*subagent_identifier
)
The arguments are defined as follows:
socket
subagent_identifier
The return values are as follows:
esnmp_init
routine has completed successfully.
The following is an example of the
esnmp_init
routine:
#include <esnmp.h> int socket; status = esnmp_init(&socket, "gated");
The
esnmp_register
routine requests registration of a single MIB subtree.
Before the master-agent can pass SNMP requests on to the subagent,
it must register the willingness to process all messages for MIB
variables subordinate to a subtree identifier.
The initialization routine
(esnmp_init
)
must be called prior to calling the
esnmp_register
routine. The
esnmp_register
function must be called for each subtree structure corresponding to each
subtree that it will be handling.
At any time subtrees can be unregistered by
calling
esnmp_unregister
and then be reregistered by calling the
esnmp_register
.
When restarting the eSNMP protocol by calling
esnmp_init
,
all registrations are cleared. All subtrees must be reregistered.
A subtree is identified by the base MIB name and its corresponding
OID number of the node which is the parent of all MIB variables
that are contained in the subtree; for example, the MIB-2
tcp
subtree has an OID of
1.3.6.1.2.1.6
.
All elements subordinate to this (those that have the same first 7 digits)
are included in the subtree's object table. The subtree
can also be a single MIB object (a leaf node) or even a specific
instance.
By registering a subtree, the subagent is indicating that it will process SNMP requests for all MIB variables (or OIDs) within that subtree's range. Therefore, a subagent should register the most fully qualified (longest) subtree that still contains its instrumented MIB variables.
For example, the Digital UNIX operating system contains support for
MIB-2 implemented as an eSNMP subagent.
This subagent does not register MIB-2 (1.3.6.1.2.1); instead,
it registers the following MIBs:
at
,
dot5
,
egp
,
fddi
,
icmp
,
interfaces
,
IP
,
snmp
,
system
,
tcp
,
and
udp
.
The master-agent requires that a subagent cannot register the same subtree more than once. Other than this one restriction, a subagent may register subtrees that overlap the OID range of subtrees that it previously registered or those of subtrees registered by other subagents.
For example, consider the two Digital UNIX daemons,
os_mibs
and
gated
.
The
os_mibs
daemon registers the
ip
subtree and the
gated
daemon registers the
ipRouteTable
subtree at a higher priority.
Requests for operations on MIB objects within
ipRouteEntry
,
such as
ipRouteIfIndex
,
will go to
gated
because it is a higher priority.
Requests for other
ip
objects, such as
ipNetToMediaIfIndex
,
will be passed to
os_mibs
.
If the
gated
process should terminate or unregister the
ipRouteEntry
subtree, subsequent requests for
ipRouteIfIndex
will go to
os_mibs
because the
ip
subtree, which includes the
ipRouteEntry
objects, will now be the highest priority in that range.
When the master-agent receives a SIGUSR1 signal, it puts its MIB registry in
to the
/var/tmp/snmpd_dump.log
file. See the
snmpd
(8)
reference page for more information.
The syntax for the
esnmp_register
routine is as follows:
int esnmp_register
(SUBTREE
*subtree,
int
timeout,
int
priority
)
The arguments are defined as follows:
subtree
SUBTREE
structure corresponding to the
subtree to be handled. The
SUBTREE
structures are externally declared and initialized in the code emitted by the
mosy
and
snmpi
utilities
(xxx_tbl.c
and
xxx_tbl.h
,
where
xxx
is the name of the subtree) taken directly from the MIB document.
timeout
priority
Subtrees that are registered with the same priority are ranked in order by time of registration. The most recent registration has the highest priority.
The
priority
argument is a mechanism for cooperating subagents to handle
different configurations.
The return values are as follows:
esnmp_register
routine has completed successfully.
esnmp_init
routine has not been called, the timeout parameter is invalid, or this subtree
has already been queued for registration.
Note that the status indicates only the initiation of the request.
The actual status returned in the master-agent's response will be
returned in a subsequent call to the
esnmp_poll
routine.
The following is an example of the
esnmp_register
routine:
#include <esnmp.h> #define RESPONSE_TIMEOUT 0 /* use the default time set in esnmp_init message */ #define REGISTRATION_PRIORITY 10 /* priority at which subtrees will register */
extern SUBTREE ipRouteEntry_subtree;
status = esnmp_register( &ipRouteEntry_subtree, RESPONSE_TIMEOUT, REGISTRATION_PRIORITY ); if (status != ESNMP_LIB_OK) {" printf ("Could not queue the 'ipRouteEntry' \n"); printf ("subtree for registration\n"); }
The
esnmp_unregister
routine unregisters a MIB subtree with the master-agent.
This routine can be called by the application code to tell the
eSNMP subagent not to process requests for variables in this
subtree anymore. You can later reregister a subtree, if needed,
by calling the
esnmp_register
routine.
The syntax for the
esnmp_unregister
routine is as follows:
int esnmp_unregister
(SUBTREE
*subtree
)
The arguments are as follows:
*subtree
subtree
structure for the
subtree
to be unregistered.
The return values are as follows:
The
esnmp_poll
routine processes a pending message that has been sent by the master-agent.
This routine is called after the user's
select()
call has indicated data is ready on the eSNMP socket.
(This socket was returned from the call to the
esnmp_init
routine). If no message is pending on the socket, the
esnmp_poll
routine will block until one is received.
If a received message indicates a problem, an entry is made to the
syslog
file and an error status is returned.
If the received message is a request for SNMP data, the object table is consulted and the appropriate method routines are called.
The syntax for the
esnmp_poll
routine is as follows:
int esnmp_poll()
The return values are as follows:
esnmp_poll
routine has completed successfully.
esnmp_init
error, a duplicate subagent identifier has already been received by the
master-agent.
esnmp_init
request was failed by master-agent, restart after a delay. See the log
file.
The
esnmp_are_you_there
routine requests the master-agent to respond immediately that it
is up and functioning.
This call does not block waiting for
a response. It is intended to cause the master-agent to reply
immediately. The response should be processed by calling the
esnmp_poll
routine.
If no response is received within the timeout period the application
code should restart the eSNMP protocol by calling the
esnmp_init
routine. There are no timers maintained by the eSNMP library.
The syntax for the
esnmp_are_you_there
routine is as follows:
int esnmp_are_you_there()
The return values are as follows:
The
esnmp_trap
routine sends a trap message to the master-agent. This function can be called
at anytime. If the eSNMP protocol has not initialized with the master-agent,
traps are queued and sent when communication is possible.
The trap message is actually sent to the master-agent after the
master-agent's response to the
esnmp_init
call has been processed. This processing happens within any API call,
for most cases during subsequent calls to the
esnmp_poll
routine. The quickest way actually to send traps to the master-agent is
to call the
esnmp_init
,
esnmp_poll
,
and
esnmp_trap
routines.
The master-agent formats the trap into an SNMP trap message and
sends it to management stations based on its current configuration.
For information on configuring the master-agent see the
snmpd
(8)
and
snmpd.conf
(4)
reference pages.
There is no response returned from the master-agent for a trap.
The syntax for the
esnmp_trap
routine is as follows:
int esnmp_trap(int
generic_trap,
int
specific_trap,
char
*enterprise,
VARBIND
*vb)
The arguments are as follows:
generic_trap
specific_trap
enterprise
vb
VARBIND
list of data (a NULL pointer indicates no data)
The return values are as follows:
The
esnmp_term
routine sends a close message to the master-agent and shuts down the eSNMP
protocol. Subagents should call this routine when terminating, so that
the master-agent can update its MIB registry more quickly.
It is important that terminating subagents call this routine, so that
system resources used by eSNMP on their behalf can be released.
The syntax for the
esnmp_term
routine is as follows:
void esnmp_term
(void)
The return values are:
esnmp_term
routine always returns
ESNMP_LIB_OK,
even if the packet could not be sent.
The
esnmp_sysuptime
routine converts UNIX system time obtained from
gettimeofday
into a value with the same timebase as
sysUpTime
.
This can be used as a
TimeTicks
data type (the time since the SNMP agent started) in units of 1/100 seconds.
The time base is obtained from the master-agent in response to the
esnmp_init
routine, so calls to this function before that time will not be accurate.
This provides a general purpose mechanism to convert UNIX
timestamps into SNMP
TimeTicks
.
The function returns the value that
sysUpTime
was when the passed timestamp was
now
.
Passing a null timestamp returns the current value of
sysUpTime
.
The syntax is as follows:
unsigned int esnmp_sysuptime
(
struct timeval
*timestamp
)
The arguments are as follows:
struct timeval
*timestamp
struct timeval
containing a value obtained from the
gettimeofday
system call. The structure is defined in
include/sys/time.h
.
A NULL pointer means return the current
sysUpTime
.
The following is an example of the
esnmp_sysuptime
routine:
#include <include/sys/time.h> #include <esnmp.h> struct timeval timestamp;
gettimeofday(×tamp, NULL); ... o_integer(vb, object, esnmp_sysuptime(×tamp));
The return is as follows:
0
gettimeofday
failed); otherwise,
timestamp
contains the time in 1/100ths seconds since the SNMP
protocol started.
The method routine calling interface contains the following functions:
*_get
*_set
Section 6.3.2.3 provides additional information on method routines.
The
*_get
routine is a method routine for the specified MIB item, which is typically
a MIB group (for example,
system
in MIB-2) or a table entry (for example,
ifEntry
in MIB-2). However, it is up to your discretion.
See the
snmpi
(8)
reference page for more information.
The
libesnmp
routines call whatever routine is specified for
Get
operations in the object table identified by the registered subtree.
The syntax for the
*_get
routine is as follows:
int
mib_item_get
( METHOD
*method
)
The arguments are:
method
METHOD
structure, which contains the following fields:
action
serial_num
serial_num
.
New SNMP requests are indicated by a new value of
serial_num
.
repeat_cnt
GetBulk
only. This value indicates the current iteration
number of a repeating
VARBIND
.
This number increments from 1 to
max_repetitions
,
and is 0 for nonrepeating
VARBIND
structures.
max_repetitions
GetBulk
.
The maximum number of repetitions
to perform. This will be 0 for nonrepeating
VARBIND
structures.
You may be able to optimize subsequent processing by knowing
the maximum number repeat calls will be made.
varbind
VARBIND
structure for which we must fill in the
OID
and data fields.
Upon entry of the method routine, the
method->varbind->name
is the OID that was requested.
Upon exit of the method routine, the
method->varbind
contains the requested data, and the
method->varbind->name
is updated to reflect the actual instance OID for the returned
VARBIND
.
The
libsnmp
routines
(o_integer
,
o_string,
o_oid,
and
o_octet
)
are generally used to load data. The
libsnmp instance2oid
routine is used to update the OID in
method->varbind->name
.
object
method->object->object_index
is this object's unique index within the object table
(useful when one method routine services many objects).
The
method->object->oid
is the OID defined for this object in the MIB.
The instance requested is derived by comparing this
OID
with the
OID
in the request found in the
method->varbind->name
.
The
oid2instance
function is useful for this.
row
Get
operations.
flags
Get
operations.
security
The return values for the
*_get
method routine are as follows:
The
*_set
method routine for a specified MIB item, which is typically
a MIB group (for example,
system
in MIB-2) or a table entry (for example,
ifEntry
in MIB-2). However, it is up to your discretion.
The
libesnmp
routines call whatever routine is specified for
Set
operations in the object table identified by the registered subtree.
This function is pointed to by some number of elements of the
subagent object table. When a request arrives for an object,
its method routine is called. The
*_set
method routine is called in response to a
Set
SNMP request.
SNMP requests may contain many
VariableBindings
(encoded MIB variables).
The
libsnmp
code executing in a subagent matches each
VariableBinding
with an object table entry. The object table's method routine is then
called.
Therefore, a method routine is called to service a single MIB variable and the same method routine may be called several times during a single SNMP request.
The syntax for the
*_set
method routine is as follows:
int
mib_item_set
( METHOD
*method
)
The arguments are as follows:
method
METHOD
structure, which contains the following fields:
action
action
value can be one of the following:
ESNMP_ACT_SET,
ESNMP_ACT_COMMIT,
ESNMP_ACT_UNDO,
or
ESNMP_ACT_CLEANUP
serial_num
serial_num
.
New SNMP requests are indicated by a new value of
serial_num
.
repeat_cnt
Set
calls.
max_repetitions
Set
calls.
varbind
VARBIND
structure which contains
the MIB variable's supplied data value and name (OID).
The instance information has already been extracted from
the OID and placed in
method->row->instance
.
object
method->object->object_index
is this object's unique index within
the object table (useful when one method routine services many objects).
The
method->object->oid
is the OID defined for this object in the MIB.
flags
libesnmp
.
If set, the
ESNMP_FIRST_IN_ROW
bit indicates that this call is the first object to be set in the row.
If set, the
ESNMP_LAST_IN_ROW
bit indicates that this call is the last object to be set in the row.
Only
METHOD
structures with the ESNMP_LAST_IN_ROW
bit set are passed to the method routines for commit, undo, and
cleanup phases.
row
ROW_CONTEXT
structure (defined in the
esnmp.h
header file).
All
Set
calls to the method routine which refer to the same group
and have the same instance number will be presented with the
same row structure. The method routines can accumulate information
in the row structures during
Set
calls for use during the omit and undo
phases. The accumulated data can be released
by the method routines during the cleanup phase.
instance
libesnmp
routine builds this array by subtracting the
object oid
from the requested variable
binding oid
.
instance_len
method->row->instance
.
context
save
state
security
The returns for the
*_set
method routine are as follows:
Every variable binding is parsed and its object is located in the object
table. A
METHOD
structure is created for each
VARBIND
.
These
METHOD
structures point to a
ROW_CONTEXT
structure, which is useful for handling these phases.
Objects in the same conceptual row all point to the same
ROW_CONTEXT
structure. This determination is made by checking the following:
VARBIND
structures have the same instance OIDs.
Each
ROW_CONTEXT
structure is loaded with the instance information for that
conceptual row. The
ROW_CONTEXT
structure
context
and
save
fields are set to NULL,
and the state field is set to
ESNMP_SET_UNKNOWN
structure.
The method routine for each object is called, being passed its
METHOD
structure with an action code of
ESNMP_ACT_SET.
If all method routines return success, a single method routine
(the last one called for the row) is called for each row, with
method->action == ESNMP_ACT_COMMIT
.
If any row reports failure, all rows that have been successfully committed
are told to undo the phase.
This is accomplished by calling a single method routine
for each row (the same one that was called for the commit phase),
with a
method->action == ESNMP_ACT_UNDO
.
Finally, each row is released. The same single method routine for
each row is called with a
method->action == ESNMP_ACT_CLEANUP
.
This occurs for every row, regardless of the results of previous processing.
ESNMP_ACT_SET
Each object's method routine is called during the
Set
phase, until all objects are processed or a method routine
returns an error status value. (This is the only phase during which
each object's method routine is called.) For variable bindings in
the same conceptual row,
method->row
points to a common
ROW_CONTEXT
.
The
method->flags
bitmask have the
ESNMP_LAST_IN_ROW
bit set, if this is the last object being called for this
ROW_CONTEXT
.
This enables you to do a final consistency check, since you have seen every
variable binding for this conceptual row.
The method routine's job in this phase is to determine if the
SetRequest
will work, return the correct SNMP error code if not, and prepare any
context data it needs to actually perform the
Set
during the commit phase.
The
method->row->context
is private to the method routine;
libesnmp
does not use it. A typical use is to store the address of an emitted
foo_type
structure that has been loaded with the data from the
VARBIND
for the conceptual row.
ESNMP_ACT_COMMIT
Even though several variable bindings may be in a conceptual row, only
the last one in order of the
SetRequest
is processed. So, for all the method routines that point to a common row,
only the last method routine is called.
This method routine must have available to it all necessary data and context
to perform the operation. It must also save a snapshot of current data or
whatever it needs to undo the
Set
if required.
The
method->row->save
is intended to hold a pointer to whatever data is needed to
accomplish this. A typical use is to store the address of an emitted
foo_type
structure that has been loaded with the current data for the
conceptual row.
The
method->row->save
is also private to the method routine;
libesnmp
does not use it.
If the set operation succeeds, return ESNMP_MTHD_noError; otherwise, back out the commit as best you can and return a value of ESNMP_MTHD_commitFailed.
If any errors were returned during the commit phase,
libesnmp
enters the undo phase; if not, it enters the cleanup phase.
Note
The undo phase may occur even if the
Set
operation in your subagent is successful because theSetRequest
spanned subagents and a different subagent failed.
ESNMP_ACT_UNDO
For each conceptual row that was successfully committed, the same method routine
is called with
method->action == ESNMP_ACT_UNDO
.
The
ROW_CONTEXT
structures that have not yet been called for the commit phase are not called for
the undo phase; they are called for cleanup phase.
The method routine should attempt to restore conditions to what they were before
it executed the commit phase. (This is typically done using the data pointed to
by the
method->row->save
.)
If successful, return ESNMP_MTHD_noError; otherwise, return ESNMP_MTHD_undoFail.
ESNMP_ACT_CLEANUP
Regardless of what else has happened, at this point each
ROW_CONTEXT
participates in cleanup phase. The same method routine that was called for commit phase
is called with
method->action == ESNMP_ACT_CLEANUP
.
This indicates the end of processing for the
SetRequest
.
The method routine should perform whatever cleanup is required; for instance,
freeing dynamic memory that might have been allocated and stored in
method->row->context
and
method->row->save
,
and so on.
The function return status value is ignored for the cleanup phase.
You must write the code for the method routines declared
in the
subtree_tbl.h
file. Each method routine has one argument, which is a pointer
to the
METHOD
structure, as follows:
int mib-group
_get(METHOD *method)
int mib-group
_set(METHOD *method)
The
Get
method routines are used to perform
Get
,
GetNext
,
and
GetBulk
operations.
Get
method routines perform the following tasks:
method->object->oid
(the object's base OID) to
method->varbind->name
(the requested OID). You can use the
oid2instance
libesnmp
routine to do this.
method->action
,
determine what data, if any, is to be returned.
VARBIND
.
Set the
method->varbind
with the OID of the actual MIB variable
instance you are returning. This is usually accomplished by loading
an array of integers with the instance OID you wish to return and
calling the
instance2OID
libesnmp
routine.
VARBIND
.
Use one of the
libesnmp
library routine with the corresponding data type to load the
method->varbind
with the data to return:
o_integer
o_string
o_octet
o_oid
These routines make a copy of the data you specify. The
libesnmp
function
manages any memory associated with copied data. The method
routine must manage the original data's memory.
The routine does any necessary conversions to the type defined in
the object table for the MIB variable and copies the
converted data into
method->varbind
.
See the Value Representation section for information on data value representation.
For SNMPV1 - Returned as an error code.
For SNMPV2 - Translated to a
noSuchInstance
exception.
For SNMPV1 - Returned as a
noSuchInstance
error.
For SNMPv2 - Translated as a
noSuchObject
exception
The values in a
VARBIND
for each data type are represented as follows.
(Refer to the
esnmp.h
file for a definition of the
OCT
and
OID
structures.)
varbind->value.sl
)
This is a 32-bit signed integer.
Use the
o_integer
routine to insert an integer value into the
VARBIND
.
Note that the prototype for the value argument is
unsigned long, so you may need to cast this to a
signed int
.
varbind->value.oct
)
This is an octet string. It is contained in the
VARBIND
as an
OCT
structure that contains a length and a pointer to a
dynamically allocated character array. Included on the end of the
character array is a null terminator that is not included
in the length.
The
DisplayString
is different only in that the character array
can be interpreted as ASCII text where the
OctetString
can be anything.
Use the
o_string
routine to insert a value into the
VARBIND
which is a buffer and a length. New space will be
allocated and the buffer copied into the new space.
Use the
o_octet
routine to insert a value into the
VARBIND
,
which is a pointer to an
OCT
structure. New space is allocated and the buffer pointed to by the
OCT
structure is copied.
varbind->value.oid
and the
varbind->name
field)
This is an object identifier. It is contained in the
VARBIND
as an
OID
structure which contains the number of elements
and a pointer to a dynamically allocated array of
unsigned integers, one for each element.
The
varbind->name
field is used to hold the object identifier
and instance information that identifies MIB variable.
Use the
OID2Instance
function to extract the instance
elements from an incoming OID on a request. Use the
Instance2OID
function to combine the instance elements
with the MIB variable's base OID to set the
VARBIND
structure's
name
field when building a response.
Use the
o_oid
function to insert an object identifier into the
VARBIND
when the OID value to be returned as data is in the
form of a pointer to an
OID
structure.
Use the
o_string
function to insert an object ID into the
VARBIND
when the OID value to be returned as data is in
the form of a pointer to an ASCII string containing the
OID in dot format; for example
1.3.6.1.2.1.3.1.1.2.0
.
This is the NULL or empty type. This is used to
indicate that there is no value. The length is 0 and
the value union in the
VARBIND
is zero-filled.
The incoming
VARBIND
structures on a
Get
,
GetNext
,
and
GetBulk
will have this data type. A method routine should never
return such a value. An incoming
Set
request never has such a value in a
VARBIND
.
varbind->value.oct
)
This is an IP address. It is contained in the
VARBIND
in an
OCT
structure which has a length of 4 and a
pointer to a dynamically allocated buffer containing
the 4 bytes of the IP address in network order.
Use the
o_integer
function to insert an IP address into
the
VARBIND
when the value is an unsigned integer in
network byte order.
Use the
o_string
function to insert an IP address into
the
VARBIND
when the value is a byte array (in network
byte order). Use a length of 4.
varbind->value.ul
)
The 32-bit counter and 32-bit gauge data types are
stored in the
VARBIND
as an
unsigned int
.
Use the
o_integer
function to insert an unsigned value into the
VARBIND
.
varbind->value.ul
)
The 32-bit timeticks type values are stored in
the
VARBIND
as an
unsigned int
,
in .01-second units.
Use the
o_integer
function to insert an unsigned value
into the
VARBIND
.
varbind->value.oct
)
The
BitString
is contained in the
VARBIND
as an
OCT
structure which contains a length equal to
the number of bytes needed to contain the value which
is ((qty-bits - 1)/8 + 2), and a pointer to a dynamically
buffer containing the bits of the
bitstring
in the form
uubbbbb..bb
,
where the first octet
(uu
)
is 0x00-0x07 and
indicates the number of unused bits in the last octet
(bb
).
The
bb
octets represent the bit string itself, where bit zero
(0) comes first and so on.
Use the
o_octet
routine to insert a value into the
VARBIND
which is a pointer to an
OCT
structure pointing to
a buffer containing the bits in the
uubbbbb..bb
form. New space will be allocated and the buffer pointed to by the
OCT
structure will be copied.
This is not compatible with SNMPv1. It will be returned or set only for SNMPv2 requests.
varbind->value.ul64
)
The 64-bit counter is stored int a
VARBIND
as an
unsigned long
which, on an Alpha machine, has a 64-bit value.
Use the
o_integer
function to insert an unsigned long value
(64 bits) into the
VARBIND
.
This is not compatible with SNMPv1. It is returned or set for SNMPv2 requests only.
This section provides information on the
libsnmp
support routines, which consists of the following:
o_integer
o_octet
o_oid
o_string
str2oid
sprintoid
instance2oid
oid2instance
inst2ip
cmp_oid
cmp_oid_prefix
clone_oid
free_oid
clone_buf
mem2oct
cmp_oct
clone_oct
free_oct
free_varbind_data
set_debug_level
is_debug_level
ESNMP_LOG
The
o_integer
routine loads an integer value into the
VARBIND
with the appropriate type.
The syntax is as follows:
int o_integer
(
VARBIND
*vb,
OBJECT
*obj, unsigned long value
)
The arguments are as follows:
VARBIND *vb
.dD
Is a pointer to the
VARBIND
structure which is
to receive the data. This function does not allocate the
VARBIND
structure.
OBJECT *obj
.dD
Is a pointer to the
OBJECT
structure for the MIB variable associated with the
OID
in the
VARBIND
.
unsigned long value
.dD
The value to be inserted into the
VARBIND
.
The real type as defined in the object structure must be one of the following; otherwise, an error is returned.
If the real type is
IpAddress
,
then it assumes that the 4-byte integer
is in network byte order and will be packaged into one of the following
octet strings:
IMPLICIT OCTET STRING (4)
The following is an example of the
o_integer
routine:
#include <esnmp.h> #include "ip_tbl.h" <-- for ipNetToMediaEntry_type definition VARBIND *vb = method->varbind; OBJECT *object = method->object; ipNetToMediaEntry_type *data;
switch(arg) { case I_atIfIndex: return o_integer(vb, object, data->ipNetToMediaIfIndex);
The following are the return values:
The
o_octet
routine loads an octet value into the
VARBIND
with the appropriate type.
The syntax is as follows:
int o_octet
(VARBIND
*vb,
OBJECT
*obj,
OCT
*oct
)
The arguments are as follows:
VARBIND *vb
.dD
Is a pointer to the
VARBIND
structure which is to receive the data.
This function does not allocate the
VARBIND
structure.
Note
If the original value in the
varbind vb
is not NULL, this routine attempts to free it. So if youmalloc
your ownvb
structure, be sure to fill it with zeros before using it.
OBJECT *obj
.dD
Is a pointer to the
OBJECT
structure for the MIB variable associated with the
OID
in the
VARBIND
.
OCT *value
.dD
Is the value to be inserted into the
VARBIND
.
The real type as defined in the object structure must be one of the following; otherwise, an error is returned:
The following is an example of the
o_octet
routine:
#include <esnmp.h> #include "ip_tbl.h" <-- for ipNetToMediaEntry_type definition VARBIND *vb = method->varbind; OBJECT *object = method->object; ipNetToMediaEntry_type *data;
switch(arg) { case I_atPhysAddress: return o_octet(vb, object, &data->ipNetToMediaPhysAddress);
The returns are as follows:
The
o_oid
routine loads an OID value into the
VARBIND
with the appropriate type.
The syntax is as follows:
int o_oid
(VARBIND
*vb,
OBJECT
*obj,
OID
*oid
)
The arguments are as follows:
VARBIND *vb
.dD
Is a pointer to the
VARBIND
structure that is
to receive the data. This function does not allocate the
VARBIND
structure.
Note
If the original value in the
varbind vb
is not NULL, this routine attempts to free it; therefore, if youmalloc
your ownvb
structure, fill it with zeros (0s) before using it.
OBJECT *obj
.dD
Is a pointer to the
OBJECT
structure for the MIB
variable associated with the oid in the
VARBIND
.
OID *value
.dD
Is the value to be inserted into the
VARBIND
structure as data.
The real type as defined in the object structure must be the following; otherwise, an error is returned:
The following is an example of the
o_oid
routine:
#include <esnmp.h> #include "ip_tbl.h" <-- for ipNetToMediaEntry_type definition VARBIND *vb = method->varbind; OBJECT *object = method->object; ipNetToMediaEntry_type *data;
switch(arg) { case I_atObjectID: return o_oid(vb, object, &data->ipNetToMediaObjectID);
The returns are as follows:
The
o_string
routine loads a string value into the
VARBIND
with the appropriate type.
The syntax is as follows:
int o_string
(
VARBIND *vb,
OBJECT *obj,
unsigned char *ptr,
int len
)
The arguments are as follows:
VARBIND *vb
.dD
Is a pointer to the
VARBIND
structure which is to receive the data.
This function does not allocate the
VARBIND
structure.
Note
If the original value in the
varbind vb
is not NULL, this routine attempts to free it; therefore, if youmalloc
your ownvb
structure, fill it with zeros (0s) before using it.
OBJECT *obj
.dD
Is a pointer to the
OBJECT
structure for the MIB variable associated with
the
oid
in the
VARBIND
.
unsigned char *ptr
.dD
Is the pointer to the buffer containing data to be inserted into the
VARBIND
as data.
int len
.dD
Is the length of the data in buffer to which
ptr
points.
The real type as defined in the object structure must be one of the following; otherwise, an error is returned:
The following is an example of the
o_string
routine:
#include <esnmp.h> #include "ip_tbl.h" <-- for ipNetToMediaEntry_type definition VARBIND *vb = method->varbind; OBJECT *object = method->object; ipNetToMediaEntry_type *data;
switch(arg) { case I_atPhysAddress: return o_string(vb, object, data->ipNetToMediaPhysAddress.ptr, data->ipNetToMediaPhysAddress.len);
The return values are as follows:
The
str2oid
routine converts a null-terminated OID string (in dot notation) to an
OID
structure.
It dynamically allocates the elements buffer and inserts
its pointer into the
OID
structure passed in.
It is the responsibility of the caller to free this buffer.
The OID can have a maximum of 128 elements.
Note that the
str20id
routine does not allocate an
OID
structure.
The syntax is as follows:
OID * str2oid
(
OID *oid,
char *s
)
The following is an example of the
str20id
routine:
#include <esnmp.h> OID abc; if (str2oid(&abc, "1.2.5.4.3.6") == NULL) DPRINTF((WARNING,"It did not work...\n"));
The returns are as follows:
OID
structure (its first argument) is returned.
The
sprintoid
routine converts an
OID
into a null-terminated string in dot notation.
An
OID
can have up to 128 elements. A full sized
OID
can require a large buffer.
The syntax is as follows:
char *sprintoid
(
char *buffer,
OID *oid
)
The following is an example of the
sprintoid
routine:
#include <esnmp.h> #define SOMETHING_BIG 1024 OID abc; char buffer[SOMETHING_BIG]; : : assume abc gets initialized with some value : printf("dots=%s\n", sprintoid(buffer, &abc));
The return values are its first argument.
The
instance2oid
routine makes a copy of the object's base OID and appends
a copy of the instance array to make a complete OID for a value.
The
instance
is an array of integers and
len
is the number of elements. The instance array may be created by
oid2instance
or constructed from key values as a result of a
get_next
search.
It dynamically allocates the elements buffer and inserts
its pointer into the
OID
structure passed in.
The caller is responsible for freeing this buffer.
Point to the
OID
structure that is to receive the new
OID
values and call this routine. Any previous value in the
OID
structure is freed (it calls
free_oid
first) and the new values are dynamically allocated and inserted.
Be sure the initial value of the new
OID
is all zeros, if you do not want it to be freed.
Note that the
instance2oid
routine does not allocate an
OID
structure, only the array containing the elements.
The syntax is as follows:
OID * instance2oid
(
OID *new,
OBJECT *obj,
unsigned int *instance,
int len
)
The arguments are as follows:
OID *new
.dD
Is a pointer to the
OID
that is to receive the new
OID
value.
OBJECT *obj
.dD
Is a pointer to the object table entry for the MIB variable
being obtained. The first part of the new
OID
is the
OID
from this MIB object table entry.
unsigned int *instance
.dD
Is a pointer to an array of
instance
values.
These values are appended to the base
OID
obtained from the MIB object table entry to construct the new
OID
.
int len
.dD
Is the number of elements in the
instance
array.
The following is an example of the
instance2oid
routine:
#include <esnmp.h> VARBIND *vb; <-- filled in OBJECT *object; <-- filled in unsigned int instance[6];
-- Construct the outgoing OID in a GETNEXT -- -- Instance is N.1.A.A.A.A where A's are IP address -- instance[0] = data->ipNetToMediaIfIndex; instance[1] = 1; for (i = 0; i < 4; i++) { instance[i+2]=((unsigned char *)(&data->ipNetToMediaNetAddress))[i]; } instance2oid(&vb->name, object, instance, 6);
The returns are as follows:
OID
(its first argument) is returned.
The
oid2instance
routine extracts the instance values from an
OID
and copies them to the specified array of integers.
It then returns the number of elements in the array.
The instance is the elements of an OID beyond those
elements that identify the MIB variable. They are used
as indexes to identify a specific instance of a MIB value.
If there are more elements in the
OID
than expected (more than specified by the
max_len
parameter), the function copies the number of elements specified
by
max_len
only and returns
the total number of elements that would have been copied had
there been space.
The syntax is as follows:
int oid2instance
(
OID *oid,
OBJECT *obj,
unsigned int *instance,
int max_len
)
The arguments are as follows:
oid
OID
containing an instance or part of an instance.
obj
instance
max_len
#include <esnmp.h> OID *incoming = &method->varbind->name; OBJECT *object = method->object; int instLength; unsigned int instance[6];
-- in a GET operation -- -- Expected Instance is N.1.A.A.A.A where A's are IP address -- instLength = oid2instance(incoming, object, instance, 6); if (instLength != 6) return ESNMP_MTHD_noSuchInstance;
The
N
will be in
instance[0]
and the IP address will be in
instance[2]
,
instance[3]
,
instance[4]
,
and
instance[5]
.
The returns are as follows:
oid
.
max_len
parameter).
The
inst2ip
routine returns an IP address derived from an OID
instance. For evaluation of an instance for
Get
and
Set
operations use the
EXACT
mode. For
GetNext
and
GetBulk
operations use the
NEXT
mode. When using the
NEXT
mode, this routine's
logic assumes that the search for data will be performed using
greater than or equal to matches.
The syntax is as follows:
int inst2ip
(
unsigned int *inst,
int length,
unsigned int *ipAddr,
int exact,
int carry
)
The arguments are as follows:
inst
unsigned int
containing the instance numbers returned by the
oid2instance
routine to be
converted to an IP address.
Each element is in the range 0 to 255. Using the EXACT
mode, the routine returns 1 if an element is out of range. Using
NEXT mode, a value greater than 255 causes that element
to overflow. It is set to 0 and the next most significant
element is incremented, so it returns a lexically
equivalent value of the next possible
ipAddress
.
length
ipaddr
value of 0.
For an exact match (such as
Get
)
there must be at exactly four elements.
ipAddr
exact
TRUE
or
FALSE
.
TRUE
means do an EXACT match. If any element is greater than
255 or if there are not exactly 4 elements, return 1.
The carry argument is ignored.
FALSE
means do a NEXT match. That is, return the lexically
next IP address if the carry is set and the length is
at least 4. If there are fewer than 4 elements, assume
the missing values are 0. If any one element contains
a value greater than 255, then zero the
value and increment the next most significant element.
Return 1 only in the case where there is a carry
from the most significant (the first) value.
carry
The following are examples of the
inst2ip
routine.
The following example converts an instance to an IP address for a
Get
operation, which is an
EXACT
match.
#include <esnmp.h> OID *incoming = &method->varbind->name; OBJECT *object = method->object; int instLength; unsigned int instance[6]; unsigned int ip_addr; int iface;
-- The instance is N.1.A.A.A.A where the A's are the IP address-- instLength = oid2instance(incoming, object, instance, 6); if (instLength == 6 && !inst2ip(&instance[2], 4, &ip_addr, TRUE,0)) { iface = (int) instance[0]; } else return ESNMP_MTHD_noSuchInstance;
The following example shows a
GetNext
where there is only one key or that the
ipaddr
is the least significant part of the
key. This is a NEXT match; therefore, a 1 is passed in for
carry
.
#include <esnmp.h> OID *incoming = &method->varbind->name; OBJECT *object = method->object; int instLength; unsigned int instance[6]; unsigned int ip_addr; int iface;
-- The instance is N.1.A.A.A.A where the A's are the IP address-- instLength = oid2instance(incoming, object, instance, 6); iface = (instLength < 1) ? 0 :(int) instance[0];
iface += inst2ip(&instance[2], instLength - 2, &ip_addr, FALSE, 1);
In the following example, if there is more than one part to a search key
and you are doing a
GetNext
,
you want to find the next
possible value for the search key so you can do a cascaded
greater-than or equal-to search.
If you have a search key of a number and two
ipAddr
values that are represented in the instance part of the OID as
N.A.A.A.A.B.B.B.B
with
N
as single valued integer and
A.A.A.A
portion making up one IP address and the
B.B.B.B
portion making up a second IP address and
a total length of 9 if all elements are given,
you start by converting the least significant part of the key,
(that would be the
B.B.B.B
portion). You do that by calling the
inst2ip
routine passing in a 1 for the carry and 5 for the length.
If the conversion of the
B.B.B.B
portion generated a carry (returned 1),
you will pass it on to the next most significant part
of the key. Therefore, convert the
A.A.A.A
portion by calling the
inst2ip
routine, passing in 1 for the length and the carry returned
from the conversion of the
B.B.B.B
portion. The most significant element
N
is a number; therefore, add the carry from the
A
conversion to the number. If that also overflows, then this is not a valid
search key.
#include <esnmp.h> OID *incoming = &method->varbind->name; OBJECT *object = method->object; int instLength; unsigned int instance[9]; unsigned int ip_addrA; unsigned int ip_addrB; int iface; int carry;
-- The instance is N.A.A.A.A.B.B.B.B -- instLength = oid2instance(incoming, object, instance, 9); iface = (instLength < 1) ? 0 :(int) instance[0]; carry = inst2ip(&instance[1],instLength - 1,&ip_addr,FALSE,1); carry = inst2ip(&instance[5],instLength - 5,&ip_addr,FALSE,carry); iface += carry; if (iface > 0xFFFFFFFF) -- a carry caused an overflow in the most significant element return ESNMP_MTHD_noSuchInstance;
The returns are as follows:
carry
is 0, the routine completed successfully.
carry
equals 1, it indicates an error if EXACT match
or there was a carry for a NEXT match.
If there was a carry, the returned
ipAddr
is 0.
The
cmp_oid
routine compares two
OID
structures. This routine does an element-by-element
comparison starting with the most significant element (element 0)
and working toward the least significant element. If all other
elements are equal, the
OID
with the fewest elements is considered less.
The syntax is as follows:
int cmp_oid
(
OID *q,
OID *p
)
The returns are as follows:
oid q
is greater than
oid p
.
oid q
is in
oid p
.
oid q
is less than
oid p
.
The
cmp_oid_prefix
routine compares an
OID
against a prefix. A prefix could be
the
OID
on an object in the object table. The elements
beyond the prefix are the instance information.
This routine does an element-by-element comparison, starting with the
most significant element (element 0) and working toward the least significant
element.
If all elements of the prefix
OID
match exactly with corresponding elements
of
OID q
,
it is considered an even match if
OID q
contains additional elements.
OID q
is considered greater than the prefix
if the first nonmatching element is larger. It is considered
smaller if the first nonmatching element is less.
The syntax is as follows:
int cmp_oid_prefix
(
OID *q,
OID *prefix
)
The following is and example of the
cmp_oid_prefix
routine:
#include <esnmp.h> OID *q; OBJECT *object; if (cmp_oid_prefix(q, &object->oid) == 0) printf("matches prefix\n");
The returns are as follows:
oid
is less than the prefix.
oid
is in the prefix.
oid
is greater than the prefix.
The
clone_oid
routine makes a copy of the
OID
structure.
Pass in a pointer to the source
OID
structure to be cloned and a pointer to the new
OID
structure that is to receive the duplicated
OID
values.
It dynamically allocates the element's buffer and inserts
its pointer into the
OID
structure passed in.
It is the responsibility of the caller to free this buffer.
Note that any previous elements buffer pointed to by the new
OID
structure will be freed and pointers to the new, dynamically allocated,
buffer will be inserted. Be sure to initialize the new
OID
structure with zeroes (0), unless it contains an element buffer that can
be freed.
Also note that this routine does not allocate an
OID
structure.
The syntax is as follows:
OID *clone_oid
(
OID *new,
OID *oid
)
The arguments are as follows:
OID *new
.dD
Is a pointer to the
OID
structure that is to receive the copy.
OID *old
.dD
Is a pointer to the
OID
structure where the data is to be obtained.
The following is an example of the
clone_oid
routine:
#include <esnmp.h> OID oid1; OID oid2; : : assume oid1 gets assigned a value : memset(&oid2, 0, sizeof(OCT)); if (clone_oid(&oid2, &oid1) == NULL) DPRINTF((WARNING, "It did not work\n"));
The returns are as follows:
The
free_oid
routine frees an
OID
structure's elements buffer.
It frees the buffer pointed to by
oid->elements
then zeros that field and
oid->nelem
.
Note that this routine does not deallocate the
OID
structure itself, only the elements buffer attached to it.
The syntax is as follows:
void free_oid
(
OID *oid
)
The following is an example of the
free_oid
routine:
#include <esnmp.h> OID oid; : : assume oid was assigned a value (perhaps with clone_oid() : and we are now finished with it. : free_oid(&oid);
The
clone_buf
routine duplicates a buffer in a dynamically allocated space.
One extra byte is always allocated on end and filled with
\0
.
If the length is less than 0, its length is set to 0.
There is always a buffer pointer, unless there is a
malloc
error.
It is the callers responsibility to free the allocated buffer.
The syntax is as follows:
char *clone_buf
(
char *str,
int len
)
The arguments are as follows:
str
len
The following is an example of the
clone_buf
routine:
#include <esnmp.h> char *str = "something nice"; char *copy; copy = clone_buf(str, strlen(str));
The returns are as follows:
malloc
error; otherwise, the pointer to allocated buffer containing a copy
of the original buffer is returned.
The
mem2oct
routine converts a string, (a buffer and length) to an
OCT
structure.
It dynamically allocates a new
buffer, copies the indicated data into it, and updates the
OCT
structure with the new buffer's address and length.
It is the responsibility of the caller to free the allocated buffer.
Note this routine does not allocate an
OCT
structure and that it does not free data previously pointed to in the
OCT
structure before making the assignment.
The syntax is as follows:
OCT * mem2oct
(
OCT *new,
char *buffer,
int len
)
The following is an example of the
mem2oct
routine:
#include <esnmp.h> char buffer; int len; OCT abc;
...buffer and len are initialized to something...
memset(&abc, 0, sizeof(OCT)); if (mem2oct(&abc, buffer, len) == NULL) DPRINTF((WARNING,"It did not work...\n"));
The following are the return values:
OCT
structure (its first argument) is returned.
The
cmp_oct
routine compares two octets. The two octets are compared
byte-by-byte for the length of the shortest
octet. If all bytes are equal, the lengths are compared.
An octet with a null pointer is considered the same as
a zero-length octet.
The syntax is as follows:
int cmp_oct
(
OCT *oct1,
OCT *oct2
)
The following is an example of the
cmp_oct
routine:
#include <esnmp.h> OCT abc, efg;
...abc and efg are initialized to something...
if (cmp_oct(&abc, &efg) > 0) DPRINTF((WARNING,"octet abc is larger than efg...\n"));
The returns are as follows:
The
clone_oct
routine makes a copy of the
OCT
structure.
It passes in a pointer to the source
OCT
structure to be cloned and a pointer to the new OCT structure that is to
receive the duplicated
OCT
structure's values.
It dynamically allocates the buffer, copies the data, and updates the new
OCT
structure with the buffer's address and length.
It is the responsibility of the caller to free this buffer.
Note that any previous buffer to which the new
OCT
structure points is freed and pointers to the new, dynamically allocated buffer
are inserted. Be sure to initialize the new
OCT
structure with zeros (0), unless it contains a buffer that can be freed.
Also note that this routine does not allocate an
OCT
structure, only the elements buffer pointed to by the
OCT
structure.
The syntax is as follows:
OCT * clone_oct
(
OCT *new,
OCT *old
)
The arguments are as follows:
OCT *new
.dD
Is a pointer to the
OCT
structure that is to receive the copy.
OCT *old
.dD
Is a pointer to the
OCT
structure where the data is to be obtained.
The following is an example of the .clone_oct routine:
#include <esnmp.h> OCT octet1; OCT octet2; : : assume octet1 gets assigned a value : memset(&octet2, 0, sizeof(OCT)); if (clone_oct(&octet2, &octet1) == NULL) DPRINTF((WARNING, "It did not work\n"));
The returms are as follows:
OCT
structure (its first argument) is returned.
The
free_oct
routine frees the buffer attached to the
OCT
structure.
It frees a dynamically allocated buffer to which the
OCT
structure points, then zeros (0) the pointer and length fields in the
OCT
structure. If the buffer is already NULL
this routine does nothing.
Note that this routine does not deallocate the
OCT
structure, only the buffer to which it points.
The syntax is as follows:
void free_oct
(
OCT *oct
)
The following is an example of the
free_oct
routine:
#include <esnmp.h> OCT octet; : : assume octet was assigned a value (perhaps with mem2oct() : and we are now finished with it. : free_oct(&octet);
The
free_varbind_data
routine frees the dynamically allocated fields within the
VARBIND
structure.
The routine performs a
free_oid
(vb
-> name
)
operation. If the
vb->type
field indicates, it then frees the
vb->value
data using either the
free_oct
or the
free_oid
routine.
It does not deallocate the
VARBIND
structure itself; only the name and data buffers to which it points.
The syntax is as follows:
void free_varbind_data
(
VARBIND *vb
)
The following is an example of the
free_varbind_data
routine:
#include <esnmp.h> VARBIND *vb;
vb = (VARBIND*)malloc(sizeof(VARBIND)); clone_oid(&vb->name, oid); clone_oct(&vb->value.oct, data);
.
.
.
free_varbind_data(vb); free(vb);
The
set_debug_level
routine sets the logging level which dictates what log messages are
generated. You should call the routine during program initialization
in response to runtime options. If not called, this
will be set to WARNING and ERROR messages to
stdout
as the default.
The following values can be set:
syslog
rather than to standard output.
callback
- A user-supplied external callback function:
void callback_function
(
int level,
char *message
)
The
level
will be
ERROR
,
WARNING
,
or
TRACE
.
If the EXTERN_LOG bit is set in
stat
,
the
callback
function will be called whenever an
ESNMP_LOG
macro is executed and the log level indicates that a log
message is to be generated.
This facility allows an implementer to control where
eSNMP library functions output log messages.
If
EXTERN_LOG
bit will not be set, pass in a NULL
pointer for the callback function argument.
The syntax is as follows:
void set_debug_level(int stat, LOG_CALLBACK_ROUTINE callback_routine)
The following is an example of the
set_debug_level
routine:
#include <esnmp.h> extern void log_handler(int level, char *message);
if (daemonize) set_debug_level(EXTERN_LOG | WARNING, log_handler); else set_debug_level(TRACE, NULL);
The
is_debug_level
routine tests the log level to see if the specified level is set.
You can set the levels as follows:
ERROR
- For when a bad error occurred, requiring restart.
WARNING
- For when a packet cannot be handled.
TRACE
- For when tracing all packets.
DAEMON_LOG
- For output going to
syslog
.
EXTERN_LOG
- For the
callback
function is to be called to output log messages.
The syntax is as follows:
int is_debug_level
(
int type
)
The return values are as follows:
TRUE
ESNMP_LOG
will generate output, or output will go to the specified destination.
FALSE
is_debug_level
routine is not set.
The following is an example of the
is_debug_level
routine:
#include <esnmp.h>
if (is_debug_level(TRACE)) dump_packet();
The
ESNMP_LOG
routine is an error declaration C macro defined in the
<esnmp.h>
header file. It gathers the information that it
can obtain and sends it to the log. If
DAEMON_LOG
is set, log messages are sent to the daemon log. If
EXTERN_LOG
is set, log messages are sent to the
callback
function; otherwise, log messages go to standard output.
Note
The
esnmp_log
routine is called using the ESNMP_LOG macro, which uses the helper routineesnmp_logs
to format part of the text. Do not use these functions without theESNMP_LOG
macro.
#define ESNMP_LOG(level, x) if (is_debug_level(level)) { \
esnmp_log(level, esnmp_logs x, __LINE__, __FILE__);}
Where
x
is
(text)
:
text - format, arguments, ....
.dD
For example a
printf
statement.
level
ERROR
WARNING
TRACE
The syntax is as follows:
ESNMP_LOG
(
level,
(
format, ...
))
The following is an example of the
ESNMP_LOG
routine:
#include <esnmp.h> ESNMP_LOG( ERROR, ("Cannot open file %s\n", file));