Chapter 9: Developing SMUX peers for SNMP agents

Table of contents

Chapter 9

Developing SMUX peers for SNMP agents

This chapter describes how to implement an SMUX peer to communicate with an SNMP agent. To understand this chapter, you should have a working knowledge of: This chapter is organized as follows: This chapter does not provide a detailed description of the SNMP protocol or of network management in general. For more information on these and related topics, see the ``References'' section. Also, read the documents listed in the ``References'' section before implementing an SMUX peer.

Introduction

Network management requires technology capable of monitoring and controlling devices on an internet.[1] Network management technology is used to monitor such parameters as network performance, resource consumption, and abnormal network behavior. It can control the state of network devices, abnormal events, and access to the internet. It can also report unusual network conditions and events.

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:


Footnotes

[1]
The term ``internet'' is used here to refer to a large communications infrastructure consisting of two or more interconnected networks.

An SMUX overview

This overview describes what the SMUX protocol and peers do. The first section describes a typical interaction between a management station, an SNMP agent, and an SMUX peer. The second section expands upon the finer points of the SMUX protocol.

An example SMUX session

Let's examine a typical scenario by walking through an example SMUX session. There are three major participants in this SMUX session: a network management station, an SNMP agent, and an SMUX peer used to manage the multi-port serial board (see Figure 9-1, ``Example SMUX session'')

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



Initialization

  1. The host computer boots, which starts the SNMP agent.

  2. The SMUX peer starts running.

  3. The peer establishes a TCP connection to the agent. The subsequent interaction between the SMUX peer and the SNMP agent is called an ``SMUX association.''

  4. The peer registers itself with the agent. During registration, the peer specifies which part of the MIB it wishes to maintain (this specified part of the MIB is called a ``MIB module''). In this example, the peer wishes to manage the MIB module that contains the variables pertaining to the multi-port serial board.



Exchange of MIB information



  1. The process of information exchange between the management station and the SMUX peer begins when the system administrator uses the network management station to ask for the baud rate for serial line 2 on the multi-port serial board.

  2. The SNMP client, running on the management station, sends a GetRequest message over the network to the agent.

  3. The agent sees that the MIB module containing the baud rate of serial line 2 has been registered by the peer, so the agent sends an SMUX GetRequest message to the peer, requesting the baud rate for serial line 2.

  4. Upon receiving the SMUX GetRequest message, the peer decodes it and finds a request for the baud rate of serial line 2 of the serial board.

  5. The peer gets the baud rate from the serial board device driver.

  6. The peer encodes the baud rate in an SMUX response message and sends the message to the SNMP agent.

  7. The agent receives the SMUX message from the peer, translates it into an SNMP GetResponse message, and sends it on to the management station.

  8. The management station receives the response message from the agent and displays the baud rate for serial line 2.



Asynchronous events

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.


Termination



  1. Ultimately, the peer will receive a signal to terminate itself.

  2. The peer removes its registration with the agent and closes the SMUX association.

  3. Any subsequent requests by the SNMP client for information contained in the unregistered module will be handled by the SNMP agent (see the section ``Terminating an SMUX association'' for further information).

How the SMUX protocol works

As shown in the preceding example, the SMUX protocol is fairly simple. As you might suspect, there are some refinements to the basic sequence of events described above. Let's take a closer look at the details of initialization, information exchange, and termination. 

Initialization

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. 

Registration

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. 

Priority

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, (231 - 1). A registration with a priority value of zero is given the highest possible priority and (231 - 1) the lowest. The highest allowed priority for each peer is specified in the agent's /etc/snmpd.peers file. If a peer attempts to register at a priority higher than allowed, the agent arbitrarily assigns that registration a priority at or below the priority specified in /etc/snmpd.peers. More than one peer can register the same MIB module, but the peer with the highest registration priority handles all management station requests for variables in that module. When a peer wishes to register a MIB module at a priority which has already been assigned to another SMUX peer, the agent continues to increment the priority value of the registration request until it finds an available priority value for that module. For example, if peer A has registered the module at priority 4, and peer B sends a registration request for the same module at priority 4, the agent will attempt to register the module to peer B at priority 5, and then 6, and so on until the agent finds an available priority for that module.

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. 

Information exchange

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. 

Agent gets data from peer

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.


Agent sets peer data

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.

  1. Upon receiving the SetRequestPDU, the peer determines several things about each variable in turn: whether the variable exists, whether it permits writing, and that the request supplies correct values and syntax. If any variable in the request does not exist, or does not permit writing, or if a variable value is improper or if a variable's syntax is incorrect, the peer returns an appropriate error code in its GetResponsePDU to the agent.

    If the peer accepts the set operation, it issues a GetResponsePDU with error-status noError.

  2. If a peer refuses the set operation, it issues a GetResponsePDU with a non-zero error-status. The agent examines all the GetResponsePDUs from the SMUX peers. If any peer has refused the set operation, the agent issues a ``rollback'' SOutPDU to all peers saying, in effect: ``Never mind. Discard any knowledge of the requested set operation.''

    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.''

