Solaris System Management Agent Developer's Guide

Chapter 7 Multiple Instance Modules

This chapter describes how to implement a module to allow more than one instance of the module to run on a host. The chapter also describes how to dynamically update modules with multiple instances.

The following topics are discussed:

Implementing Multiple Instances of a Module

For some types of modules, multiple instances of the module can be run simultaneously on a single host. For example, consider a module that monitors the status of a single printer. For a system with several printers, the printer-monitoring module must be loaded multiple times, once for each printer. In that scenario, several separate instances of the printer module must be running simultaneously. For such modules, you must distinguish the different instances that are loaded and running.

SNMPv2 introduced the concept of contexts to identify MIB modules that can have multiple instances. Each SNMP context is represented by a separate MIB subtree.

In SMA, you can implement multiple instances of a module only when the agent is configured to use SNMPv3. You need to specify an SNMPv3 user and password when loading and unloading modules. You specify an instance name by assigning a string to the contextName member of the netsnmp_handler_registration struct in the module.

The following procedure tells you how to implement multiple-instance modules. The procedure uses examples from demo_module_6, which you can adapt to your own module.

ProcedureHow To Implement Multiple Instance Modules

  1. As root, stop the agent if the agent is already running.


    # svcadm disable -t svc:/application/management/sma:default
    
  2. Set up an SNMPv3 user.

    For example, set up user name myuser with password mypassword as follows:


    # $ /usr/sfw/bin/net-snmp-config 
    --create-snmpv3-user myuser
    
    Enter authentication pass-phrase: 
    mypassword
     
    Enter encryption pass-phrase: 
    [press return to reuse the authentication pass-phrase]
  3. Edit the module to register context names that the module handles.

    Find the init_module routine in the module. Add code to register context names that the module handles.

    For example, you might add the following code:

    void 
    init_filesize(void) 
    { 
    // Declare the OID
    static oid filesize_oid[] = { 1,3,6,1,4,1,42,2,2,4,4,6,1,1,0 };
    
    
    // Declare a registration handler
    netsnmp_handler_registration *myreg1; 
    
    // Declare pointers to character arrays initialized
    // to the context name strings
    char *filexcon = "fileX"; 
    char *fileycon = "fileY"; 
    
    // Create a registration handler for the OID. 
    // filesize is the name of handler.
    // get_filesize is the function to call when an SNMP 
    // request for the OID is received, filesize_oid is the 
    // OID for which the handler is registered,
    // OID_LENGTH(filesize_oid) passes the length of the 
    // OID array to the agent.
    // HANDLER_CAN_RONLY is a constant that specifies that 
    // this handler only handles get requests.
    myreg1 = netsnmp_create_handler_registration 
    ("filesize", get_filesize, 
                 filesize_oid, OID_LENGTH(filesize_oid), 
                 HANDLER_CAN_RONLY);
                          
    // Assign the string fileX to the contextName member of the 
    // netsnmp_handler_registration struct
    myreg1->contextName=filexcon; 
    
    // Register the netsnmp_handler_registration struct with the
    // agent.  netsnmp_register_read_only is a helper function
    // that notifies the agent that this module only handles snmp 
    // get requests. 
    netsnmp_register_read_only_instance(myreg1);
    }
     

demo_module_6 Code Example for Multiple Instance Modules

The demo_module_6 code is located by default in /usr/demo/sma_snmp/demo_module_6. The README_demo_module_6 file within that directory contains instructions that describe how to perform the following tasks:

The demo_module_6 example shows how to write a module that registers an object in two different contexts. The example also shows how to check for the contextName in a request and return a different value depending on the value of the contextName.

demo_module_6 registers one object, filesize, in two different contexts, fileX, and fileY. The OIDs are registered by using a read-only instance handler helper. The OIDs do not need to be read-only. You could also register the OIDs using any of the SMA instance handler helper APIs.

The function get_filesize() is registered to handle GET requests for instances of the filesize object. The get_filesize() function checks the contextName in the reginfo structure that is passed to the function by the SMA. If the value of contextName is fileX, the function returns fileX_data, which has been set to the integer 111. If the value of contextName is fileY, the function returns fileY_data, which has been set to the integer 999.

Enabling Dynamic Updates to a Multiple Instance Module

When you perform a dynamic update to a module, you use a command to modify a module that is loaded and running with System Management Agent. The SMA does not provide a mechanism for dynamically adding and removing instances of managed objects in a multi-instance module. However, you can code your module to enable an administrator or application to use the snmpset command to update the module.

The demo_module_7 code example is used to show how to update a module that has been registered with the agent.

demo_module_7 Code Example for Dynamic Updates of Multiple Instance Modules

The demo_module_7 code example shows how to implement multiple instance modules. The demo is by default located in the directory /usr/demo/sma_snmp/demo_module_7. The README_demo_module_7 file in that directory contains instructions that describe how to perform the following tasks:

