Login | Register

Documentation

Documentation -> Development Manual 3.1 -> Module Development

This page has been visited 1920 times.


Pages for other versions: devel 3.3 3.2 3.1 Older versions:


Module Development

1.  Introduction

Due to the OpenSIPS modular architecture, the easiest way to add new features ( new parameters, script functions, MI function etc ) is to incorporate them into a new OpenSIPS module.
An OpenSIPS module is actually a shared library ( .so file ) which OpenSIPS can dinamically load at OpenSIPS startup, if the module is loaded from within the OpenSIPS script, by using the loadmodule directive :

loadmodule "mynewmod.so"


Upon loading a new module, the OpenSIPS core will lookup the exports variable, of type struct module_exports. This structure and variable are of the utmost importance when developing a new OpenSIPS module

        struct module_exports{
              char* name;                     /*!< null terminated module name */
              char *version;                  /*!< module version */
              char *compile_flags;            /*!< compile flags used on the module */
              unsigned int dlflags;           /*!< flags for dlopen */

              cmd_export_t* cmds;             /*!< null terminated array of the exported
                                           commands */

              param_export_t* params;         /*!< null terminated array of the exported
                                           module parameters */


              stat_export_t* stats;           /*!< null terminated array of the exported
                                           module statistics */


              mi_export_t* mi_cmds;           /*!< null terminated array of the exported
                                           MI functions */


              pv_export_t* items;             /*!< null terminated array of the exported
                                           module items (pseudo-variables) */


              proc_export_t* procs;           /*!< null terminated array of the additional
                                           processes reqired by the module */


              init_function init_f;           /*!< Initialization function */
              response_function response_f;   /*!< function used for responses,
                                           returns yes or no; can be null */

              destroy_function destroy_f;     /*!< function called when the module should
                                           be "destroyed", e.g: on opensips exit */

              child_init_function init_child_f;/*!< function called by all processes
                                            after the fork */

        };


The module_exports contents ( along with the above coments ) are self-explanatory.
Further on, we will discuss about each member of the module_exports structure and how it is meant to be used when building a nw OpenSIPS. Purely as an example, See below the exports used by the dialog module

        struct module_exports exports= {
              "dialog",        /* module's name */
              MODULE_VERSION,
              DEFAULT_DLFLAGS, /* dlopen flags */
              cmds,            /* exported functions */
              mod_params,      /* param exports */
              mod_stats,       /* exported statistics */
              mi_cmds,         /* exported MI functions */
              mod_items,       /* exported pseudo-variables */
              0,               /* extra processes */
              mod_init,        /* module initialization function */
              0,               /* reply processing function */
              mod_destroy,
              child_init       /* per-child init function */
        };
2.  Compiling a module

Further on, we will be following the various options we have in building our new module, named ournewmod.
After creating the ournewmod folder in the OpenSIPS modules/ path, we should create a Makefile for our module, located also in the ournewmod folder. The most basic Makefile for a module with no external library dependencies is the following :

# $Id$
#
# WARNING: do not run this directly, it should be run by the master Makefile

include ../../Makefile.defs
auto_gen=
NAME=ournewmod.so
LIBS=

include ../../Makefile.modules


If the module has external library dependencies, they should be linked in the module's Makefile as well. Eg, the cachedb_memcached module :

include ../../Makefile.defs
auto_gen=
NAME=cachedb_memcached.so
DEFS+=-I$(LOCALBASE)/include
LIBS=-L$(LOCALBASE)/lib -lmemcached

include ../../Makefile.modules


If our new module depends on external libraries, the module must not be left to compile by default !

This must be done by editing the Makefile.conf.template file - where we specify which modules are to not be compiled by default, along with the dependencies they have.
We should add a new line to Makefile.conf.template, with the following format :

modulename= Module Description | module dependency


Also, we should modify Makefile.conf.template to add our new module's name to the exclude_modules list of modules that will not compile by default.

3.  Initializing the module

In the context of initializing our new module, there are two types of functions that will help us :

3.1  mod_init

This function must be specified in the init_f member of our module_exports exports structure.
It is ran from within a single process' context ( the attendant ), after the full OpenSIPS config has been parsed ( our own module parameters included ), and all the helper APIs are initialized at this point ( shared memory, locking, timer processes, etc ).
The purpose of the function is to check the integrity of how the module was configured from the OpenSIPS script, to initialize needed structures, etc. Also, some critical resources ( like new timer processes, see above section ) can ONLY be initialized from the mod_init() function of our new modules.
Prototype of the function is

/* MUST return 0 in case of success, anything else in case of error */
typedef int (*init_function)(void);