The agent collates the responses from all peers involved in the SetRequest, and sends a GetResponsePDU back to the management station. Peers do not send response messages to the ``commit'' and ``rollback'' SOutPDUs.

Peer sends trap to agent

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:

coldStart (0)

This means the peer is initializing itself and objects it manages may be altered.

warmStart (1)

This means the peer is re-initializing itself, but the objects it manages are not altered.

linkDown (2)

This means an attached interface has changed from the ``up'' state to the ``down'' state.

linkUp (3)

This means an attached interface has changed from the ``down'' state to ``up''. This trap is not meaningful from the peer's perspective.

authenticationFailure (4)

This occurs when a PDU has been received from an SNMP entity which falsely claimed to belong to a specific community. This trap is not meaningful for peers, as they do not perform any authentication checking.

egpNeighborLoss (5)

This means an EGP (Exterior Gateway Protocol) peer has changed to the ``down'' state. This is not a meaningful trap for peers.

enterpriseSpecific (6)

This means the peer has noticed some enterprise-specific event has occurred. The specific-trap field of the PDU contains a value which indicates exactly which type of event this was. These values were defined as representing specific types of events by the programmer who implemented the peer.



Terminating an SMUX association

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:

goingDown

Either the peer or the agent is being brought down.

unsupportedVersion

The agent sends a ClosePDU, indicating a version mismatch.

packetFormat

The PDU received has been encoded incorrectly or was corrupted in transit, resulting in a decoding error.

protocolError

An incorrect sequence of PDUs, or an unexpected PDU was received.

internalError

A fatal error has occurred in the processing of a PDU.

authenticationFailure

The agent sends a ClosePDU, indicating that the ``OID-password'' pair in the OpenPDU did not match with any of those present in the local configuration file.
Termination can be initiated by either the peer or the agent except for unsupportedVersion and authenticationFailure type terminations; these types can only be initiated by the 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 SMUX API contains a reference peer called foosmuxd which you can modify for your SMUX peer. foosmuxd is a ``dummy'' peer, in that it pretends to manage an imaginary multi-port serial board; because the serial board doesn't actually exist, foosmuxd ``gets'' hard-coded data. If your peer needs to communicate with a device or to read/write a file in order to manage the device, you have to add this capability to foosmuxd. If you are developing your own peer, you should replace all occurrences of the string ``foo'' with your company name or some string which uniquely describes your peer.

The foo SMUX peer daemon is implemented with the following five files:


MIB modules

The first task in implementing an SMUX peer is to create the MIB module that the peer registers. It contains the variables used to configure or administer the device to be managed. This section describes how to define a MIB module and compile it so that the peer can read it. It also describes the syntax of a compiled module. 

Defining MIB modules

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:

  1. If you are defining a MIB module for something specific to a UNIX system, it should probably go under the BSD UNIX MIB. Contact Marshall Rose or Keith Sklower to get a number under the UNIX enterprise tree; refer to ``References'' for address information. In this case, the definition might start something like this:
              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 }

  2. If you are defining a MIB module on a multilateral, experimental basis (for example, for a protocol like the NTP), then you should contact the Internet Assigned Numbers Authority (iana@isi.edu) and ask for an experimental number. In this case, the definition might start something like this:
              FOO-MIB DEFINITIONS ::= BEGIN
    

    IMPORTS experimental, OBJECT-TYPE FROM RFC1155-SMI;

    foo OBJECT IDENTIFIER ::= { experimental 99 }

  3. In most cases, you will be defining a MIB module for something specific to your enterprise. You should contact the Internet Assigned Numbers Authority (iana@isi.edu) and ask for an enterprise number (assuming you don't already have one). In this case, the definition might begin as does our example in foosmuxd.my:
              FOO-MIB DEFINITIONS ::= BEGIN
    

    IMPORTS enterprises, OBJECT-TYPE FROM RFC1155-SMI;

    foo OBJECT IDENTIFIER ::= { enterprises 9999 }


In any event, the final OBJECT IDENTIFIER points to the root of the tree that your program is going to manage. foo is the root of the subtree managed by foosmuxd. For simplicity's sake, foosmuxd manages the entire foo MIB. In real life, this wouldn't be the case because it precludes other peers from managing subtrees of the foo MIB.

Following this start, you have the actual definitions of the MIB objects, followed by

        END


Compiling MIB modules

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:

  1. Comments start with ``--'' or ``#'' at the beginning of a line.

  2. Object identifiers are defined thus:

    #
    #
    #name value
    #
    internet iso.3.6.1

  3. Object types are defined thus:

    #
    #
    #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.


Run the post_mosy program on the compiled output to produce an ASCII file containing the names and numeric object identifiers of each object in the compiled output.

post_mosy -i foosmuxd.defs

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.

#define statements

Whether or not you choose to compile your MIB module, the next step is to modify the file foomib.c by adding the appropriate #define statements. This file is a collection of data and functions to implement a MIB module for the peer. If you compiled the MIB module by executing the make command on it, you can use your version of foosmuxd.defs as a reference when making these modifications.

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

Global declarations

Next, your peer program will have to declare this global variable in the foomib.c file:

    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];

