00001 /* 00002 * Template MIB group implementation - example.c 00003 * 00004 */ 00005 00006 /* 00007 * include important headers 00008 */ 00009 #include <net-snmp/net-snmp-config.h> 00010 #if HAVE_STDLIB_H 00011 #include <stdlib.h> 00012 #endif 00013 #if HAVE_STRING_H 00014 #include <string.h> 00015 #else 00016 #include <strings.h> 00017 #endif 00018 00019 #include <net-snmp/net-snmp-includes.h> 00020 #include <net-snmp/agent/net-snmp-agent-includes.h> 00021 00022 /* 00023 * header_generic() comes from here 00024 */ 00025 #include "util_funcs/header_generic.h" 00026 00027 /* 00028 * include our .h file 00029 */ 00030 #include "example.h" 00031 00032 00033 /* 00034 * Certain objects can be set via configuration file directives. 00035 * These variables hold the values for such objects, as they need to 00036 * be accessible to both the config handlers, and the callback routine. 00037 */ 00038 #define EXAMPLE_STR_LEN 300 00039 #define EXAMPLE_STR_DEFAULT "life the universe and everything" 00040 int example_int = 42; 00041 char example_str[EXAMPLE_STR_LEN]; 00042 00043 /* 00044 * Forward declarations for the config handlers 00045 */ 00046 void example_parse_config_exampleint(const char *token, 00047 char *cptr); 00048 void example_parse_config_examplestr(const char *token, 00049 char *cptr); 00050 void example_free_config_exampleint(void); 00051 void example_free_config_examplestr(void); 00052 00053 00054 /********************* 00055 * 00056 * Initialisation & common implementation functions 00057 * 00058 *********************/ 00059 00060 /* 00061 * This array structure defines a representation of the 00062 * MIB being implemented. 00063 * 00064 * The type of the array is 'struct variableN', where N is 00065 * large enough to contain the longest OID sub-component 00066 * being loaded. This will normally be the maximum value 00067 * of the fifth field in each line. In this case, the second 00068 * and third entries are both of size 2, so we're using 00069 * 'struct variable2' 00070 * 00071 * The supported values for N are listed in <agent/var_struct.h> 00072 * If the value you need is not listed there, simply use the 00073 * next largest that is. 00074 * 00075 * The format of each line is as follows 00076 * (using the first entry as an example): 00077 * 1: EXAMPLESTRING: 00078 * The magic number defined in the example header file. 00079 * This is passed to the callback routine and is used 00080 * to determine which object is being queried. 00081 * 2: ASN_OCTET_STR: 00082 * The type of the object. 00083 * Valid types are listed in <snmp_impl.h> 00084 * 3: NETSNMP_OLDAPI_RONLY (or NETSNMP_OLDAPI_RWRITE): 00085 * Whether this object can be SET or not. 00086 * 4: var_example: 00087 * The callback routine, used when the object is queried. 00088 * This will usually be the same for all objects in a module 00089 * and is typically defined later in this file. 00090 * 5: 1: 00091 * The length of the OID sub-component (the next field) 00092 * 6: {1}: 00093 * The OID sub-components of this entry. 00094 * In other words, the bits of the full OID that differ 00095 * between the various entries of this array. 00096 * This value is appended to the common prefix (defined later) 00097 * to obtain the full OID of each entry. 00098 */ 00099 struct variable2 example_variables[] = { 00100 {EXAMPLESTRING, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, 00101 var_example, 1, {1}}, 00102 {EXAMPLEINTEGER, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE, 00103 var_example, 2, {2, 1}}, 00104 {EXAMPLEOBJECTID, ASN_OBJECT_ID, NETSNMP_OLDAPI_RONLY, 00105 var_example, 2, {2, 2}}, 00106 {EXAMPLETIMETICKS, ASN_TIMETICKS, NETSNMP_OLDAPI_RONLY, 00107 var_example, 1, {3}}, 00108 {EXAMPLEIPADDRESS, ASN_IPADDRESS, NETSNMP_OLDAPI_RONLY, 00109 var_example, 1, {4}}, 00110 {EXAMPLECOUNTER, ASN_COUNTER, NETSNMP_OLDAPI_RONLY, 00111 var_example, 1, {5}}, 00112 {EXAMPLEGAUGE, ASN_GAUGE, NETSNMP_OLDAPI_RONLY, 00113 var_example, 1, {6}}, 00114 {EXAMPLETRIGGERTRAP, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE, 00115 var_example, 1, {7}}, 00116 {EXAMPLETRIGGERTRAP2, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE, 00117 var_example, 1, {8}} 00118 }; 00119 00120 /* 00121 * This array defines the OID of the top of the mib tree that we're 00122 * registering underneath. 00123 * Note that this needs to be the correct size for the OID being 00124 * registered, so that the length of the OID can be calculated. 00125 * The format given here is the simplest way to achieve this. 00126 */ 00127 oid example_variables_oid[] = { 1, 3, 6, 1, 4, 1, 2021, 254 }; 00128 00129 00130 00131 /* 00132 * This function is called at the time the agent starts up 00133 * to do any initializations that might be required. 00134 * 00135 * In theory it is optional and can be omitted if no 00136 * initialization is needed. In practise, every module 00137 * will need to register itself (or the objects being 00138 * implemented will not appear in the MIB tree), and this 00139 * registration is typically done here. 00140 * 00141 * If this function is added or removed, you must re-run 00142 * the configure script, to detect this change. 00143 */ 00144 void 00145 init_example(void) 00146 { 00147 /* 00148 * Register ourselves with the agent to handle our mib tree. 00149 * The arguments are: 00150 * descr: A short description of the mib group being loaded. 00151 * var: The variable structure to load. 00152 * (the name of the variable structure defined above) 00153 * vartype: The type of this variable structure 00154 * theoid: The OID pointer this MIB is being registered underneath. 00155 */ 00156 REGISTER_MIB("example", example_variables, variable2, 00157 example_variables_oid); 00158 00159 00160 /* 00161 * Register config handlers for the two objects that can be set 00162 * via configuration file directive. 00163 * Also set a default value for the string object. Note that the 00164 * example integer variable was initialised above. 00165 */ 00166 strncpy(example_str, EXAMPLE_STR_DEFAULT, EXAMPLE_STR_LEN); 00167 00168 snmpd_register_config_handler("exampleint", 00169 example_parse_config_exampleint, 00170 example_free_config_exampleint, 00171 "exampleint value"); 00172 snmpd_register_config_handler("examplestr", 00173 example_parse_config_examplestr, 00174 example_free_config_examplestr, 00175 "examplestr value"); 00176 snmpd_register_config_handler("examplestring", 00177 example_parse_config_examplestr, 00178 example_free_config_examplestr, 00179 "examplestring value"); 00180 00181 /* 00182 * One common requirement is to read values from the kernel. 00183 * This is usually initialised here, to speed up access when the 00184 * information is read in, as a response to an incoming request. 00185 * 00186 * This module doesn't actually use this mechanism, 00187 * so this call is commented out here. 00188 */ 00189 /* 00190 * auto_nlist( "example_symbol", 0, 0 ); 00191 */ 00192 } 00193 00194 /********************* 00195 * 00196 * Configuration file handling functions 00197 * 00198 *********************/ 00199 00200 void 00201 example_parse_config_exampleint(const char *token, char *cptr) 00202 { 00203 example_int = atoi(cptr); 00204 } 00205 00206 void 00207 example_parse_config_examplestr(const char *token, char *cptr) 00208 { 00209 /* 00210 * Make sure the string fits in the space allocated for it. 00211 */ 00212 if (strlen(cptr) < EXAMPLE_STR_LEN) 00213 strcpy(example_str, cptr); 00214 else { 00215 /* 00216 * Truncate the string if necessary. 00217 * An alternative approach would be to log an error, 00218 * and discard this value altogether. 00219 */ 00220 strncpy(example_str, cptr, EXAMPLE_STR_LEN - 4); 00221 example_str[EXAMPLE_STR_LEN - 4] = 0; 00222 strcat(example_str, "..."); 00223 example_str[EXAMPLE_STR_LEN - 1] = 0; 00224 } 00225 } 00226 00227 /* 00228 * We don't need to do anything special when closing down 00229 */ 00230 void 00231 example_free_config_exampleint(void) 00232 { 00233 } 00234 00235 void 00236 example_free_config_examplestr(void) 00237 { 00238 } 00239 00240 /********************* 00241 * 00242 * System specific implementation functions 00243 * 00244 *********************/ 00245 00246 /* 00247 * Define the callback function used in the example_variables structure. 00248 * This is called whenever an incoming request refers to an object 00249 * within this sub-tree. 00250 * 00251 * Four of the parameters are used to pass information in. 00252 * These are: 00253 * vp The entry from the 'example_variables' array for the 00254 * object being queried. 00255 * name The OID from the request. 00256 * length The length of this OID. 00257 * exact A flag to indicate whether this is an 'exact' request 00258 * (GET/SET) or an 'inexact' one (GETNEXT/GETBULK). 00259 * 00260 * Four of the parameters are used to pass information back out. 00261 * These are: 00262 * name The OID being returned. 00263 * length The length of this OID. 00264 * var_len The length of the answer being returned. 00265 * write_method A pointer to the SET function for this object. 00266 * 00267 * Note that name & length serve a dual purpose in both roles. 00268 */ 00269 00270 u_char * 00271 var_example(struct variable *vp, 00272 oid * name, 00273 size_t * length, 00274 int exact, size_t * var_len, WriteMethod ** write_method) 00275 { 00276 /* 00277 * The result returned from this function needs to be a pointer to 00278 * static data (so that it can be accessed from outside). 00279 * Define suitable variables for any type of data we may return. 00280 */ 00281 static char string[EXAMPLE_STR_LEN]; /* for EXAMPLESTRING */ 00282 static oid oid_ret[8]; /* for EXAMPLEOBJECTID */ 00283 static long long_ret; /* for everything else */ 00284 00285 /* 00286 * Before returning an answer, we need to check that the request 00287 * refers to a valid instance of this object. The utility routine 00288 * 'header_generic' can be used to do this for scalar objects. 00289 * 00290 * This routine 'header_simple_table' does the same thing for "simple" 00291 * tables. (See the AGENT.txt file for the definition of a simple table). 00292 * 00293 * Both these utility routines also set up default values for the 00294 * return arguments (assuming the check succeeded). 00295 * The name and length are set suitably for the current object, 00296 * var_len assumes that the result is an integer of some form, 00297 * and write_method assumes that the object cannot be set. 00298 * 00299 * If these assumptions are correct, this callback routine simply 00300 * needs to return a pointer to the appropriate value (using 'long_ret'). 00301 * Otherwise, 'var_len' and/or 'write_method' should be set suitably. 00302 */ 00303 DEBUGMSGTL(("example", "var_example entered\n")); 00304 if (header_generic(vp, name, length, exact, var_len, write_method) == 00305 MATCH_FAILED) 00306 return NULL; 00307 00308 00309 /* 00310 * Many object will need to obtain data from the operating system in 00311 * order to return the appropriate value. Typically, this is done 00312 * here - immediately following the 'header' call, and before the 00313 * switch statement. This is particularly appropriate if a single 00314 * interface call can return data for all the objects supported. 00315 * 00316 * This example module does not rely on external data, so no such 00317 * calls are needed in this case. 00318 */ 00319 00320 /* 00321 * Now use the magic number from the variable pointer 'vp' to 00322 * select the particular object being queried. 00323 * In each case, one of the static objects is set up with the 00324 * appropriate information, and returned mapped to a 'u_char *' 00325 */ 00326 switch (vp->magic) { 00327 case EXAMPLESTRING: 00328 strcpy(string, example_str); 00329 /* 00330 * Note that the assumption that the answer will be an 00331 * integer does not hold true in this case, so the length 00332 * of the answer needs to be set explicitly. 00333 */ 00334 *var_len = strlen(string); 00335 return (u_char *) string; 00336 00337 case EXAMPLEINTEGER: 00338 /* 00339 * Here the length assumption is correct, but the 00340 * object is writeable, so we need to set the 00341 * write_method pointer as well as the current value. 00342 */ 00343 long_ret = example_int; 00344 *write_method = write_exampleint; 00345 return (u_char *) & long_ret; 00346 00347 case EXAMPLEOBJECTID: 00348 oid_ret[0] = 1; 00349 oid_ret[1] = 3; 00350 oid_ret[2] = 6; 00351 oid_ret[3] = 1; 00352 oid_ret[4] = 4; 00353 oid_ret[5] = oid_ret[6] = oid_ret[7] = 42; 00354 /* 00355 * Again, the assumption regarding the answer length is wrong. 00356 */ 00357 *var_len = 8 * sizeof(oid); 00358 return (u_char *) oid_ret; 00359 00360 case EXAMPLETIMETICKS: 00361 /* 00362 * Here both assumptions are correct, 00363 * so we just need to set up the answer. 00364 */ 00365 long_ret = 363136200; /* 42 days, 42 minutes and 42.0 seconds */ 00366 return (u_char *) & long_ret; 00367 00368 case EXAMPLEIPADDRESS: 00369 /* 00370 * ipaddresses get returned as a long. ick 00371 */ 00372 /* 00373 * we're returning 127.0.0.1 00374 */ 00375 long_ret = ntohl(INADDR_LOOPBACK); 00376 return (u_char *) & long_ret; 00377 00378 case EXAMPLECOUNTER: 00379 long_ret = 42; 00380 return (u_char *) & long_ret; 00381 00382 case EXAMPLEGAUGE: 00383 long_ret = 42; /* Do we detect a theme running through these answers? */ 00384 return (u_char *) & long_ret; 00385 00386 case EXAMPLETRIGGERTRAP: 00387 /* 00388 * This object is essentially "write-only". 00389 * It only exists to trigger the sending of a trap. 00390 * Reading it will always return 0. 00391 */ 00392 long_ret = 0; 00393 *write_method = write_exampletrap; 00394 return (u_char *) & long_ret; 00395 00396 case EXAMPLETRIGGERTRAP2: 00397 /* 00398 * This object is essentially "write-only". 00399 * It only exists to trigger the sending of a v2 trap. 00400 * Reading it will always return 0. 00401 */ 00402 long_ret = 0; 00403 *write_method = write_exampletrap2; 00404 return (u_char *) & long_ret; 00405 00406 default: 00407 /* 00408 * This will only be triggered if there's a problem with 00409 * the coding of the module. SNMP requests that reference 00410 * a non-existant OID will be directed elsewhere. 00411 * If this branch is reached, log an error, so that 00412 * the problem can be investigated. 00413 */ 00414 DEBUGMSGTL(("snmpd", "unknown sub-id %d in examples/var_example\n", 00415 vp->magic)); 00416 } 00417 /* 00418 * If we fall through to here, fail by returning NULL. 00419 * This is essentially a continuation of the 'default' case above. 00420 */ 00421 return NULL; 00422 } 00423 00424 /********************* 00425 * 00426 * Writeable object SET handling routines 00427 * 00428 *********************/ 00429 int 00430 write_exampleint(int action, 00431 u_char * var_val, 00432 u_char var_val_type, 00433 size_t var_val_len, 00434 u_char * statP, oid * name, size_t name_len) 00435 { 00436 /* 00437 * Define an arbitrary maximum permissible value 00438 */ 00439 #define MAX_EXAMPLE_INT 100 00440 static long intval; 00441 static long old_intval; 00442 00443 switch (action) { 00444 case RESERVE1: 00445 /* 00446 * Check that the value being set is acceptable 00447 */ 00448 if (var_val_type != ASN_INTEGER) { 00449 DEBUGMSGTL(("example", "%x not integer type", var_val_type)); 00450 return SNMP_ERR_WRONGTYPE; 00451 } 00452 if (var_val_len > sizeof(long)) { 00453 DEBUGMSGTL(("example", "wrong length %x", var_val_len)); 00454 return SNMP_ERR_WRONGLENGTH; 00455 } 00456 00457 intval = *((long *) var_val); 00458 if (intval > MAX_EXAMPLE_INT) { 00459 DEBUGMSGTL(("example", "wrong value %x", intval)); 00460 return SNMP_ERR_WRONGVALUE; 00461 } 00462 break; 00463 00464 case RESERVE2: 00465 /* 00466 * This is conventially where any necesary 00467 * resources are allocated (e.g. calls to malloc) 00468 * Here, we are using static variables 00469 * so don't need to worry about this. 00470 */ 00471 break; 00472 00473 case FREE: 00474 /* 00475 * This is where any of the above resources 00476 * are freed again (because one of the other 00477 * values being SET failed for some reason). 00478 * Again, since we are using static variables 00479 * we don't need to worry about this either. 00480 */ 00481 break; 00482 00483 case ACTION: 00484 /* 00485 * Set the variable as requested. 00486 * Note that this may need to be reversed, 00487 * so save any information needed to do this. 00488 */ 00489 old_intval = example_int; 00490 example_int = intval; 00491 break; 00492 00493 case UNDO: 00494 /* 00495 * Something failed, so re-set the 00496 * variable to its previous value 00497 * (and free any allocated resources). 00498 */ 00499 example_int = old_intval; 00500 break; 00501 00502 case COMMIT: 00503 /* 00504 * Everything worked, so we can discard any 00505 * saved information, and make the change 00506 * permanent (e.g. write to the config file). 00507 * We also free any allocated resources. 00508 * 00509 * In this case, there's nothing to do. 00510 */ 00511 break; 00512 00513 } 00514 return SNMP_ERR_NOERROR; 00515 } 00516 00517 int 00518 write_exampletrap(int action, 00519 u_char * var_val, 00520 u_char var_val_type, 00521 size_t var_val_len, 00522 u_char * statP, oid * name, size_t name_len) 00523 { 00524 long intval; 00525 00526 DEBUGMSGTL(("example", "write_exampletrap entered: action=%d\n", 00527 action)); 00528 switch (action) { 00529 case RESERVE1: 00530 /* 00531 * The only acceptable value is the integer 1 00532 */ 00533 if (var_val_type != ASN_INTEGER) { 00534 DEBUGMSGTL(("example", "%x not integer type", var_val_type)); 00535 return SNMP_ERR_WRONGTYPE; 00536 } 00537 if (var_val_len > sizeof(long)) { 00538 DEBUGMSGTL(("example", "wrong length %x", var_val_len)); 00539 return SNMP_ERR_WRONGLENGTH; 00540 } 00541 00542 intval = *((long *) var_val); 00543 if (intval != 1) { 00544 DEBUGMSGTL(("example", "wrong value %x", intval)); 00545 return SNMP_ERR_WRONGVALUE; 00546 } 00547 break; 00548 00549 case RESERVE2: 00550 /* 00551 * No resources are required.... 00552 */ 00553 break; 00554 00555 case FREE: 00556 /* 00557 * ... so no resources need be freed 00558 */ 00559 break; 00560 00561 case ACTION: 00562 /* 00563 * Having triggered the sending of a trap, 00564 * it would be impossible to revoke this, 00565 * so we can't actually invoke the action here. 00566 */ 00567 break; 00568 00569 case UNDO: 00570 /* 00571 * We haven't done anything yet, 00572 * so there's nothing to undo 00573 */ 00574 break; 00575 00576 case COMMIT: 00577 /* 00578 * Everything else worked, so it's now safe 00579 * to trigger the trap. 00580 * Note that this is *only* acceptable since 00581 * the trap sending routines are "failsafe". 00582 * (In fact, they can fail, but they return no 00583 * indication of this, which is the next best thing!) 00584 */ 00585 DEBUGMSGTL(("example", "write_exampletrap sending the trap\n")); 00586 send_easy_trap(SNMP_TRAP_ENTERPRISESPECIFIC, 99); 00587 DEBUGMSGTL(("example", "write_exampletrap trap sent\n")); 00588 break; 00589 00590 } 00591 return SNMP_ERR_NOERROR; 00592 } 00593 00594 /* 00595 * this documents how to send a SNMPv2 (and higher) trap via the 00596 * send_v2trap() API. 00597 * 00598 * Coding SNMP-v2 Trap: 00599 * 00600 * The SNMPv2-Trap PDU contains at least a pair of object names and 00601 * values: - sysUpTime.0 whose value is the time in hundredths of a 00602 * second since the netwok management portion of system was last 00603 * reinitialized. - snmpTrapOID.0 which is part of the trap group SNMPv2 00604 * MIB whose value is the object-id of the specific trap you have defined 00605 * in your own MIB. Other variables can be added to caracterize the 00606 * trap. 00607 * 00608 * The function send_v2trap adds automaticallys the two objects but the 00609 * value of snmpTrapOID.0 is 0.0 by default. If you want to add your trap 00610 * name, you have to reconstruct this object and to add your own 00611 * variable. 00612 * 00613 */ 00614 00615 00616 00617 int 00618 write_exampletrap2(int action, 00619 u_char * var_val, 00620 u_char var_val_type, 00621 size_t var_val_len, 00622 u_char * statP, oid * name, size_t name_len) 00623 { 00624 long intval; 00625 00626 /* 00627 * these variales will be used when we send the trap 00628 */ 00629 oid objid_snmptrap[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 }; /* snmpTrapOID.0 */ 00630 oid demo_trap[] = { 1, 3, 6, 1, 4, 1, 2021, 13, 990 }; /*demo-trap */ 00631 oid example_string_oid[] = 00632 { 1, 3, 6, 1, 4, 1, 2021, 254, 1, 0 }; 00633 static netsnmp_variable_list var_trap; 00634 static netsnmp_variable_list var_obj; 00635 00636 DEBUGMSGTL(("example", "write_exampletrap2 entered: action=%d\n", 00637 action)); 00638 switch (action) { 00639 case RESERVE1: 00640 /* 00641 * The only acceptable value is the integer 1 00642 */ 00643 if (var_val_type != ASN_INTEGER) { 00644 DEBUGMSGTL(("example", "%x not integer type", var_val_type)); 00645 return SNMP_ERR_WRONGTYPE; 00646 } 00647 if (var_val_len > sizeof(long)) { 00648 DEBUGMSGTL(("example", "wrong length %x", var_val_len)); 00649 return SNMP_ERR_WRONGLENGTH; 00650 } 00651 00652 intval = *((long *) var_val); 00653 if (intval != 1) { 00654 DEBUGMSGTL(("example", "wrong value %x", intval)); 00655 return SNMP_ERR_WRONGVALUE; 00656 } 00657 break; 00658 00659 case RESERVE2: 00660 /* 00661 * No resources are required.... 00662 */ 00663 break; 00664 00665 case FREE: 00666 /* 00667 * ... so no resources need be freed 00668 */ 00669 break; 00670 00671 case ACTION: 00672 /* 00673 * Having triggered the sending of a trap, 00674 * it would be impossible to revoke this, 00675 * so we can't actually invoke the action here. 00676 */ 00677 break; 00678 00679 case UNDO: 00680 /* 00681 * We haven't done anything yet, 00682 * so there's nothing to undo 00683 */ 00684 break; 00685 00686 case COMMIT: 00687 /* 00688 * Everything else worked, so it's now safe 00689 * to trigger the trap. 00690 * Note that this is *only* acceptable since 00691 * the trap sending routines are "failsafe". 00692 * (In fact, they can fail, but they return no 00693 * indication of this, which is the next best thing!) 00694 */ 00695 00696 /* 00697 * trap definition objects 00698 */ 00699 00700 var_trap.next_variable = &var_obj; /* next variable */ 00701 var_trap.name = objid_snmptrap; /* snmpTrapOID.0 */ 00702 var_trap.name_length = sizeof(objid_snmptrap) / sizeof(oid); /* number of sub-ids */ 00703 var_trap.type = ASN_OBJECT_ID; 00704 var_trap.val.objid = demo_trap; /* demo-trap objid */ 00705 var_trap.val_len = sizeof(demo_trap); /* length in bytes (not number of subids!) */ 00706 00707 00708 /* 00709 * additional objects 00710 */ 00711 00712 00713 var_obj.next_variable = NULL; /* No more variables after this one */ 00714 var_obj.name = example_string_oid; 00715 var_obj.name_length = sizeof(example_string_oid) / sizeof(oid); /* number of sub-ids */ 00716 var_obj.type = ASN_OCTET_STR; /* type of variable */ 00717 var_obj.val.string = example_str; /* value */ 00718 var_obj.val_len = strlen(example_str); 00719 DEBUGMSGTL(("example", "write_exampletrap2 sending the v2 trap\n")); 00720 send_v2trap(&var_trap); 00721 DEBUGMSGTL(("example", "write_exampletrap2 v2 trap sent\n")); 00722 00723 break; 00724 00725 } 00726 return SNMP_ERR_NOERROR; 00727 }
1.5.7.1
Last modified: Tuesday, 23-Dec-2025 17:22:04 UTC
For questions regarding web content and site functionality, please write to the net-snmp-users mail list.