



          draft            Exporting MIBs for BSD UNIX          Jan 1991


                            How to export a MIB module
                              from a BSD UNIX daemon
                          using the 4BSD/ISODE SMUX API

                             Fri Jan 11 15:09:43 1991



                                 Marshall T. Rose
                     Performance Systems International, Inc.
                                  mrose@psi.com





          1.  Status of this Memo

          This document defines an API for UNIX daemons wishing to
          implement a MIB module on a BSD UNIX system using the
          4BSD/ISODE SNMP software.  This is a local mechanism.





























          M.T. Rose                                             [Page 1]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          2.  The Environment

          This document gives an overview of how one modifies a UNIX
          program to export a MIB module to the local SNMP agent.

          All of the files necessary to interface to the SNMP agent is
          contained in the ISODE source tree, in the snmp/ directory.
          Since this document avoids giving the actual C procedural
          definitions, you should familiarize yourself with the lint
          library, llib-lisnmp.

          For the purposes of example, throughout this document we will
          reference a simple UNIX daemon, unixd, which implements a MIB
          module for "mbuf statistics".


          2.1.  The ISODE

          As you might have guessed, this document assumes that you're
          running the ISODE on your system.  You should read the READ-ME
          file at the base of the source tree for instructions on how to
          configure, generate, and install the ISODE.  In the future, an
          abbreviated set of instructions, for those interested in
          running the only 4BSD/ISODE SNMP software, will be written.

          For now, follow the READ-ME, generate the base system and
          SNMP, e.g.,

               % ./make all all-snmp

          and then install only the 4BSD/ISODE SNMP software:

               # ./make inst-all inst-snmp

          However, be sure to read the entire READ-ME file, as there are
          other steps involved in installing the SNMP software.














          M.T. Rose                                             [Page 2]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          3.  MIB Modules

          The first thing you need to do is to define the actual managed
          objects which your program will implement.  This is done by
          writing a "MIB module".  The syntax of the module is defined
          in the Internet-standard SMI, RFC1155.  It is not the purpose
          of this document to describe the rules used for writing a MIB
          module.  However, here are the basics to speed you on your
          way.

          The MIB module is defined in a file called module.my, e.g.,
          unix.my.  In general, there are three kinds of MIB modules:

          (1)  If you are defining a MIB module for something UNIX
               specific, it should probably go under the BSD UNIX MIB
               (contact Marshall Rose or Keith Sklower to get a number
               under the UNIX enterprise tree).  In this case, the
               definition might start something like this:

                    SendMail-MIB { iso 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, e.g., for a protocol like the NTP,
               then you should contact the Internet Assigned Numbers
               Authority (jkrey@isi.edu) and ask for an experimental
               number.  In this case, the definition might start
               something like this:

                    FIZBIN-MIB DEFINITIONS ::= BEGIN

                    IMPORTS
                            experimental, OBJECT-TYPE
                                FROM RFC1155-SMI;

                    fizBin OBJECT IDENTIFIER ::= { experimental 99 }





          M.T. Rose                                             [Page 3]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          (3)  Otherwise, if you are defining a MIB module for something
               specific to your enterprise, then you contact the
               Internet Assigned Numbers Authority (jkrey@isi.edu) and
               ask for an enterprise number (assuming you don't already
               have one).  In this case, the definition might start
               something like this:

                    FIZZBIN-MIB DEFINITIONS ::= BEGIN

                    IMPORTS
                            enterprises, OBJECT-TYPE
                                FROM RFC1155-SMI;

                    cheetah OBJECT IDENTIFIER ::= { enterprises 9999 }

                    fizBin OBJECT IDENTIFIER ::= { cheetah 1 }


          Regardless of the kind of MIB module, this last OBJECT
          IDENTIFIER points to the root of the tree which your agent is
          going to export.

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

               END



          3.1.  Compiling MIB modules

          The next step is to compile the MIB module into a form that
          your program can read.  This is done using the mosy program:

               % others/mosy/xmosy module.my

          which will create the file module.defs.  In most cases you
          will need to prefix this file with definitions from the root
          of the OBJECT IDENTIFIER tree, e.g.,

               % cat $(INCDIR)isode/snmp/smi.defs module.defs > daemon.defs

          where the daemon.defs file will be the one which you install
          with your program binary.






          M.T. Rose                                             [Page 4]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          3.1.1.  The Syntax of compiled MIB modules

          The syntax is pretty simple:

          (1)  Comments start with "--" or "#" at the beginning of a
               line.

          (2)  Object identifiers are defined like this:

                    name            value

               e.g.,

                    internet        iso.3.6.1


          (3)  Object types are defined like this:

                    name      oid       syntax         access     status

               e.g.,

                    sysDescr  system.1  DisplayString  read-only  mandatory

               where "name" and "oid" are fairly obvious.  For the rest:

                    "syntax" is the name of a defined syntax;

                    "access" is one of read-only, read-write", or none;
                    and,

                    "status" is one of mandatory, optional,
                    deprecated, or obsolete.

               Names of objects are always case-sensitive.















          M.T. Rose                                             [Page 5]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          4.  SMUX Peers

          A program which exports a MIB module is termed a "SMUX peer"
          (SMUX is the name of the protocol used by these programs to
          communicate with an SNMP agent.)

          There is a textual database, $(ETCDIR)snmpd.peers, which
          defines the SMUX peers known to the local SNMP agent.  The
          syntax of this file is pretty simple:

          (1)  Comments start with "#" at the beginning of a line.

          (2)  Each peer is identified in a single line:

                    name        oid         password    [priority]

               where "name" and "oid" are fairly obvious.  For the rest:

                    "password" is a string which the agent will use to
                    authenticate the SMUX peer;
                    and,

                    "priority",
                    if present,
                    is the highest priority with which the SMUX peer can register subtrees.

               The name/oid pairs are assigned by the authority for the
               BSD UNIX MIB (contact Marshall Rose or Keith Sklower to
               register the name of your program and get an for your
               program).

          Note that this file contains things resembling passwords in
          the clear.  As such, it should be protected mode 0600 and
          owned by root.
















          M.T. Rose                                             [Page 6]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          5.  Daemon Skeleton

          Now it's time to modify your daemon to export the MIB.  Your
          source code should include these lines:

               #include <isode/snmp/smux.h>
               #include <isode/snmp/objects.h>
               #include <isode/tailor.h>

          which will include the definitions for: talking to the SNMP
          agent via the SMUX protocol, the object management package,
          and the ISODE tailoring subsystem.

          When loading your daemon, you should add this to the end of
          your command to the loader:

               -lisnmp -lisode

          which includes the 4BSD/SNMP and ISODE libraries.

          You should decide if you want to use the ISODE logging
          subsystem.  This is a convenient mechanism for using a unified
          logging system.  If you are modifying an already existing
          daemon, you probably have your own logging package.
          Otherwise, you should consider using the ISODE subsystem.


          5.1.  Declarations

          There are some global variables that your program will have to
          declare:

               int     debug = 0;
               static  char   *myname = "unixd";

               static LLog     _pgm_log = {
                   "unixd.log", NULLCP, NULLCP,
                   LLOG_FATAL | LLOG_EXCEPTIONS | LLOG_NOTICE,
                   LLOG_FATAL, -1, LLOGCLS | LLOGCRT | LLOGZER, NOTOK
               };
               static  LLog   *pgm_log = &_pgm_log;

               static  OID     subtree = NULLOID;
               static  struct smuxEntry *se = NULL;






          M.T. Rose                                             [Page 7]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


               static  int     smux_fd = NOTOK;
               static  int     rock_and_roll = 0;
               static  int     dont_bother_anymore = 0;

               static  int     quantum = 0;

          You only need the definition of _pgm_log and pgm_log if you
          will be using the ISODE logging subsystem.


          5.2.  Initialization

          When your program initializes itself, it should contain some
          code like this:

                   if (myname = rindex (argv[0], '/'))
                       myname++;
                   if (myname == NULL || *myname == NULL)
                       myname = argv[0];

                   isodetailor (myname, 0);
                   ll_hdinit (pgm_log, myname);

               /* scan argv, set debug if need be... */

                   if (debug)
                       ll_dbinit (pgm_log, myname);

          Of course, if you're not using the ISODE logging subsystem,
          you don't have the calls to ll_hdinit or ll_dbinit.

          After cracking argv, your program should do the usual
          detaching actions.  Usually at this point, your program reads
          the compiled MIB module definitions and starts talking to the
          SNMP agent.


          5.3.  Reading the compiled MIB module

          You program should call the readobjects routine to read the
          module, look-up the subtree which it will export to the SNMP
          agent, and call the getsmuxEntrybyname routine to find its
          entry in the snmpd.peers database.  Finally, it should call a
          routine you define, e.g., init_mib, to initialize it's
          internal MIB structures.  (We'll look at this routine in





          M.T. Rose                                             [Page 8]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          greater detail later on.)

                   OT      ot;

                   if (readobjects ("unixd.defs") == NOTOK)
                       error ("readobjects: %s", PY_pepy);

                   if ((ot = text2obj ("mbuf")) == NULL)
                       error ("object
                   subtree = ot -> ot_name;

                   if (se = getsmuxEntrybyname ("unixd")) == NULL)
                       error ("no SMUX entry for

                   init_mib ();

          If any of these routines fail, then don't bother trying to
          export the MIB module.


          5.4.  Talking to the SNMP agent

          All of the SMUX routines return NOTOK on failure.  Unless
          otherwise noted, these routines also return OK on success.  On
          failure, the variable

               extern int  smux_errno;

          is set to a symbolic value defined in smux.h, one of:

               invalidOperation
               parameterMissing
               systemError
               youLoseBig
               congestion
               inProgress

          In addition, the variable

               extern char smux_info[BUFSIZ];

          contains a printable explanation of what happened on failure.

          All errors are FATAL except for inProgress.  This means retry
          your operation later on.





          M.T. Rose                                             [Page 9]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          5.4.1.  Initialization

          You program should call the routine smux_init, which
          initializes a SMUX connection to the local SNMP agent.  This
          starts a TCP connection, but most likely does not complete it.
          You program will need to call an open routine (there is only
          one at present) to finish the connect and establish the SMUX
          association.

          If successful, the return value is a file-descriptor, suitable
          for use with select, etc.

          On failure, smux_errno will be set to one of congestion,
          youLoseBig, or systemError.  In this case, you should probably
          have your program retry the operation every 5 minutes or so.

               if ((smux_fd = smux_init (debug)) == NOTOK)
                   error ("smux_init: %s [%s]",
                          smux_error (smux_errno), smux_info);
               else
                   rock_and_roll = 0;



          5.4.2.  Opening

          Once smux_init returns OK, you should start selecting for
          writability on the file-descriptor returned.  Once select says
          your program can write to the fd, your program should call the
          routine smux_simple_open, which establishes the SMUX
          association.

          On failure, smux_error will be set to one of parameterMissing,
          invalidOperation, inProgress, systemError, congestion, or
          youLoseBig.  If the error code is inProgress, then your
          program should continue retrying the fd for writability, and
          then call smux_simple_open again.  Otherwise, your program
          should take the appropriate action based on the error code
          returned.

               if (smux_simple_open (&se -> se_identity,
                                     "SMUX UNIX daemon",
                                     se -> se_password,
                                     strlen (se -> se_password))
                       == NOTOK) {





          M.T. Rose                                            [Page 10]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                   if (smux_errno == inProgress)
                       return;

                   error ("smux_simple_open: %s [%s]",
                          smux_error (smux_errno), smux_info);
                   smux_fd = NOTOK;
               }
               else
                   rock_and_roll = 1;



          5.4.3.  Closing

          If, for some reason, your program wishes to close the SMUX
          association.  There are several reasons that are allowed:

               goingDown
               unsupportedVersion
               packetFormat
               protocolError
               internalError
               authenticationFailure

          On failure, smux_error will be set to one of invalidOperation,
          congestion, or youLoseBig.


          5.4.4.  Registering Subtrees

          Once smux_simple_open returns OK, your program should register
          the MIB module that your daemon will export by calling
          smux_register.  A subtree can be registered in one of three
          modes:

               readOnly
               readWrite
               delete

          which are all fairly obvious.

          Note that a return value of OK from smux_register means only
          that the registration request was queued for the SNMP agent
          via the SMUX protocol.  Some time later the SNMP agent's
          response will be forthcoming.





          M.T. Rose                                            [Page 11]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          On failure, smux_error will be set to one of parameterMissing,
          invalidOperation, congestion, or youLoseBig.  Your program
          should take the appropriate action based on the error code
          returned.

               if (smux_register (subtree, -1, readOnly) == NOTOK) {
                   error ("smux_register: %s [%s]",
                           smux_error (smux_errno), smux_info);
                   smux_fd = NOTOK;
               }



          5.4.5.  Main Loop

          At this point, your program is more or less in it's main loop
          (actually, it probably entered the main loop after the very
          first call to smux_init).

               int     nfds;   /* these are set for other fd's... */

               fd_set  ifds;
               fd_set  ofds;

                   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;






          M.T. Rose                                            [Page 12]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                       if ((n = xselect (nfds, &rfds, &wfds, NULLFD, secs))
                               == NOTOK) {
                           error ("xselect failed");
                            ...
                       }

               /* check fd's for other purposes here... */

                       if (smux_fd == NOTOK && !dont_bother_anymore) {
                           if (n == 0) {
                               if ((smux_fd = smux_init (debug)) == NOTOK)
                                   error ("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)) {
                                   if (smux_simple_open (&se -> se_identity,
                                                 "SMUX UNIX daemon",
                                                 se -> se_password,
                                                 strlen (se -> se_password))
                                           == NOTOK) {
                                       if (smux_errno != inProgress) {
                                           error ("smux_simple_open: %s [%s]",
                                                  smux_error (smux_errno),
                                                  smux_info);
                                           smux_fd = NOTOK;
                                       }
                                   }
                                   else {
                                       rock_and_roll = 1;

                                       if (smux_register (subtree, -1,
                                                   readOnly) == NOTOK) {
                                           error ("smux_register: %s [%s]",
                                                  smux_error (smux_errno),
                                                  smux_info);





          M.T. Rose                                            [Page 13]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                                           smux_fd = NOTOK;
                                       }
                               }
                   }

          So, all that remains is to take a look at the routine
          doit_smux mentioned above.  This is called when select
          indicates the SMUX file-descriptor is ready for reading.


          5.4.6.  Events

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

          Note that the event is filled-in from a static area.  On the
          next call to smux_init, smux_close, or smux_wait, the value
          will be overwritten.  As such, do not free this structure
          yourself.

          On failure, smux_error will be set to one of parameterMissing,
          invalidOperation, inProgress, or youLoseBig.  Your program
          should take 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;

                       error ("smux_wait: %s [%s]",
                              smux_error (smux_errno), smux_info);
               losing: ;
                       smux_fd = NOTOK;
                       return;
                   }

          Next, your program should switch based on the actual event
          returned:

               type_SNMP_SMUX__PDUs_registerResponse
               type_SNMP_SMUX__PDUs_get__request
               type_SNMP_SMUX__PDUs_get__next__request





          M.T. Rose                                            [Page 14]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


               type_SNMP_SMUX__PDUs_set__request
               type_SNMP_SMUX__PDUs_commitOrRollback
               type_SNMP_SMUX__PDUs_close

          The actual code is fairly straight-forward:

               switch (event -> offset) {
                   case type_SNMP_SMUX__PDUs_registerResponse:
                       {
                           struct type_SNMP_RRspPDU *rsp =
                                       event -> un.registerResponse;

                           if (rsp -> parm == int_SNMP_RRspPDU_failure) {
                               error ("SMUX registration of %s failed",
                                      oid2ode (subtree));
                               dont_bother_anymore = 1;
                               (void) smux_close (goingDown);
                               break;
                           }
                       }
                       if (smux_trap (int_SNMP_generic__trap_coldStart,
                                      0, (struct type_SNMP_VarBindList *) 0)
                               == NOTOK) {
                           error ("smux_trap: %s [%s]",
                                  smux_error (smux_errno), smux_info);
                           break;
                       }
                       return;

                   case type_SNMP_SMUX__PDUs_get_request:
                   case type_SNMP_SMUX__PDUs_get__next__request:
                       get_smux (event -> un.get__request, event -> offset);
                       return;

                   case type_SNMP_SMUX__PDUs_close:
                       notice ("SMUX close: %s",
                               smux_error (event -> un.close -> parm));
                       break;

                   case type_SNMP_SMUX__PDUs_set__request:
                   case type_SNMP_SMUX__PDUs_commitOrRollback:
                       set_smux (event);
                       return;

                   default:





          M.T. Rose                                            [Page 15]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                       error ("bad SMUX operation: %d", event -> offset);
                       (void) smux_close (protocolError);
                       break;
               }
               smux_fd = NOTOK;

          Note the use of the smux_trap routine to send a coldStart trap
          once the daemon has successful registered the MIB module it is
          exporting.  The trap codes are:

               int_SNMP_generic__trap_coldStart
               int_SNMP_generic__trap_warmStart
               int_SNMP_generic__trap_linkDown
               int_SNMP_generic__trap_linkUp
               int_SNMP_generic__trap_authenticationFailure
               int_SNMP_generic__trap_egpNeighborLoss
               int_SNMP_generic__trap_enterpriseSpecific

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

          This peer doesn't implement sets, so it handles the SNMP set
          operation thusly:

               static  set_smux (event)
               struct type_SNMP_SMUX__PDUs *event;
               {
                   switch (event -> offset) {
                       case type_SNMP_SMUX__PDUs_set__request:
                           {
                               register struct type_SNMP_GetRequest__PDU *pdu =
                                                   event -> un.get__response;

                               pdu -> error__status =
                                           int_SNMP_error__status_noSuchName;
                               pdu -> error__index =
                                           pdu -> variable__bindings ? 1 : 0;

                               if (smux_response (pdu) == NOTOK) {
                                   advise (LLOG_EXCEPTIONS, NULLCP,
                                           "smux_response: %s [%s]",
                                           smux_error (smux_errno),
                                           smux_info);
                                   smux_fd = NOTOK;
                               }





          M.T. Rose                                            [Page 16]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                           }
                           break;

                       case type_SNMP_SMUX__PDUs_commitOrRollback:
                           {
                               struct type_SNMP_SOutPDU *cor =
                                               event -> un.commitOrRollback;

                               if (cor -> parm == int_SNMP_SOutPDU_commit) {
                                                   /* "should not happen" */
                                   (void) smux_close (protocolError);
                                   smux_fd = NOTOK;
                               }
                           }
                           break;
                   }
               }



          So, all that remains is to take a look at the routine get_smux
          which implements the SNMP get and powerful get-next operators.
          We will return to this routine once the structures and
          routines which implement the managed object abstraction are
          explained.

























          M.T. Rose                                            [Page 17]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          6.  Managed Objects

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


          6.1.  Syntax

          The syntax of MIB objects is modeled by the OS structure:

               typedef struct object_syntax {
                   char   *os_name;                    /* syntax name */

                   IFP     os_encode;                  /* data -> PE */
                   IFP     os_decode;                  /* PE -> data */
                   IFP     os_free;                    /* free data */

                   IFP     os_parse;                   /* str -> data */
                   IFP     os_print;                   /* data -> tty */

                        ...
               }               object_syntax, *OS;
               #define NULLOS  ((OS) 0)

          The syntaxes defined by the Internet-standard MIB are already
          implemented:

               syntax  structure
               INTEGER integer
               OctetString     struct qbuf (OCTET STRING)
               ObjectID        struct OIDentifier (OBJECT IDENTIFIER)
               NULL    char
               DisplayString   struct qbuf
               IpAddress       struct sockaddr_in
               NetworkAddress  struct sockaddr_in
               Counter integer
               Gauge   integer
               TimeTicks       integer
               ClnpAddress     struct sockaddr_iso

          where "syntax" is the name appearing in a compiled MIB module,
          and "structure" is the C language data type corresponding to
          the object's syntax.





          M.T. Rose                                            [Page 18]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          To take a syntax name and get back the structure, use the
          routine text2syn.


          6.1.1.  Abstractions for Standard Syntaxes

          Here are the structures and routine used to implement the
          low-level MIB abstractions.


          6.1.1.1.  integer

          This is used for the INTEGER, Counter, Gauge, and TimeTicks
          syntax.

          The definition is:

               typedef int integer;

          which is hardly surprising.


          6.1.1.2.  struct qbuf

          This is used for the OctetString (OBJECT IDENTIFIER) and
          DisplayString syntaxes.

          The definition is:

               struct qbuf {
                   struct qbuf *qb_forw;   /* doubly-linked list */
                   struct qbuf *qb_back;   /*   .. */

                   int     qb_len;         /* length of data */
                   char   *qb_data;        /* current pointer into data */
                   char    qb_base[1];     /* extensible... */
               };

          The macro QBFREE is used to traverse qb_forw to free all qbufs
          in the ring:

               QBFREE (qb)
               struct qbuf *qb;

          To allocate a new string from the qbuf use:





          M.T. Rose                                            [Page 19]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


               char *qb2str (q)
               struct qbuf *q

          The string is NULL-terminated, but there may be other NULLs in
          the string to foil things like strlen.

          To allocate a new qbuf of len octets from string s, use:

               struct qbuf str2qb (s, len, head)
               char    *s;
               int      len,
                        head;

          (head should always be 1).

          To free an allocated qbuf, use qb_free which calls QBFREE and
          then free on its argument.


          6.1.1.3.  struct OIDentifier

          This is used for the ObjectID (OBJECT IDENTIFIER) 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)

          To compare two OIDs, use

               int     oid_cmp (p, q)
               OID     p,
                       q;

          which returns -1 if p<q, 1 if p>q, 0 otherwise.

          To allocate a new OID and copy it from another, use oid_cpy.

          To free an allocated OID, use oid_free.







          M.T. Rose                                            [Page 20]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          To take an OID and produce a string in numeric form use

               char   *sprintoid (oid)
               OID     oid;

          The result is returned in a static area.  The inverse routine
          is:

               OID     str2oid (s)
               char    *s;

          which returns an OID from a static area


          6.1.1.4.  struct sockaddr_in

          This is used for the IpAddress and NetworkAddress syntaxes.
          Presumably you are overly familiar with this structure.


          6.1.1.5.  struct sockaddr_iso

          This is used for the ClnpAddress syntax.  If your are not
          running a BSD/OSI system, don't worry about this.


          6.1.2.  Defining a new Syntax

          The routine add_syntax is used to define a new syntax.  (if
          this routine returns NOTOK, then the internal syntax table has
          overflowed; adjust the constant MAXSYN in the file syntax.c).
          The first argument is the name of the syntax.  The other five
          are pointers to integer-valued routines that are called to
          operate on an opaque pointer:

               /* returns OK if x is encoded into pe (allocates pe),
                  NOTOK otherwise */

               f_encode (x, pe)
               pointer *x;
               PE      *pe;


               /* returns OK if pe is decoded into x (allocates x),
                  NOTOK otherwise */





          M.T. Rose                                            [Page 21]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


               f_decode (x, pe)
               pointer **x;
               PE        pe;


               /* free's an allocated x (from f_decode or f_parse) */

               f_free (x)
               pointer *x;


               /* returns OK if s is decoded into x (allocates x),
                  NOTOK otherwise */

               f_parse (x, s)
               pointer **x;
               char     *s;


               /* prints x on the user's tty */

               f_print (x, os)
               pointer *x;
               OS       os;

          Presentation elements (PEs) are discussed later on.


          6.2.  Objects

          MIB objects are modeled by the OT 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 */





          M.T. Rose                                            [Page 22]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


               #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 lots of routines to manipulate MIB objects.

          The routine name2obj takes an object identifier and returns
          the object type, either exact or prefix, e.g.,

               name2obj ( OID { ipRouteDest.0.0.0.0 } )

          returns the object type for "ipRouteDest"

          The routine text2obj takes a string and returns the exact
          object type, e.g.,

               text2obj ("ipRouteDest")

          will succeed, but

               text2obj ("ipRouteDest.0.0.0.0")

          will fail.

          The routine text2oid takes a string and returns the object
          identifier associated with the corresponding object.  The
          string can be numeric (e.g., "1.3.6.1"), symbolic (e.g.,
          "internet"), or symbolic.numeric (e.g., "iso.3.6.1").

          The routine oid2ode takes an OID and returns a string suitable
          for pretty-printing, in the form symbolic or symbolic.numeric.


          6.3.  Instances

          MIB instances are modeled by the OI structure:

               typedef struct object_instance {
                   OID     oi_name;                /* instance OID */






          M.T. Rose                                            [Page 23]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                   OT      oi_type;                /* prototype */
               }               object_instance, *OI;
               #define NULLOI  ((OI) 0)

          There are lots of routines to manipulate MIB instances.

          The routine name2inst takes a variable name and returns the
          corresponding instance, e.g.,

               name2inst ( OID { ipRouteDest.0.0.0.0 } )

          will return an OI with oi_name set to its argument and oi_type
          set to the object type for "ipRouteDest".

          The routine next2inst finds the closest object type before the
          variable name and returns an OI corresponding to that object
          type.

          The routine text2inst first calls text2oid to get the OID
          corresponding to the argument, then calls name2obj to get the
          type associated with that OID.





























          M.T. Rose                                            [Page 24]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          7.  Linkage

          It is now time to tie up all the loose ends.  When your
          program starts, it needs to establish some linkage between the
          objects defined in the compiled MIB module and the data
          structures in your program.  Further, when the SNMP agent asks
          to manipulate the object instances in the MIB module your
          program exported (e.g., using the powerful SNMP get-next
          operator), you need to have a small protocol engine to field
          these requests.

          The way the linkage is established is to associate a C routine
          with each of the leaf objects defined in the compiled MIB
          module.  When the protocol engine fields a request from the
          SNMP agent, it will invoke that routine to "do the right
          thing".  Typically, for each table in the compiled MIB module,
          you define a C routine to handle requests for the leaf objects
          of that table.  In addition, there is usually one more routine
          defined to handle those leaf objects not found under any one
          table.  For each C routine you define, you define a group of
          constant integer symbols which identify a leaf object that is
          handled by that routine (e.g., for each routine, you start
          numbering the symbols starting at 0 and going up).  By
          convention, these symbols have the same name as the leaf
          objects.


          7.1.  Initialization

          Earlier it was noted that your program will probably call a
          routine called init_mib which initializes it's internal MIB
          structures.  The routine should look something like this:

               register OT     ot;

               if (ot = text2obj ("mbufS"))
                   ot -> ot_getfnx = o_mbuf,
                   ot -> ot_info = (caddr_t) mbufS;

                ...

               if (ot = text2obj ("mbufType"))
                   ot -> ot_getfnx = o_mbufType,
                   ot -> ot_info = (caddr_t) mbufType;






          M.T. Rose                                            [Page 25]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          where we can imagine that o_mbuf and o_mbufType are routines
          that are defined in your program, and mbufS and mbufType are
          constant symbols defined with those routines.


          7.2.  Generic Event Handling

          Earlier it was noted that your program will probably call a
          routine called get_smux which implements the SNMP get and
          powerful get-next operators.  The routine should look
          something like this:

               static  get_smux (pdu, offset)
               register struct type_SNMP_GetRequest__PDU *pdu;
               int     offset;
               {
                   int     idx,
                           status;
                   object_instance ois;
                   register struct type_SNMP_VarBindList *vp;

                   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 == type_SNMP_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
                                   || (ot = oi -> oi_type) -> ot_getfnx
                                           == NULLIFP) {
               no_name: ;
                               pdu -> error__status =
                                       int_SNMP_error__status_noSuchName;





          M.T. Rose                                            [Page 26]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                               goto out;
                           }

               try_again: ;
                       switch (ot -> ot_access) {
                           case OT_NONE:
                               if (offset ==
                                       type_SNMP_SMUX__PDUs_get__next__request)
                                   goto get_next;
                               goto no_name;

                           case OT_RDONLY:
                               if (offset ==
                                       type_SNMP_SMUX__PDUs_set__request) {
                                   pdu -> error__status =
                                               int_SNMP_error__status_readOnly;
                                   goto out;
                               }
                               break;

                           case OT_RDWRITE:
                               break;
                       }

                       switch (status = (*ot -> ot_getfnx) (oi, v, offset)) {
                           case NOTOK:     /* get-next wants a bump */
               get_next: ;
                               oi = &ois;
                               for (;;) {
                                   if ((ot = ot -> ot_next) == NULLOT) {
                                       pdu -> error__status =
                                             int_SNMP_error__status_noSuchName;
                                       goto out;
                                   }
                                   oi -> oi_name =
                                               (oi -> oi_type = ot) -> ot_name;
                                   if (ot -> ot_getfnx)
                                       goto try_again;
                               }

                           case int_SNMP_error__status_noError:
                               break;

                           default:
                               pdu -> error__status = status;





          M.T. Rose                                            [Page 27]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                               goto out;
                       }
                   }
                   idx = 0;

               out: ;
                   pdu -> error__index = idx;

                   if (smux_response (pdu) == NOTOK) {
                       error ("smux_response: %s [%s]",
                              smux_error (smux_errno), smux_info);
                       smux_fd = NOTOK;
                   }
               }

          The actual code is fairly straight-forward: First, the
          variable quantum is set to the request ID for this
          transaction.  The SMUX protocol requires that this number
          change for each SNMP operation that the SNMP agent fields, so
          your program can use it as a cookie to see when it should re-
          read kernel variables.  (A single SNMP operation received by
          the SNMP agent might result in multiple SMUX protocol
          transactions with your 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
               int_SNMP_error__status_noError
               int_SNMP_error__status_tooBig
               int_SNMP_error__status_noSuchName
               int_SNMP_error__status_badValue
               int_SNMP_error__status_readOnly
               int_SNMP_error__status_genErr

          the first value is returned only if the powerful 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 all self-explanatory.






          M.T. Rose                                            [Page 28]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          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.


          7.3.  Specific Event Handling: Outside a table

          Now let's look at one of these user-defined routines, which
          handles leaf objects that are not part of a table:

               static  int     lastq = -1;
               static  struct mbstat mbstat;


               #define mbufS           0
               #define mbufClusters    1
                ...
               #define mbufFrees       6


               static int  o_mbuf (oi, v, offset)
               OI      oi;
               register struct type_SNMP_VarBind *v;
               int     offset;
               {
                   int     ifvar;
                   register struct mbstat *m = &mbstat;
                   register OID    oid = oi -> oi_name;
                   register OT     ot = oi -> oi_type;

                   ifvar = (int) ot -> ot_info;
                   switch (offset) {
                       case type_SNMP_SMUX__PDUs_get__request:
                           if (oid -> oid_nelem !=
                                       ot -> ot_name -> oid_nelem + 1
                                   || oid -> oid_elements[oid -> oid_nelem - 1]
                                           != 0)
                               return int_SNMP_error__status_noSuchName;
                           break;

                       case type_SNMP_SMUX__PDUs_get__next__request:
                           if (oid -> oid_nelem
                                   == ot -> ot_name -> oid_nelem) {
                               OID     new;





          M.T. Rose                                            [Page 29]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                               if ((new = oid_extend (oid, 1)) == NULLOID)
                                   return int_SNMP_error__status_genErr;
                               new -> oid_elements[new -> oid_nelem - 1] = 0;

                               if (v -> name)
                                   free_SNMP_ObjectName (v -> name);
                               v -> name = new;
                           }
                           else
                               return NOTOK;
                           break;

                       default:
                           return int_SNMP_error__status_genErr;
                   }

                   if (quantum != lastq) {
                       lastq = quantum;

                       if (getkmem (nl + N_MBSTAT, (caddr_t) m, sizeof *m)
                               == NOTOK)
                           return int_SNMP_error__status_genErr;
                   }

                   switch (ifvar) {
                       case mbufS:
                           return o_integer (oi, v, m -> m_mbufs);

                       case mbufClusters:
                           return o_integer (oi, v, m -> m_clusters);

                ...

                       case mbufFrees:
                           return o_integer (oi, v, m -> m_mbfree);

                       default:
                           return int_SNMP_error__status_noSuchName;
                   }
               }

          The actual code is fairly straight-forward: First, the
          constant offsets are defined.  Then, the o_mbuf routine is
          defined.






          M.T. Rose                                            [Page 30]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          The first action is to determine which leaf object is being
          referenced.  This corresponding symbolic constant is placed in
          the variable ifvar.  Then a switch is made based on the
          operation.

          (1)  For the get operation, all instances are identified by
               the object type followed by ".0" (e.g., "mbufS.0").  So,
               the code checks to see if the OID associated with the
               object instance is exactly one longer than the OID
               associated with the object type, and that the extra sub-
               identifier has the value 0.

          (2)  For the powerful get-next operation, there are really two
               cases, depending on whether some instance identifier is
               present.  If some instance identifier is present, then
               for a non-tabular leaf object, the next variable belongs
               to some other object type, so the routine simply returns
               the value NOTOK, and the get_smux routine will find the
               next object accordingly.  Otherwise, if no instance is
               identified, a new OID is constructed and initialized.
               The old OID is free'd and the new one inserted in its
               place.

          Now that the correct instance has been identified, a check is
          made to see if kmem should be consulted.  (Obviously other
          programs might consult other data stores.) Finally, the
          instance value is encoded and the routine returns.


          7.3.1.  Instance handling

          Several routines are provided to encode instance values:

          The o_number routine encodes an integer value, such as an
          INTEGER, Counter, Guage, or TimeTick.  The macro o_integer is
          simply a synonym for o_number.

          The o_string routine encodes a string value, such as an
          OctetString or DisplayString, e.g.,

               o_string (oi, v, "lo0", strlen ("lo0"));

          or

               o_string (oi, v, ether_addr, sizeof ether_addr);





          M.T. Rose                                            [Page 31]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          The o_specific routine takes a structure corresponding to a
          syntax, such as an OID for ObjectID, and does the encoding,
          e.g.,

               OID nullSpecific = ...;

               o_specific (oi, v, nullSpecific);



          7.3.2.  A special case

          A special user-defined routine is provided for those cases for
          leaf-objects containing information that is initialized on
          start-up, o_generic.  For example:

               char    buffer[BUFSIZ];

               if (ot = text2obj ("sysName"))
                   ot -> ot_getfnx = o_generic,
                   ot -> ot_info = (caddr_t) sysName;

               (void) gethostname (buffer, sizeof buffer);
               (void) (*ot -> ot_syntax -> os_parse)
                               ((struct qbuf **) &ot -> ot_info, buffer);

          The idea is that the ot_info field contains a pointer to a
          data structure corresponding to the syntax of the object.
          Since all of the structures are rather strange (except for
          integers), o_generic probably won't receive a lot of use.


          7.4.  Specific Event Handling: Inside a table

          Now let's look at one of these user-defined routines, which
          handles leaf objects that are part of a table:

               #define mbufType        0
               #define mbufAllocates   1


               static int  o_mbufType (oi, v, offset)
               OI      oi;
               register struct type_SNMP_VarBind *v;
               int     offset;





          M.T. Rose                                            [Page 32]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


               {
                   int     ifnum,
                           ifvar;
                   register struct mbstat *m = &mbstat;
                   register OID    oid = oi -> oi_name;
                   register OT     ot = oi -> oi_type;

                   ifvar = (int) ot -> ot_info;
                   switch (offset) {
                       case type_SNMP_SMUX__PDUs_get__request:
                           if (oid -> oid_nelem
                                   != ot -> ot_name -> oid_nelem + 1)
                               return int_SNMP_error__status_noSuchName;
                           ifnum =
                                 oid -> oid_elements[oid -> oid_nelem - 1];
                           if (ifvar >= sizeof m -> m_mtypes /
                                           sizeof m -> m_mtypes[0])
                               return int_SNMP_error__status_noSuchName;
                           break;

                       case type_SNMP_SMUX__PDUs_get__next__request:
                           if (oid -> oid_nelem
                                   == ot -> ot_name -> oid_nelem) {
                               OID     new;

                               ifnum = 0;

                               if ((new = oid_extend (oid, 1)) == NULLOID)
                                   return int_SNMP_error__status_genErr;
                               new -> oid_elements[new -> oid_nelem - 1] =
                                               ifnum;

                               if (v -> name)
                                   free_SNMP_ObjectName (v -> name);
                               v -> name = new;
                           }
                           else {
                               int     i = ot -> ot_name -> oid_nelem;

                               ifnum = oid -> oid_elements[i] + 1;
                               if (ifnum >= sizeof m -> m_mtypes /
                                               sizeof m -> m_mtypes[0])
                                   return NOTOK;

                               oid -> oid_elements[i] = ifnum;





          M.T. Rose                                            [Page 33]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


                               oid -> oid_nelem = i + 1;
                           }
                           break;

                       default:
                           return int_SNMP_error__status_genErr;
                   }

                   if (quantum != lastq) {
                       lastq = quantum;

                       if (getkmem (nl + N_MBSTAT, (caddr_t) m, sizeof *m)
                               == NOTOK)
                           return int_SNMP_error__status_genErr;
                   }

                   switch (ifvar) {
                       case mbufType:
                           return o_integer (oi, v, ifnum);

                       case mbufAllocates:
                           return o_integer (oi, v,
                                             m -> m_mtypes[ifnum]);

                       default:
                           return int_SNMP_error__status_noSuchName;
                   }
               }

          The actual code is fairly straight-forward: First, the
          constant offsets are defined.  Then, the o_mbufType routine is
          defined.

          The first action is to determine which leaf object is being
          referenced.  This corresponding symbolic constant is placed in
          the variable ifvar.  Then a switch is made based on the
          operation.  Because these are tabular objects, the instance
          refers to a row in the table (and the leaf object refers to a
          column in the table, of course).

          (1)  For the get operation, all instances are identified by
               the object type followed by ".rowno" (e.g.,
               "mbufAllocates.10") refers to the value in the
               mbufAllocates column of the thenth row).  So, the code
               checks to see if the OID associated with the object





          M.T. Rose                                            [Page 34]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


               instance is exactly one longer than the OID associated
               with the object type, and that the extra sub-identifier
               is within the range 0..rowno-1.

          (2)  For the powerful get-next operation, there are really two
               cases, depending on whether some instance identifier is
               present.  If no instance is identified, a new OID is
               constructed and initialized for the first row (row 0) of
               the table. The old OID is free'd and the new one inserted
               in its place.  Otherwise, if some instance identifier is
               present, then the corresponding row must be identified,
               the row number incremented, and a check made to see if
               this is still in range (if not, NOTOK is returned to
               signal that the next object type should be used).
               Otherwise, the OID is updated in place.

          Now that the correct instance has been identified, a check is
          made to see if kmem should be consulted.  (Obviously other
          programs might consult other data stores.) Finally, the
          instance value is encoded and the routine returns.


          7.4.1.  The general case

          Obviously, this is a simple example of table handling.  In
          general, the table will be implemented via linked lists,
          sorted according to object instance.  In this case, there is
          usually a special routine that is called to get a particular
          instance or the next instance.  Take a look at the routines
          get_tbent and o_smuxTree in the file snmpd.c.  These implement
          the smuxTree table.



















          M.T. Rose                                            [Page 35]





          draft            Exporting MIBs for BSD UNIX          Jan 1991


          Table of Contents


          1 Status of this Memo ...................................    1
          2 The Environment .......................................    2
          2.1 The ISODE ...........................................    2
          3 MIB Modules ...........................................    3
          3.1 Compiling MIB modules ...............................    4
          3.1.1 The Syntax of compiled MIB modules ................    5
          4 SMUX Peers ............................................    6
          5 Daemon Skeleton .......................................    7
          5.1 Declarations ........................................    7
          5.2 Initialization ......................................    8
          5.3 Reading the compiled MIB module .....................    8
          5.4 Talking to the SNMP agent ...........................    9
          5.4.1 Initialization ....................................   10
          5.4.2 Opening ...........................................   10
          5.4.3 Closing ...........................................   11
          5.4.4 Registering Subtrees ..............................   11
          5.4.5 Main Loop .........................................   12
          5.4.6 Events ............................................   14
          6 Managed Objects .......................................   18
          6.1 Syntax ..............................................   18
          6.1.1 Abstractions for Standard Syntaxes ................   19
          6.1.1.1 integer .........................................   19
          6.1.1.2 struct qbuf .....................................   19
          6.1.1.3 struct OIDentifier ..............................   20
          6.1.1.4 struct sockaddr_in ..............................   21
          6.1.1.5 struct sockaddr_iso .............................   21
          6.1.2 Defining a new Syntax .............................   21
          6.2 Objects .............................................   22
          6.3 Instances ...........................................   23
          7 Linkage ...............................................   25
          7.1 Initialization ......................................   25
          7.2 Generic Event Handling ..............................   26
          7.3 Specific Event Handling: Outside a table ............   29
          7.3.1 Instance handling .................................   31
          7.3.2 A special case ....................................   32
          7.4 Specific Event Handling: Inside a table .............   32
          7.4.1 The general case ..................................   35










          M.T. Rose                                            [Page 36]