Initialization of global data

Next, your peer will initialize the values of the MIB objects and the MIB database used by the peer. You will need to make more modifications to foomib.c to accomplish this.

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.


NOTE: This initialization is application dependent. In this example SMUX peer the data is artificial and hard-coded; in a ``real life'' peer these variables will be collected from a device or file upon initialization, or dynamically on an ``as needed'' basis.

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.


NOTE: Throughout the following source code examples, many functions are referred to. The main ones are: For more information on these or any other functions referenced in this chapter, please refer to that function's reference manual page, or smux_util(SLIB).

       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;
       }

Writing get functions

You have written your MIB module, made the #define statements, declared global variables and initialized the values of the MIB objects and the MIB database used by the peer. The next step is to write ``get'' functions for single and multiple instances. This requires additional changes to foomib.c.

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; }

Writing set functions

Now let's look at implementing ``set'' functions for single and multiple instances. This will require additional changes to foomib.c. When a SetRequest PDU is being processed, control is passed through this set function, and it is here that single instance objects are written.

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;

Generating traps

You may find it useful (or perhaps necessary) to have your SMUX peer generate traps based on the occurrence of certain types of events, such as specific errors. Implementing these enterpriseSpecific traps will involve the following steps:

  1. Define the required enterpriseSpecific traps.

  2. Make sure that all objects appearing in the VarBindList have been defined in the peer's MIB module. If they haven't been, you will have to define them there now.

  3. Write code that constructs a VarBindList and then issues a trap message.

Now consider this example, in which foosmuxd could be modified to generate a trap when a serial line ``dies'':

  1. First, the file smuxpeer.h must be edited to #define this enterpriseSpecific trap:
        #define    LINE_DEATH_ERROR_TRAP    1
    

  2. When a serial line death occurs, this example trap will convey the board status and serial line number. Both of these values are already defined in the foo MIB under the object names boardStatus and serialLineNumber, so no further modification of the foo MIB module is necessary.

  3. The following code should be put in the code path that is executed whenever the serial line dies:


Configuration files

There are two configuration files of interest to the programmer implementing an SMUX peer: the foosmuxd.defs file discussed previously in this chapter under ``Compiling MIB modules'' and the file /etc/snmpd.peers. 

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
of an SMUX peer.

object-id
a dot-notation representation of the SMUX peer's object identifier.

password
in unencrypted form, which the agent compares with the password received in the OpenPDU from the peer. As the passwords are not encrypted, this file must be in protected mode ``0600'' and owned by root.

[priority]
registration: priority is what the SNMP agent uses to determine which SMUX peer to consult when more than one peer registers the same subtree. Legal values are 0 to (231 - 1); 0 has the highest priority, (231 - 1) the lowest. A special value of -1 can also be used to represent the highest available priority. If this optional field is supplied, it specifies the highest priority with which the SMUX peer can register subtrees.
The syntax for entries in /etc/snmpd.peers is shown below, along with an example entry. Tokens are separated by white space; to avoid unexpected separation of multiple-word tokens, surround the token with double quotes. Lines beginning with ``#'' are regarded as comments and hence ignored by the agent.
   # <name>    <object-id>         <password>       [<priority>]
   #
   "foo"       1.3.6.1.4.1.9999    "aintNoThing"    -1
The 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.

Compiling an SMUX peer

Next, modify foosmuxd.mk so that it refers to your peer files by replacing all occurrences of ``foo'' with the identifying string you chose for your peer.

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: 

  1. Ensure that there is an entry in /etc/snmpd.peers for the peer.

  2. Start the peer running in background with the command:

    foosmuxd

    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

When the peer is running in foreground, you can kill it by hitting the <Del> key. To kill a peer running in background use the kill command.

Testing an SMUX peer

The task of testing an SMUX peer is similar to testing an administration or configuration interface to a device. All MIB variables should be read to ensure that they contain the expected values. All read-write access MIB variables should be read and written with many possible values and the tester must ensure that the results are unsurprising.

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.


NOTE: You may not need to modify foosmuxd.c in order to implement your peer.

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.

Initialization

