Blame AGENT.txt

Packit Service b38f0b
Note, this is based on the text from a web page, which can be found in 
Packit Service b38f0b
the documentation section of the http://www.net-snmp.org web page.
Packit Service b38f0b
Packit Service b38f0b
Extending the UCD-SNMP agent
Packit Service b38f0b
============================
Packit Service b38f0b
Packit Service b38f0b
This document describes the procedure for writing code to extend
Packit Service b38f0b
the functionality of the v4 UCD-SNMP network management agent.
Packit Service b38f0b
Modules written using this procedure should also work with the v5
Packit Service b38f0b
Net-SNMP agent, though such modules would not take advantage of the
Packit Service b38f0b
new handler-based helper mechanism.  See the on-line documentation
Packit Service b38f0b
for more information and examples of the newer approach.
Packit Service b38f0b
We would be very interested in comment and feedback about how useful
Packit Service b38f0b
(or otherwise) you find this description, and ways in which it could
Packit Service b38f0b
be improved.
Packit Service b38f0b
Packit Service b38f0b
The information is designed to be read in order - the structure being:
Packit Service b38f0b
Packit Service b38f0b
  1. Overview & Introduction
Packit Service b38f0b
  2. MIB files, and how they relate to the agent implementation
Packit Service b38f0b
  3. Header files
Packit Service b38f0b
  4. The basic structure of module implementation code
Packit Service b38f0b
  5. The details of non-table based implementations
Packit Service b38f0b
  6. The details of simple table based implementations
Packit Service b38f0b
  7. The details of more general table based implementations
Packit Service b38f0b
  8. How to implement SET-able variables
Packit Service b38f0b
Packit Service b38f0b
While the document is intended to be generally self-contained,
Packit Service b38f0b
it does occasionally refer to code files shipped with the main UCD
Packit Service b38f0b
distribution (in particular the example module), and it may prove
Packit Service b38f0b
useful to have these files available for reference.
Packit Service b38f0b
Packit Service b38f0b
1. How to write a Mib module
Packit Service b38f0b
============================
Packit Service b38f0b
Packit Service b38f0b
Introduction
Packit Service b38f0b
------------
Packit Service b38f0b
Packit Service b38f0b
The design of the UCD SNMP agent has always been shaped by the desire to be
Packit Service b38f0b
able to extend its functionality by adding new modules. One of the earliest
Packit Service b38f0b
developments from the underlying CMU code base was the ability to call
Packit Service b38f0b
external scripts, and this is probably the simplest method of extending the
Packit Service b38f0b
agent.
Packit Service b38f0b
However, there are circumstances where such an approach is felt to be
Packit Service b38f0b
inappropriate - perhaps from considerations of speed, access to the
Packit Service b38f0b
necessary data, reliability or elegance. In such cases, the obvious solution
Packit Service b38f0b
is to provide C code that can be compiled into the agent itself to implement
Packit Service b38f0b
the desired module. Many of the more recent developments in the code
Packit Service b38f0b
structure have been intended to ease this process. In particular, one of the
Packit Service b38f0b
more recent additions to the suite is the tool mib2c. This is designed to
Packit Service b38f0b
take a portion of the MIB tree (as defined by a MIB file) and generate the
Packit Service b38f0b
code skeleton necessary to implement this. This document will cover the use
Packit Service b38f0b
mib2c, as well as describing the requirements and functionality of the code
Packit Service b38f0b
in more detail.
Packit Service b38f0b
Packit Service b38f0b
In order to implement a new MIB module, three files are necessary, and these
Packit Service b38f0b
will be considered in turn. Note that, by the very nature of the task, this
Packit Service b38f0b
document cannot cover the details of precisely how to obtain the necessary
Packit Service b38f0b
information from the operating system or application. Instead, it describes
Packit Service b38f0b
the code framework that is needed, freeing the implementer from needing to
Packit Service b38f0b
understand the detailed internals of the agent, and allowing them to
Packit Service b38f0b
concentrate on the particular problem in hand.
Packit Service b38f0b
Packit Service b38f0b
It may prove useful to examine some of the existing module implementations
Packit Service b38f0b
and examples in the light of this description, and suitable examples will be
Packit Service b38f0b
referred to at the appropriate points. However, it should be remembered that
Packit Service b38f0b
the UCD agent seeks to support a wide variety of systems, often with
Packit Service b38f0b
dramatically differing implementations and interfaces, and this is reflected
Packit Service b38f0b
in the complexity of the code. Also, the agent has developed gradually over
Packit Service b38f0b
the years, and there is often some measure of duplication or redundancy as a
Packit Service b38f0b
result.
Packit Service b38f0b
As the FAQ states, the official slogan of the UCD-SNMP developers is
Packit Service b38f0b
Packit Service b38f0b
     The current implementation is non-obvious and may need to be
Packit Service b38f0b
     improved.
Packit Service b38f0b
Packit Service b38f0b
This document describes the ideal, straightforward cases - real life is
Packit Service b38f0b
rarely so simple, and the example modules may prove easier to follow at a
Packit Service b38f0b
first reading.
Packit Service b38f0b
It is also advisable to have a compiled and installed implementation
Packit Service b38f0b
available before starting to extend the agent. This will make debugging and
Packit Service b38f0b
testing the agent much easier.
Packit Service b38f0b
Packit Service b38f0b
A note regarding terminology - the word "module" is widely used throughout
Packit Service b38f0b
this document, with a number of different meanings.
Packit Service b38f0b
Packit Service b38f0b
   * support for a new MIB,
Packit Service b38f0b
     i.e. the whole of the functionality that is required. This is usually
Packit Service b38f0b
     termed a MIB module;
Packit Service b38f0b
   * a self-contained subset of this, implemented as a single unit.
Packit Service b38f0b
     This is usually termed an implementation module (or simply "a module");
Packit Service b38f0b
   * the combination of such subsets, usually termed a module group.
Packit Service b38f0b
Packit Service b38f0b
Note that the first and third of these are often synonymous - the
Packit Service b38f0b
difference being that a MIB module refers to the view from outside the
Packit Service b38f0b
agent, regarding this as a seamless whole and hiding the internal
Packit Service b38f0b
implementation. A "module group" is used where the internal structure is of
Packit Service b38f0b
more relevance, and recognises the fact that the functionality may be
Packit Service b38f0b
provided by a number of co-operating implementation modules.
Packit Service b38f0b
Packit Service b38f0b
Anyway, enough waffle - on with the details: The three files needed are
Packit Service b38f0b
Packit Service b38f0b
   * a MIB definition file;
Packit Service b38f0b
   * a C header file;
Packit Service b38f0b
   * a C implementation file.
Packit Service b38f0b
Packit Service b38f0b
The next part looks at the MIB definition file, and how this impacts on the
Packit Service b38f0b
agent implementation.
Packit Service b38f0b
Packit Service b38f0b
2. The MIB File
Packit Service b38f0b
===============
Packit Service b38f0b
Packit Service b38f0b
The first file needed is the MIB file that defines the MIB module to be
Packit Service b38f0b
implemented.
Packit Service b38f0b
Strictly speaking, this is not absolutely necessary, as the agent itself
Packit Service b38f0b
does not make any direct use of the MIB definitions. However, it is
Packit Service b38f0b
advisable to start with this for three reasons:
Packit Service b38f0b
Packit Service b38f0b
   * It provides an initial specification for what is to be implemented.
Packit Service b38f0b
     Code development is always easier if you know what you are meant to be
Packit Service b38f0b
     writing!
Packit Service b38f0b
   * If the new MIB file is read in with the other MIB files,
Packit Service b38f0b
     this lets the applications provided with the suite be used to test the
Packit Service b38f0b
     new agent, and report (hopefully meaningful) symbolic OIDs and values,
Packit Service b38f0b
     rather than the bare numeric forms.
Packit Service b38f0b
     (N.B: Remember to tell the application to load the new MIB. See the
Packit Service b38f0b
     relevant question in the FAQ)
Packit Service b38f0b
   * The tool mib2c uses this description to produce the two code files.
Packit Service b38f0b
     This is by far the easiest way to develop a new module.
Packit Service b38f0b
     (Note that the v5 version of mib2c is generally similar, but does
Packit Service b38f0b
     not correspond exactly to the v4 version described here)
Packit Service b38f0b
Packit Service b38f0b
If the intention is to implement a 'standard' MIB module, or a
Packit Service b38f0b
vendor-specific one, then the construction of this file will have already
Packit Service b38f0b
been done for you. If the intention is to provide a totally new, private
Packit Service b38f0b
module, then you will need to write this yourself, in addition to the agent
Packit Service b38f0b
code files.
Packit Service b38f0b
A description of MIB file format and syntax is beyond the scope of this
Packit Service b38f0b
document, and most books on SNMP management should provide some information
Packit Service b38f0b
on this subject. One book which concentrates on this is
Packit Service b38f0b
Packit Service b38f0b
     Understanding SNMP MIBS
Packit Service b38f0b
     (Perkins & McGinnis, Prentice Hall, ISBN 0-13-437708-7).
