blob: e51bbdbb9de3a20335626fd8ffebfa198a97da3a [file] [log] [blame]
Wes Hardakerf4029451999-03-10 23:07:05 +00001Note, this is actually the text from a web page, which can be found in
2the documentation section of the http://ucd-snmp.ucdavis.edu web page.
3
4Extending the UCD-SNMP agent
5
6This is a first draft at documenting the procedure for writing code to
7extend the functionality of the UCD-SNMP network management agent.
8
9It is copyright the author, and must not be distributed without explicit
10permission. This is purely because I do not yet regard this as sufficiently
11well checked or polished to be suitable for distribution. However, there is
12a clear need for this sort of information, hence it is being made available
13over the Web as an interim measure.
14
15If you do make use of these documents, please can you contact me regarding
16how useful and accurate (or otherwise) you found the information. The more
17feedback I receive, the faster this guide will reach a presentable state -
18at which point it can be distributed with the main UCD-SNMP package.
19
20The information is designed to be read in order - the structure being:
21
22 1. Overview & Introduction
23 2. MIB files, and how they relate to the agent implementation
24 3. Header files
25 4. The basic structure of module implementation code
26 5. The details of non-table based implementations
27 6. The details of simple table based implementations
28 7. The details of more general table based implementations
29 8. How to implement SET-able variables
30
31How to write a Mib module
32
33Introduction
34
35The design of the UCD SNMP agent has always been shaped by the desire to be
36able to extend its functionality by adding new modules. One of the earliest
37developments from the underlying CMU code base was the ability to call
38external scripts, and this is probably the simplest method of extending the
39agent.
40However, there are circumstances where such an approach is felt to be
41inappropriate - perhaps from considerations of speed, access to the
42necessary data, reliability or elegance. In such cases, the obvious solution
43is to provide C code that can be compiled into the agent itself to implement
44the desired module. Many of the more recent developments in the code
45structure have been intended to ease this process. In particular, one of the
46more recent additions to the suite is the tool mib2c. This is designed to
47take a portion of the MIB tree (as defined by a MIB file) and generate the
48code skeleton necessary to implement this. This document will cover the use
49mib2c, as well as describing the requirements and functionality of the code
50in more detail.
51
52In order to implement a new MIB module, three files are necessary, and these
53will be considered in turn. Note that, by the very nature of the task, this
54document cannot cover the details of precisely how to obtain the necessary
55information from the operating system or application. Instead, it describes
56the code framework that is needed, freeing the implementer from needing to
57understand the detailed internals of the agent, and allowing them to
58concentrate on the particular problem in hand.
59
60It may prove useful to examine some of the existing module implementations
61and examples in the light of this description, and suitable examples will be
62referred to at the appropriate points. However, it should be remembered that
63the UCD agent seeks to support a wide variety of systems, often with
64dramatically differing implementations and interfaces, and this is reflected
65in the complexity of the code. Also, the agent has developed gradually over
66the years, and there is often some measure of duplication or redundancy as a
67result.
68As the FAQ states, the official slogan of the UCD-SNMP developers is
69
70 The current implementation is non-obvious and may need to be
71 improved.
72
73This document describes the ideal, straightforward cases - real life is
74rarely so simple, and the example modules may prove easier to follow at a
75first reading.
76It is also advisable to have a compiled and installed implementation
77available before starting to extend the agent. This will make debugging and
78testing the agent much easier.
79
80A note regarding terminology - the word "module" is widely used throughout
81this document, with a number of different meanings.
82
83 * support for a new MIB,
84 i.e. the whole of the functionality that is required. This is usually
85 termed a MIB module;
86 * a self-contained subset of this, implemented as a single unit.
87 This is usually termed an implementation module (or simply "a module");
88 * the combination of such subsets usually termed a module group.
89
90Note that the first and third of these are effectively synonymous - the
91difference being that a MIB module refers to the view from outside the
92agent, regarding this as a seamless whole and hiding the internal
93implementation. A "module group" is used where the internal structure is of
94more relevance, and recognises the fact that the functionality may be
95provided by a number of co-operating units.
96
97Anyway, enough waffle - on with the details: The three files needed are
98
99 * a MIB definition file;
100 * a C header file;
101 * a C implementation file.
102
103The next part looks at the MIB definition file, and how this impacts on the
104agent implementation.
105
106The MIB File
107
108The first file needed is the MIB file that defines the MIB module to be
109implemented.
110Strictly speaking, this is not absolutely necessary, as the agent itself
111does not make any direct use of the MIB definitions. However, it is
112advisable to start with this for three reasons:
113
114 * It provides an initial specification for what is to be implemented.
115 Code development is always easier if you know what you are meant to be
116 writing!
117 * If the new MIB file is read in with the other MIB files,
118 this lets the applications provided with the suite be used to test the
119 new agent, and report (hopefully meaningful) symbolic OIDs and values,
120 rather than the bare numeric forms.
121 (N.B: Remember to tell the application to load the new MIB. See the
122 relevant question in the FAQ)
123 * The tool mib2c uses this description to produce the two code files.
124 This is by far the easiest way to develop a new module.
125
126If the intention is to implement a 'standard' MIB module, or a
127vendor-specific one, then the construction of this file will have already
128been done for you. If the intention is to provide a totally new, private
129module, then you will need to write this yourself, in addition to the agent
130code files.
131A description of MIB file format and syntax is beyond the scope of this
132document, and most books on SNMP management should provide some information
133on this subject. One book which concentrates on this is
134
135 Understanding SNMP MIBS
136 (Perkins & McGinnis, Prentice Hall, ISBN 0-13-437708-7).
137
138This blatant plug is wholly unrelated to the fact that David Perkins is an
139active member of the development group, and is regarded as our resident
140"protocol guru and policeman". Information on other books covering SNMP and
141Network Management more generally is available on the SimpleWeb site (among
142other places). See the FAQ for more details.
143
144Assigned OID numbers
145
146One word of advice - even if you are developing a totally private MIB
147module, you will still need to position this somewhere within the overall
148MIB tree. Please do NOT simply choose a location "at random". Any such is
149likely to have either been assigned to some other organisation, or may be so
150assigned some time in the future. However much you may regard your project
151as a totally internal affair, such projects have a tendency to exceed their
152expected scope, both in terms of lifetime and distribution (not to mention
153the potential OID clash if you subsequently need to use elements from the
154legitimate owner's tree).
155It is simple and cheap (i.e. free!) to obtain your own official segment of
156the MIB tree (see http://www.iana.org for an application form), and having
157done so, you then have complete global authority over it. If you have
158problems with this, it's worth contacting the development team (email:
159ucd-snmp-coders@ucd-snmp.ucdavis.edu) for advice. Please do think to the
160future, and be a good Net citizen by using a legitimately assigned OID as
161the root of your new MIB.
162
163MIB division
164
165The next point to consider, whether writing by hand or using mib2c,
166implementing an existing MIB, or writing a new one, is whether and how to
167divide up the MIB tree. This is a purely internal implementation decision,
168and will not be visible to management applications querying the agent. A
169sensible choice of partitioning will result in a simpler, clearer
170implementation, which should ease both the initial development and
171subsequent maintenance of the module.
172Unfortunately, this choice is one of the module-specific decisions, so must
173be made on a case-by-case basis. For a simple, self-contained module, it may
174well be reasonable to implement the module as a single block (examples
175include the SNMP statistics subtree RFC 1907 or the TCP subtree RFC 2011).
176More complex and diverse modules (such as the Host Resources MIB - RFC 1514)
177are more naturally considered as a number of individual sub-modules.
178Some guidelines to bear in mind when deciding on this division:
179
180 * Individual variables within a particular MIB sub-tree would normally be
181 handled in the same implementation module;
182 * Separate scalar subtrees should normally be in different implementation
183 modules;
184 * Variables that rely on the same underlying data structure to retrieve
185 their values, should probably be in the same implementation module (and
186 conversely, though less so, those that don't shouldn't).
187
188As an initial rule of thumb, a good initial division is likely to be
189obtained by treating each table and each scalar sub-tree separately. This
190can be seen in the current agent, where most of the MIB-II modules (RFC
1911213) are implemented separately (see the files under mibgroup/mibII).
192However it is quite acceptable to combine scalar and table handling, though
193they will usually be implemented using separate routines, even if these are
194in the same source file. This is the approach used by mib2c, which
195constructs a single pair of code files, but uses a separate routine for each
196table (and another for all the scalar variables).
197Ultimately, the final consideration (concerning the underlying data) is the
198most important, and should guide the basic division. For example, the Host
199Resources Running Software and Running Software Performance modules, while
200separate in the MIB tree, use the same underlying kernel data and so are
201implemented together.
202
203MIB name
204
205The final requirement at this stage is to choose a name for each
206implementation module. This should be reasonably short, meaningful, unique
207and unlikely to clash with other (existing or future) modules. Mib2c uses
208the label of the root node of the MIB sub-tree as this name, and this is a
209reasonable choice in most cases.
210Recent changes to the agent code organisation have introduced the idea of
211module groups of related implementation modules. This is used, for example,
212to identify the constituent modules of a 'split' MIB (such as the Host
213Resources MIB), or those relating to a particular organisation (such as
214UCD).
215As with the division, this naming and grouping is a purely internal matter,
216and is really only visible when configuring and compiling the agent.
217
218So much for the MIB file. The next part considers the C header file.
219
220The C code header file
221
222If the MIB file is the definition of the module for external network
223management applications (where applications includes network management
224personnel!), then the header file has traditionally served effectively the
225same purpose for the agent itself.
226Recent changes to the recommended code structure has resulted in the header
227file getting increasingly simpler. It now simply contains definitions of the
228publically visible routines, and can be generated completely by mib2c.
229
230Function prototypes
231
232For those interested in the details of this file (for example, if coding a
233module by hand), then the details of these definitions are as follows. Every
234header file will have the following two function prototype definitions
235
236 void init_wombat (void);
237 u_char *var_wombat (
238 struct variable *, oid *, int *, int, int *,
239 WriteMethod **write);
240
241If the module includes any tables, or other collections of variables that
242are implemented in separate routines, then this second definition will be
243repeated for each of these.
244In addition, if any of the variables can be SET (and it is intended to
245implement them as such), there will be a function prototype definitions for
246each of these, of the form:
247
248 int write_varName ( int, u_char *, u_char, int,
249 u_char *, oid * )
250
251(You don't need to understand these prototypes, just copy them!)
252
253Module dependancies
254
255This header file is also used to inform the compilation system of any
256dependancies between this module and any others. There is one utility module
257which is required by almost every module, and this is included using the
258directive
259
260 config_require( util_funcs )
261
262(which is produced automatically by mib2c). This same syntax can be used to
263trigger the inclusion of other related modules. An example of this can be
264seen in mibII/route_write.h which relies on the mibII/ip module.
265
266One use of this directive is to define a module group, so that it can be
267included or excluded from the agent very simply. Examples of this can be
268seen in mibgroup/mibII.h or mibgroup/host.h, which list the consituent
269sub-modules of the MIB-II and Host Resources respectively.
270
271Header file protection
272
273Normally, the only other contents of the header file will be the
274#ifdef/#endif statements surrounding the whole file. This is used to ensure
275that the header file is only included once by any source code file (or more
276accurately, that there is no effect if it is inadvertantly included a second
277time).
278Again, as with the rest of the header file, this is generated automatically
279by mib2c.
280
281Having finished all the preparatory work (or let mib2c deal with it), the
282next part starts to look at the code file that actually implements the
283module.
284
285Core structure of the implementation code
286
287The core work of implementing the module is done in the C code file. As
288indicated earlier, much of the detail of this will be dependent on the
289particular module being implemented, and this can only be described by the
290individual programmer concerned.
291However, there is a fairly clearly defined framework that the implementation
292will need to follow, though this varies slightly depending on the style of
293the module being implemented (in particular whether it forms a table or a
294series of individual values). The differences will be covered in the
295following pages, but we first need to consider the overall shape of the
296framework, and the elements that are common to all styles. These are
297essentially the compulsory routines, the common header definitions, and
298assorted initialisation code.
299As with the header file, most of this will be generated automatically by
300mib2c.
301
302Standard includes
303
304Certain header files are either compulsory, or required so frequently that
305they should be included as a matter of course. These are as follows:
306
307 #include <config.h> // local SNMP configuration details
308 #include "mib_module_config.h" // list of which modules are supported
309 #if STDC_HEADERS
310 #include <stdlib.h>
311 #include <string.h>
312 #else
313 #if HAVE_STDLIB_H
314 #include <stdlib.h>
315 #endif
316 #endif
317
318 #include <sys/types.h>
319
320All of these will usually be the first files to be included.
321
322 #include "mibincl.h" // Standard set of SNMP includes
323 #include "util_funcs.h" // utility function declarations
324 #include "read_config.h" // if the module uses run-time
325 // configuration controls
326 #include "auto_nlist.h" // structures for a BSD-based
327 // kernel using nlist
328 #include "../../../snmplib/system.h"
329
330 #include "name.h" // the module-specific header
331
332These conventionally come at the end of the list of includes. In between
333will come all the standard system-provided header files required for the
334particular module being implemented.
335
336Module definition
337
338Much of the code defining the contents of the MIB has traditionally been
339held in the header file. However, much of this has slowly migrated to the
340code file, and this is now the recommended location for it (as typified by
341the output of mib2cstruct variableN (where N is the length of the longest
342siffix in the table). Thus
343
344 struct variableN wombat_variables[] = {
345
346 };
347
348Each entry corresponds to one variable in the MIB tree (or one column in the
349case of table entries), and these should be listed in increasing OID order.
350A single entry consists of six fields:
351
352 * a magic number (a #defined integer constant)
353 * a type indicator (from the values listed in )
354 * an access indicator (essentially RWRITE or RONLY)
355 * the name of the routine used to handle this entry
356 * the length of the OID suffix used, and
357 * an array of integers specifying this suffix (more on this in a moment)
358
359Thus a typical variable entry (including the magic number definition) would
360look like:
361
362 #define WOMBAT_XYZ 3
363 { WOMBAT_XYZ, ASN_OCTET_STR, RONLY, var_wombat, 1, {3}}
364
365Note that the precise values used for these magic numbers are not important,
366as long as they are distinct. They will usually be incrementing from 1, or
367be defined to be the same as the corresponding MIB value final
368sub-identifier (or indeed both!)
369Note also that in practise, only certain sizes of the structure variableN
370are defined (listed in <agent/var_struct.h>), being sufficient to meet the
371common requirements. If your particular module needs a non-supported value,
372the easiest thing is simply to use the next largest value that is supported.
373
374In addition, the module needs to declare the location within the MIB tree
375where it belongs. This is done using a declaration of the form
376
377 oid wombat_variables_oid[] = { 1,3,6,1,x,y,z,.... }
378
379where the contents of the array list the object identifier of the root of
380the module.
381
382Module initialisation
383
384Many modules require some form of initialisation before they can start
385providing the necessary information. This is done by providing a routine
386called init_{name} (where {name} is the name of the module).
387This routine is required, and should at the very least register this module
388with the main agent. In other words, specify the list of variables being
389implemented (from the variableN structure) and declare where these fit into
390the overall MIB tree.
391
392This is done by using the REGISTER_MIB macro, as follows:
393
394 REGISTER_MIB( "wombat", wombat_variables, variableN,
395 wombat_variables_oid );
396
397where "wombat" is used for identification purposed (and is usually the name
398being used for the module), wombat_variables is the structure defining the
399variables being implemented, variableN is the type used for this structure,
400and wombat_variables_oid is the location of the root.
401
402In fact, this macro is simply a wrapper round the routine register_mib(),
403but this fact can safely be ignored.
404
405One common requirement, particularly on older operating systems or for the
406more obscure areas of the system, is to be able to read data directly from
407kernel memory. The preparation for this is typically done here by one or
408more statements of the form
409
410 #ifdef {NAME}_SYMBOL
411 auto_nlist( {NAME}_SYMBOL, 0, 0);
412 #endif
413
414where {NAME}_SYMBOL is defined as part of the system-specific configuration,
415to be the name of the appropriate kernel variable or data structure. (The
416two 0 values are because the kernel information is simply being primed at
417this point - this call will be reused later when the actual values are
418required). Note that this is probably the first thing described so far which
419isn't provided by mib2c!
420
421Other possibilities for initialisation may include registering the new
422config file handlers (which are documented in the read_config(5) man page),
423and perhaps registering the MIB module (either in whole or in part) in the
424sysOR table.
425
426Variable handling
427
428The other obligatory routine is that which actually handles a request for a
429particular variable instance. This is the routine that appeared in the
430variableN structure in the header file, so while the name is not fixed, it
431should be the same as was used there.
432This routine has six parameters, which will be described in turn.
433
434Four of these parameters are used for passing in information about the
435request, these being:
436
437 struct variable *vp;
438 // The entry in the variableN array from the
439 // header file, for the variable under consideration.
440 // Note that the name field of this structure has been
441 // completed into a fully qualified OID, by prepending
442 // the prefix common to the whole array.
443 oid *name; // The OID from the request
444 int *length; // The length of this OID
445 int exact; // A flag to indicate whether this is an exact
446 // request (GET/SET) or an 'inexact' one (GETNEXT)
447
448Four of the parameters (plus the function result) are used to return
449information about the answer (if any). The function returns a pointer to the
450actual data for the variable requested (or NULL if this data is not
451available for any reason). The other result parameters are:
452
453 oid *name; // The OID being returned
454 int *length; // The length of this OID
455 int *var_len; // The length of the answer being returned
456 WriteMethod **write_method;
457 // A pointer to the SET function for this variable
458
459Note that two of the parameters (name and length) serve a dual purpose,
460being used for both input and output.
461
462The first thing that this routine needs to do is to validate the request, to
463ensure that it does indeed lie in the range implemented by this particular
464module. This is done in slightly different ways, depending on the style of
465the module, so this will be discussed in more detail later. At the same
466time, it is common to retrieve some of the information needed for answering
467the query.
468
469Then the routine uses the Magic Number field from the vp parameter (i.e.
470from the relevant entry of the headers variableN array) to determine which
471of the possible variables being implemented should be considered. This is
472done using a switch statement, which should have as many cases as there are
473entries in the variableN array (or more precisely, as many as specify this
474routine as their handler), plus an additional default case to handle an
475erroneous call.
476Each branch of the switch statement needs to ensure that the return
477parameters are filled in correctly, set up a (static) return variable with
478the correct data, and then return a pointer to this value. These can be done
479separately for each branch, or once at the start, being overridden in
480particular branches if necessary.
481
482In fact, the default validation routines make the assumption that the
483variable is both read-only, and of integer type (which includes COUNTER and
484GAUGE types), and set the return paramaters write_method and var_len
485appropriately. These settings can then be corrected for those cases when
486either or both of these assumptions are wrong. Examples of this can be seen
487in mibII/snmp_mib.c (the case SNMPENABLEAUTHENTRAPS sets the write_method
488parameter) and mibII/interfaces.c (where the case IFDESCR needs to return a
489string, rather than an integer).
490
491Note that because the routine returns a pointer to a static result, a
492suitable variable must be declared somewhere for this. Two global variables
493are provided for this purpose - long_return (for integer results) and
494return_buf (for other types). This latter is a generic array (of type
495u_char) that can contain up to 256 bytes of data. Alternatively, static
496variables can be declared, either within the code file, or local to this
497particular variable routine. This last is the approach adopted by mib2c,
498which defines four such local variables, (long_ret, string, objid and c64).
499
500Mib2c requirements
501
502Most of the code described here is generated by mib2c. The main exceptions
503(which therefore need to be provided by the programmer) are
504
505 * Any initialisation, other than the basic registration
506 (including kernel data initialisation, config file handling, or sysOR
507 registration).
508 * Retrieving the necessary data, and returning it.
509 * The var_len (and possibly write_method) return parameters for variable
510 types that are not recognised by mib2c
511
512Everything else should be useable as generated.
513
514 ------------------------------------------------------------------------
515This concludes the preliminary walk-through of the general structure of the
516C implementation. To fill in the details, we will need to consider the
517various styles of module separately. The next part will look at scalar (i.e.
518non-table based) modules.
519
520Non-table-based modules
521
522Having looked at the general structure of a module implementation, it's now
523time to look at this in more detail. We'll start with the simplest style of
524module - a collection of independent variables. This could easily be
525implemented as a series of completely separate modules - the main reason for
526combining them is to avoid the proliferation of multiple versions of very
527similar code.
528
529Recall that the variable handling routine needs to cover two distinct
530purposes - validation of the request, and provision of the answer. In this
531style of module, these are handled separately. Once again, mib2c does much
532of the donkey work, generating the whole of the request validation code (so
533the description of this section can be skipped if desired), and even
534providing a skeleton for returning the data. This latter still requires some
535input from the programmer, to actually return the correct results (rather
536than dummy values).
537
538Request Validation
539
540This is done using a standard utility function header_generic. The
541parameters for this are exactly the same as for the main routine, and are
542simply passed through directly. It returns an integer result, as a flag to
543indicate whether the validation succeeded or not.
544If the validation fails, then the main routine should return immediately,
545leaving the parameters untouched, and indicate the failure by returning a
546NULL value. Thus the initial code fragment of a scalar-variable style
547implementation will typically look like:
548
549 u_char *
550 var_system(vp, name, length, exact, var_len, write_method)
551 {
552 if (header_generic(vp, name, length, exact, var_len, write_method)
553 == MATCH_FAILED )
554 return NULL;
555
556 [ etc, etc, etc ]
557 }
558
559Although the utility function can be used as a "black box", it's worth
560looking more closely at exactly what it does (since the tabular modules will
561need to do something fairly similar). It has two (or possibly three)
562separate functions:
563
564 * checking that the request is valid,
565 * setting up the OID for the result,
566 * and (optionally) setting up default values for the other return
567 parameters.
568
569In order to actually validate the request, the header routine first needs to
570construct the OID under consideration, in order to compare it with that
571originally asked for. The driving code has already combined the OID prefix
572(constant throughout the module) with the entry-specific suffix, before
573calling the main variable handler. This is available via the name field of
574the parameter vp. For a scalar variable, completing the OID is therefore
575simply a matter of appending the instance identifier 0 to this. The full OID
576is built up in a local oid array newname defined for this purpose.
577This gives the following code fragment:
578
579 int
580 header_generic(vp, name, length, exact, var_len, write_method)
581 {
Wes Hardaker84f4a451999-03-10 23:14:17 +0000582 oid newname[MAX_OID_LEN];
Wes Hardakerf4029451999-03-10 23:07:05 +0000583
584 memcpy((char *)newname, (char *)vp->name,
585 (int)vp->namelen * sizeof(oid));
586 newname[ vp->namelen ] = 0;
587
588 :
589 }
590
591Having formed the OID, this can then be compared against the variable
592specified in the original request, which is available as the name parameter.
593This comparison is done using the snmp_oid_compare function, which takes the
594two OIDs (together with their respective lengths), and returns -1, 0 or 1
595depending on whether the first OID precedes, matches or follows the second.
596
597In the case of an 'exact' match (i.e. a GET/SET/etc), then the request is
598valid if the two OIDs are identical (snmp_oid_compare returns 0). In the
599case of a GETNEXT (or GETBULK) request, it's valid if the OID being
600considered comes after that of the original request (snmp_oid_compare
601returns 1).
602
603This gives the code fragment
604
605 result = snmp_oid_compare(name, *length, newname, (int)vp->namelen + 1);
606 // +1 because of the extra instance sub-identifier
607 if ((exact && (result != 0)) // GET match fails
608 || (!exact && (result >= 0))) // GETNEXT match fails
609 return(MATCH_FAILED);
610
611Note that in this case, we're only interested in the single variable
612indicated by the vp parameter. The fact that this module may well implement
613other variables as well is ignored. The 'lexically next' requirement of the
614GETNEXT request is handled by working through the variable entries in order
615until one matches. And yes, this is not the most efficient implementation
616possible!
617Note that in releases prior to 3.6, the snmp_oid_compare function was called
618simply compare.
619
620Finally, having determined that the request is valid, this routine must
621update the name and length parameters to return the OID being processed. It
622may also set default values for either or both of the other two return
623parameters.
624
625 memcpy( (char *)name,(char *)newname,
626 ((int)vp->namelen + 1) * sizeof(oid));
627 *length = vp->namelen + 1;
628 *write_method = 0; // Non-writeable
629 *var_len = sizeof(long); // default to long results
630 return(MATCH_SUCCEEDED);
631
632These three code fragments combine to form the full header_generic code
633which can be seen in the file util_funcs.c
634
635Note: This validation used to be done using a separate function for each
636module (conventionally called header_{name}), and many modules may still be
637coded in this style. The code for these are to all intents and purposes
638identical to the header_generic routine described above.
639
640Data Retrieval
641
642The other job still outstanding is that of retrieving any necessary data,
643and returning the appropriate answer to the original request. This must be
644done even if mib2c is being used to generate the framework of the
645implementation. As has been indicated earlier, the different cases are
646handled using a switch statement, with the Magic Number field of the vp
647parameter being used to distinguish between them.
648The data necessary for answering the request can be retrieved for each
649variable individually in the relevant case statement (as is the case with
650the system group), or using a common block of data before processing the
651switch (as is done for the ICMP group, among others).
652
653With many of the modules implemented so far, this data is read from a kernel
654structure. This can be done using the auto_nlist routine already mentioned,
655providing a variable in which to store the results and an indication of its
656size (see the !HAVE_SYS_TCPIPSTATS_H case of the ICMP group for an example).
657Alternatively, there may be ioctl calls on suitable devices, specific system
658calls, or special files that can be read to provide the necessary
659information.
660
661If the available data provides the requested value immediately, then this
662can be returned directly (as is done with most of the ICMP values).
663Otherwise, if the requested value needs to be calculated by combining two or
664more items of data (e.g. IPINHDRERRORS in mibII/ip.c) or by applying a
665mapping or other calculation involving available information (e.g.
666IPFORWARDING from the same group) or just because it feels neater that way
667(e.g. the interfaces group), then it can use the global static variable
668long_return or local equivalents (such as generated bymib2c).
669
670In each of these cases, the routine should return a pointer to the result
671value, casting this to the pseudo-generic (u_char *)
672
673 ------------------------------------------------------------------------
674So much for the scalar case. The next part looks at how to handle simple
675tables.
676
677Simple tables
678
679Having considered the simplest style of module implementation, we now turn
680our attention to the next style - a simple table. The tabular nature of
681these is immediately apparent from the MIB definition file, but the
682qualifier simple deserves a word of explanation.
683A simple table, in this context, has four characteristics:
684
685 1. It is indexed by a single integer value;
686 2. Such indices run from 1 to a determinable maximum;
687 3. All indices within this range are valid;
688 4. The data for a particular index can be retrieved directly
689 (e.g. by indexing into an underlying data structure).
690
691If any of the conditions are not met, then the table is not a pure simple
692one, and the techniques described here are not applicable. The next section
693of this guide will cover the more general case. (In fact, it may be possible
694to use the bulk of the techniques covered here, though special handling will
695be needed to cope with the invalid assumption or assumptions). Note that
696mib2c assumes that all tables are simple.
697
698As with the scalar case, the variable routine needs to provide two basic
699functions - request validation and data retrieval.
700
701Validation
702
703This is provided by the shared utility routine header_simple_table As with
704the scalar header routine, this takes the same parameters as the main
705variable routine, with one addition - the maximum valid index. Mib2c
706generates a dummy token for this, which must be replaced by the appropriate
707value.
708As with the header routine, it also returns an indication of whether the
709request was valid, as well as setting up the return parameters with the
710matching OID information, and defaults for the other two.
711Note that in releases prior to 3.6, this job was performed by the routine
712checkmib. However, the return values of this were the reverse of those for
713generic_header. A version of checkmib is still available for compatability
714purposes, but you are encouraged to use header_simple_table instead.
715
716The basic code fragment (see ucd-snmp/disk.c) is therefore of the form:
717
718 unsigned char *
719 var_extensible_disk(vp, name, length, exact, var_len, write_method)
720 {
721 if (header_simple_table(vp,name,length,exact,var_len,write_method,numdisks)
722 == MATCH_FAILED)
723 return(NULL);
724
725 [ etc, etc, etc ]
726
727 }
728
729Note that the maximum index value parameter does not have to be a
730permanently fixed constant. It specifies the maximum valid index at the time
731the request is processed, and a subsequent request may have a different
732maximum.
733An example of this can be seen in mibII/sysORTable.c where the table is held
734purely internally to the agent code, including its size (and hence the
735maximum valid index). This maximum could also be retrieved via a system
736call, or via a kernel data variable.
737
738Data Retrieval
739
740As with the scalar case, the other required function is to retrieve the data
741requested. However, given the definition of a simple table this is simply a
742matter of using the single, integer index sub-identifier to index into an
743existing data structure. This index will always be the last index of the OID
744returned by header_simple_table, so can be obtained as name[*length-1].
745A good example of this type of table can be seen in ucd-snmp/disk.c
746
747With some modules, this underlying table may be relatively large, or only
748accessible via a slow or cumbersome interface. The implementation described
749so far may prove unacceptably slow, particularly when walking a MIB tree
750requires the table to be loaded afresh for each variable requested.
751
752In these circumstances, a useful technique is to cache the table when it is
753first read in, and use that cache for subsequent requests. This can be done
754by having a separate routine to read in the table. This uses two static
755variables, one a structure or array for the data itself, and the other an
756additional timestamp to indicate when the table was last loaded. When a call
757is made to this routine to "read" the table, it can first check whether the
758cached table is "new enough". If so, it can return immediately, and the
759system will use the cached data.
760Only if the cached version is sufficiently old that it's probably out of
761date, is it necessary to retrieve the current data, updating the cached
762version and the timestamp value.
763This is particularly useful if the data itself is relatively static, such as
764a list of mounted filesystems. There is an example of this technique in the
765Host Resources implementation.
766
767As with the scalar case, mib2c simply provides placeholder dummy return
768values. It's up to the programmer to fill inthe details.
769
770The next part concludes the examination of the detailed implementation by
771looking at more general tables.
772
773General Tables
774
775Some table structures are not suitable for the simple table approach, due to
776the failure of one or more of the assumptions listed earlier. Perhaps they
777are indexed by something other than a single integer (such as a 4-octet IP
778address), or the maximum index is not easily determinable (such as the
779interfaces table), or not all indices are valid (running software), or the
780necessary data is not directly available (interfaces again).
781In such circumstances, a more general approach is needed. In contrast with
782the two styles already covered, this style of module will commonly combine
783the two functions of request validation and data retrieval. Note that mib2c
784will assume the simple table case, and this will need to be corrected.
785
786General table algorithm
787
788The basic algorithm is as follows:
789
790 Perform any necessary initialization, then walk through the
791 underlying instances, retrieving the data for each one, until the
792 desired instance is found. If no valid entry is found, return
793 failure.
794
795For an exact match (GET and similar), identifying the desired instance is
796trivial - construct the OID (from the 'vp' variable parameter and the index
797value or values), and see whether it matches the requested OID.
798For GETNEXT, the situation is not quite so simple. Depending on the
799underlying representation of the data, the entries may be returned in the
800same order as they should appear in the table (i.e. lexically increasing by
801index). However, this is not guaranteed, and the natural way of retrieving
802the data may be in some "random" order. In this case, then the whole table
803needs to be traversed for each request. in order to determine the
804appropriate successor.
805This random order is the worst case, and dictates the structure of the code
806used in most currently implemented tables. The ordered case can be regarded
807as a simplification of this more general one.
808
809The algorithm outlined above can now be expanded into the following
810pseudo-code:
811
812 Init_{Name}_Entry(); // Perform any necessary initialisation
813
814 while (( index = Get_Next_{Name}_Entry() ) != -1 ) {
815 // This steps through the underlying table,
816 // returning the current index, or -1
817 // (or some similar end-marker) when all
818 // the entries have been examined.
819 // Note that this routine should also return the
820 // data for this entry, either directly, or
821 // via some external location
822
823 construct OID from vp->name and index
824 compare new OID and request
825 if valid {
826 save current data
827 if finished // exact match, or ordered table
828 break; // so don't look at any more entries
829
830 }
831
832 // Otherwise, we need to loop round, and examine
833 // the next entry in the table. Either because
834 // the entry wasn't valid for this request,
835 // or the entry was a possible "next" candidate,
836 // but we don't know that there isn't there's a
837 // better one later in the table.
838 }
839
840 if no saved data // Nothing matched
841 return failure
842
843 // Otherwise, go on to the switch handling
844 // we've already covered in the earlier styles.
845
846This is now very close to the actual code used in many current
847implementations (such as the the routine header_ifEntry in
848mibII/interfaces.c). Notice that the pseudo-code fragment if valid expands
849in practise to
850
851 if ((exact && (result == 0)) ||
852 // GET request, and identical OIDs
853 (!exact && (result < 0)) )
854 // GETNEXT, and candidate OID is later
855 // than requested OID.
856
857This is a very common expression, that can be seen in most of the table
858implementations.
859
860Notice also that the interfaces table returns immediately the first valid
861entry is found, even for GETNEXT requests. This is because entries are
862returned in lexical order, so the first succeeding entry will be the one
863that's required.
864(As an aside, this also means that the underlying data can be saved
865implicitly within the 'next entry' routine - not very clean, but it saves
866some unnecessary copying).
867
868The more general case can be seen in the TCP and UDP tables (see mibII/tcp.c
869and mibII/udp.c). Here, the if valid fragment expands to:
870
871 if ( exact && (result == 0)) {
872 // save results
873 break;
874 }
875 else if (!exact && (result < 0)) {
876 if ( .... ) { // no saved OID, or this OID
877 // precedes the saved OID
878 // save this OID into 'lowest'
879 // save the results into Lowinpcb
880 // don't break, since we still need to look
881 // at the rest of the table
882 }
883 }
884
885The GET match handling is just as we've already seen - is this the requested
886OID or not. The GETNEXT case is more complicated. As well as considering
887whether this is a possible match (using the same test we've already seen),
888we also have to check whether this is a better match than anything we've
889already seen. This is done by comparing the current candidate (newname) with
890the best match found so far (lowest).
891Only if this extra comparison shows that the new OID is earlier than the
892saved one, do we need to save both the new OID, and any associated data
893(such as the inpcb block, and state flag). But having found one better
894match, we don't know that there isn't an even better one later on. So we
895can't break out of the enclosing loop - we need to keep going and examine
896all the remaining entries of the table.
897
898These two cases (the TCP and UDP tables) also show a more general style of
899indexing. Rather than simply appending a single index value to the OID
900prefix, these routines have to add the local four-octet IP address plus port
901(and the same for the remote end in the case of the TCP table). This is the
902purpose of the op and cp section of code that precedes the comparison.
903
904These two are probably among the most complex cases you are likely to
905encounter. If you can follow the code here, then you've probably cracked the
906problem of understanding how the agent works.
907
908Finally, the next part discusses how to implement a writable (or SETable)
909object in a MIB module.
910
911How to implement a SETable object
912
913Finally, the only remaining area to cover is that of setting data - the
914handling of SNMPSET. Particular care should be taken here for two reasons.
915
916Firstly, any errors in the earlier sections can have limited affect. The
917worst that is likely to happen is that the agent will either return invalid
918information, or possibly crash. Either way, this is unlikely to affect the
919operation of the workstation as a whole. If there are problems in the
920writing routine, the results could be catastrophic (particularly if writing
921data directly into kernel memory).
922
923Secondly, this is the least well understood area of the agent, at least by
924the author. There are relatively few variables that are defined as
925READ-WRITE in the relevant MIBs, and even fewer that have actually been
926implemented as such. I'm therefore describing this from a combination of my
927understanding of how SETs ought to work, and what's actually been done by
928others (which do not necessarily coincide).
929
930There are also subtle differences between the setting of simple scalar
931variables (or individual entries within a table), and the creation of a new
932row within a table. This will therefore be considered separately.
933
934With these caveats, and a healthy dose of caution, let us proceed. Note that
935the UCD-SNMP development team can accept no responsibility for any damage or
936loss resulting from either following or ignoring the information presented
937here. You coded it - you fix it!
938
939Write routine
940
941The heart of SET handling is the write_method parameter from the variable
942handling routine. This is a pointer to the relevant routine for setting the
943variable in question. Mib2c will generate one such routine for each setable
944variable. This routine should be declared using the template
945
946 int
947 write_variable(
948 int action,
949 u_char *var_val,
950 u_char var_val_type,
951 int var_val_len,
952 u_char *statP,
953 oid *name,
954 int name_len );
955
956Most of these parameters are fairly self explanatory:
957The last two hold the OID to be set, just as was passed to the main variable
958routine.
959
960The second, third and fourth parameters provide information about the new
961desired value, both the type, value and length. This is very similar to the
962way that results are returned from the main variable routine. In fact, this
963routine should also return the resulting value after the SET using these
964parameters. (In most cases this will be the same as the requested value, so
965nothing needs to be done).
966
967The return value of the routine is simply an indication of whether the
968current stage of the SET was successful or not. We'll come back to this in a
969minute. Note that it is the responsibility of this routine to check that the
970OID and value provided are appropriate for the variable being implemented.
971This includes (but is not limited to) checking:
972
973 * the OID is recognised as one this routine can handle
974 (this should be true if the routine only handles the one variable, and
975 there are no errors in the main variable routine or driving code, but
976 it does no harm to check).
977 * the value requested is the correct type expected for this OID
978 * the value requested is appropriate for this OID
979 (within particular ranges, suitable length, etc, etc)
980
981There are two parameters remaining to be considered.
982
983The fifth parameter is called statP and is only used when creating a new
984table row. When dealing with scalar values or single elements of tables, you
985can safely ignore this parameter.
986
987Actions
988
989The final parameter to consider is the first one - action To understand
990this, it's necessary to know a bit about how SETs are implemented.
991The design of SNMP calls for all variables in a SET request to be done "as
992if simultaneously" - i.e. they should all succeed or all fail. However, in
993practise, the variables are handled in succession. Thus, if one fails, it
994must be possible to "undo" any changes made to the other variables in the
995request.
996This is a well understood requirement in the database world, and is usually
997implemented using a "multi-stage commit". This is certainly the mechanism
998expected within the SNMP community (and has been made explicit in the work
999of the AgentX extensibility group). In other words, the routine to handle
1000setting a variable will be called more than once, and the routine must be
1001able to perform the appropriate actions depending on how far through the
1002process we currently are. This is determined by the value of the action
1003parameter.
1004
1005This is implemented using three basic phases:
1006
1007RESERVE is used to check the syntax of all the variables provided, and to
1008allocate any resources required for performing the SET. After this stage,
1009the expectation is that the set ought to succeed, though this is not
1010guaranteed.
1011(In fact, with the UCD agent, this is done in two passes - RESERVE1, and
1012RESERVE2, to allow for dependancies between variables).
1013
1014If any of these calls fail (in either pass) the write routines are called
1015again with the FREE action, to release any resources that have been
1016allocated. The agent will then return a failure response to the requesting
1017application.
1018
1019Assuming that the RESERVE phase was successful, the next stage is indicated
1020by the action value ACTION. This is used to actually implement the set
1021operation. However, this must either be done into temporary (persistent)
1022storage, or the previous value stored similarly, in case any of the
1023subsequent ACTION calls fail.
1024
1025If this does happen (for example due to an apparently valid, but
1026unacceptable value, or an unforeseen problem), then the list of write
1027routines are called again, with the UNDO action. This requires the routine
1028to reset the value that was changed to its previous value (assuming it was
1029actually changed), and then to release any resources that had been
1030allocated. As with the FREE phase, the agent will then return an indication
1031of the error to the requesting application.
1032
1033Only once the ACTION phase has completed successfully, can the final COMMIT
1034phase be run. This is used to complete any writes that were done into
1035temporary storage, and then release any allocated resources. Note that all
1036the code in this phase should be "safe" code that cannot possibly fail (cue
1037hysterical laughter). The whole intent of the ACTION/COMMIT division is that
1038all of the fallible code should be done in the ACTION phase, so that it can
1039be backed out if necessary.
1040
1041Table row creation
1042
1043What about creating new rows in a table, I hear you ask. Good Question. It
1044is believed that this uses the fifth parameter statP. This is described as
1045pointing to "the referenced object". Unfortunately, the author has no
1046experience of implementing such tables, doesn't fully understand the code,
1047and no-one has explained the mechanism to him.
1048
1049I'd rather not pontificate at length about how to create table rows, without
1050a little more inner conviction that what I say might stand a chance of being
1051vaguely correct. However, if pushed, I suspect that a null value for statP
1052is intended to indicate a new row should be created, while a non-null statP
1053would indicate a change to an existing row.
1054
1055But this could be completely wrong. And how you use this parameter is a
1056mystery to me. Anyone who has experience of doing this, please get in touch!
1057 ------------------------------------------------------------------------
1058And that's it. Congratulations for getting this far. If you understand
1059everything that's been said, then you now know as much as the rest of us
1060about the inner workings of the UCD-SNMP agent. (Well, very nearly).
1061All that remains is to try putting this into practise. Good luck!
1062
1063And if you've found this helpful, gifts of money, chocolate, alcohol, and
1064above all feedback, would be most appreciated :-)
1065
1066 ------------------------------------------------------------------------
1067Copyright 1999 - D.T.Shield. Not to be distributed without the explicit
1068permission of the author.