Open internetworking technologies such as TCP/IP allow an almost limitless combination of devices and media from different vendors to participate in an internet. Any functional management technology for an internet, therefore, must be vendor-independent. The Simple Network Management Protocol (SNMP) is one management technology used to monitor and control devices on TCP/IP-based networks. It allows the retrieval and alteration of network information maintained in the Management Information Base (MIB) by these devices.
The MIB is a tree-structured collection of objects which are accessible via SNMP. The branches of the tree comprise logically related groups of managed objects. All implementations of SNMP can expect to find internet standard object groups in the MIB; objects that are specific to a particular vendor's SNMP implementation can be defined in the enterprises branch of the MIB. These abstract objects are templates that describe an object identifier (name) and type for the actual object instances which are manipulated by SNMP. To access an object instance on a managed device, both the object's identifier and the instance identifier must be known.
SNMP systems are typically composed of network management stations and SNMP agents in network devices such as hosts, gateways, routers, and terminal servers. Network management stations are used to issue requests for retrieving or changing information in the MIB; SNMP agents process the requests and carry out the necessary operations to fulfill the management stations' requests.
A management station controls and monitors object instances on managed devices by using three basic SNMP operations: get, get-next, and set. Agents carry out and respond to these operations, and use a fourth SNMP operation, called a trap, to report unusual conditions or events to the management station.
SNMP agents are usually implemented as user processes that read information either from the kernel variables or from some sort of stable storage, such as files. Other user processes, like routed and gated, are sometimes employed to perform various network services. The SNMP agent needs to talk to these other processes to make their information available to the network management station.
The SMUX (SNMP Multiplexing) protocol defines a mechanism for communication between an SNMP agent and multiple user daemons (called SMUX peers). The SMUX protocol has the advantage of being vendor-independent: with SMUX, a single SNMP agent can be used to control and monitor devices from different vendors, regardless of how those vendors have implemented SMUX peers in their own products.
If you are unfamiliar with SNMP, you may wish to read the following information about it before you implement your SMUX peer:
The next four subsections, ``Initialization'' through
``Termination,'' describe the sequence of events
in this interaction.
In this example, the SMUX
peer is used to manage the multi-port serial board installed
in a host running the SCO UNIX system.
The system administrator wants to use the network management station
to get the baud rate for serial line 2 of the multi-port serial
board installed in the host.