Packit Service b38f0b
Packit Service b38f0b
This blatant plug is wholly unrelated to the fact that David Perkins is an
Packit Service b38f0b
active member of the development group, and is regarded as our resident
Packit Service b38f0b
"protocol guru and policeman". (In fact, this book concentrates on MIB
Packit Service b38f0b
files in rather more detail than is appropriate in more general SNMP works).
Packit Service b38f0b
Information on other books covering SNMP and Network Management more generally
Packit Service b38f0b
is available on the SimpleWeb site (among other places).
Packit Service b38f0b
See the FAQ for more details.
Packit Service b38f0b
Packit Service b38f0b
Assigned OID numbers
Packit Service b38f0b
--------------------
Packit Service b38f0b
Packit Service b38f0b
One word of advice - even if you are developing a totally private MIB
Packit Service b38f0b
module, you will still need to position this somewhere within the overall
Packit Service b38f0b
MIB tree. Please do NOT simply choose a location "at random". Any such is
Packit Service b38f0b
likely to have either been assigned to some other organisation, or may be so
Packit Service b38f0b
assigned some time in the future. However much you may regard your project
Packit Service b38f0b
as a totally internal affair, such projects have a tendency to exceed their
Packit Service b38f0b
expected scope, both in terms of lifetime and distribution (not to mention
Packit Service b38f0b
the potential OID clash if you subsequently need to use elements from the
Packit Service b38f0b
legitimate owner's tree).
Packit Service b38f0b
It is simple and cheap (i.e. free!) to obtain your own official segment of
Packit Service b38f0b
the MIB tree (see http://www.iana.org for an application form), and having
Packit Service b38f0b
done so, you then have complete global authority over it. If you have
Packit Service b38f0b
problems with this, it's worth contacting the development team (email:
Packit Service b38f0b
net-snmp-coders@lists.sourceforge.net) for advice. Please do think to the
Packit Service b38f0b
future, and be a good Net citizen by using a legitimately assigned OID as
Packit Service b38f0b
the root of your new MIB.
Packit Service b38f0b
Packit Service b38f0b
MIB division
Packit Service b38f0b
------------
Packit Service b38f0b
Packit Service b38f0b
The next point to consider, whether writing by hand or using mib2c,
Packit Service b38f0b
implementing an existing MIB, or writing a new one, is whether and how to
Packit Service b38f0b
divide up the MIB tree. This is a purely internal implementation decision,
Packit Service b38f0b
and will not be visible to management applications querying the agent. A
Packit Service b38f0b
sensible choice of partitioning will result in a simpler, clearer
Packit Service b38f0b
implementation, which should ease both the initial development and
Packit Service b38f0b
subsequent maintenance of the module.
Packit Service b38f0b
Unfortunately, this choice is one of the module-specific decisions, so must
Packit Service b38f0b
be made on a case-by-case basis. For a simple, self-contained module, it may
Packit Service b38f0b
well be reasonable to implement the module as a single block (examples
Packit Service b38f0b
include the SNMP statistics subtree RFC 1907 or the TCP subtree RFC 2011).
Packit Service b38f0b
More complex and diverse modules (such as the Host Resources MIB - RFC 1514)
Packit Service b38f0b
are more naturally considered as a number of individual sub-modules.
Packit Service b38f0b
Some guidelines to bear in mind when deciding on this division:
Packit Service b38f0b
Packit Service b38f0b
   * A MIB sub-tree consisting purely of scalar objects with a common
Packit Service b38f0b
     OID prefix would normally be handled in a single implementation module;
Packit Service b38f0b
   * Separate scalar subtrees would normally be in different implementation
Packit Service b38f0b
     modules;
Packit Service b38f0b
   * A table can either be handled within the same implementation module
Packit Service b38f0b
     as related scalar objects in the same subtree, or in a separate
Packit Service b38f0b
     implementation module;
Packit Service b38f0b
   * Variables that rely on the same underlying data structure to retrieve
Packit Service b38f0b
     their values, should probably be in the same implementation module (and
Packit Service b38f0b
     conversely, (though less so) those that don't, shouldn't).
Packit Service b38f0b
Packit Service b38f0b
As an initial rule of thumb, a good initial division is likely to be
Packit Service b38f0b
obtained by treating each table and each scalar sub-tree separately. This
Packit Service b38f0b
can be seen in the current agent, where most of the MIB-II modules (RFC
Packit Service b38f0b
1213) are implemented in separate files (see the files under mibgroup/mibII).
Packit Service b38f0b
Note that many of these combine scalar and table handling in the same file,
Packit Service b38f0b
though they are implemented using separate routines.
Packit Service b38f0b
  This is also the approach used by mib2c, which constructs a single pair of
Packit Service b38f0b
code files, but uses a separate routine for each table (and another for all
Packit Service b38f0b
the scalar variables).
Packit Service b38f0b
  Ultimately, the final consideration (concerning the underlying data) is
Packit Service b38f0b
the most important, and should guide the basic division. For example, the
Packit Service b38f0b
Host Resources Running Software and Running Software Performance modules,
Packit Service b38f0b
while separate in the MIB tree, use the same underlying kernel data and so
Packit Service b38f0b
are implemented together.
Packit Service b38f0b
Packit Service b38f0b
MIB name
Packit Service b38f0b
--------
Packit Service b38f0b
Packit Service b38f0b
The final requirement at this stage is to choose a name for each
Packit Service b38f0b
implementation module. This should be reasonably short, meaningful, unique
Packit Service b38f0b
and unlikely to clash with other (existing or future) modules. Mib2c uses
Packit Service b38f0b
the label of the root node of the MIB sub-tree as this name, and this is a
Packit Service b38f0b
reasonable choice in most cases.
Packit Service b38f0b
Recent changes to the agent code organisation have introduced the idea of
Packit Service b38f0b
module groups of related implementation modules. This is used, for example,
Packit Service b38f0b
to identify the constituent modules of a 'split' MIB (such as the Host
Packit Service b38f0b
Resources MIB), or those relating to a particular organisation (such as
Packit Service b38f0b
UCD).
Packit Service b38f0b
As with the division, this naming and grouping is a purely internal matter,
Packit Service b38f0b
and is really only visible when configuring and compiling the agent.
Packit Service b38f0b
Packit Service b38f0b
So much for the MIB file. The next part considers the C header file.
Packit Service b38f0b
Packit Service b38f0b
3. The C code header file
Packit Service b38f0b
=========================
Packit Service b38f0b
Packit Service b38f0b
If the MIB file is the definition of the module for external network
Packit Service b38f0b
management applications (where applications includes network management
Packit Service b38f0b
personnel!), then the header file has traditionally served effectively the
Packit Service b38f0b
same purpose for the agent itself.
Packit Service b38f0b
Recent changes to the recommended code structure has resulted in the header
Packit Service b38f0b
file becoming increasingly simpler. It now simply contains definitions of the
Packit Service b38f0b
publically visible routines, and can be generated completely by mib2c.
Packit Service b38f0b
Packit Service b38f0b
Function prototypes
Packit Service b38f0b
-------------------
Packit Service b38f0b
Packit Service b38f0b
For those interested in the details of this file (for example, if coding a
Packit Service b38f0b
module by hand), then the details of these definitions are as follows. Every
Packit Service b38f0b
header file will have the following two function prototype definitions
Packit Service b38f0b
Packit Service b38f0b
        extern void          init_example (void);
Packit Service b38f0b
        extern FindVarMethod var_example;
Packit Service b38f0b
Packit Service b38f0b
If the module includes any tables, or other collections of variables that
Packit Service b38f0b
are implemented in separate routines, then this second definition will be
Packit Service b38f0b
repeated for each of these.
Packit Service b38f0b
In addition, if any of the variables can be SET (and it is intended to
Packit Service b38f0b
implement them as such), there will be a function prototype definitions for
Packit Service b38f0b
each of these, of the form:
Packit Service b38f0b
Packit Service b38f0b
        extern WriteMethod write_varName;
Packit Service b38f0b
Packit Service b38f0b
These prototypes are in fact typedef'ed in <agent/snmp_vars.h>.
Packit Service b38f0b
Packit Service b38f0b
Module dependencies
Packit Service b38f0b
-------------------
Packit Service b38f0b
Packit Service b38f0b
This header file is also used to inform the compilation system of any
Packit Service b38f0b
dependancies between this module and any others. There is one utility module
Packit Service b38f0b
which is required by almost every module, and this is included using the
Packit Service b38f0b
directive
Packit Service b38f0b
Packit Service b38f0b
        config_require( util_funcs )
Packit Service b38f0b
Packit Service b38f0b
(which is produced automatically by mib2c). This same syntax can be used to
Packit Service b38f0b
trigger the inclusion of other related modules. An example of this can be
Packit Service b38f0b
seen in mibII/route_write.h which relies on the mibII/ip module, thus:
Packit Service b38f0b
Packit Service b38f0b
        config_require( mibII/ip )
Packit Service b38f0b
Packit Service b38f0b
One use of this directive is to define a module group, by supplying a header
Packit Service b38f0b
file consisting exclusively of such config_require directives.  It can then
Packit Service b38f0b
be included or excluded from the agent very simply. Examples of this can be
Packit Service b38f0b
seen in mibgroup/mibII.h or mibgroup/host.h, which list the consituent
Packit Service b38f0b
sub-modules of the MIB-II and Host Resources MIBs respectively.
Packit Service b38f0b
Packit Service b38f0b
MIB file information
Packit Service b38f0b
--------------------
Packit Service b38f0b
Packit Service b38f0b
Most of the information in this file is (understandably) aimed at the network
Packit Service b38f0b
management agent itself.  However, there is one common header file directive
Packit Service b38f0b
that is actually intended to affect the utility commands that are included
Packit Service b38f0b
within the full distribution:
Packit Service b38f0b
Packit Service b38f0b
	config_add_mib( HOST-RESOURCES-MIB )
Packit Service b38f0b
Packit Service b38f0b
  This is used to add the MIB file being implemented to the default list of
Packit Service b38f0b
MIBs loaded by such commands.  This means that querying the agent will return
Packit Service b38f0b
informative names and values, rather than the raw numeric forms that SNMP
Packit Service b38f0b
actually works with.  Of course, it is always possible for the utilities
Packit Service b38f0b
to specify that this MIB should be loaded anyway.  But specifying this file
Packit Service b38f0b
within the module header file is a useful hint that a particular MIB should
Packit Service b38f0b
be loaded, without needing to ask for it explicitly.
Packit Service b38f0b
  Note that this will only affect the binaries compiled as part of the same
Packit Service b38f0b
configuration run.  It will have no effect on pre-installed binaries, or
Packit Service b38f0b
those compiled following a different configuration specification.
Packit Service b38f0b
Packit Service b38f0b
Magic Numbers
Packit Service b38f0b
-------------
Packit Service b38f0b
Packit Service b38f0b
The other common element within the header file defines a set of "magic
Packit Service b38f0b
numbers" - one for each object within the implementation module. In fact,
Packit Service b38f0b
this can equally well appear within the main code file, as part of the
Packit Service b38f0b
variable structure (which will be described in the next part).
Packit Service b38f0b
  This is the technique used by mib2c, but most handcrafted modules have
Packit Service b38f0b
tended to define these as part of the header file, probably for clarity.
Packit Service b38f0b
Packit Service b38f0b
  The only necessity is that the names and values are distinct (or more
Packit Service b38f0b
precisely, the values are distinct within a single variable handling routine).
Packit Service b38f0b
In practise, they tend to be defined using integers incrementing from 1,
Packit Service b38f0b
or as the same as the final sub-identifier of the corresponding MIB object
Packit Service b38f0b
(or indeed both, as these are frequently themselves successive integers).
Packit Service b38f0b
  This is not mandatory, and a counter-example can be seen in the
Packit Service b38f0b
example module, where two of the object form a sub-tree, and the corresponding
Packit Service b38f0b
magic numbers are based on the final *two* sub-identifiers (to ensure that
Packit Service b38f0b
the values are unique).  But this construction is definitely unusual, and
Packit Service b38f0b
the majority of modules simply use successive integers.
Packit Service b38f0b
Packit Service b38f0b
Header file protection
Packit Service b38f0b
----------------------
Packit Service b38f0b
Packit Service b38f0b
Normally, the only other contents of the header file will be the
Packit Service b38f0b
#ifndef/#define/#endif statements surrounding the whole file. This is used
Packit Service b38f0b
to ensure that the header file is only included once by any source code file
Packit Service b38f0b
(or more accurately, that there is no effect if it is inadvertantly included
Packit Service b38f0b
a second time).
Packit Service b38f0b
Again, as with the rest of the header file, this is generated automatically
Packit Service b38f0b
by mib2c.
Packit Service b38f0b
Packit Service b38f0b
Having finished all the preparatory work (or let mib2c deal with it), the
Packit Service b38f0b
next part starts to look at the code file that actually implements the
Packit Service b38f0b
module.
Packit Service b38f0b
Packit Service b38f0b
4. Core structure of the implementation code
Packit Service b38f0b
============================================
Packit Service b38f0b
Packit Service b38f0b
The core work of implementing the module is done in the C code file. As
Packit Service b38f0b
indicated earlier, much of the detail of this will be dependent on the
Packit Service b38f0b
particular module being implemented, and this can only be described by the
Packit Service b38f0b
individual programmer concerned.
Packit Service b38f0b
However, there is a fairly clearly defined framework that the implementation
Packit Service b38f0b
will need to follow, though this varies slightly depending on the style of
Packit Service b38f0b
the module being implemented (in particular whether it forms a table or a
Packit Service b38f0b
series of individual values). The differences will be covered in the
Packit Service b38f0b
following pages, but we first need to consider the overall shape of the
Packit Service b38f0b
framework, and the elements that are common to all styles. These are
Packit Service b38f0b
essentially the compulsory routines, the common header definitions, and
Packit Service b38f0b
assorted initialisation code.
Packit Service b38f0b
As with the header file, most of this will be generated automatically by
Packit Service b38f0b
mib2c.
Packit Service b38f0b
Packit Service b38f0b
Standard includes
Packit Service b38f0b
-----------------
Packit Service b38f0b
Packit Service b38f0b
Certain header files are either compulsory, or required so frequently that
Packit Service b38f0b
they should be included as a matter of course. These are as follows:
Packit Service b38f0b
Packit Service b38f0b
  #include <config.h>                   // local SNMP configuration details
Packit Service b38f0b
  #include "mib_module_config.h"        // list of which modules are supported
Packit Service b38f0b
  #if HAVE_STDLIB_H
Packit Service b38f0b
  #include <stdlib.h>
Packit Service b38f0b
  #endif
Packit Service b38f0b
  #if HAVE_STRING_H
Packit Service b38f0b
  #include <string.h>
Packit Service b38f0b
  #else
Packit Service b38f0b
  #include <strings.h>
Packit Service b38f0b
  #endif
Packit Service b38f0b
Packit Service b38f0b
  #include <sys/types.h>
Packit Service b38f0b
Packit Service b38f0b
All of these will usually be the first files to be included.
Packit Service b38f0b
Packit Service b38f0b
  #include "mibincl.h"                  // Standard set of SNMP includes
Packit Service b38f0b
  #include "util_funcs.h"               // utility function declarations
Packit Service b38f0b
  #include "read_config.h"              // if the module uses run-time
Packit Service b38f0b
                                        //      configuration controls
Packit Service b38f0b
  #include "auto_nlist.h"               // structures for a BSD-based
Packit Service b38f0b
                                        //      kernel using nlist
Packit Service b38f0b
  #include "system.h"
Packit Service b38f0b
Packit Service b38f0b
  #include "name.h"                     // the module-specific header
Packit Service b38f0b
Packit Service b38f0b
These conventionally come at the end of the list of includes. In between
Packit Service b38f0b
will come all the standard system-provided header files required for the
Packit Service b38f0b
library functions used in the file.
Packit Service b38f0b
Packit Service b38f0b
Module definition
Packit Service b38f0b
-----------------
Packit Service b38f0b
Packit Service b38f0b
Much of the code defining the contents of the MIB has traditionally been
Packit Service b38f0b
held in the header file. However, much of this has slowly migrated to the
Packit Service b38f0b
code file, and this is now the recommended location for it (as typified by
Packit Service b38f0b
the output of mib2c).
Packit Service b38f0b
  The main element of this is a variable structure specifying the details of 
Packit Service b38f0b
the objects implemented.  This takes the form of an unconstrained array of
Packit Service b38f0b
type struct variableN (where N is the length of the longest suffix in the
Packit Service b38f0b
table). Thus
Packit Service b38f0b
Packit Service b38f0b
                struct variable2 example_variables[] = {
Packit Service b38f0b
			<individual entries go here>
Packit Service b38f0b
                };
Packit Service b38f0b
Packit Service b38f0b
Each entry corresponds to one object in the MIB tree (or one column in the
Packit Service b38f0b
case of table entries), and these should be listed in increasing OID order.
Packit Service b38f0b
A single entry consists of six fields:
Packit Service b38f0b
Packit Service b38f0b
   * a magic number (the #defined integer constant described above)
Packit Service b38f0b
   * a type indicator (from the values listed in <snmplib/snmp_impl.h>)
Packit Service b38f0b
   * an access indicator (essentially NETSNMP_OLDAPI_RWRITE or
Packit Service b38f0b
     NETSNMP_OLDAPI_RONLY)
Packit Service b38f0b
   * the name of the routine used to handle this entry
Packit Service b38f0b
   * the length of the OID suffix used, and
Packit Service b38f0b
   * an array of integers specifying this suffix (more on this in a moment)
Packit Service b38f0b
Packit Service b38f0b
Thus a typical variable entry would look like:
Packit Service b38f0b
Packit Service b38f0b
        { EXAMPLESTRING, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
Packit Service b38f0b
          var_example, 1, {1}}
Packit Service b38f0b
Packit Service b38f0b
If the magic numbers have not been defined in the header file, then they
Packit Service b38f0b
should be defined here, usually comming immediately before the corresponding
Packit Service b38f0b
variable entry.  This is the technique used by mib2c.
Packit Service b38f0b
Packit Service b38f0b
Note that in practise, only certain sizes of the structure variableN
Packit Service b38f0b
are defined (listed in <agent/var_struct.h>), being sufficient to meet the
Packit Service b38f0b
common requirements. If your particular module needs a non-supported value,
Packit Service b38f0b
the easiest thing is simply to use the next largest value that is supported.
Packit Service b38f0b
Packit Service b38f0b
The module also needs to declare the location within the MIB tree where
Packit Service b38f0b
it should be registered. This is done using a declaration of the form
Packit Service b38f0b
Packit Service b38f0b
        oid example_variables_oid[] = { 1,3,6,1,4,1,2021,254 }
Packit Service b38f0b
Packit Service b38f0b
where the contents of the array give the object identifier of the root of
Packit Service b38f0b
the module.
Packit Service b38f0b
Packit Service b38f0b
Module initialisation
Packit Service b38f0b
---------------------
Packit Service b38f0b
Packit Service b38f0b
Many modules require some form of initialisation before they can start
Packit Service b38f0b
providing the necessary information. This is done by providing a routine
Packit Service b38f0b
called init_{name} (where {name} is the name of the module).
Packit Service b38f0b
This routine is theoretically optional, but in practise is required to
Packit Service b38f0b
register this module with the main agent at the very least. This specifies
Packit Service b38f0b
the list of variables being implemented (from the variableN structure)
Packit Service b38f0b
and declare where these fit into the overall MIB tree.
Packit Service b38f0b
Packit Service b38f0b
This is done by using the REGISTER_MIB macro, as follows:
Packit Service b38f0b
Packit Service b38f0b
        REGISTER_MIB( "example",  example_variables, variable2,
Packit Service b38f0b
                        example_variables_oid );
Packit Service b38f0b
Packit Service b38f0b
where "example" is used for identification purposed (and is usually the name
Packit Service b38f0b
being used for the module), example_variables is the structure defining the
Packit Service b38f0b
variables being implemented, variable2 is the type used for this structure,
Packit Service b38f0b
and example_variables_oid is the location of the root.
Packit Service b38f0b
Packit Service b38f0b
In fact, this macro is simply a wrapper round the routine register_mib(),
Packit Service b38f0b
but the details of this can safely be ignored, unless more control over the
Packit Service b38f0b
registration is required.
Packit Service b38f0b
Packit Service b38f0b
One common requirement, particularly on older operating systems or for the
Packit Service b38f0b
more obscure areas of the system, is to be able to read data directly from
Packit Service b38f0b
kernel memory. The preparation for this is typically done here by one or
Packit Service b38f0b
more statements of the form
Packit Service b38f0b
Packit Service b38f0b
        #ifdef {NAME}_SYMBOL
Packit Service b38f0b
        auto_nlist( {NAME}_SYMBOL, 0, 0);
Packit Service b38f0b
        #endif
Packit Service b38f0b
Packit Service b38f0b
where {NAME}_SYMBOL is defined as part of the system-specific configuration,
Packit Service b38f0b
to be the name of the appropriate kernel variable or data structure. (The
Packit Service b38f0b
two 0 values are because the kernel information is simply being primed at
Packit Service b38f0b
this point - this call will be reused later when the actual values are
Packit Service b38f0b
required). Note that this is probably the first thing described so far which
Packit Service b38f0b
isn't provided by mib2c!
Packit Service b38f0b
Packit Service b38f0b
Other possibilities for initialisation may include registering config file
Packit Service b38f0b
directive handlers (which are documented in the read_config(5) man page), and
Packit Service b38f0b
registering the MIB module (either in whole or in part) in the sysOR table.
Packit Service b38f0b
The first of these is covered in the example module, and the second in many
Packit Service b38f0b
of the other modules within the main UCD distribution.
Packit Service b38f0b
Packit Service b38f0b
Variable handling
Packit Service b38f0b
-----------------
Packit Service b38f0b
Packit Service b38f0b
The other obligatory routine is that which actually handles a request for a
Packit Service b38f0b
particular variable instance. This is the routine that appeared in the
Packit Service b38f0b
variableN structure, so while the name is not fixed, it should be the same
Packit Service b38f0b
as was used there.
Packit Service b38f0b
This routine has six parameters, which will be described in turn.
Packit Service b38f0b
Packit Service b38f0b
Four of these parameters are used for passing in information about the
Packit Service b38f0b
request, these being:
Packit Service b38f0b
Packit Service b38f0b
        struct variable *vp;
Packit Service b38f0b
                // The entry in the variableN array from the
Packit Service b38f0b
                //   header file, for the object under consideration.
Packit Service b38f0b
                // Note that the name field of this structure has been
Packit Service b38f0b
                //   completed into a fully qualified OID, by prepending
Packit Service b38f0b
                //   the prefix common to the whole array.
Packit Service b38f0b
        oid *name;      // The OID from the request
Packit Service b38f0b
        int *length;    // The length of this OID
Packit Service b38f0b
        int exact;      // A flag to indicate whether this is an exact
Packit Service b38f0b
                        // request (GET/SET) or an 'inexact' one (GETNEXT)
Packit Service b38f0b
Packit Service b38f0b
Four of the parameters are used to return information about the answer.
Packit Service b38f0b
The function also returns a pointer to the actual data for the variable
Packit Service b38f0b
requested (or NULL if this data is not available for any reason).
Packit Service b38f0b
The other result parameters are:
Packit Service b38f0b
Packit Service b38f0b
        oid *name;      // The OID being returned
Packit Service b38f0b
        int *length;    // The length of this OID
Packit Service b38f0b
        int *var_len;   // The length of the answer being returned
Packit Service b38f0b
        WriteMethod **write_method;
Packit Service b38f0b
                        // A pointer to the SET function for this variable
Packit Service b38f0b
Packit Service b38f0b
Note that two of the parameters (name and length) serve a dual purpose,
Packit Service b38f0b
being used for both input and output.
Packit Service b38f0b
Packit Service b38f0b
The first thing that this routine needs to do is to validate the request, to
Packit Service b38f0b
ensure that it does indeed lie in the range implemented by this particular
Packit Service b38f0b
module. This is done in slightly different ways, depending on the style of
Packit Service b38f0b
the module, so this will be discussed in more detail later.
Packit Service b38f0b
  At the same time, it is common to retrieve some of the information needed
Packit Service b38f0b
for answering the query.
Packit Service b38f0b
Packit Service b38f0b
Then the routine uses the Magic Number field from the vp parameter to determine
Packit Service b38f0b
which of the possible variables being implemented is being requested. This is
Packit Service b38f0b
done using a switch statement, which should have as many cases as there are
Packit Service b38f0b
entries in the variableN array (or more precisely, as many as specify this
Packit Service b38f0b
routine as their handler), plus an additional default case to handle an
Packit Service b38f0b
erroneous call.
Packit Service b38f0b
Each branch of the switch statement needs to ensure that the return
Packit Service b38f0b
parameters are filled in correctly, set up a (static) return variable with
Packit Service b38f0b
the correct data, and then return a pointer to this value. These can be done
Packit Service b38f0b
separately for each branch, or once at the start, being overridden in
Packit Service b38f0b
particular branches if necessary.
Packit Service b38f0b
Packit Service b38f0b
In fact, the default validation routines make the assumption that the
Packit Service b38f0b
variable is both read-only, and of integer type (which includes the COUNTER
Packit Service b38f0b
and GAUGE types among others), and set the return paramaters write_method and
Packit Service b38f0b
var_len appropriately. These settings can then be corrected for those cases
Packit Service b38f0b
when either or both of these assumptions are wrong. Examples of this can be
Packit Service b38f0b
seen in the example module.
Packit Service b38f0b
EXAMPLEINTEGER is writeable, so this branch sets the write_method parameter,
Packit Service b38f0b
and EXAMPLEOBJECTID is not an integer, so this branch sets the var_len
Packit Service b38f0b
parameter.  In the case of EXAMPLESTRING, both assumptions are wrong, so this
Packit Service b38f0b
branch needs to set both these parameters explicitly.
Packit Service b38f0b
Packit Service b38f0b
Note that because the routine returns a pointer to a static result, a
Packit Service b38f0b
suitable variable must be declared somewhere for this. Two global variables
Packit Service b38f0b
are provided for this purpose - long_return (for integer results) and
Packit Service b38f0b
return_buf (for other types). This latter is a generic array (of type
Packit Service b38f0b
u_char) that can contain up to 256 bytes of data. Alternatively, static
Packit Service b38f0b
variables can be declared, either within the code file, or local to this
Packit Service b38f0b
particular variable routine. This last is the approach adopted by mib2c,
Packit Service b38f0b
which defines four such local variables, (long_ret, string, objid and c64).
Packit Service b38f0b
Packit Service b38f0b
Mib2c requirements
Packit Service b38f0b
------------------
Packit Service b38f0b
Packit Service b38f0b
Most of the code described here is generated by mib2c. The main exceptions
Packit Service b38f0b
(which therefore need to be provided by the programmer) are
Packit Service b38f0b
Packit Service b38f0b
   * Any initialisation, other than the basic registration
Packit Service b38f0b
     (including kernel data initialisation, config file handling, or sysOR
Packit Service b38f0b
     registration).
Packit Service b38f0b
   * Retrieving the necessary data, and setting the appropriate return
Packit Service b38f0b
     value correctly.
Packit Service b38f0b
   * The var_len (and possibly write_method) return parameters for variable
Packit Service b38f0b
     types that are not recognised by mib2c
Packit Service b38f0b
   * The contents of any write routines (see later).
Packit Service b38f0b
Packit Service b38f0b
Everything else should be useable as generated.
Packit Service b38f0b
Packit Service b38f0b
This concludes the preliminary walk-through of the general structure of the
Packit Service b38f0b
C implementation. To fill in the details, we will need to consider the
Packit Service b38f0b
various styles of module separately. The next part will look at scalar (i.e.
Packit Service b38f0b
non-table based) modules.
Packit Service b38f0b
Packit Service b38f0b
5. Non-table-based modules
Packit Service b38f0b
==========================
Packit Service b38f0b
Packit Service b38f0b
Having looked at the general structure of a module implementation, it's now
Packit Service b38f0b
time to look at this in more detail. We'll start with the simplest style of
Packit Service b38f0b
module - a collection of independent variables. This could easily be
Packit Service b38f0b
implemented as a series of completely separate modules - the main reason for
Packit Service b38f0b
combining them is to avoid the proliferation of multiple versions of very
Packit Service b38f0b
similar code.
Packit Service b38f0b
Packit Service b38f0b
Recall that the variable handling routine needs to cover two distinct
Packit Service b38f0b
purposes - validation of the request, and provision of the answer. In this
Packit Service b38f0b
style of module, these are handled separately. Once again, mib2c does much
Packit Service b38f0b
of the donkey work, generating the whole of the request validation code (so
Packit Service b38f0b
the description of this section can be skipped if desired), and even
Packit Service b38f0b
providing a skeleton for returning the data. This latter still requires some
Packit Service b38f0b
input from the programmer, to actually return the correct results (rather
Packit Service b38f0b
than dummy values).
Packit Service b38f0b
Packit Service b38f0b
Request Validation
Packit Service b38f0b
------------------
Packit Service b38f0b
Packit Service b38f0b
This is done using a standard utility function header_generic. The
Packit Service b38f0b
parameters for this are exactly the same as for the main routine, and are
Packit Service b38f0b
simply passed through directly. It returns an integer result, as a flag to
Packit Service b38f0b
indicate whether the validation succeeded or not.
Packit Service b38f0b
If the validation fails, then the main routine should return immediately,
Packit Service b38f0b
leaving the parameters untouched, and indicate the failure by returning a
Packit Service b38f0b
NULL value. Thus the initial code fragment of a scalar-variable style
Packit Service b38f0b
implementation will typically look like:
Packit Service b38f0b
Packit Service b38f0b
    u_char  *
Packit Service b38f0b
    var_system(vp, name, length, exact, var_len, write_method)
Packit Service b38f0b
    {
Packit Service b38f0b
        if (header_generic(vp, name, length, exact, var_len, write_method)
Packit Service b38f0b
                                                == MATCH_FAILED )
Packit Service b38f0b
            return NULL;
Packit Service b38f0b
Packit Service b38f0b
        [ etc, etc, etc ]
Packit Service b38f0b
    }
Packit Service b38f0b
Packit Service b38f0b
Although the utility function can be used as a "black box", it's worth
Packit Service b38f0b
looking more closely at exactly what it does (since the table-handling
Packit Service b38f0b
modules will need to do something fairly similar). It has two (or possibly
Packit Service b38f0b
three) separate functions:
Packit Service b38f0b
Packit Service b38f0b
   * checking that the request is valid,
Packit Service b38f0b
   * setting up the OID for the result,
Packit Service b38f0b
   * and (optionally) setting up default values for the other return
Packit Service b38f0b
     parameters.
Packit Service b38f0b
Packit Service b38f0b
In order to actually validate the request, the header routine first needs to
Packit Service b38f0b
construct the OID under consideration, in order to compare it with that
Packit Service b38f0b
originally asked for. The driving code has already combined the OID prefix
Packit Service b38f0b
(constant throughout the module) with the entry-specific suffix, before
Packit Service b38f0b
calling the main variable handler. This is available via the name field of
Packit Service b38f0b
the parameter vp. For a scalar variable, completing the OID is therefore
Packit Service b38f0b
simply a matter of appending the instance identifier 0 to this. The full OID
Packit Service b38f0b
is built up in a local oid array newname defined for this purpose.
Packit Service b38f0b
This gives the following code fragment:
Packit Service b38f0b
Packit Service b38f0b
    int
Packit Service b38f0b
    header_generic(vp, name, length, exact, var_len, write_method)
Packit Service b38f0b
    {
Packit Service b38f0b
        oid newname[MAX_OID_LEN];
Packit Service b38f0b
Packit Service b38f0b
        memcpy((char *)newname, (char *)vp->name,
Packit Service b38f0b
                        (int)vp->namelen * sizeof(oid));
Packit Service b38f0b
        newname[ vp->namelen ] = 0;
Packit Service b38f0b
Packit Service b38f0b
                :
Packit Service b38f0b
    }
Packit Service b38f0b
Packit Service b38f0b
Having formed the OID, this can then be compared against the variable
Packit Service b38f0b
specified in the original request, which is available as the name parameter.
Packit Service b38f0b
This comparison is done using the snmp_oid_compare function, which takes the
Packit Service b38f0b
two OIDs (together with their respective lengths), and returns -1, 0 or 1
Packit Service b38f0b
depending on whether the first OID precedes, matches or follows the second.
Packit Service b38f0b
Packit Service b38f0b
In the case of an 'exact' match (i.e. a GET/SET/etc), then the request is
Packit Service b38f0b
only valid if the two OIDs are identical (snmp_oid_compare returns 0). In
Packit Service b38f0b
the case of a GETNEXT (or GETBULK) request, it's valid if the OID being
Packit Service b38f0b
considered comes after that of the original request (snmp_oid_compare
Packit Service b38f0b
returns -1).
Packit Service b38f0b
Packit Service b38f0b
This gives the code fragment
Packit Service b38f0b
Packit Service b38f0b
        result = snmp_oid_compare(name, *length, newname, (int)vp->namelen + 1);
Packit Service b38f0b
                        // +1 because of the extra instance sub-identifier
Packit Service b38f0b
        if ((exact && (result != 0))            // GET match fails
Packit Service b38f0b
                || (!exact && (result >= 0)))   // GETNEXT match fails
Packit Service b38f0b
            return(MATCH_FAILED);
Packit Service b38f0b
Packit Service b38f0b
Note that in this case, we're only interested in the single variable
Packit Service b38f0b
indicated by the vp parameter. The fact that this module may well implement
Packit Service b38f0b
other variables as well is ignored. The 'lexically next' requirement of the
Packit Service b38f0b
GETNEXT request is handled by working through the variable entries in order
Packit Service b38f0b
until one matches. And yes, this is not the most efficient implementation
Packit Service b38f0b
possible!
Packit Service b38f0b
Note that in releases prior to 3.6, the snmp_oid_compare function was called
Packit Service b38f0b
simply compare.
Packit Service b38f0b
Packit Service b38f0b
Finally, having determined that the request is valid, this routine must
Packit Service b38f0b
update the name and length parameters to return the OID being processed. It
Packit Service b38f0b
also sets default values for the other two return parameters.
Packit Service b38f0b
Packit Service b38f0b
        memcpy( (char *)name,(char *)newname,
Packit Service b38f0b
                ((int)vp->namelen + 1) * sizeof(oid));
Packit Service b38f0b
        *length = vp->namelen + 1;
Packit Service b38f0b
        *write_method = 0;              // Non-writeable
Packit Service b38f0b
        *var_len = sizeof(long);        // default to integer results
Packit Service b38f0b
        return(MATCH_SUCCEEDED);
Packit Service b38f0b
Packit Service b38f0b
These three code fragments combine to form the full header_generic code
Packit Service b38f0b
which can be seen in the file util_funcs.c
Packit Service b38f0b
Packit Service b38f0b
Note: This validation used to be done using a separate function for each
Packit Service b38f0b
module (conventionally called header_{name}), and many modules may still be
Packit Service b38f0b
coded in this style. The code for these are to all intents and purposes
Packit Service b38f0b
identical to the header_generic routine described above.
Packit Service b38f0b
Packit Service b38f0b
Data Retrieval
Packit Service b38f0b
--------------
Packit Service b38f0b
Packit Service b38f0b
The other main job of the request handling routine is to retrieve any
Packit Service b38f0b
necessary data, and return the appropriate answer to the original request.
Packit Service b38f0b
This must be done even if mib2c is being used to generate the framework of
Packit Service b38f0b
the implementation. As has been indicated earlier, the different cases are
Packit Service b38f0b
handled using a switch statement, with the Magic Number field of the vp
Packit Service b38f0b
parameter being used to distinguish between them.
Packit Service b38f0b
The data necessary for answering the request can be retrieved for each
Packit Service b38f0b
variable individually in the relevant case statement (as is the case with
Packit Service b38f0b
the system group), or using a common block of data before processing the
Packit Service b38f0b
switch (as is done for the ICMP group, among others).
Packit Service b38f0b
Packit Service b38f0b
With many of the modules implemented so far, this data is read from a kernel
Packit Service b38f0b
structure. This can be done using the auto_nlist routine already mentioned,
Packit Service b38f0b
providing a variable in which to store the results and an indication of its
Packit Service b38f0b
size (see the !HAVE_SYS_TCPIPSTATS_H case of the ICMP group for an example).
Packit Service b38f0b
Alternatively, there may be ioctl calls on suitable devices, specific system
Packit Service b38f0b
calls, or special files that can be read to provide the necessary
Packit Service b38f0b
information.
Packit Service b38f0b
Packit Service b38f0b
If the available data provides the requested value immediately, then the
Packit Service b38f0b
individual branch becomes a simple assignment to the appropriate static
Packit Service b38f0b
return variable - either one of the global static variables (e.g. long_return)
Packit Service b38f0b
or the local equivalents (such as generated by mib2c).
Packit Service b38f0b
Otherwise, the requested value may need to be calculated by combining two or
Packit Service b38f0b
more items of data (e.g. IPINHDRERRORS in mibII/ip.c) or by applying a
Packit Service b38f0b
mapping or other calculation involving available information (e.g.
Packit Service b38f0b
IPFORWARDING from the same group).
Packit Service b38f0b
Packit Service b38f0b
In each of these cases, the routine should return a pointer to the result
Packit Service b38f0b
value, casting this to the pseudo-generic (u_char *)
Packit Service b38f0b
Packit Service b38f0b
So much for the scalar case. The next part looks at how to handle simple
Packit Service b38f0b
tables.
Packit Service b38f0b
Packit Service b38f0b
6. Simple tables
Packit Service b38f0b
================
Packit Service b38f0b
Packit Service b38f0b
Having considered the simplest style of module implementation, we now turn
Packit Service b38f0b
our attention to the next style - a simple table. The tabular nature of
Packit Service b38f0b
these is immediately apparent from the MIB definition file, but the
Packit Service b38f0b
qualifier "simple" deserves a word of explanation.
Packit Service b38f0b
A simple table, in this context, has four characteristics:
Packit Service b38f0b
Packit Service b38f0b
  1. It is indexed by a single integer value;
Packit Service b38f0b
  2. Such indices run from 1 to a determinable maximum;
Packit Service b38f0b
  3. All indices within this range are valid;
Packit Service b38f0b
  4. The data for a particular index can be retrieved directly
Packit Service b38f0b
     (e.g. by indexing into an underlying data structure).
Packit Service b38f0b
Packit Service b38f0b
If any of the conditions are not met, then the table is not a pure simple
Packit Service b38f0b
one, and the techniques described here are not applicable. The next section
Packit Service b38f0b
of this guide will cover the more general case. (In fact, it may be possible
Packit Service b38f0b
to use the bulk of the techniques covered here, though special handling will
Packit Service b38f0b
be needed to cope with the invalid assumption or assumptions). Note that
Packit Service b38f0b
mib2c assumes that all tables are simple.
Packit Service b38f0b
Packit Service b38f0b
As with the scalar case, the variable routine needs to provide two basic
Packit Service b38f0b
functions - request validation and data retrieval.
Packit Service b38f0b
Packit Service b38f0b
Validation
Packit Service b38f0b
----------
Packit Service b38f0b
Packit Service b38f0b
This is provided by the shared utility routine header_simple_table. As with
Packit Service b38f0b
the scalar header routine, this takes the same parameters as the main
Packit Service b38f0b
variable routine, with one addition - the maximum valid index. Mib2c
Packit Service b38f0b
generates a dummy token for this, which must be replaced by the appropriate
Packit Service b38f0b
value.
Packit Service b38f0b
As with the header routine, it also returns an indication of whether the
Packit Service b38f0b
request was valid, as well as setting up the return parameters with the
Packit Service b38f0b
matching OID information, and defaults for var_len and write_method.
Packit Service b38f0b
Note that in releases prior to 3.6, this job was performed by the routine
Packit Service b38f0b
checkmib. However, the return values of this were the reverse of those for
Packit Service b38f0b
generic_header and header_simple_table. A version of checkmib is still
Packit Service b38f0b
available for compatability purposes, but you are encouraged to use
Packit Service b38f0b
header_simple_table instead.
Packit Service b38f0b
Packit Service b38f0b
The basic code fragment (see ucd-snmp/disk.c) is therefore of the form:
Packit Service b38f0b
Packit Service b38f0b
        unsigned char *
Packit Service b38f0b
        var_extensible_disk(vp, name, length, exact, var_len, write_method)
Packit Service b38f0b
        {
Packit Service b38f0b
            if (header_simple_table(vp,name,length,exact,var_len,write_method,numdisks)
Packit Service b38f0b
                                        == MATCH_FAILED)
Packit Service b38f0b
                return(NULL);
Packit Service b38f0b
Packit Service b38f0b
            [ etc, etc, etc ]
Packit Service b38f0b
Packit Service b38f0b
        }
Packit Service b38f0b
Packit Service b38f0b
Note that the maximum index value parameter does not have to be a
Packit Service b38f0b
permanently fixed constant. It specifies the maximum valid index at the time
Packit Service b38f0b
the request is processed, and a subsequent request may have a different
Packit Service b38f0b
maximum.
Packit Service b38f0b
An example of this can be seen in mibII/sysORTable.c where the table is held
Packit Service b38f0b
purely internally to the agent code, including its size (and hence the
Packit Service b38f0b
maximum valid index). This maximum could also be retrieved via a system
Packit Service b38f0b
call, or via a kernel data variable.
Packit Service b38f0b
Packit Service b38f0b
Data Retrieval
Packit Service b38f0b
--------------
Packit Service b38f0b
Packit Service b38f0b
As with the scalar case, the other required function is to retrieve the data
Packit Service b38f0b
requested. However, given the definition of a simple table this is simply a
Packit Service b38f0b
matter of using the single, integer index sub-identifier to index into an
Packit Service b38f0b
existing data structure. This index will always be the last index of the OID
Packit Service b38f0b
returned by header_simple_table, so can be obtained as name[*length-1].
Packit Service b38f0b
A good example of this type of table can be seen in ucd-snmp/disk.c
Packit Service b38f0b
Packit Service b38f0b
With some modules, this underlying table may be relatively large, or only
Packit Service b38f0b
accessible via a slow or cumbersome interface. The implementation described
Packit Service b38f0b
so far may prove unacceptably slow, particularly when walking a MIB tree
Packit Service b38f0b
requires the table to be loaded afresh for each variable requested.
Packit Service b38f0b
Packit Service b38f0b
In these circumstances, a useful technique is to cache the table when it is
Packit Service b38f0b
first read in, and use that cache for subsequent requests. This can be done
Packit Service b38f0b
by having a separate routine to read in the table. This uses two static
Packit Service b38f0b
variables, one a structure or array for the data itself, and the other an
Packit Service b38f0b
additional timestamp to indicate when the table was last loaded. When a call
Packit Service b38f0b
is made to this routine to "read" the table, it can first check whether the
Packit Service b38f0b
cached table is "new enough". If so, it can return immediately, and the
Packit Service b38f0b
system will use the cached data.
Packit Service b38f0b
Only if the cached version is sufficiently old that it's probably out of
Packit Service b38f0b
date, is it necessary to retrieve the current data, updating the cached
Packit Service b38f0b
version and the timestamp value.
Packit Service b38f0b
This is particularly useful if the data itself is relatively static, such as
Packit Service b38f0b
a list of mounted filesystems. There is an example of this technique in the
Packit Service b38f0b
Host Resources implementation.
Packit Service b38f0b
Packit Service b38f0b
As with the scalar case, mib2c simply provides placeholder dummy return
Packit Service b38f0b
values. It's up to the programmer to fill in the details.
Packit Service b38f0b
Packit Service b38f0b
The next part concludes the examination of the detailed implementation by
Packit Service b38f0b
looking at more general tables.
Packit Service b38f0b
Packit Service b38f0b
7. General Tables
Packit Service b38f0b
=================
Packit Service b38f0b
Packit Service b38f0b
Some table structures are not suitable for the simple table approach, due to
Packit Service b38f0b
the failure of one or more of the assumptions listed earlier. Perhaps they
Packit Service b38f0b
are indexed by something other than a single integer (such as a 4-octet IP
Packit Service b38f0b
address), or the maximum index is not easily determinable (such as the
Packit Service b38f0b
interfaces table), or not all indices are valid (running software), or the
Packit Service b38f0b
necessary data is not directly accessible (interfaces again).
Packit Service b38f0b
In such circumstances, a more general approach is needed. In contrast with
Packit Service b38f0b
the two styles already covered, this style of module will commonly combine
Packit Service b38f0b
the two functions of request validation and data retrieval. Note that mib2c
Packit Service b38f0b
will assume the simple table case, and this will need to be corrected.
Packit Service b38f0b
Packit Service b38f0b
General table algorithm
Packit Service b38f0b
-----------------------
Packit Service b38f0b
Packit Service b38f0b
The basic algorithm is as follows:
Packit Service b38f0b
Packit Service b38f0b
     Perform any necessary initialization, then walk through the
Packit Service b38f0b
     underlying instances, retrieving the data for each one, until the
Packit Service b38f0b
     desired instance is found. If no valid entry is found, return
Packit Service b38f0b
     failure.
Packit Service b38f0b
Packit Service b38f0b
For an exact match (GET and similar), identifying the desired instance is
Packit Service b38f0b
trivial - construct the OID (from the 'vp' variable parameter and the index
Packit Service b38f0b
value or values), and see whether it matches the requested OID.
Packit Service b38f0b
For GETNEXT, the situation is not quite so simple. Depending on the
Packit Service b38f0b
underlying representation of the data, the entries may be returned in the
Packit Service b38f0b
same order as they should appear in the table (i.e. lexically increasing by
Packit Service b38f0b
index). However, this is not guaranteed, and the natural way of retrieving
Packit Service b38f0b
the data may be in some "random" order. In this case, then the whole table
Packit Service b38f0b
needs to be traversed for each request. in order to determine the
Packit Service b38f0b
appropriate successor.
Packit Service b38f0b
This random order is the worst case, and dictates the structure of the code
Packit Service b38f0b
used in most currently implemented tables. The ordered case can be regarded
Packit Service b38f0b
as a simplification of this more general one.
Packit Service b38f0b
Packit Service b38f0b
The algorithm outlined above can now be expanded into the following
Packit Service b38f0b
pseudo-code:
Packit Service b38f0b
Packit Service b38f0b
        Init_{Name}_Entry();    // Perform any necessary initialisation
Packit Service b38f0b
Packit Service b38f0b
        while (( index = Get_Next_{Name}_Entry() ) != EndMarker ) {
Packit Service b38f0b
                        // This steps through the underlying table,
Packit Service b38f0b
                        //   returning the current index,
Packit Service b38f0b
                        //   or some suitable end-marker when all
Packit Service b38f0b
                        //   the entries have been examined.
Packit Service b38f0b
                        // Note that this routine should also return the
Packit Service b38f0b
                        //   data for this entry, either via a parameter
Packit Service b38f0b
                        //   or using some external location.
Packit Service b38f0b
Packit Service b38f0b
            construct OID from vp->name and index
Packit Service b38f0b
            compare new OID and request
Packit Service b38f0b
            if valid {
Packit Service b38f0b
                save current data
Packit Service b38f0b
                if finished     // exact match, or ordered table
Packit Service b38f0b
                    break;      //  so don't look at any more entries
Packit Service b38f0b
Packit Service b38f0b
            }
Packit Service b38f0b
Packit Service b38f0b
                //  Otherwise, we need to loop round, and examine
Packit Service b38f0b
                //    the next entry in the table.  Either because
Packit Service b38f0b
                //    the entry wasn't valid for this request,
Packit Service b38f0b
                //    or the entry was a possible "next" candidate,
Packit Service b38f0b
                //    but we don't know that there isn't there's a
Packit Service b38f0b
                //    better one later in the table.
Packit Service b38f0b
        }
Packit Service b38f0b
Packit Service b38f0b
        if no saved data        // Nothing matched
Packit Service b38f0b
           return failure
Packit Service b38f0b
Packit Service b38f0b
                // Otherwise, go on to the switch handling
Packit Service b38f0b
                //  we've already covered in the earlier styles.
Packit Service b38f0b
Packit Service b38f0b
This is now very close to the actual code used in many current
Packit Service b38f0b
implementations (such as the the routine header_ifEntry in
Packit Service b38f0b
mibII/interfaces.c). Notice that the pseudo-code fragment if valid expands
Packit Service b38f0b
in practise to
Packit Service b38f0b
Packit Service b38f0b
        if ((exact && (result == 0))  ||
Packit Service b38f0b
                        // GET request, and identical OIDs
Packit Service b38f0b
            (!exact && (result < 0)) )
Packit Service b38f0b
                        // GETNEXT, and candidate OID is later
Packit Service b38f0b
                        //  than requested OID.
Packit Service b38f0b
Packit Service b38f0b
This is a very common expression, that can be seen in most of the table
Packit Service b38f0b
implementations.
Packit Service b38f0b
Packit Service b38f0b
Notice also that the interfaces table returns immediately the first valid
Packit Service b38f0b
entry is found, even for GETNEXT requests. This is because entries are
Packit Service b38f0b
returned in lexical order, so the first succeeding entry will be the one
Packit Service b38f0b
that's required.
Packit Service b38f0b
(As an aside, this also means that the underlying data can be saved
Packit Service b38f0b
implicitly within the 'next entry' routine - not very clean, but it saves
Packit Service b38f0b
some unnecessary copying).
Packit Service b38f0b
Packit Service b38f0b
The more general case can be seen in the TCP and UDP tables (see mibII/tcp.c
Packit Service b38f0b
and mibII/udp.c). Here, the if valid fragment expands to:
Packit Service b38f0b
Packit Service b38f0b
        if ( exact && (result == 0)) {
Packit Service b38f0b
                // save results
Packit Service b38f0b
                break;
Packit Service b38f0b
        }
Packit Service b38f0b
        else if (!exact && (result < 0)) {
Packit Service b38f0b
            if ( .... ) {       // no saved OID, or this OID
Packit Service b38f0b
                                //  precedes the saved OID
Packit Service b38f0b
                // save this OID into 'lowest'
Packit Service b38f0b
                // save the results into Lowinpcb
Packit Service b38f0b
                // don't break, since we still need to look
Packit Service b38f0b
                //      at the rest of the table
Packit Service b38f0b
            }
Packit Service b38f0b
        }
Packit Service b38f0b
Packit Service b38f0b
The GET match handling is just as we've already seen - is this the requested
Packit Service b38f0b
OID or not. If so, save the results and move on to the switch statement.
Packit Service b38f0b
  The GETNEXT case is more complicated. As well as considering whether this
Packit Service b38f0b
is a possible match (using the same test we've already seen), we also have to
Packit Service b38f0b
check whether this is a better match than anything we've already seen. This
Packit Service b38f0b
is done by comparing the current candidate (newname) with the best match found
Packit Service b38f0b
so far (lowest).
Packit Service b38f0b
  Only if this extra comparison shows that the new OID is earlier than the
Packit Service b38f0b
saved one, do we need to save both the new OID, and any associated data
Packit Service b38f0b
(such as the inpcb block, and state flag). But having found one better
Packit Service b38f0b
match, we don't know that there isn't an even better one later on. So we
Packit Service b38f0b
can't break out of the enclosing loop - we need to keep going and examine
Packit Service b38f0b
all the remaining entries of the table.
Packit Service b38f0b
Packit Service b38f0b
These two cases (the TCP and UDP tables) also show a more general style of
Packit Service b38f0b
indexing. Rather than simply appending a single index value to the OID
Packit Service b38f0b
prefix, these routines have to add the local four-octet IP address plus port
Packit Service b38f0b
(and the same for the remote end in the case of the TCP table). This is the
Packit Service b38f0b
purpose of the op and cp section of code that precedes the comparison.
Packit Service b38f0b
Packit Service b38f0b
These two are probably among the most complex cases you are likely to
Packit Service b38f0b
encounter. If you can follow the code here, then you've probably cracked the
Packit Service b38f0b
problem of understanding how the agent works.
Packit Service b38f0b
Packit Service b38f0b
Finally, the next part discusses how to implement a writable (or SETable)
Packit Service b38f0b
object in a MIB module.
Packit Service b38f0b
Packit Service b38f0b
8. How to implement a SETable object
Packit Service b38f0b
====================================
Packit Service b38f0b
Packit Service b38f0b
Finally, the only remaining area to cover is that of setting data - the
Packit Service b38f0b
handling of SNMPSET. Particular care should be taken here for two reasons.
Packit Service b38f0b
Packit Service b38f0b
Firstly, any errors in the earlier sections can have limited effect. The
Packit Service b38f0b
worst that is likely to happen is that the agent will either return invalid
Packit Service b38f0b
information, or possibly crash. Either way, this is unlikely to affect the
Packit Service b38f0b
operation of the workstation as a whole. If there are problems in the
Packit Service b38f0b
writing routine, the results could be catastrophic (particularly if writing
Packit Service b38f0b
data directly into kernel memory).
Packit Service b38f0b
Packit Service b38f0b
Secondly, this is the least well understood area of the agent, at least by
Packit Service b38f0b
the author. There are relatively few variables that are defined as READ-WRITE
Packit Service b38f0b
in the relevant MIBs, and even fewer that have actually been implemented as
Packit Service b38f0b
such. I'm therefore describing this from a combination of my understanding
Packit Service b38f0b
of how SETs ought to work, personal experience of very simple SET handling
Packit Service b38f0b
and what's actually been done by others (which do not necessarily coincide).
Packit Service b38f0b
Packit Service b38f0b
There are also subtle differences between the setting of simple scalar
Packit Service b38f0b
variables (or individual entries within a table), and the creation of a new
Packit Service b38f0b
row within a table. This will therefore be considered separately.
Packit Service b38f0b
Packit Service b38f0b
With these caveats, and a healthy dose of caution, let us proceed. Note that
Packit Service b38f0b
the UCD-SNMP development team can accept no responsibility for any damage or
Packit Service b38f0b
loss resulting from either following or ignoring the information presented
Packit Service b38f0b
here. You coded it - you fix it!
Packit Service b38f0b
Packit Service b38f0b
Write routine
Packit Service b38f0b
-------------
Packit Service b38f0b
Packit Service b38f0b
The heart of SET handling is the write_method parameter from the variable
Packit Service b38f0b
handling routine. This is a pointer to the relevant routine for setting the
Packit Service b38f0b
variable in question. Mib2c will generate one such routine for each setable
Packit Service b38f0b
variable. This routine should be declared using the template
Packit Service b38f0b
Packit Service b38f0b
        int
Packit Service b38f0b
        write_variable(
Packit Service b38f0b
           int      action,
Packit Service b38f0b
           u_char   *var_val,
Packit Service b38f0b
           u_char   var_val_type,
Packit Service b38f0b
           int      var_val_len,
Packit Service b38f0b
           u_char   *statP,
Packit Service b38f0b
           oid      *name,
Packit Service b38f0b
           int      name_len );
Packit Service b38f0b
Packit Service b38f0b
Most of these parameters are fairly self explanatory:
Packit Service b38f0b
The last two hold the OID to be set, just as was passed to the main variable
Packit Service b38f0b
routine.
Packit Service b38f0b
Packit Service b38f0b
The second, third and fourth parameters provide information about the new
Packit Service b38f0b
desired value, both the type, value and length. This is very similar to the
Packit Service b38f0b
way that results are returned from the main variable routine.
Packit Service b38f0b
Packit Service b38f0b
The return value of the routine is simply an indication of whether the
Packit Service b38f0b
current stage of the SET was successful or not. We'll come back to this in a
Packit Service b38f0b
minute. Note that it is the responsibility of this routine to check that the
Packit Service b38f0b
OID and value provided are appropriate for the variable being implemented.
Packit Service b38f0b
This includes (but is not limited to) checking:
Packit Service b38f0b
Packit Service b38f0b
   * the OID is recognised as one this routine can handle
Packit Service b38f0b
     (this should be true if the routine only handles the one variable, and
Packit Service b38f0b
     there are no errors in the main variable routine or driving code, but
Packit Service b38f0b
     it does no harm to check).
Packit Service b38f0b
   * the value requested is the correct type expected for this OID
Packit Service b38f0b
   * the value requested is appropriate for this OID
Packit Service b38f0b
     (within particular ranges, suitable length, etc, etc)
Packit Service b38f0b
Packit Service b38f0b
There are two parameters remaining to be considered.
Packit Service b38f0b
Packit Service b38f0b
The fifth parameter, statP, is the value that would be returned from a GET
Packit Service b38f0b
request on this particular variable. It could be used to check that the
Packit Service b38f0b
requested new value is consistent with the current state, but its main use
Packit Service b38f0b
is to denote that a new table row is being created.
Packit Service b38f0b
In most cases (particularly when dealing with scalar values or single elements
Packit Service b38f0b
of tables), you can normally simply ignore this parameter.
Packit Service b38f0b
Packit Service b38f0b
Actions
Packit Service b38f0b
-------
Packit Service b38f0b
Packit Service b38f0b
The final parameter to consider is the first one - action. To understand
Packit Service b38f0b
this, it's necessary to know a bit about how SETs are implemented.
Packit Service b38f0b
The design of SNMP calls for all variables in a SET request to be done "as
Packit Service b38f0b
if simultaneously" - i.e. they should all succeed or all fail. However, in
Packit Service b38f0b
practise, the variables are handled in succession. Thus, if one fails, it
Packit Service b38f0b
must be possible to "undo" any changes made to the other variables in the
Packit Service b38f0b
request.
Packit Service b38f0b
This is a well understood requirement in the database world, and is usually
Packit Service b38f0b
implemented using a "multi-stage commit". This is certainly the mechanism
Packit Service b38f0b
expected within the SNMP community (and has been made explicit in the work
Packit Service b38f0b
of the AgentX extensibility group). In other words, the routine to handle
Packit Service b38f0b
setting a variable will be called more than once, and the routine must be
Packit Service b38f0b
able to perform the appropriate actions depending on how far through the
Packit Service b38f0b
process we currently are. This is determined by the value of the action
Packit Service b38f0b
parameter.
Packit Service b38f0b
Packit Service b38f0b
This is implemented using three basic phases:
Packit Service b38f0b
Packit Service b38f0b
RESERVE is used to check the syntax of all the variables provided, that the
Packit Service b38f0b
values being set are sensible and consistent, and to allocate any resources
Packit Service b38f0b
required for performing the SET. After this stage, the expectation is that
Packit Service b38f0b
the set ought to succeed, though this is not guaranteed.
Packit Service b38f0b
(In fact, with the UCD agent, this is done in two passes - RESERVE1, and
Packit Service b38f0b
RESERVE2, to allow for dependancies between variables).
Packit Service b38f0b
Packit Service b38f0b
If any of these calls fail (in either pass) the write routines are called
Packit Service b38f0b
again with the FREE action, to release any resources that have been
Packit Service b38f0b
allocated. The agent will then return a failure response to the requesting
Packit Service b38f0b
application.
Packit Service b38f0b
Packit Service b38f0b
Assuming that the RESERVE phase was successful, the next stage is indicated
Packit Service b38f0b
by the action value ACTION. This is used to actually implement the set
Packit Service b38f0b
operation. However, this must either be done into temporary (persistent)
Packit Service b38f0b
storage, or the previous value stored similarly, in case any of the
Packit Service b38f0b
subsequent ACTION calls fail.
Packit Service b38f0b
 This can be seen in the example module, where both write routines have
Packit Service b38f0b
static 'old' variables, to hold the previous value of the relevant object.
Packit Service b38f0b
Packit Service b38f0b
If the ACTION phase does fail (for example due to an apparently valid, but
Packit Service b38f0b
unacceptable value, or an unforeseen problem), then the list of write
Packit Service b38f0b
routines are called again, with the UNDO action. This requires the routine
Packit Service b38f0b
to reset the value that was changed to its previous value (assuming it was
Packit Service b38f0b
actually changed), and then to release any resources that had been
Packit Service b38f0b
allocated. As with the FREE phase, the agent will then return an indication
Packit Service b38f0b
of the error to the requesting application.
Packit Service b38f0b
Packit Service b38f0b
Only once the ACTION phase has completed successfully, can the final COMMIT
Packit Service b38f0b
phase be run. This is used to complete any writes that were done into
Packit Service b38f0b
temporary storage, and then release any allocated resources. Note that all
Packit Service b38f0b
the code in this phase should be "safe" code that cannot possibly fail (cue
Packit Service b38f0b
hysterical laughter). The whole intent of the ACTION/COMMIT division is that
Packit Service b38f0b
all of the fallible code should be done in the ACTION phase, so that it can
Packit Service b38f0b
be backed out if necessary.
Packit Service b38f0b
Packit Service b38f0b
Table row creation
Packit Service b38f0b
------------------
Packit Service b38f0b
Packit Service b38f0b
What about creating new rows in a table, I hear you ask. Good Question.
Packit Service b38f0b
This case can often be detected by the fact that a GET request would have
Packit Service b38f0b
failed, and hence the fifth parameter, statP, will be null.  This contrasts
Packit Service b38f0b
with changing the values of an element of an existing row, when the statP
Packit Service b38f0b
parameter would hold the previous value.
Packit Service b38f0b
Packit Service b38f0b
The details of precisely how to create a new row will clearly depend on the
Packit Service b38f0b
underlying format of the table.  However, one implementation strategy would
Packit Service b38f0b
be as follows:
Packit Service b38f0b
Packit Service b38f0b
  *  The first column object to be SET would return a null value from the
Packit Service b38f0b
	var_name routine.  This null statP parameter would be the signal
Packit Service b38f0b
	to create a new temporary instance of the underlying data structure,
Packit Service b38f0b
	filled with dummy values.
Packit Service b38f0b
  *  Subsequent column objects would return pointers to the appropriate
Packit Service b38f0b
	field of this new data structure from the var_name routine,
Packit Service b38f0b
	which would then be filled in by the write routine.
Packit Service b38f0b
  *  Once all the necessary fields had been SET, the completed temporary
Packit Service b38f0b
	instance could be moved into the "standard" structure (or copied,
Packit Service b38f0b
	or otherwise used to set things up appropriately).
Packit Service b38f0b
Packit Service b38f0b
However, this is purely a theoretical strategy, and has not been tried
Packit Service b38f0b
by the author.  No guarantees are given as to whether this would actually
Packit Service b38f0b
work.  There are also questions regarding how to handle incomplete
Packit Service b38f0b
or overlapping SET requests.
Packit Service b38f0b
Anyone who has experience of doing this, please get in touch!
Packit Service b38f0b
Packit Service b38f0b
  ------------------------------------------------------------------------
Packit Service b38f0b
And that's it. Congratulations for getting this far. If you understand
Packit Service b38f0b
everything that's been said, then you now know as much as the rest of us
Packit Service b38f0b
about the inner workings of the UCD-SNMP agent. (Well, very nearly).
Packit Service b38f0b
All that remains is to try putting this into practise. Good luck!
Packit Service b38f0b
Packit Service b38f0b
And if you've found this helpful, gifts of money, chocolate, alcohol, and
Packit Service b38f0b
above all feedback, would be most appreciated :-)
Packit Service b38f0b
Packit Service b38f0b
  ------------------------------------------------------------------------
Packit Service b38f0b
Copyright 1999, 2000 - D.T.Shield.
Packit Service b38f0b
This file may be distributed as part of a source or binary packaging
Packit Service b38f0b
of the Net-SNMP software suite.  It may not be distributed independently
Packit Service b38f0b
without the explicit permission of the author.