Since this function is called from the context of only one process, after OpenSIPS forks, each OpenSIPS process will receive a copy of what the attendat process had.
Due to this, do NOT use the mod_init function to initialize structures / connections that should have different instances for each OpenSIPS process.

3.2  child_init

This function must be specified in the init_child_f member of our module_exports exports structure.
It is ran from within the context of ALL OpenSIPS processes, right after the new processes has been forked.
The purpose of the function is to create various connectors ( db, cachedb, etc ) which should be different for each created OpenSIPS process and to initialize various other variables depending on the OpenSIPS process context we are currently in.
Prototype of the function is

/* MUST return 0 in case of success, anything else in case of error */
typedef int (*child_init_function)(int rank);


The function will receive an integer parameter, indicating the type of OpenSIPS process that is currently running our function. Below are all the available options :

#define PROC_MAIN      0  /* Main opensips process */
#define PROC_TIMER    -1  /* Timer attendant process */
#define PROC_MODULE   -2  /* Extra process requested by modules */
#define PROC_TCP_MAIN -4  /* TCP main process */
#define PROC_BIN      -8  /* Any binary interface listener */


A positive value for our rank parameter denotes that we are currently operating in the context of an OpenSIPS listener ( UDP, TCP or SCTP ).

If we must do time consuming operations ( eg. load many rows from a database ) , we should be doing this inside the child_init() function for a single process ( eg. rank == 1 would be the context of our first UDP listener) , instead of the mod_init() function.
This will make OpenSIPS startup faster, and also we will be able to process traffic faster ( at least the traffic that does not explicitly depend on having our module's internal data fully populated ).

4.  Destroying the module

This function must be specified in the destroy_function member of our module_exports exports structure.
It is ran from within a single process' context ( the attendant ), when OpenSIPS is about to shutdown.
The purpose of the function is to cleanup various resources that OpenSIPS has been using ( shared memory, DB connections, etc ). Also, the destroy_function is a good time to save whatever state that the module was keeping into a persistent storage, so that they can be loaded afterwards, when OpenSIPS starts. ( eg. the dialog module saves all the dialog states in the database in the destroy function ).
Prototype of the function is

typedef void (*destroy_function)();
5.  Adding module Parameters

Adding new module parameters is done by populating the params member in our module's exports structure. At OpenSIPS startup, OpenSIPS will parse the provided script and set our internal variables accordingly to what the OpenSIPS script writer has configured. The parameter definition ( param_export_t ) is the following :

struct param_export_ {
        char* name;             /*!< null terminated param. name */
        modparam_t type;        /*!< param. type */
        void* param_pointer;    /*!< pointer to the param. memory location */
};


The OpenSIPS modules can export both string and integer parameters.
Find below some examples for each of them. Note how the param_export_t structure does not receive any length parameters as to indicate how many parameters the module exports - rather, the structure must end with a row full of 0,

int enable_stats = 0;
static str db_url = {NULL,0};

static param_export_t mod_params[]={
        { "enable_stats",          INT_PARAM, &enable_stats         },
        { "db_url",                STR_PARAM, &db_url.s             },
        { 0,0,0 }
}


Example of setting these parameters from the OpenSIPS script, for our ournewmod module.

loadmodule "ournewmod.so"

modparam("ournewmod","enable_stats", 1)
modparam("ournewmod","db_url","mysql://vlad:mypw@localhost/opensips")


Also, OpenSIPS supports triggering a module's internal function when the script writer set a particular parameter. This can prove useful if the provided parameter needs to be converted to a form that the module knows how to process, or simply if one parameter should be able to be set multiple times.
Find an example of such parameters below, where an NoSQL URL can be set multiple times in order to initialize as many back-end connections :

static param_export_t params[]={
        { "cachedb_url",                 STR_PARAM|USE_FUNC_PARAM, (void *)&set_connection},
        {0,0,0}
};

int set_connection(unsigned int type, void *val)
{
        LM_INFO("Our parameter has been set : value is %s\n",(char *)val);
        /* continue processing, eg : add our new parameter to a list to be further processed */
}
6.  Adding module Functions

Adding new module parameters is done by populating the cmds member in our module's exports structure.
The exported functions structure is the following :

struct cmd_export_ {
        char* name;             /* null terminated command name */
        cmd_function function;  /* pointer to the corresponding function */
        int param_no;           /* number of parameters used by the function */
        fixup_function fixup;   /* pointer to the function called to "fix" the
                                                           parameters */

        free_fixup_function
                                free_fixup; /* pointer to the function called to free the
                                                           "fixed" parameters */

        int flags;              /* Function flags */
};


Very similar to the params member in the exports structure, the cmds member MUST be NULL terminated.
At startup, OpenSIPS tries to locate each function called in the script either in the core functions, or in the list of functions exported by all list modules.

In order to overload a particular function, you can simply list it twice with the same name in the cmds structure, but change the param_no field.

A script function exported by a module has the following definition :

typedef  int (*cmd_function)(struct sip_msg*, char*, char*, char*, char*, char*, char*);


As can be seen, all the OpenSIPS module functions receive string parameters ONLY, and a function can have a maximum of 6 parameters. The SIP message that is currently being processed is also transmitted as the first parameter, although this is transparent to the script writer ( he just provides the parameters idx 1 to 5 )

The flags member in the cmd_export_ structure dictates where within the OpenSIPS script can that particular function be called.

Current options here are :

#define REQUEST_ROUTE 1   /*!< Request route block */
#define FAILURE_ROUTE 2   /*!< Negative-reply route block */
#define ONREPLY_ROUTE 4   /*!< Received-reply route block */
#define BRANCH_ROUTE  8   /*!< Sending-branch route block */
#define ERROR_ROUTE  16   /*!< Error-handling route block */
#define LOCAL_ROUTE  32   /*!< Local-requests route block */
#define STARTUP_ROUTE 64  /*!< Startup route block */
#define TIMER_ROUTE  128  /*!< Timer route block */
#define EVENT_ROUTE  256  /*!< Event route block */


Allowing multiple types of routes by provided a bitmask of the above values is also supported.
A very important concept to grasp here is the fixup_function. This function is called just once, when the script is initially parsed, and it serves as an optimization, where the provided parameters are further parsed so that we can speed up the runtime function.
Examples for the use cases of the fixup function, just to provide a few :

  • since all our module parameters are strings, sometimes our module might need an integer parameter to be passed in that string. The fixup function can be used to convert that string to integer only once
  • if we accept it, our functions can accept pseudo-variables into the provided parameters. In this case, the fixup can be used to lookup the pvar's spec, and then at runtime we will be left just with evaluating that specific pvar in the context of the current SIP message


Further on we will follow the implementation of lb_is_destination from the load_balancer module, to fully grasp the concept. The function definition is the following :

        {"lb_is_destination",(cmd_function)w_lb_is_dst4,     4,    fixup_is_dst,
                0, REQUEST_ROUTE|FAILURE_ROUTE|ONREPLY_ROUTE|BRANCH_ROUTE|LOCAL_ROUTE},


As noted, the function receives 4 parameters. The desired usage case for lb_is_destination(ip,port,group,active) is the following :

  • ip - string or pvar with the IP to check
  • port - string or pvar with the Port to check - if empty, we skip any port check
  • group - integer or pvar containing integer with the load_balancer group to check
  • active - integer. if 1, we accept just active destinations for our check


Knowing these, the fixup_is_dst is the following :

static int fixup_is_dst(void** param, int param_no)
{
        if (param_no==1) {
                /* the ip to test */
                return fixup_pvar(param);
        } else if (param_no==2) {
                /* the port to test */
                if (*param==NULL) {
                        return 0;
                } else if ( *((char*)*param)==0 ) {
                        pkg_free(*param);
                        *param = NULL;
                        return 0;
                }
                return fixup_pvar(param);
        } else if (param_no==3) {
                /* the group to check in */
                return fixup_igp(param);
        } else if (param_no==4) {
                /*  active only check ? */
                return fixup_uint(param);
        } else {
                LM_CRIT("bug - too many params (%d) in lb_is_dst()\n",param_no);
                return -1;
        }
}


The fixup function will get called for each parameter provided, with the param_no parameter representing the index of the parameter we are parsing ( starting with 1, since the first parameter is the actual SIP msg )
mod_fix.h exports many helper functions that can be used for fixups. See the file for a full list of currently implemented fixups.
The above fixup functions will replace the parameter that you will receive in the main function with their respective output. Thus, in the main function you will not receive any more the plain text parameters that were provided by the script writer, but rather you'll receive the pvar's spec after it was parsed, or directly the integer value supplied by the script writer. Here is how the w_lb_is_dst4 handles the provided parameters after fixup :

static int w_lb_is_dst4(struct sip_msg *msg,char *ip,char *port,char *grp,
                        char *active)
{
        int ret, group;

        if (fixup_get_ivalue(msg, (gparam_p)grp, &group) != 0) {
                LM_ERR("Invalid lb group pseudo variable!\n");
                return -1;
        }

        ret = lb_is_dst(*curr_data, msg, (pv_spec_t*)ip, (pv_spec_t*)port,
                        group, (int)(long)active);


As we can see, the input char* parameters are casted to their according valus after fixup.
Again, mod_fix.h provides varius functions for accessing the results of the fixup.In our example, fixup_get_ivalue is used to get the provided integer value ( either directly from the plaintext provided integer, or it will extract the integer value from the spec that was parsed at fixup ). Also, note how the active parameter is cast directly to long, since we're accepting only plain-text integers for that parameter, and it has been already converted for us at fixup time.

The return code of the script exported functions from the module are very important.
A strictly positive return code will mean success, while a stricly negative return code will signal a failure
Returning 0 in a function exporting to the script will STOP the script execution after the function ends. Use a 0 return code with caution and only when absolutely necessary.

7.  Adding module MI Functions

Adding new module MI functions is done by populating the mi_cmds member in our module's exports structure.
For the full description of the used structures and functions, please see the generic Management Interface API section.

The MI functions in the mi_cmds member of the exports structure will be automatically registered by the module interface.

8.  Adding module Statistics

Adding new module exported statistics is done by populating the stats member in our module's exports structure.
For the full description of the used structures and functions for statistics, please see the generic Statistics API section.

The statistics in the stats member of the exports structure will be automatically registered by the module interface. If our new module named mynewmod exports a statistic called mycustomstat we will be able to fetch that statistic by using opensipsctl :
opensipsctl fifo get_statistics mynewmod mycustomstat

9.  Adding module Pseudo-variables

Adding new module pseudo-variables is done by populating the items member in our module's exports structure.
For the full description of the used structures and functions for pseudo-variables, please see the generic Pseudovariables section.

10.  Adding module dedicated Processes

For certain use cases, our module might need to talk to external entities which are not SIP based.
For such cases, we will need to have one ( or multiple ) processes which will be dedicated to handling such communication. Examples for this include the RTPProxy module ( which handles the communication with an external RTP Proxy engine ) or even the mi_fifo and mi_datagram ( which read MI commands from a FIFO file or respectively an UDP socket ).
Adding new module parameters is done by populating the procs member in our module's exports structure. The proc_export_t structure describing extra requested processes is the following :

struct proc_export_ {
        char *name;                             /* name of the new task */
        mod_proc_wrapper pre_fork_function;     /* function to be run before the fork */
        mod_proc_wrapper post_fork_function;    /* function to be run after the fork */
        mod_proc function;                      /* actual function that will be run in the context of the new process */
        unsigned int no;                        /* number of processes that will be forked to run the above function */
        unsigned int flags;                     /* flags for our new processes - only PROC_FLAG_INITCHILD makes sense here*/
};

typedef void (*mod_proc)(int no);
typedef int (*mod_proc_wrapper)();
 


The function that will run in the context of the new process must never terminate. Once the function exits, the entire OpenSIPS will stop.


The pre_fork_function and post_fork_function serve as helpers to create various auxiliary needed by the starting of the main process.

They are both executed within the context of the attendant OpenSIPS process


The no member of the structure dictates how many processes OpenSIPS will fork in order to run the respective function.
It can come in handy when there is a big work-load to be handled, at your module logic will spread the work load across all the forked processes, by making use of the no parameter provided to the process function.

The number of processes forked by OpenSIPS for a particular function is not neccesarily static.


Below is an example of how the MI datagram handles the forking of processes. The default value for the number of processes is MI_CHILD_NO, but that number is also configurable by the children_count parameter, as seen below.

static proc_export_t mi_procs[] = {
        {"MI Datagram",  pre_datagram_process,  post_datagram_process,
                        datagram_process, MI_CHILD_NO, PROC_FLAG_INITCHILD },
        {0,0,0,0,0,0}
};


static param_export_t mi_params[] = {
        {"children_count",      INT_PARAM,    &mi_procs[0].no           },


The flags member of the structure can be 0 or PROC_FLAG_INITCHILD. If PROC_FLAG_INITCHILD is provided, all the child_init function from the loaded modules will also be run in the context of our new module process.

11.  Module APIs

Within OpenSIPS, one module may sometimes need to access the functionality of another module (a common example are modules desiring to do operations on a per dialog basis, thus requiring part of the dialog module's functionality). Rather than directly accessing this functionality from within the target module, OpenSIPS uses the concept of a 'module exported API'.
The common approach used throughout OpenSIPS is that the target module should implement a form of loading it's API - which in fact translates into populating a structure with pointers of the functions that need to be exported, as well as various other structure members and parameters.
A module which needs to operate with the above API should first call (within it's mod_init() callback) the function to bind to the needed module's API, and then operate with the received structure.


Page last modified on December 11, 2019, at 07:20 PM