This page has been visited 3781 times.
Pages for other versions: devel 3.5 3.4 Older versions: 3.3 3.2 3.1
Module Development |
Table of Content (hide)
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 :
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
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
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 :
If the module has external library dependencies, they should be linked in the module's Makefile as well. Eg, the cachedb_memcached module :
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 :
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.
In the context of initializing our new module, there are two types of functions that will help us :
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
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.
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
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 :
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 ).
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
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 :
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,
Example of setting these parameters from the OpenSIPS script, for our ournewmod module.
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 :
Adding new module parameters is done by populating the cmds member in our module's exports structure.
The exported functions structure is the following :
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 :
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 :
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 :
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 :
As noted, the function receives 4 parameters. The desired usage case for lb_is_destination(ip,port,group,active) is the following :
Knowing these, the fixup_is_dst is the following :
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 :
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.
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.
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
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.
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 :
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.
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.
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.