Writing a Plugin

The code in this topic uses calls from the following files in sandbox/src/junos/lib/libmsp-svcs.

Calls in sandbox/src/junos/lib/libsvcs-mgmt are also relevant.

You can find detailed documentation for these calls in the SDK Library Reference sections of this documentation, under libmsp-svcs and libsvcs-mgmt.

The functions for interacting with the policy database are in sandbox/src/lib/libmsp-svcs/h/jnx/msp_policy_db.h.

For an introduction to plugins, sessions, flows, and service sets, see Plugin Functionality and Service Chaining, in the Fundamentals topics.

Plugin Setup and Configuration

If your plugin will work with services and service sets, you configure them in your configuration DDL. For example:

services {
   acme-svc1 {
       */ rules for this service */
   }
   acme-svc2 {
       */ rules for this service */
   }
   service-set sset1 {

         extension-service acme-svc1 {    
              /* customer-specific-rules-here*/ ;
         } 

         extension-service acme-svc2 {    
              /* customer-specific-rules-here*/;
         } 


         service-order {
              acme-svc1;
              acme-svc2;
         }
         interface-service {        // Indicates the service is interface-style
            service-interface {
                ms-x/y/0.0;         // Specifies the Multiservices PIC on which to load this
                                    // policy 
            }
        }
   }
}
service-order defines the order in which services are applied for this service set. For all packets matching this service set, acme-svc1 is applied first, then acme-svc2. This ability to chain services is a major benefit to using plugins.

The chassis configuration includes values for the object cache and policy database size that you should also set. For example:

chassis {
    fpc 5 {
        pic 3 {
            adaptive-services {
                service-package {
                    extension-provider {
                        control-cores 1;
                        data-cores 6;
                        object-cache-size 512;
                        policy-db-size 128
                }
            }
        }
    }
}

For more information on control and data cores, see Architecture of the Multiservices PIC CPU; for more information about the object cache, see Memory Management.

policy-db-size defines the size in megabytes of policies used by the services you expect your plugin to be accessing. A policy for a service includes the service, service set, flow type, and plugin IDs in addition to policy rules specific to the service. The size should be less than the value of object-cache-size.

Package Configuration

You must also supply a package configuration XML file so that the system can build the plugin. (For details about where to place this file and how to reference it in the package manifest, see the Installation Guide.) After installation, the system places this file in /opt/sdk/pkg/package-name.xml on the Multiservices PIC.

In the following sample configuration file, the restart tag enclosing supported tells the system to restart the plugin upon failure. There is no default if you do not include this tag: the Multiservices PIC reboots upon failure unless you set the debugger-on-panic option in the configuration (for details on this option, see Using the debugger-on-panic Option). If you have a chain of plugins, the system disables restart unless all the package headers include the restart supported setting.

<pkg-config>
  <!-- Package configuration header - one per config file -->
  <header>
       <!-- config file syntax version -->
       <version>1.0</version>
       <!-- unique package name -->
       <name>jnx-msptest-plugin</name>
       <restart>supported</restart>
  </header>
 
  <!-- List of plugins -->
  <plug-in>
       <!-- unique plugin name -->
       <name>jnx-msptest-plugin1</name>
       <!-- plugin shared object path -->
       <path>/opt/sdk/lib/libjnx-msptest-plugin1.so</path>
       <!-- plugin entry point, this function is called when
            plugin source is loaded -->
       <entry>jnx_msptest_plugin1_register</entry>
   </plug-in>
</pkg-config>

Registering the Plugin

To register the plugin with the system, your code specifies both a control handler and a data handler, and then calls the function msvcs_plugin_register(), passing both handlers. You also pass a parameters structure, which is defined as follows:

typedef struct msvcs_plugin_params_s {
    u_int8_t     spp_name[MSVCS_MAX_PLUGIN_NAME_LEN];  /* Plugin name */
    msvcs_evh_t  spp_evh;                              /* Data event handler */
    msvcs_control_evh_t spp_control_evh;               /* Control event handler */
} msvcs_plugin_params_t;

The following code saves the plugin ID in a state structure that will later be attached to a session:

     params.spp_evh = &jnx_msptest_plugin1_data_handler;
     params.spp_control_evh = &jnx_msptest_plugin1_control_handler;
      
     pstate.plugin_id = msvcs_plugin_register(&params);

The package configuration XML file (see Package Configuration) includes the name of this registration function.

Data Handler

The data handler works with the session layer to process packets. Session functions are in the msvcs_session.h header file and include functions to open and close sessions; set up and retrieve session extension handles that attach state to sessions; manipulate the idle session timeout; ignore, drop, and reset sessions; and walk through the session table.

Attaching State to a Session

The following example attaches state to a session. The state structure to which the code passes a pointer is declared in the control handler as follows (the oc variable is an allocator handle for the object cache):

typedef struct jnx_msptest_plugin1_state_s {
          u_int32_t          plugin_id;
          int                flow_type;
          pconn_server_t     *ctrl_server;
          pconn_session_t    *data_session;
      
          msp_oc_handle_t     oc;
     } jnx_msptest_plugin1_state_t;

In additional code not shown here, the control and data components declare a variable, pstate, to point to this structure, which is then used wherever the code needs to know the plugin, flow, server, or session currently operating.

In this code, the session extension handle is a block of memory allocated by the plugin and attached to the session to track plugin-specific state.

switch(ev) {

    /* Initialize any global state */
    case MSVCS_EV_SM_INIT:
        return jnx_msptest_plugin1_sm_init(sctx);

    /* Initialize any per thread state */
    case MSVCS_EV_INIT:
        break;

    case MSVCS_EV_PKT_PROC:
        return MSVCS_ST_PKT_FORWARD;

    case MSVCS_EV_FIRST_PKT_PROC:
        /* Try to attach to the session */

        plugin_ext = msp_objcache_alloc(pstate.oc, 
                                        msvcs_state_get_cpuid(),
                                        sctx->sc_sset_id);

        if (plugin_ext) {
            if (msvcs_session_set_ext_handle(sctx->sc_session, 
                                             pstate.plugin_id,
                                             plugin_ext, NULL)) {
                msp_log(LOG_ERR, "failed to setup session extension\n");
            }
        } else {
            msp_log(LOG_ERR, "failed to allocate session extension\n");
        }

        msp_log(LOG_INFO, "setup session extension\n");

        return MSVCS_ST_PKT_FORWARD;
                                     
    case MSVCS_EV_SESSION_OPEN:
    case MSVCS_EV_SESSION_CLOSE:
        break;

    case MSVCS_EV_SESSION_DESTROY:

        if (msvcs_session_get_ext_handle(sctx->sc_session, 
                                         pstate.plugin_id,
                                         &plugin_ext, NULL)) {
            msp_log(LOG_ERR, "failed to get session extension\n");
        } else {
            msp_objcache_free(pstate.oc, plugin_ext, 
                              msvcs_state_get_cpuid(), 
                              sctx->sc_sset_id);
        }

        msp_log(LOG_INFO, "free session extension\n");
        
        break;
    }

    return MSVCS_ST_OK;
}