Figure 9-1 Example SMUX session
Not all messages in this system originate with the management station. If
the peer detects a noteworthy condition from the serial board, the peer
sends an unsolicited message called an
``SMUX trap'' to the agent.
The agent translates it into an
SNMP
trap, which is then sent to
the network management station.
The SNMP agent listens for SNMP messages from SNMP clients on UDP port 161. The agent also listens on TCP port 199 for connection requests from SMUX peers. When an SMUX peer starts running, it calls a routine which initiates a TCP connection with the agent. Once the TCP connection is established, the peer initializes the SMUX association by sending a Protocol Data Unit (PDU) message called an OpenPDU to the agent.
The SNMP agent checks authentication information in the OpenPDU against that contained in the configuration file /etc/snmpd.peers. This file lists all SMUX peers known to the agent, passwords for each, and (optionally) maximum allowable priority for each (refer to the section ``Priority'' for more information about this parameter). If for any reason the agent refuses the association with this SMUX peer, the agent sends the peer a ClosePDU and closes the TCP connection. Otherwise, the agent accepts this association and no response PDU is sent back to the peer.
Once the SMUX association has been established, the peer registers the MIB module it wishes to manage by sending a RReqPDU to the agent. The agent responds by returning a RRspPDU. The peer must issue a separate RReqPDU for each MIB module it wants to manage; the agent accepts or declines the requests by sending the appropriate RRspPDUs in the same order as it received the RReqPDUs from the peer.
What happens when more than one SMUX peer attempts to register the same MIB module? The SMUX protocol has a priority mechanism built in to handle these cases.
Each peer registration has associated
with it an integer priority value in the range 0 to
the maximum value of a 32-bit signed long integer, (2
And what happens when a peer attempts to register a MIB module which encompasses a MIB module already registered by another SMUX peer? When peer B registers a MIB module which encompasses the module registered by peer A, peer B assumes responsibility for managing A's module as well.
RReqPDUs may have a special priority value of -1. When a peer uses this priority to register a MIB module, the agent assigns the highest available priority (that is, the lowest integer value available for that module) to that registration.
In the example SMUX session, the agent used the SNMP ``get'' operation to retrieve information from the peer. The agent can also use the SNMP ``set'' operation to write values to variables in the peer's MIB module. This section takes a closer look at the get and set operations.
The sequence of events during the execution of an SNMP ``get'' or ``get-next'' operation is represented in Figure 9-2.

Figure 9-2 An SNMP ``get'' or ``get-next'' operation
When the SNMP client on the network management station issues a GetRequest or GetNextRequest PDU to the agent, the agent determines which MIB modules contain the variable(s) requested.
The agent processes each variable in turn, and issues a GetRequestPDU or GetNextRequestPDU to the appropriate SMUX peer for each variable.
Upon receiving the request PDU from the agent, the peer performs the specified operations and returns a GetResponsePDU to the agent. If the peer is unable to process the request, it returns an error code in the GetResponsePDU.
Then the agent collates the result contained in the response(s)
and issues a
GetResponsePDU to the network management station.
The sequence of events for the execution of an SNMP ``set''
operation is represented in
Figure 9-3, ``An SNMP ``set'' operation''.

Figure 9-3 An SNMP ``set'' operation
When the network management station user wishes to alter the
value of one or more
MIB
variables, the
SNMP
client on the management
station issues a
SetRequestPDU for that variable to the agent. The agent processes
each variable sequentially, determining which
MIB
modules contain
the variable(s) in the request. For those variables in registered
MIB
modules, the agent sends a SetRequestPDU to the appropriate peer.
Because any peer may accept or refuse a set operation, carrying out a set operation is a two-phase process.
If the peer accepts the set operation, it issues a GetResponsePDU with error-status noError.
However, if all peers accepted the set operation, the agent issues a ``commit'' SOutPDU to all peers saying, in effect, ``Proceed with the set operation.''
You will recall from ``Asynchronous events'' that a peer sends an unsolicited ``trap'' message to the agent when the peer detects an unusual event or condition. This trap message is sent in a TrapPDU; the sequence of events in this operation is represented in Figure 9-4.

Figure 9-4 An SNMP ``trap'' operation
Up to this point, we have referred to traps sent to the agent
by the peer; you should be aware that the agent also generates
traps, which it sends to the network management station.
The TrapPDU contains a generic-trap field which contains
a value in the range zero to six, which corresponds to one the
following extraordinary events or conditions:
A ClosePDU is issued to end an SMUX association. This PDU removes all registrations for the peer and closes the TCP connection between the agent and the peer.
There are several possible reasons for the termination of an SMUX association between a peer and an agent:
The SMUX peer removes its MIB module registrations before it terminates the SMUX association with the agent. A peer can only remove registration for the modules it registered. If the peer does not record the priority at which it registered a module, it can unregister the module with a priority -1; the agent removes that peer's highest priority registration for that module.
Subsequent requests from the management station for information contained
in the now unregistered module are responded to by the SNMP agent.
If the unregistered module has since been registered by another peer,
the agent forwards the requests to that peer.
If the agent is now managing the MIB module,
the agent performs the requested operations.
However, if the MIB module in question has not been
registered to another peer and that module contains variables which are
inaccessible to the agent, the agent sends a response PDU
containing a noSuchName error code to the management station.
Implementing an SMUX peer
In the previous section,
we described the interaction between a management station,
an SNMP agent and an SMUX
peer, and discussed how the SMUX protocol works.
This section describes the tasks required to implement
an SMUX peer:
The foo SMUX peer daemon is implemented with the following five files:
First, write a MIB module to define the actual managed
objects which the SMUX peer is to manage.
RFC 1212 ``Concise MIB Definitions''
defines a format for writing MIB modules;
you may find it a useful place to start
While describing these rules is not within the scope
of this chapter, you may find some of the following
basic information helpful.
Define the MIB module in a file called *.my; for the reference peer, the file is called foosmuxd.my. MIB modules fall into 3 broad categories:
SendMail-MIB { iso(1) org(3) dod(6) internet(1) private(4)
enterprises(1) unix (4) sendmail (99) }
DEFINITIONS ::= BEGIN
IMPORTS
unix --*, OBJECT-TYPE *--
FROM RFC1155-SMI;
sendMail OBJECT IDENTIFIER ::= { unix 99 }
FOO-MIB DEFINITIONS ::= BEGIN
IMPORTS
experimental, OBJECT-TYPE
FROM RFC1155-SMI;
foo OBJECT IDENTIFIER ::= { experimental 99 }
FOO-MIB DEFINITIONS ::= BEGIN
IMPORTS
enterprises, OBJECT-TYPE
FROM RFC1155-SMI;
foo OBJECT IDENTIFIER ::= { enterprises 9999 }
Following this start, you have the actual definitions of the MIB objects, followed by
END
Once you have defined a MIB module, you can compile it.
This step is not absolutely necessary,
because the make command runs the mosy compiler
automatically when make compiles the entire peer
program (see
``Compiling an SMUX peer'').
You can invoke make on the
MIB module to verify that
mosy will compile the module into a
form that the peer program can read.
The command to run mosy on
foosmuxd.my is:
make -f foosmuxd.mk foosmuxd.defs
Replace the string ``foo'' with the
identifying string for your peer.
The syntax of the compiled module foosmuxd.defs is:
#
#
#name value
#
internet iso.3.6.1
#
#
#name oid syntax access status
#
productName serialBoard.1 DisplayString read-only mandatory
where ``name'' and ``oid'' are obvious.
For the rest:
``syntax'' is the name of a defined syntax;
``access'' is one of read-only, read-write,
or none;
``status'' is one of mandatory, optional, deprecated, or obsolete.
Names of objects are always case-sensitive.
The resulting ASCII file looks something like this:
"ccitt" "0" "iso" "1" "internet" "1.3.6.1" "directory" "1.3.6.1.1" "mgmt" "1.3.6.1.2" "experimental" "1.3.6.1.3" "private" "1.3.6.1.4" "enterprises" "1.3.6.1.4.1" "foo" "1.3.6.1.4.1.9999" "serialBoard" "1.3.6.1.4.1.9999.1" "productName" "1.3.6.1.4.1.9999.1.1" "boardStatus" "1.3.6.1.4.1.9999.1.2" "exampleIpAddr" "1.3.6.1.4.1.9999.1.3" "exampleObjectID" "1.3.6.1.4.1.9999.1.4" "numberLines" "1.3.6.1.4.1.9999.1.5" "serialLineTable" "1.3.6.1.4.1.9999.1.6" "serialLineEntry" "1.3.6.1.4.1.9999.1.6.1" "serialLineNumber" "1.3.6.1.4.1.9999.1.6.1.1" "serialLineBaudRate" "1.3.6.1.4.1.9999.1.6.1.2" "serialLineTermLocation" "1.3.6.1.4.1.9999.1.6.1.3" "joint-iso-ccitt" "2"This output file is meant to be read as configuration data by network management utilities.
Begin by modifying all occurrences of the string ``foo'' to the identifying string you chose for your peer.
Then, for every leaf object ID in your MIB module, create a #define with the same name and set the value equal to the last octet of the object ID. In foomib.c the upper case #define convention is followed. The following #define statements are for the leaves of the enterprises.foo.serialBoard MIB module:
#define PRODUCTNAME 1 /* serialBoard.1 */
#define BOARDSTATUS 2 /* serialBoard.2 */
#define EXAMPLEIPADDR 3 /* serialBoard.3 */
#define EXAMPLEOBJECTID 4 /* serialBoard.4 */
#define NUMBERLINES 5 /* serialBoard.5 */
The three #define statements shown below are for the leaves of the
enterprises.foo.serialBoard.serialLineTable.serialLineEntry
MIB module:
#define SERIALLINENUMBER 1 /* serialLineEntry.1 */
#define SERIALLINEBAUDRATE 2 /* serialLineEntry.2 */
#define SERIALLINETERMLOCATION 3 /* serialLineEntry.3 */
Now, for each label that is associated with an object type,
create a #define to represent the same value as shown in your
MIB module.
The following #define statements are for boardStatus
and serialLineStatus:
#define ENABLED 1
#define DISABLED 2
The following #defines are for serialLineBaudRate:
#define BAUD1200 1
#define BAUD2400 2
#define BAUD9600 3
#define BAUD19800 4
This #define is for the maximum size of the
serialLineTable:
#define MAXTABLESIZE 64
char *myname = "foosmuxd";
replacing the string ``foo'' with the identifying
string you chose for your peer.
Now, for each MIB module managed by this peer, put one entry in the triples[] table. In this example, there is only the one foo MIB module for the sake of simplicity. init_foo and sync_foo are functions implemented in this file for the foo MIB module.
int init_foo(), sync_foo();
struct triple triples[] = {
{"foo", NULL, readWrite, init_foo, sync_foo},
{NULL}
};
Then, for each single instance leaf object in your
MIB,
create a variable
with the same name. Use the appropriate C data type.
If the variable is read-write, also make a
``new_name'' variable of the same
data type. The ``new_name'' is used by
the SMUX peer to implement an
SMUX
``commit'' or ``rollback'' SOutPDU.
static char *productName; static int boardStatus; static int new_boardStatus; static struct in_addr exampleIpAddr; static struct in_addr new_exampleIpAddr; static int newflag_exampleIpAddr; static OID exampleObjectID; static OID new_exampleObjectID; static int numberLines;Next, create a structure for each table in your MIB. For each element of the structure use the same name as in the MIB. Use the appropriate C data type. If the variable is read-write, also make a ``new_name'' variable of the same data type. The ``new_name'' is used by the peer to implement an SMUX ``commit'' or ``rollback'' SOutPDU.
struct sltable {
int serialLineNumber;
int serialLineBaudRate;
int new_serialLineBaudRate;
char serialLineTermLocation[LOCATIONSIZE];
char new_serialLineTermLocation[LOCATIONSIZE];
int newflag_serialLineTermLocation;
} serialLineTable[MAXTABLESIZE];
The initializing routine init_foo()
is executed when the peer first starts running.
It is only for the foo MIB module.
init_foo() has two primary functions:
it initializes the values of the MIB
objects and it initializes the MIB
data structures used by the SMUX peer.
For each MIB module, make one of the following functions of name init_foo(), replacing the string ``foo'' with the identifying string you chose for your peer. The following example is for the foo MIB module:
int
init_foo()
{
Next, initialize the values of the MIB objects.
Initialize the value of each single instance leaf object
and each instance of variables in tables.
For each single instance variable initialize the value. If the object has an access of read-write set the ``new_name'' variable to a null value.
productName = "Foo Serial I/O Board";
boardStatus = ENABLED;
new_boardStatus = 0;
exampleIpAddr.s_addr = 0;
newflag_exampleIpAddr = 0;
exampleObjectID = make_obj_id_from_dot(DEFAULT_EXAMPLEOBJECTID);
new_exampleObjectID = NULLOID;
numberLines = 32;
Now, for each table in the
MIB
module, add a for() loop. This example is for the
serialLineTable, which has numberLines
rows:
for (i = 0; i < numberLines; i++) {
For every object in the table, add an assignment statement to the
for() loop that
initializes the variable to a legal value.
A ``real world''
SMUX
peer would probably read these start-up or default
values from the device being managed or from a configuration file.
serialLineTable[i].serialLineNumber = i + 1;
You should
also add an assignment statement to set the new pending value to null.
serialLineTable[i].new_serialLineBaudRate = 0;
Next, initialize the MIB database used by the SMUX peer.
For every leaf object in the
MIB module,
add an ``if'' statement
as in the following examples. The ot_getfnx
pointer points to the
function used to process the GetRequest operation
for this object. The
ot_setfnx pointer points to the function used to process the
SetRequest operation
for this object. Objects that have an access of
read-only have an ot_setfnx pointer that points to null. The
ot_info is the last octet in the object
ID
as specified in the #define above.
if (ot = text2obj("productName")) {
ot->ot_getfnx = get_single_instance;
ot->ot_info = (caddr_t) PRODUCTNAME;
}
if (ot = text2obj("boardStatus")) {
ot->ot_getfnx = get_single_instance;
ot->ot_setfnx = set_single_instance;
ot->ot_info = (caddr_t) BOARDSTATUS;
}
if (ot = text2obj("exampleIpAddr")) {
ot->ot_getfnx = get_single_instance;
ot->ot_setfnx = set_single_instance;
ot->ot_info = (caddr_t) EXAMPLEIPADDR;
}
if (ot = text2obj("exampleObjectID")) {
ot->ot_getfnx = get_single_instance;
ot->ot_setfnx = set_single_instance;
ot->ot_info = (caddr_t) EXAMPLEOBJECTID;
}
if (ot = text2obj("numberLines")) {
ot->ot_getfnx = get_single_instance;
ot->ot_info = (caddr_t) NUMBERLINES;
}
if (ot = text2obj("serialLineNumber")) {
ot->ot_getfnx = get_serialLineTable;
ot->ot_info = (caddr_t) SERIALLINENUMBER;
}
if (ot = text2obj("serialLineBaudRate")) {
ot->ot_getfnx = get_serialLineTable;
ot->ot_setfnx = set_serialLineTable;
ot->ot_info = (caddr_t) SERIALLINEBAUDRATE;
}
if (ot = text2obj("serialLineTermLocation")) {
ot->ot_getfnx = get_serialLineTable;
ot->ot_setfnx = set_serialLineTable;
ot->ot_info = (caddr_t) SERIALLINETERMLOCATION;
}
When a GetRequest or GetNextRequest
PDU is being processed, control is
passed through the get function, and it is in the
get function that objects are read.
For every single instance leaf object in your MIB module, add a ``case'' to this switch statement. Use the appropriate function (o_string(), o_integer(), and so on) according to the object's syntax.
switch (ifvar) {
case PRODUCTNAME:
return o_string(oi, v, productName, strlen(productName));
case BOARDSTATUS:
return o_integer(oi, v, boardStatus);
case EXAMPLEIPADDR:
{
struct sockaddr_in tmp_IpAddr;
tmp_IpAddr.sin_addr = exampleIpAddr;
return o_ipaddr(oi, v, (char *) & tmp_IpAddr);
}
case EXAMPLEOBJECTID:
return o_specific(oi, v, exampleObjectID);
case NUMBERLINES:
return o_integer(oi, v, numberLines);
default:
return error__status_noSuchName;
}
To get multiple instances,
get_serialLineTable() is executed to process
GetRequest and GetNextRequest PDUs
for a VarBind of an Object in the serialLineTable.
For each table in your MIB make one of the following
functions of name get_tableName():
static int
get_serialLineTable(oi, v, offset)
OI oi;
register struct type_SNMP_VarBind *v;
int offset;
{
When a GetRequest or GetNextRequest PDU is being processed,
control is passed through this get function, and it is here that
the multiple instance objects are read.
For every object in the table that this function services, add a ``case'' to this ``switch'' statement. Use the appropriate function (o_string(), o_integer(), and so on) according to the object's syntax.
switch (ifvar) {
case SERIALLINENUMBER:
return o_integer(oi, v, ifnum);
case SERIALLINEBAUDRATE:
return o_integer(oi, v,
serialLineTable[ifnum].serialLineBaudRate);
case SERIALLINETERMLOCATION:
return o_string(oi, v,
serialLineTable[ifnum].serialLineTermLocation,
strlen(serialLineTable[ifnum].serialLineTermLocation));
default:
return error__status_noSuchName;
}
For every single instance leaf object in your MIB module with read-write access, add a ``case'' to this ``switch'' statement. For your object, use one of the examples below which has the same syntax.
switch (ifvar) {
case BOARDSTATUS:
if ((*os->os_decode) (&value, v->value) == NOTOK)
return error__status_badValue;
i = *((int *) value);
(*os->os_free) (value);
if ((i != 1) && (i != 2))
return error__status_badValue;
new_boardStatus = i;
return error__status_noError;
case EXAMPLEIPADDR:
if ((*os->os_decode) (&value, v->value) == NOTOK)
return error__status_badValue;
new_exampleIpAddr = ((struct sockaddr_in *) value)->sin_addr;
newflag_exampleIpAddr = 1;
(*os->os_free) (value);
return error__status_noError;
case EXAMPLEOBJECTID:
if ((*os->os_decode) (&value, v->value) == NOTOK)
return error__status_badValue;
if ((OID) value == NULLOID)
new_exampleObjectID = NULLOID;
else
new_exampleObjectID = oid_cpy((OID) value);
(*os->os_free) (value);
return error__status_noError;
default:
return error__status_noSuchName;
}
This function is executed when processing SetRequest
PDUs for a VarBind of
an Object in the serialLineTable.
For each table in your MIB module that also contains objects with read-write access, make one of the following functions of name set_tableName(). If the table does not contain any read-write access objects, this function is unnecessary.
static int
set_serialLineTable(oi, v, offset)
OI oi;
register struct type_SNMP_VarBind *v;
int offset;
{
When a SetRequest PDU is being processed, control is passed to
this function. The multiple instance objects are set at this
point.
For every read-write access object in the table that
this function services, add a ``case'' to this
``switch'' statement. Use
the appropriate function ( o_string(), o_integer(),
and so on) according
to the object's syntax.
switch (ifvar) {
case SERIALLINEBAUDRATE:
if ((*os->os_decode) (&value, v->value) == NOTOK)
return error__status_badValue;
i = *((int *) value);
(*os->os_free) (value);
if ((i != BAUD1200) && (i != BAUD2400) && (i != BAUD9600)
&& (i != BAUD19800))
return error__status_badValue;
serialLineTable[ifnum].new_serialLineBaudRate = i;
return error__status_noError;
case SERIALLINETERMLOCATION:
if ((*os->os_decode) (&value, v->value) == NOTOK)
return error__status_badValue;
cp = ((OctetString *) value);
printf ("NEW LOCATION = %& \n", cp->octet_ptr);
serialLineTable[ifnum].newflag_serialLineTermLocation = 1;
if (cp == NULL) {
serialLineTable[ifnum].new_serialLineTermLocation[0]
= '\0';
} else {
if (cp->length < LOCATIONSIZE) {
(void) strncpy(serialLineTable[ifnum].new_serialLineTermLocation,
cp->octet_ptr, cp->length);
serialLineTable[ifnum].new_serialLineTermLocation[cp->length]='\0';
} else {
LIB_ERROR("\n WARNING: serialLineTermLocation string too long\n");
(void) strncpy(serialLineTable[ifnum].new_serialLineTermLocation,
cp->octet_ptr, LOCATIONSIZE - 1);
/* Silently ignore DisplayStrings that are too long. */
serialLineTable[i].new_serialLineTermLocation[
LOCATIONSIZE - 1] = '\0';
}
}
(*os->os_free) (value);
return error__status_noError;
default:
return error__status_noSuchName;
}
You will recall that a ``set'' is a two-phase process: the agent
receives the SetRequestPDU from the client running on
the management station and sends the request on to the appropriate
peers, then collates the peer responses; depending upon the responses
from the peers, the agent then issues either a ``commit'' or
a``rollback''
SOutPDU. When the peer processes a
SMUX
``commit'' or ``rollback''
SOutPDU, it executes the sync_foo()
function. sync_foo completes the set operation: if it
receives a ``commit'' SOoutPDU from the agent,
sync_foo writes the new values to the variables; if it
receives a ``rollback'' SOoutPDU, sync_foo
discards the new pending values without writing them.
For each MIB module that also contains objects with read-write access, make one of the following functions of name sync_MIBmodule().
int
sync_foo(cor)
int cor;
{
In the ``switch'' statement where the commit instruction is processed,
all variables must be updated with the values that were temporarily
stored in the new variables.
At this point the hardware and/or file configuration should be updated.
Unlike a real
SMUX
peer, the example peer does not communicate with a
device and it does not write to a configuration file.
For each single instance object in the MIB module that has a read-write access, add an ``if'' statement that checks if there is a new value to commit. This is shown in the following example:
if (new_boardStatus)
boardStatus = new_boardStatus;
if (newflag_exampleIpAddr)
exampleIpAddr = new_exampleIpAddr;
if (new_exampleObjectID != NULLOID)
exampleObjectID = oid_cpy(new_exampleObjectID);
For each table in the
MIB
module that contains a read-write
access object, add a
for() loop:
for (i = 0; i < numberLines; i++) {
For every object in the table that has a read-write access,
add an ``if'' statement inside the for() loop
that checks if there is a new value to commit.
This is shown in the following example:
if (serialLineTable[i].new_serialLineBaudRate) {
serialLineTable[i].serialLineBaudRate
= serialLineTable[i].new_serialLineBaudRate;
serialLineTable[i].new_serialLineBaudRate = 0;
}
if (serialLineTable[i].newflag_serialLineTermLocation) {
(void) strcpy(serialLineTable[i].serialLineTermLocation,
serialLineTable[i].new_serialLineTermLocation);
serialLineTable[i].newflag_serialLineTermLocation = 0;
}
In the ``switch'' statement where the rollback instruction is
processed,
all temporary variables that were holding pending values must be zeroed.
For each single instance object in the MIB module that has an access of read-write, add an assignment statement that zeroes the new value as shown:
new_boardStatus = 0;
newflag_exampleIpAddr = 0;
new_exampleObjectID = NULLOID;
For each table in the
MIB
module that contains a read-write
access object, add a
for() loop:
for (i = 0; i < numberLines; i++) {
For every object in the table that has a read-write access,
add an ``if'' statement inside the for() loop
that zeroes the new value as in the following example:
serialLineTable[i].new_serialLineBaudRate = 0;
serialLineTable[i].newflag_serialLineTermLocation = 0;
#define LINE_DEATH_ERROR_TRAP 1
OID mibvar_oid_ptr[2]; VarBindList *constructed_varbindlist;
mibvar_oid_ptr[0] = text2oid("boardStatus.0");
trap_varbindlist = make_varbind(mibvar_oid_ptr[0], INTEGER_TYPE,
0, (long) boardStatus, NULL, (OID) 0);
mibvar_oid_ptr[1] = text2oid("serialLineNumber.2");
trap_varbindlist->next = make_varbind(mibvar_oid_ptr[1],
INTEGER_TYPE, 0, (long) 2, NULL, (OID) 0);
if (smux_trap(trap_enterpriseSpecific, LINE_DEATH_ERROR_TRAP,
(struct type_SNMP_VarBindList) trap_varbindlist) == NOTOK)
LIB_ERROR2("smux_trap: %s [%s]", smux_error(smux_error),
smux_info);
The SNMP agent uses the file /etc/snmpd.peers to authenticate registration requests from SMUX peers. The file entries consist of four items, the last of which is optional:
# <name> <object-id> <password> [<priority>] # "foo" 1.3.6.1.4.1.9999 "aintNoThing" -1The name/OID pairs are assigned by the authority for the BSD UNIX MIB. Contact Marshall Rose (see ``References'' for address information) to register the name of your peer and get an OID for it.
You have now completed your modifications to the
reference peer and the next step is to compile your peer.
All of the files you have modified should reside
in the directory /usr/src/smux.
Make this your current directory and enter:
make -f foosmuxd.mk
replacing the string ``foo'' with the identifying
string you chose for your peer.
Executing an SMUX peer
To execute the peer and start it running as root:
replacing the string ``foo'' with the identifying string
you chose for your peer.
To run the peer in foreground with
debugging switched on, type:
foosmuxd -v
Forming
SNMP
requests with the
SNMP
client commands
getone(ADMN),
getmany(ADMN),
and
setany(ADMN)
is often easier and less expensive than using a network management
station. Please refer to the manual pages for these commands for more
detailed information.
Some examples of using these commands follow:
To walk the entire foo MIB module,
use the following command:
getmany localhost public enterprises.9999
To read just the product name from the foo
MIB
module, use the following command:
getone localhost public enterprises.9999.1.1.0
To read the BAUD rate of serial line number 2,
use the following command:
getone localhost public enterprises.9999.1.6.1.2.2
To set the BAUD rate of serial line 2 to a value of 2400 bps,
use the following command:
setany localhost foo enterprises.9999.1.6.1.2.2 -i 2
Packaging your SMUX peer
Once you have finished testing your
SMUX
peer and are satisfied that it is of high quality,
it is ready for shipment to customers.
Normally, it will be shipped with the product it was designed to manage.
A typical package would include a disk containing the device driver, the
SMUX
peer daemon (foosmuxd), and the
MIB
module (foosmuxd.my).
It is important to include your
MIB
module:
it can be read in by
many kinds of management software, such that the network management
workstation can more effectively control your device.
Description of reference peer foosmuxd
This section is intended as a reference for the applications
programmer who needs additional information about how the main
SMUX reference peer program (foosmuxd.c) works:
how the program reads the compiled MIB
module(s), initiates the TCP
connection, opens the SMUX
association, registers the MIB
modules it wishes to manage, processes get and set requests,
handles error recovery, and terminates the SMUX association.
Reading compiled MIB modules
The reference peer program calls the
readobjects routine to read the
compiled module, look-up the module which it
will register with the SNMP agent,
and call the getsmuxEntrybyname routine
to find its entry in the snmpd.peers database.
OT ot;
char objectsfile[BUFSIZ];
if ((se = getsmuxEntrybyname(myname)) == NULL) {
LIB_PERROR("no SMUX entry for - NEWD");
exit(-1);
}
(void) sprintf(objectsfile, "%s.defs", myname);
if (readobjects(objectsfile) == NOTOK) {
LIB_PERROR("readobjects: ");
exit(-1);
}
for (tc = triples; tc->t_tree; tc++)
if (ot = text2obj(tc->t_tree)) {
tc->t_name = ot->ot_name;
(void) (*tc->t_init) ();
} else
LIB_ERROR1("text2obj (\"%s\") fails", tc->t_tree);
If either of these routines fails,
do not register the MIB module.
If smux_init is successful, the return value is a file descriptor suitable for use with xselect.[1] On failure, smux_errno will be set to one of congestion, youLoseBig, or systemError. In this case, foosmuxd retries the operation every 5 minutes or so.
if ((smux_fd = smux_init (debug)) == NOTOK)
LIB_ERROR2 ("smux_init: %s [%s]",
smux_error (smux_errno), smux_info);
else
rock_and_roll = 0;
if (smux_simple_open(&se->se_identity, "SMUX NEWD daemon",
se->se_password, strlen(se->se_password))
== NOTOK) {
if (smux_errno == inProgress)
return;
LIB_ERROR2("smux_simple_open: %s [%s]", smux_error(smux_errno),
smux_info);
smux_fd = NOTOK;
return;
}
A return value of OK
from smux_register means only
that the registration request was queued for the
SNMP agent via the SMUX protocol.
At some later time, the agent will issue a response PDU.
On failure, smux_error will be set to one of parameterMissing, invalidOperation, congestion, or youLoseBig. foosmuxd takes the appropriate action based on the error code returned.
if (smux_register(tc->t_name, -1, tc->t_access) == NOTOK) {
LIB_ERROR2("smux_register: %s [%s]", smux_error(smux_errno), smux_info);
smux_fd = NOTOK;
return;
}
for (;;) { int n, secs; fd_set rfds, wfds;
secs = NOTOK; rfds = ifds; /* struct copy */ wfds = ofds;
if (smux_fd == NOTOK && !dont_bother_anymore) secs = 5 * 60L; else if (rock_and_roll) FD_SET(smux_fd, &rfds); else FD_SET(smux_fd, &wfds); if (smux_fd >= nfds) nfds = smux_fd + 1;
if ((n = xselect(nfds, &rfds, &wfds, NULLFD, secs)) == NOTOK) { LIB_PERROR("failed, xselect"); exit(-1); }
if (smux_fd == NOTOK && !dont_bother_anymore) { if (n == 0) { if ((smux_fd = smux_init(debug)) == NOTOK) LIB_ERROR2("smux_init: %s [%s]", smux_error(smux_errno),smux_info); else rock_and_roll = 0; } } else if (rock_and_roll) { if (FD_ISSET(smux_fd, &rfds)) doit_smux(); } else if (FD_ISSET(smux_fd, &wfds)) start_smux(); }
The event is filled-in from a static area. On the next call to smux_init, smux_close, or smux_wait, the value is overwritten.
On failure, smux_errno will be set to one of parameterMissing, invalidOperation, inProgress, or youLoseBig. See the section ``Error recovery'' for an explanation of these error codes. The peer program takes the appropriate action based on the error code returned.
struct type_SNMP_SMUX__PDUs *event;
if (smux_wait(&event, NOTOK) == NOTOK) {
if (smux_errno == inProgress)
return;
LIB_ERROR2("smux_wait: %s [%s]", smux_error(smux_errno), smux_info);
smux_fd = NOTOK;
return;
}
Next, the peer program switches based on the actual event
returned:
The actual code is fairly straightforward:SMUX__PDUs_registerResponse SMUX__PDUs_get__request SMUX__PDUs_get__next__request SMUX__PDUs_set__request SMUX__PDUs_commitOrRollback SMUX__PDUs_close
switch (event->offset) {
case SMUX__PDUs_registerResponse:
if (!tc->t_name)
goto unexpected;
{
struct type_SNMP_RRspPDU *rsp = event->un.registerResponse;
if (rsp->parm == RRspPDU_failure)
LIB_ERROR1("SMUX registration of %s failed\n", tc->t_tree);
else {
if (debug)
printf("SMUX register: %s out=%d\n", tc->t_tree, rsp->parm);
got_at_least_one = 1;
}
}
for (tc++; tc->t_tree; tc++)
if (tc->t_name) {
if (smux_register(tc->t_name, -1, tc->t_access)
== NOTOK) {
LIB_ERROR2("smux_register: %s [%s]\n",
smux_error(smux_errno), smux_info);
goto losing;
}
if (debug)
printf("SMUX register: %s in=%d\n", tc->t_tree, -1);
break;
}
if (!tc->t_tree) {
if (!got_at_least_one) {
dont_bother_anymore = 1;
(void) smux_close(goingDown);
goto losing;
}
if (smux_trap(trap_coldStart, 0,
(struct type_SNMP_VarBindList *) 0) == NOTOK) {
LIB_ERROR2("smux_trap: %s [%s]", smux_error(smux_errno),
smux_info);
goto losing;
}
}
break;
case SMUX__PDUs_get__request:
case SMUX__PDUs_get__next__request:
case SMUX__PDUs_set__request:
do_smux(event->un.get__request, event->offset);
break;
case SMUX__PDUs_commitOrRollback:
{
register struct triple *tz;
for (tz = triples; tz->t_tree; tz++)
if (tz->t_name)
(void) (*tz->t_sync) (event->un.commitOrRollback->parm);
}
break;
case SMUX__PDUs_close:
if (debug)
printf("SMUX close: %s\n", smux_error(event->un.close->parm));
goto losing;
case SMUX__PDUs_simple:
case SMUX__PDUs_registerRequest:
case SMUX__PDUs_get__response:
case SMUX__PDUs_trap:
unexpected: ;
LIB_ERROR1("unexpectedOperation: %d", event->offset);
(void) smux_close(protocolError);
goto losing;
default:
LIB_ERROR1("badOperation: %d", event->offset);
(void) smux_close(protocolError);
goto losing;
}
Note the use of the smux_trap routine
to send a coldStart trap
once the peer has successfully registered the
MIB module it will be managing.
The trap codes are:
If this routine fails, smux_errno will be set to one of invalidOperation, congestion, or youLoseBig.trap_coldStart trap_warmStart trap_linkDown trap_linkUp trap_authenticationFailure trap_egpNeighborLoss trap_enterpriseSpecific
static
do_smux(pdu, offset)
register struct type_SNMP_GetRequest__PDU *pdu;
int offset;
{
int idx, status;
object_instance ois;
register struct type_SNMP_VarBindList *vp;
IFP method;
quantum = pdu->request__id;
idx = 0;
for (vp = pdu->variable__bindings; vp; vp = vp->next) {
register OI oi;
register OT ot;
register struct type_SNMP_VarBind *v = vp->VarBind;
idx++;
if (offset == SMUX__PDUs_get__next__request) {
if ((oi = name2inst(v->name)) == NULLOI
&& (oi = next2inst(v->name)) == NULLOI)
goto no_name;
if ((ot = oi->oi_type)->ot_getfnx == NULLIFP)
goto get_next;
} else {
if ((oi = name2inst(v->name)) == NULLOI)
goto no_name;
ot = oi->oi_type;
if ((offset == SMUX__PDUs_get__request
? ot->ot_getfnx : ot->ot_setfnx) == NULLIFP) {
no_name: ;
pdu->error__status = error__status_noSuchName;
goto out;
}
}
try_again: ; switch (offset) { case SMUX__PDUs_get__request: if (!(method = ot->ot_getfnx)) goto no_name; break;
case SMUX__PDUs_get__next__request: if (!(method = ot->ot_getfnx)) goto get_next; break;
case SMUX__PDUs_set__request: if (!(method = ot->ot_setfnx)) goto no_name; break;
default: goto no_name; }
switch (status = (method) (oi, v, offset)) {
case NOTOK: /* get-next wants a bump */
get_next: ;
oi = &ois ;
for (;;) {
if ((ot = ot->ot_next) == NULLOT) {
pdu->error__status = error__status_noSuchName;
goto out;
}
oi->oi_name = (oi->oi_type = ot)->ot_name;
if (ot->ot_getfnx)
goto try_again;
}
case error__status_noError:
break;
default:
pdu->error__status = status;
goto out;
}
}
idx = 0;
out: ;
pdu->error__index = idx;
if (smux_response(pdu) == NOTOK) {
LIB_ERROR2("smux_response: %s [%s]", smux_error(smux_errno),
smux_info);
smux_fd = NOTOK;
}
}
The actual code is fairly straightforward. First, the variable quantum is set to the request ID for this transaction. The SMUX protocol requires that this number increment for each SNMP operation that the agent handles, so the peer program can use it to see when it should re-read its local variables. (A single SNMP operation received by the agent might result in multiple SMUX protocol transactions with the program.)
Next, the code loops through the list of variables requested.
The object instance is determined and loaded into oin and the
corresponding object type is loaded into ot, and the access is
checked.
Finally, the user-defined routine is invoked. This routine returns one of these values:
The first value is returned only if the get-next operator is being invoked and the routine didn't have any more object instances for the object type in question. The remainder are similar to the error codes returned by the SNMP agent and are self-explanatory.NOTOK error__status_noError error__status_tooBig error__status_noSuchName error__status_badValue error__status_readOnly error__status_genErr
Once an answer is returned, the loop either continues or is
broken and a response is written back using the smux_response
routine. On failure, smux_error will be set to one of
parameterMissing, invalidOperation,
or youLoseBig.
Encoding and decoding PDUs
There are two routines which handle encoding and decoding operations
on
SMUX
PDUs:
encode_SMUX_PDU and decode_SMUX_PDU.
encode_SMUX_PDU creates an
ASN.1
encoded
SMUX
PDU
from the
SMUX
PDU
structure specified by pdu_ptr.
ASN.1
encoding is required for the transfer of an
SMUX
PDU
from an agent to a peer and vice versa.
encode_SMUX_PDU traces down the structure of VarBinds
that have been added to the
SMUX
PDU,
and builds the
ASN.1
encoding of the
SMUX
PDU
in the packlet pointer of the
SMUX
PDU
data structure specified by pdu_ptr.
When this
ASN.1
encoding is complete,
SMUX
PDU
processing is complete,
and the structure is ready to be transferred.
If successful,
encode_SMUX_PDU returns 0, if unsuccessful, it returns -1.
decode_SMUX_PDU takes an
SMUX
PDU
from the packet received and parses the information into the library's
internal
PDU
format, including all VarBind instances. If this routine
fails, it returns
NULL.
Terminating foosmuxd's SMUX association
The reference
SMUX
peer invokes the routine smux_register when it is
ready to terminate the
SMUX
association with the local agent. smux_register
sends an RReqPDU to the agent, with ``priority'' set to
-1 and ``operation'' set to 0; this un-registers the peer's
highest priority registration for the specified
MIB
module:
Then the peer calls smux_close, which packages an SMUX_PDUs_close PDU, encodes it in ASN.1 format, sends the PDU to the agent, and then closes the TCP end-point.void close_conn() { /* * - De-register the subtrees that were previously registered * - Then gracefully close the smux connection * - and then the tcp connection itself. */
for (tc = triples; tc->t_tree; tc++) smux_register (tc -> t_name, -1, 0); smux_close(goingDown); exit(0); }
smux_close takes one argument, which will be one of the following: goingDown, packetFormat, unsupportedVersion, protocolError, internalError, or authenticationFailure. See the previous section ``Terminating an SMUX association'' within the section ``An SMUX overview'' for an explanation of these error codes.
If this routine fails, it sets smux_errno to one of the
following: congestion, invalidOperation, or
youLoseBig. See the following section ``Error recovery''
for an explanation of these error codes.
Error recovery
All of the SMUX routines return NOTOK on failure.
The function
char * smux_error(smux_errno) extern int smux_errno;returns an appropriate string based on the error number passed to it. The parameter value is set by functions in the SMUX/SNMP library and the value could be any of: parameterMissing, invalidOperation, inProgress, systemError, congestion, or youLoseBig. The variable
extern char smux_info[BUFSIZ];contains a printable explanation of what caused the failure. All errors are FATAL except inProgress; if the error code is inProgress, the peer program continues retrying the file descriptor for writability, and then calls smux_simple_open again. Otherwise, the program takes the appropriate action based on the error code returned.
typedef int (*IFP) ();
typedef struct object_syntax {
char *os_name; /* syntax name */
int os_type; /* syntax type */
IFP os_decode;
IFP os_free; /* free data */
} *OS;
#define NULLOS ((OS) 0)
The syntaxes defined by the Internet-standard
MIB
are already
implemented:
Table 9-1 Internet-standard MIB defined syntaxes
--------------------------------------------------------------------- Syntax Structure type --------------------------------------------------------------------- INTEGER integer OctetString struct _OctetString ObjectID struct OIDentifier NULL char DisplayString struct _OctetString IpAddress struct sockaddr_in NetworkAddress struct sockaddr_in Counter integer Gauge integer TimeTicks integer ClnpAddress struct _OctetStringwhere ``syntax'' is the name appearing in a compiled MIB module, and ``structure type'' is the C language data type corresponding to the object's syntax. The structure types are described below.
To take a syntax name and get back the structure, use the
routine text2syn().
Here are the structures and routines used to implement some of these low-level MIB abstractions: Integer, OctetString, ObjectID, and IPAddress.
integer is used for the INTEGER, Counter, Gauge, and TimeTicks syntaxes. The definition is:
typedef int integer;
struct _OctetString is used for the OctetString, DisplayString, and ClnpAddress syntaxes. The definition is:
typedef struct _OctetString {
unsigned char *octet_ptr; /* list of octets */
long length; /* number of elements */
};
struct OIDentifier is used for the ObjectID syntax. The definition is:
typedef struct OIDentifier { int oid_nelem; /* number of sub-identifiers */
unsigned int *oid_elements; /* the (ordered) list of sub-identifiers */ } OIDentifier, *OID; #define NULLOID ((OID) 0)
struct sockaddr_in is used for the
IpAddress and NetworkAddress syntaxes.
It is assumed that you are familiar with this structure.
If not, consult the file /usr/include/sys/netinet/in.h.
Objects
MIB objects are modeled by the
OT (Object Type) structure:
There are many routines to manipulate MIB objects; these are described in smux_util(SLIB).typedef struct object_type { char *ot_text; /* OBJECT DESCRIPTOR */ char *ot_id; /* OBJECT IDENTIFIER */ OID ot_name; /* .. */
OS ot_syntax; /* SYNTAX */
int ot_access; /* ACCESS */ #define OT_NONE 0x00 #define OT_RDONLY 0x01 #define OT_RDWRITE 0x02
int ot_status; /* STATUS */ #define OT_OBSOLETE 0x00 #define OT_MANDATORY 0x01 #define OT_OPTIONAL 0x02 #define OT_DEPRECATED 0x03
... } object_type, *OT; #define NULLOT ((OT) 0)
typedef struct object_instance {
OID oi_name; /* instance OID */
OT oi_type; /* prototype */
} object_instance, *OI;
#define NULLOI ((OI) 0)
There are several routines to manipulate MIB instances;
these are described in
smux_util(SLIB).
or
Keith Sklower:
sklower@vangogh.cs.berkeley.edu
agent -- a piece of SNMP software running on a network device which retrieves or changes network management information at the request of an SNMP client. This may involve ``get'' or ``set'' operations on MIB information maintained by SMUX peers.
ASN.1 -- Abstract Syntax Notation One, the OSI language for describing abstract syntax; ASN.1 is used in SNMP messages.
client -- a piece of SNMP software, usually run on a network management station, which requests MIB information and receives responses from an SNMP agent.
ClosePDU -- a type of Protocol Data Unit issued either by an SNMP agent to an SMUX peer, or by an SMUX peer to an SNMP agent, to terminate an SMUX association. This PDU removes all the peer's MIB module registrations and closes the TCP connection between agent and peer.
Counter --
an object syntax type representing a non-negative integer, which increases
monotonically until it reaches the maximum value (2
decode -- to convert a string of octets into an ASN.1 instance of a data type.
encode -- to serialize an ASN.1 instance of a data type into a string of octets.
export -- to register a MIB module; the process whereby an SMUX peer informs an SNMP agent that the peer is assuming responsibility for managing a MIB module.
Gauge --
an object syntax type representing a non-negative integer, which may increase or
decrease, but which latches at a maximum value (2
get -- an SNMP operation for retrieving network management information, in which the SNMP client issues a GetRequestPDU to an SNMP agent; for each variable in a GetRequestPDU, the named instance is retrieved (either by the agent or an SMUX peer) and returned to the SNMP client in a GetResponsePDU with the each variable's value field containing the information requested. If the instance is non-existent, the GetResponsePDU contains a noSuchName error code.
get-next -- an SNMP operation for retrieving network management information, in which the SNMP client issues a GetNextRequestPDU to an SNMP agent; for each variable in the GetNextRequestPDU, the instance which lexicographically follows the named instance is retrieved (either by the agent or an SMUX peer). If the end of the lexicographic space is reached, a GetResponsePDU is returned to the SNMP client containing a noSuchName error code. Otherwise, the GetResponsePDU returned contains the filled-in name and value fields of the requested variables.
instance -- an actual occurrence of an object which is manipulated by a management protocol such as SNMP and SMUX.
ipAddress -- an object syntax type representing an IP address.
MIB -- Management Information Base; a tree-structured naming convention that is used to access network data via a network management protocol such as SNMP.
MIB module -- a subtree of the MIB.
mosy -- the Managed Object Syntax-compiler (YACC-based) that reads the ASN.1 definitions of a MIB module and produces an output file which can be used by the SMUX library functions. mosy is invoked automatically when you execute the make command.
network management station -- a host running a network management protocol (SNMP) and network mangement applications which monitor and control network devices such as routers, gateways, hosts, and terminal servers; these devices run SNMP agent and/or SMUX peer daemons which perform the actual management operations requested by the network management station.
object -- an abstract management object with an associated syntax, access, status, and name (OBJECT IDENTIFIER); a managed object is a template -- it is the instances of the object which are actually manipulated by get, get-next, and set operations.
OctetString -- an ASN.1 primitive data type, taking zero or more octets as its value. Each byte in an octet string may take any value in the range 0 to 255.
OID -- an OBJECT IDENTIFIER; an object syntax type consisting of a sequence of non-negative integer values that traverses a tree (for example, 1.3.6.1). The OID denotes an authoritatively named object regardless of the semantics (for example, a standards document, an ASN.1 module, and so on) associated with the object.
OpenPDU -- a Protocol Data Unit sent by an SMUX peer to an SNMP agent to initiate an SMUX association with that agent. An OpenPDU contains authentication information (OID-password pair) which the agent compares to data specified for that peer in the configuration file /etc/snmpd.peers.
PDU -- Protocol Data Unit; a data object exchanged by protocol machines (such as management stations, SMUX peers, and SNMP agents) and consisting of both protocol control information and user data. PDU is sometimes used as a synonym for ``packet''.
peer -- a piece of software, running on a network device, which manages a specific MIB module. A peer receives requests from, and issues responses to, an SNMP agent via the SMUX protocol.
priority --
a peer registration request parameter which allows an SNMP agent
to resolve conflicting registration
attempts. All peer
registration requests have an associated priority, which has
an integer value in the range 0 (indicating highest possible priority) to
(2
registration -- the process whereby an SMUX peer informs an SNMP agent that the peer is assuming responsibility for servicing management station requests involving objects contained in a specific MIB module (or subtree). See also priority.
RreqPDU -- a registration request Protocol Data Unit; issued by an SMUX peer to request registration for a specific MIB module at either a specific priority or at priority -1. See also priority.
RrspPDU -- a registration response Protocol Data Unit; issued by an SNMP agent in response to a peer's registration request. This PDU contains the integer priority value that the agent has assigned to the peer's MIB module registration. See also RreqPDU, registration, and priority.
set -- an SNMP operator; for each variable contained in the SetRequestPDU, the named instance is identified. If the named instance doesn't exist, or the instance doesn't allow writing, or the value supplied in the request is syntactically incorrect or contains an out-of-range value, a GetResponsePDU is returned with an appropriate error code. Otherwise, all the variables are written simultaneously and a GetResponsePDU is issued. See also ``Agent sets peer data'' section.
SOutPDU -- a type of Protocol Data Unit sent by an SNMP agent to an SMUX peer to initiate the second phase of a set operation. An SOutPDU can be either of two types: ``commit'' or ``rollback''. A commit SOutPDU instructs the peer to proceed with writing the new pending values to the variables in the SetRequestPDU. A rollback SOutPDU instructs the peer to forget the new pending values for the variables in the SetRequestPDU and to leave the existing values intact. See also ``Agent sets peer data'' section.
Time Ticks --
an object syntax type representing a non-negative integer
which counts the time
in hundredths of a second (0.01 sec) since some epoch;
maximum value is 2
trap -- a type of PDU sent by an SNMP agent or an SMUX peer to alert the management station to the occurrence of a noteworthy event. Traps are unsolicited PDUs: they are not responses to request PDUs.
VarBind -- a variable binding containing a name and a value. The value portion is only meaningful in non-error response PDUs, TrapPDUs, and SetRequestPDUs; it is meaningless in Get and GetNext requests. By convention, the value field for GetRequestPDUs and GetNextRequestPDUs is always an instance of the ASN.1 data type NULL.
variable -- a specific instance of a managed object; an object instance.