Modifying the demo_module_7 Code

The following procedure lists the steps you should follow to enable your module to be dynamically updated. The procedure uses examples from the demo_module_7.c code to illustrate each step. The code contains modifications to code templates that were produced by using mib2c on a MIB group in SDK-DEMO1–MIB.txt.

The demo_module_7 example registers new instances as contexts that represent files. Subsequent snmpget requests to these contexts retrieve the size of a specified file.

ProcedureHow to Enable Dynamic Update of a Multi-Instance Module

  1. Define two objects in the MIB for the module:

    • A string with read-write MAX-ACCESS that, when set, registers the specified string as a context name.

    • A string with read-write MAX-ACCESS that, when set, unregisters the specified string context name.

    For example, the following objects, which are defined in the SDK-DEMO1-MIB.txt file, register and unregister a context string that is set with an snmpset request:

     me1createContext OBJECT-TYPE
        SYNTAX      OCTET STRING (SIZE(0..1024))
        MAX-ACCESS  read-write
        STATUS      current
        DESCRIPTION
                    "String which when set, registers a context."
        ::= { me1MultiGroup 2 }
        
      me1removeContext OBJECT-TYPE
        SYNTAX      OCTET STRING (SIZE(0..1024))
        MAX-ACCESS  read-write
        STATUS      current
        DESCRIPTION
                    "String which when set, unregisters a context."
        ::= { me1MultiGroup 3 }
  2. In the module, declare the location within the MIB tree where the OIDs for the context objects should be registered.

    For example, the following code declares the OIDs for context strings:

    // Registers a context
       static oid me1createContext_oid[] =
      { 1,3,6,1,4,1,42,2,2,4,4,6,1,2,0 }; 
    
       // Unregisters a context
       static oid me1removeContext_oid[] =
      { 1,3,6,1,4,1,42,2,2,4,4,6,1,3,0 };
  3. In the module, register both OIDs of the context objects with the SMA.

    The following code shows an example:

        // Create a read-write registration handler named filesize, 
        // which calls the set_createContext function to service snmp requests
        // for the me1createContext_oid object.  The OID_LENGTH argument
        // calculates the length of the me1createContext_oid.
        myreg1 = netsnmp_create_handler_registration
                                           ("filesize",
             set_createContext,
             me1createContext_oid,
             OID_LENGTH(me1createContext_oid),
             HANDLER_CAN_RWRITE);
             
         
        // Create a read-write registration handler named filesize, 
        // which calls the set_removeContext function to service snmp requests
        // for the me1removeContext_oid object.  The OID_LENGTH argument
        // calculates the length of the me1removeContext_oid.
        myreg1 = netsnmp_create_handler_registration
            ("filesize",
              set_removeContext,
              me1removeContext_oid,
              OID_LENGTH(me1removeContext_oid),
              HANDLER_CAN_RWRITE);
  4. In the set_createContext() function handler code, extract the context name string from the SNMP message. Register the string as a new context.

    The following code shows an example:

    int
    set_createContext(netsnmp_mib_handler *handler,
          netsnmp_handler_registration *reginfo,
          netsnmp_agent_request_info *reqinfo,
          netsnmp_request_info *requests)
    {
    
    // This handler handles set requests on the m1createContext_oid.
    // The handler extracts the string from the snmp set request and
    // uses it to register a new context for the me1filesize_oid.
    //
    // For detailed info. on net-snmp set processing, 
    // see http://www.net-snmp.org/tutorial-5/toolkit/mib_module/index.html 
    // The agent calls each SNMP mode in sequence. We include a case
    // statement with only a break statement for each snmp set mode the
    // the agent handles.  In this example, we implement only the
    // snmp set action mode.  The case statement 
    // transfers control to the default: case when no other condition 
    // is satisfied. 
    netsnmp_handler_registration *myreg;
    char *context_names[256];
    
    switch(reqinfo->mode) {
    
    case MODE_SET_RESERVE1:
                break;
    case MODE_SET_RESERVE2:
                break;
    case MODE_SET_FREE:
                break;
    case MODE_SET_ACTION:
    
        // You must allocate memory for this variable because
        // the unregister_mib function frees it.
        filename = malloc(requests->requestvb->val_len + 1);
        snprintf(filename, sizeof(filename), "%s",  (u_char *)
                    requests->requestvb->val.string);
                    
        // Create a registration handler for the me1filesize_oid 
        // object in the new context name specified by 
        // the snmp set on the me1createContext OID. 
        myreg = netsnmp_create_handler_registration 
    	         ("test",
                 get_test,
                 me1filesize_oid,
                 OID_LENGTH(me1filesize_oid),
                 HANDLER_CAN_RONLY);
        myreg->contextName=filename;
        break;
    case MODE_SET_COMMIT:
                break;
    case MODE_SET_UNDO:
                break;
    
    default:
        /* we should never get here, so this is a really bad error */
        DEBUGMSGTL(("filesize", "default CALLED\n"));
    }
        return SNMP_ERR_NOERROR;
    }
  5. In the set_removeContext handler code, extract the context name string from the SNMP message. Unregister the context.

    The following code shows an example:

    // This handler handles set requests on the m1removeContext_oid     
    int
    set_removeContext(netsnmp_mib_handler *handler,
          netsnmp_handler_registration *reginfo,
          netsnmp_agent_request_info *reqinfo,
          netsnmp_request_info *requests)
    {
    
        static int PRIORITY = 0;
        static int SUB_ID = 0;
        static int RANGE_UBOUND = 0;
    
        switch(reqinfo->mode) {
    
            case MODE_SET_RESERVE1:
                break;
            case MODE_SET_RESERVE2:
                break;
            case MODE_SET_ACTION:
    
           snprintf(filename, sizeof(filename), "%s\n",  (u_char *)
                 requests->requestvb->val.string);
      unregister_mib_context(me1filesize_oid, OID_LENGTH(me1filesize_oid), 
           PRIORITY, SUB_ID, RANGE_UBOUND,
           filename);
           break;
    
               case MODE_SET_COMMIT:
                   break;
               case MODE_SET_FREE:
                   break;
               case MODE_SET_UNDO:
                   break;
    
        default:
            /* we should never get here, so this is a really bad error */
            DEBUGMSGTL(("filesize", "set_removeContext CALLED\n"));
        }
      return SNMP_ERR_NOERROR;
    }
  6. In the handler code for a new context, get the context string from the reginfo->contextName variable.

     /* This handler is called to handle snmp get requests for 
           the me1filesize_oid for a specified context name. */
       
    int
    get_test(netsnmp_mib_handler *handler,
          netsnmp_handler_registration *reginfo,
          netsnmp_agent_request_info *reqinfo,
          netsnmp_request_info *requests)
    {
    
    /* We are never called for a GETNEXT if it's registered as an
       "instance", as it's "magically" handled for us.  */
    
    /* An instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */
    
    struct stat buf;
    static int fd = 0;
    
    switch(reqinfo->mode) {
    
    case MODE_GET:
    
       if (strcmp(reginfo->contextName, filename) == 0) 
    
         // An open() for reading only returns without delay.
         if ((fd = open(filename, O_NONBLOCK | O_RDONLY)) == -1)
            DEBUGMSGTL(("filesize", "ERROR\n")); 
    
         if (fstat(fd, &buf) == -1)
           DEBUGMSGTL(("filesize", "ERROR\n")); 
         else
           DEBUGMSGTL(("filesize", "FILE SIZE IN BYTES = %d:\n", buf.st_size));
           
         snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, 
         (u_char *) &buf.st_size /* XXX: a pointer to the scalar's data */, 
         sizeof(buf.st_size) /* XXX: the length of the data in bytes */);
    
            break;
    
       default:
        /* we should never get here, so this is a really bad error */
           return SNMP_ERR_GENERR;
    }
    
    return SNMP_ERR_NOERROR;
    }