foosmuxd calls the routine smux_init, which initializes an SMUX connection to the local SNMP agent. This starts a TCP connection, but most likely does not complete it. foosmuxd needs to call the open routine smux_simple_open (see ``Opening'') to complete the connection and establish the SMUX association.

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;


Footnotes

[1]
xselect is a wrapper for select or poll. xselect works similar to select.

Opening

Once smux_init succeeds, the peer program starts selecting for writability on the file descriptor returned. Once xselect says the program can write to the fd, foosmuxd calls the routine smux_simple_open, which tries to establish an SMUX association.

    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; }

Registering MIB modules

Once smux_simple_open returns OK, the peer program calls smux_register to register the MIB module it will manage. A MIB module can be registered in one of three modes: readOnly, readWrite, or delete. delete mode is used to remove an existing registration.

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;
    }

Main loop

At this point, the peer program is in its main loop.

   

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(); }

Events

When xselect indicates the SMUX file descriptor is ready for reading, the peer program calls the routine smux_wait to return the next event from the SNMP agent.

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:

SMUX__PDUs_registerResponse SMUX__PDUs_get__request SMUX__PDUs_get__next__request SMUX__PDUs_set__request SMUX__PDUs_commitOrRollback SMUX__PDUs_close

The actual code is fairly straightforward:
   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:

trap_coldStart trap_warmStart trap_linkDown trap_linkUp trap_authenticationFailure trap_egpNeighborLoss trap_enterpriseSpecific

If this routine fails, smux_errno will be set to one of invalidOperation, congestion, or youLoseBig.

Get and set

When xselect indicates the SMUX file descriptor is ready for reading, the reference peer program calls the routine smux_wait to return the next event from the SNMP agent. The peer program invoked The routine do_smux, which implements the SNMP ``get'', ``get-next'' and ``set'' operations. The routine looks like this:

   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:

NOTOK error__status_noError error__status_tooBig error__status_noSuchName error__status_badValue error__status_readOnly error__status_genErr

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.

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:

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); }

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.

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.

Structure and syntax of managed objects

A managed MIB object is an abstraction with a syntax and semantics. The syntax defines what instances of the object look like on the network. The semantics define what the object actually is.

Syntax

The syntax of MIB objects is modeled by the OS (Object Syntax) structure:

              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 _OctetString
where ``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, Counter, Gauge, and TimeTicks

integer is used for the INTEGER, Counter, Gauge, and TimeTicks syntaxes. The definition is:

            typedef int integer;


OctetString, DisplayString, and ClnpAddress

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 */
               };


ObjectID

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)



IPAddress and NetworkAddress

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:

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)

There are many routines to manipulate MIB objects; these are described in smux_util(SLIB).

Instances

MIB object instances are modeled by the OI (Object Instance) structure:

               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).

References

  1. Manual pages: octetstring(SLIB), oid(SLIB), smux_api(SLIB), smux_pdu(SLIB), smux_util(SLIB), snmp_authentication(SLIB), snmp_pdu(SLIB), varbind(SLIB), mosy(ADMN), post_mosy(ADMN), snmpd.peers(SFF), snmpd.comm(SFF)

  2. Chapter 10, ``Configuring the Simple Network Management Protocol (SNMP)'' in the Networking Guide within the Networking Guide

  3. Rose, Marshall T. (1991) The Simple Book; Prentice Hall, Englewood Cliffs NJ. ISBN: 0-13-812611-9.

  4. RFC 1155, ``Structure and Identification of Management Information for TCP/IP-based Internets''; Marshall Rose and K. McCloghrie.

  5. RFC 1156, ``Management Information Base for Network Management of TCP/IP-based Internets'' K. McCloghrie and M. Rose.

  6. RFC 1157, ``A Simple Network Management Protocol (SNMP)'' J. Case, M. Fedor, H. Schoffstall, and J. Davin.

  7. RFC 1212, ``Concise MIB Definitions'' Marshall Rose and K. McCloghrie.

  8. RFC 1213, ``Management Information Base for Network Management of TCP/IP-based Internets: MIB-II'' K. McCloghrie and M. Rose.

  9. RFC 1227, ``SNMP MUX Protocol and MIB'' M. Rose.
RFCs can be obtained from SRI International, Menlo Park, CA 94025 in the following ways:
To obtain an OID and register the name of your sub-agent (for your MIB module) under the BSD UNIX enterprise tree, contact: Marshall T. Rose:

mrose@dbc.mtview.ca.us

or Keith Sklower:

sklower@vangogh.cs.berkeley.edu

Glossary

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\31 - 1), when it wraps back to zero.

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 (231 - 1).

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 (231 - 1). Many peers may register the same MIB module, but they must do so at different priorities. The peer with the highest registration priority for a given MIB module is consulted exclusively for all operations on objects in that module. See also the ``Priority'' section.

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 231 - 1.

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.