Control Handler

The control handler running on the Routing Engine interacts with the policy database to get the service set information and send it to the Multiservices PIC.

Engine Processing the Service Set Information

The service set resolution blob (SSRB) is created by the JUNOS daemon spd whenever a new service set configuration is committed. You write a daemon to retrieve the SSRB that contains the service set information and send the information to the Multiservices PIC.

To retrieve the SSRB, you use the callbacks in the libsvcs-mgmt library. When your plugin starts up, it first checks for any existing SSRB blobs in the kernel in a synchronous handler. The system reads through the blobs in the SSRB database and invokes the synchronous callback for each SSRB blob found. Your plugin can then supply its own data (if any), and continue performing its own work.

In addition, the plugin registers an asynchronous event handler to capture any new additions of SSRB blobs to the kernel. Whenever a new SSRB is added, or an existing one is deleted or changed, the system invokes the asynchronous callback. Again, the plugin can then supply its own data.

For more information on using these callbacks, see Using the libsvcs-mgmt Library.

In the sample code, the message event handler msptest_kcom_ssrb_async_handler, calls the msptest_ipc_send() function to send the message that activates or deactivates the service set on the Multiservices PIC using the data in the SSRB:

switch (kpb_msg->op) {
     
     case JUNOS_KCOM_PUB_BLOB_MSG_ADD:
     
         syslog(LOG_INFO,"public ssrb add event <%s, %d>",
           pub_ssrb->svc_set_name, pub_ssrb->svc_set_id);
    
         msptest_ipc_send(pub_ssrb, PLUGIN_SVC_SET_ACTIVATE);
    
         break;
      
...
  

msptest_ipc_send() calls libconn functions to establish a session, open a connection, and send the service set ID, service ID, and a generation number from the Routing Engine to the Multiservices PIC. (For introductory information about the libconn library, see IP Socket-Based Inter-Process Communication.)

Attaching to the Policy Database

On the Multiservices PIC, the control handler, which is invoked in the code that registers the plugin (see Registering the Plugin), creates a server to listen and attach to the policy database:

 if (ev == MSVCS_CONTROL_EV_INIT) {
        /* Create a server to listen to ipcs from Routing Engine */
        if (msp_policy_db_attach(&pdb_handle)) {
            logging(LOG_ERR, "%s: failed to create policy handle\n",
                    __FUNCTION__);
            return -1;
        }
...

      if (plugin_test_server_create(*(evContext *)sctx->scc_ev_ctxt) < 0) {
            return -1;

The code passes the server a field in the sctx context structure. This structure is used to deliver the data required by the plugin handler with each event. It is declared in the plugin's header file as follows:

typedef struct svcs_context_s {
    void       *sc_shm;          /* Shared memory handle */
    void       *sc_pkt;          /* Packet */
    void       *sc_session;      /* Session */
    int         sc_sid;          /* Session ID */
    int         sc_gennum;       /* Generation number */
    int         sc_pid;          /* Plugin ID */
    sctx_flags_t   sc_flags;     /* Flags */
} svcs_context_t;

The generation number (sc_gennum in this structure) is used to identify a particular service set instance across multiple deletions and additions. Each time the system creates an SSRB for a given service set, it creates a new generation number to identify that instance. The SSRB stores the generation number corresponding to the service set, and all the policy updates to the Multiservices PIC are expected to contain the generation number.

The plugin_test_server_create() code uses libconn functions to create a server, passing the callbacks receive_connection() and receive_message(). receive_connection() gets the connection and starts a libconn session. receive_message() retrieves the SSRB with the service set data whenever a message comes in.

The message is declared as follows:

typedef struct plugin_msg_s {
          u_int32_t sset_id;
          u_int32_t gennum;
          u_int32_t svc_id;
          u_int8_t  name[PLUGIN_MAX_NAME_LEN];
      } plugin_msg_t;

Parameters for accessing the policy database are declared in the msp_policy_db_params_t structure:

typedef struct msp_policy_db_params_s {
      
 msp_policy_db_handle_t   handle;     /* Handle of the policy DB */
 u_int32_t                svc_set_id; /* Service Set Id */
 u_int32_t                svc_id;     /* Service Id */
 u_int32_t                plugin_id;  /* Plugin Id */
 char                     plugin_name[MSP_PLUGIN_NAME_LEN]; /* Plugin name */
 u_int8_t                 policy_op;  /* Policy operation */
 union {
     msp_policy_db_add_params_t  add_params; /* Policy Add specific params */
     msp_policy_db_del_params_t  del_params; /* Policy Del specific params */
     msp_policy_db_get_params_t  get_params; /* Policy Get specific params */
     }op;
} msp_policy_db_params_t;

Your code should allocate a chunk of shared memory for the policy rules from the memory allocated for the policy database size in the configuration (see Plugin Setup and Configuration). You use the plugin functions to attach this chunk to the service set (see Attaching to the Policy Database). This memory is managed through the shm_allocator, which is passed through the MSVCS_CONTROL_EV_INIT event as scc_shm.

This code updates the service set with the new policy information and adds the policy. This example uses a dummy policy. Real code might (for example) point to data such as firewall rules, IDP signatures, or any other policy-related data needed by your packet processing functions.

    params.handle = pdb_handle;
    params.svc_set_id = pmsg->sset_id;
    params.svc_id = pmsg->svc_id;
    params.plugin_id = pstate->plugin_id;
    strncpy(params.plugin_name, pmsg->name, PLUGIN_MAX_NAME_LEN);

    if (msg->subtype == PLUGIN_SVC_SET_ACTIVATE) {
        params.policy_op = MSP_POLICY_DB_POLICY_ADD;
        params.op.add_params.gen_num = pmsg->gennum;
        params.op.add_params.policy = (void *)0x11223344;
    } else {
        params.policy_op = MSP_POLICY_DB_POLICY_DEL;
        params.op.del_params.gen_num = pmsg->gennum;
    }

         /* 
         * This is just a dummy policy, ideally this would be
         * some shared memory block that's accessible from data
         * as well.
         */
        params.op.add_params.policy = (void *)0x11223344;
    } else {

        /* 
         * Get the policy and free the memory that was allocated. 
         * This block of memory should be delayed to synchronize
         * with accesses from the data plane. Start a timer and
         * free after 3 seconds.
         */
        params.policy_op = MSP_POLICY_DB_POLICY_DEL;
        params.op.del_params.gen_num = pmsg->gennum;
    }

    err = msp_policy_db_op(&params);

...

2007-2009 Juniper Networks, Inc. All rights reserved. The information contained herein is confidential information of Juniper Networks, Inc., and may not be used, disclosed, distributed, modified, or copied without the prior written consent of Juniper Networks, Inc. in an express license. This information is subject to change by Juniper Networks, Inc. Juniper Networks, the Juniper Networks logo, and JUNOS are registered trademarks of Juniper Networks, Inc. in the United States and other countries. All other trademarks, service marks, registered trademarks, or registered service marks are the property of their respective owners.
Generated on Sun May 30 20:26:47 2010 for Juniper Networks Partner Solution Development Platform JUNOS SDK 10.2R1 by Doxygen 1.4.5