Registering New Instances in the Module

The demo_module_7 code example module registers context name strings that represent files. GET requests to these contexts retrieve the size of the file.

You do not need to edit the module to register new instances. The module can be dynamically updated to register new instances through the snmpset command. A management application passes the file name to the module by issuing an snmpset command, of the following format:


/usr/sfw/bin/snmpset -v 3 -u username -l authNoPriv -A "password" \
 hostname createContext_OID s "filename"

For example, the register_file script in the demo_module_7 directory issues a command that registers the file /usr/sfw sbin/snmpd as a new context name with the module:


/usr/sfw/bin/snmpset -v 3 -u myuser -l authNoPriv \
-A "mypassword" localhost .1.3.6.1.4.1.42.2.2.4.4.6.1.2.0 \
s "/usr/sfw/sbin/snmpd"

The module registers the set_createContext handler to handle incoming snmpset requests for the specified OID. The set_createContext handler registers the new file name as a context string in the contextName member of the netsnmp_registration_handler struct for the me1filesize_oid.

A management application can request the size of the file in blocks by issuing an snmpget command of the following format:


/usr/sfw/bin/snmpget -v 3 -u username -n contextname\
 -l authNoPriv -A "password" hostname me1filesize_oid

For example, the get_filesize script in the demo_module_7 directory issues a command that is similar to the following command:


/usr/sfw/bin/snmpget -m+SDK-DEMO6-MIB -v 3 -u myuser \
-n "/usr/sfw/sbin/snmpd" -l authNoPriv -A "mypassword" localhost \
.1.3.6.1.4.1.42.2.2.4.4.6.1.1.0