|
|
BSD 4.3reno
draft Exporting MIBs for BSD UNIX May 1990
How to export a MIB module
from a BSD UNIX daemon
using the 4BSD/ISODE SMUX API
Sun May 13 14:54:13 1990
Marshall T. Rose
Performance Systems International, Inc.
11800 Sunrise Valley Drive
Suite 1100
Reston, VA 22091
[email protected]
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 May 1990
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 May 1990
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 ([email protected]) 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 May 1990
(3) Otherwise, if you are defining a MIB module for something
specific to your enterprise, then you contact the
Internet Assigned Numbers Authority ([email protected]) 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 May 1990
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 May 1990
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 May 1990
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 May 1990
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 May 1990
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 May 1990
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 May 1990
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 May 1990
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 May 1990
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 May 1990
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 May 1990
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;
default:
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
M.T. Rose [Page 15]
draft Exporting MIBs for BSD UNIX May 1990
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.
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 16]
draft Exporting MIBs for BSD UNIX May 1990
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 17]
draft Exporting MIBs for BSD UNIX May 1990
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 18]
draft Exporting MIBs for BSD UNIX May 1990
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 19]
draft Exporting MIBs for BSD UNIX May 1990
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 20]
draft Exporting MIBs for BSD UNIX May 1990
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 21]
draft Exporting MIBs for BSD UNIX May 1990
#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 22]
draft Exporting MIBs for BSD UNIX May 1990
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 23]
draft Exporting MIBs for BSD UNIX May 1990
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 24]
draft Exporting MIBs for BSD UNIX May 1990
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 25]
draft Exporting MIBs for BSD UNIX May 1990
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 26]
draft Exporting MIBs for BSD UNIX May 1990
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 27]
draft Exporting MIBs for BSD UNIX May 1990
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 28]
draft Exporting MIBs for BSD UNIX May 1990
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 29]
draft Exporting MIBs for BSD UNIX May 1990
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 30]
draft Exporting MIBs for BSD UNIX May 1990
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 31]
draft Exporting MIBs for BSD UNIX May 1990
{
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 32]
draft Exporting MIBs for BSD UNIX May 1990
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 33]
draft Exporting MIBs for BSD UNIX May 1990
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 34]
draft Exporting MIBs for BSD UNIX May 1990
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 ....................................... 17
6.1 Syntax .............................................. 17
6.1.1 Abstractions for Standard Syntaxes ................ 18
6.1.1.1 integer ......................................... 18
6.1.1.2 struct qbuf ..................................... 18
6.1.1.3 struct OIDentifier .............................. 19
6.1.1.4 struct sockaddr_in .............................. 20
6.1.1.5 struct sockaddr_iso ............................. 20
6.1.2 Defining a new Syntax ............................. 20
6.2 Objects ............................................. 21
6.3 Instances ........................................... 22
7 Linkage ............................................... 24
7.1 Initialization ...................................... 24
7.2 Generic Event Handling .............................. 25
7.3 Specific Event Handling: Outside a table ............ 28
7.3.1 Instance handling ................................. 30
7.3.2 A special case .................................... 31
7.4 Specific Event Handling: Inside a table ............. 31
7.4.1 The general case .................................. 34
M.T. Rose [Page 35]
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.