Login | Register

Documentation

Documentation -> Manuals -> OpenSIPS 3.1 Development Manual

This page has been visited 25272 times.



OpenSIPS 3.1 Development Manual

1.  Introduction

The focus of the following document will be on the general architecture of OpenSIPS, as well as presenting all the major components and APIs that OpenSIPS exposes for building new modules / features.
The tutorial does not aim to teach Linux / C programming. Below are a list of prerequisites :

  • C programming under Linux
  • Multi-Process programming under Linux
  • Basic Networking Concepts
  • Basic SIP knowledge


The tutorial is accurate as of OpenSIPS 3.1 LTS

2.  General Architecture

TBD \\

TBD
TBD

3.  Memory Management

OpenSIPS has its own memory allocator. This provides some important advantages over the system memory allocator:

  • the ability to hard limit the memory that OpenSIPS uses
  • improved performance over the system memory allocator
  • multiple allocators are available, each with different use cases (a standard allocator, a memory debugging allocator – useful for debugging memory leaks and memory corruptions, and a high-performance allocator – built for highly parallel workloads on multi-core systems).


Furthermore, since OpenSIPS is a multi-process application, the use of shared memory is required in many very common scenarios. The OpenSIPS allocators also hide the shared memory implementations details and expose a very simple API, very similar to the system allocator.
On OpenSIPS startup, all custom allocators will request the maximum configured OpenSIPS memory from the operating system, and then proceed to manage it internally as OpenSIPS starts to use memory while processing traffic.
From a development perspective, there are two types of memory: one that is tied to the context of a single Process (Private Memory), and another that is shared between all OpenSIPS processes (Shared Memory).

3.1  Private (PKG) Memory

Private memory is only specific to a single OpenSIPS process. Since it has no visibility outside the current process, no locking mechanisms are required while managing such memory, hence allocating private memory will be faster than allocating shared memory.

Common use case: before forking OpenSIPS processes, the developer stores some static variables in the private memory of the main process. After forking, each child process will have its own clone of the private memory chunk (same memory address pointer!).


mem/mem.h exposes all the private memory related functions

/*
Parameters :
      size - size in bytes of the request private memory
Returns :
      the actual allocated buffer, or NULL is case of error
*/

void *pkg_malloc(unsigned int size);

/*
Parameters :
      buf - the buffer to be freed
*/

void pkg_free(void *buf)

/*
Parameters :
      buf - buffer that we want to reallocate
      size - the new desired buffer size
Returns :
        the new buffer address if reallocation is successful, or NULL in case of error.

Note that pkg_realloc(NULL,size) is equivalent to pkg_malloc(size)
*/

void *pkg_realloc(void *buf, unsigned int size);
3.2  Shared (SHM) Memory

Shared memory can be accessible from all OpenSIPS processes. Thus, generally speaking, all write access to a shared memory buffer should be guarded by some form of synchronization mechanism in order to ensure consistency.
Also, since allocations of chunks of shared memory can be requested from all the OpenSIPS processes, the shared memory allocator internally uses locking in order to ensure consistency. Due to this, as a general guideline, it is recommended to avoid big fragmentation of the shared memory ( by requesting many small chunks ), by merging as much information that is meaningfully tied together in a single shared memory chunk.

mem/shm_mem.h exposes all the shared memory related functions :

/*
Parameters :
      size - size in bytes of the request shared memory
Returns :
      the actual allocated buffer, or NULL is case of error
*/

void *shm_malloc(unsigned int size);

/*
Parameters :
      buf - the buffer to be freed
*/

void shm_free(void *buf)

/*
Parameters :
      buf - buffer that we want to reallocate
      size - the new desired buffer size
Returns :
        the new buffer address if reallocation is successful, or NULL in case of error.

Note that shm_realloc(NULL,size) is equivalent to shm_malloc(size)
*/

void *shm_realloc(void *buf, unsigned int size);
3.3  Memory Allocators

To find out more about the strengths and weaknesses of each memory allocator, be sure to read this comprehensive blog post

4.  Parsing SIP Messages

The OpenSIPS SIP message parser is a lazy parser, which performs very well in terms of performance. The behavior is the following :

  • when the message is received, only the critical headers are parsed (e.g. topmost Via header)
  • for known header types, when the developer wants to extract the first occurrence of a header, they do not have to parse the entire message, but rather the parser will stop parsing when the first needed header type was identified.


Also, it is important to note that there are two types of parsing:

  • parsing in the sense of identifying header boundaries and separating between the header name and header body (e.g. parsing the 'To : vlad@opensips.org;tag=123\r\n' header, and identifying that first, the header is present, and secondly that the body is 'vlad@opensips.org;tag=123\r\n')
  • in-depth parsing of a specific header type, for identifying header specific information (in our example, extracting the to-tag in the To header, etc.).


All parsing is done in a stateful manner, in the sense that the OpenSIPS parser knows which header was parsed and internally keeps a parsing bitmask. Thus, once the To header was first parsed, any subsequent attempts of parsing the To header will return immediately.
Parser implementations are generally exported in the parser/ folder.

4.1  Generic Header Parser

The generic SIP header parser is exposed by parser/msg_parser.h. The function to be used is:

/*
Parameters :
      msg : the SIP message that needs to be parsed – see parser/msg_parser.h for details on the struct sip_msg structure
      flags : bitmask of header types that need to be parsed
      next : specifies whether the parser should explicitly force the parsing of new headers from the provided bitmask, even though those header types were already previously found. Can be useful when trying to find a second occurrence of a header ( in the case that header can appear multiple times in a SIP message – eg. Route )

Returns :
      0 in case of error, -1 in case of error ( either header was not found or some other error occurred ).
*/

int parse_headers(struct sip_msg* msg, hdr_flags_t flags, int next);


Example of usage :

if (parse_headers(req, HDR_CALLID_F|HDR_TO_F|HDR_FROM_F, 0) < 0 || !req->callid || !req->to || !req->from) {
      LM_ERR("bad request or missing CALLID/TO/FROM hdr\n");
      return -1;
}

HDR_EOH_F can be used in order to parse all the headers in the current SIP message.

The parse_headers() function will not duplicate SIP headers at all – the hooks in the struct sip_msg structure will be populated with pointers that point directly in the SIP message buffer.


Upon return, if successful, the function will populate the respective hooks in the sip_msg structure for the known headers. The hdr_field* structures are allocated in pkg memory, and will automatically be freed when the SIP message processing has finished.
After successful parsing, the developer can access the header name and body as such:

LM_INFO("The callid header name is %.*s and the callid header body is %.*s\n", req->callid->name.len, req->callid->name.s, req->callid->bodylen.  req->callid->body.s);
4.2  Specific Header Parsing

For parsing a specific header type and extracting the header type relevant information, the parser/ directory contains all implementations for known headers. The naming convention is that parser/parse_X.h will expose the parsing for the header named X.
For example, here is the implementation of the parsing of the To header, exposed by parser/parse_to.h :

int parse_to_header( struct sip_msg *msg)
{      
      struct to_body* to_b;

      if ( !msg->to && ( parse_headers(msg,HDR_TO_F,0)==-1 || !msg->to)) {
            LM_ERR("bad msg or missing To header\n");
            goto error;
      }

      /* maybe the header is already parsed! */
      if (msg->to->parsed)
            return 0;

      /* bad luck! :-( - we have to parse it */
      /* first, get some memory */
      to_b = pkg_malloc(sizeof(struct to_body));
      if (to_b == 0) {
            LM_ERR("out of pkg_memory\n");
            goto error;
      }

      /* now parse it!! */
      memset(to_b, 0, sizeof(struct to_body));
      parse_to(msg->to->body.s,msg->to->body.s+msg->to->body.len+1,to_b);
      if (to_b->error == PARSE_ERROR) {
            LM_ERR("bad to header\n");
            pkg_free(to_b);
            goto error;
      }

      msg->to->parsed = to_b;

      return 0;
error:
      return -1;
}


Note that the void *parsed element in the hdr_field structure will contain the header specific parser structure, which will also be allocated into private memory and automatically freed when the SIP message processing has finished.


After parse_to_header() successfully returns, the developer can start accessing the parsed TO header in the following way :

LM_INFO("The To header tag value is %.*s\n", get_to(msg)->tag_value.len, get_to(msg)->tag_value.s);
4.3  Parsing SIP URIs

The OpenSIPS parser also exposes the functionality of parsing individual SIP URI.
parser/parse_uri.h exposes :

/*
Parameters :
      buf - the string which contains our SIP URI
      len - length of the SIP URI buffer
      uri - structure which will be populated by the function in case of success.
      See full struct sip_uri members in parser/msg_parser.h
Returns :
      0 in case of success, negative value in case of error parsing the URI
*/

int parse_uri(char *buf, int len, struct sip_uri* uri);


The parse_uri() function does not allocate any memory, it just populates the sip_uri structure with references to the buf parameter provided as input.
Here is an example of parsing the To header URI:

      /* make sure TO header is parsed before this */
      struct to_body *tb = get_to(msg);
      if (parse_uri(tb->uri.s, tb->uri.len , &tb->parsed_uri)<0) {
            LM_ERR("failed to parse To uri\n");
            return -1;
      }

      LM_INFO(“TO URI user is %.*s and TO URI domain is %.*s\n”,
      tb->parsed_uri.user.len, tb->parsed_uri.user.s,
      tb->parsed_uri.domain.len, tb->parsed_uri.domain.s);
4.4  Parsing the SDP Body

OpenSIPS exposes functions for operating on the SIP message body.
parser/msg_parser.h exposes

/*
Parameters :
      msg - the SIP message to fetch the body for
      body - output param, which will hold the body pointer inside the SIP message and the body length, or {NULL,0} in case of no body present
Returns :
      0 in case of success, or -1 in the case of parsing errors ( the function needs to internally parse all the headers in order to detect the body length ).
*/

int get_body(struct sip_msg *msg, str *body)


For parsing the SDP body and extracting various information about the session(s) , parser/sdp/sdp.h exposes

/*
Parameters :
      _m - the SIP message to have it's SDP parsed
Returns :
      0 in case of success, negative in case of error
*/

int parse_sdp(struct sip_msg* _m);


The function will internally populate _m->sdp , see parser/sdp/sdp.h for more details on the sdp_info structure.

5.  Changing SIP Messages

The standard mechanism for performing changes on SIP messages within OpenSIPS is by using the so called lumps system.
The lump system works very similarly to the diff/patch tools in the Linux environment – the developer adds add and/or remove operations to a SIP message. The lumps are stored in a list, and are only applied after the OpenSIPS script is fully executed and before the SIP message is relayed. Because of this, changes done on a SIP message are not immediately reflected on the SIP message upon further inspection ( eg. Adding a new header from the script and then checking for the header's existence ).
When talking about the SIP message they have an effect on, the lumps can be split into the following categories :

5.1  SIP Message Lumps

This type of lumps operate on the current SIP message context.
From operational point of view, they are also split into two categories :

Delete Lumps

data_lump.h exposes

/*
Parameters :
      msg - the SIP message the lump will affect
      offset - the offset in the SIP message at which to start deleting
      len - the number of characters to delete from the SIP message
      type - indication on which header the current lump affects ( can be 0 )
Returns :
      the created lump structure for deleting part of the SIP message. Can be further used to chain together different types of lumps in the message attached list of lumps. NULL is returned in case of internal error.
*/

struct lump* del_lump(struct sip_msg* msg, unsigned int offset,
        unsigned int len, enum _hdr_types_t type);


Example of deleting the RPID header :

      /* first parse the header to figure out where it actually starts in the SIP message */
      if( parse_headers(msg,HDR_RPID_F,0)<0 || msg->rpid == NULL ){
            LM_DBG(“No rpid header – nothing to delete \n”);
            return 0;
      }

      /* delete the entire RPID header */
      if ( del_lump(msg, msg->rpid->name.s-msg->buf, msg->rpid->len,HDR_RPID_T )== NULL) {
            LM_ERR(“Failed to delete RPID header \n”);
            return -1;
      }
Add Lumps

data_lump.h exposes

/*
Parameters :
      after/before - the lump where we will connect our new lump
      new_hdr - string to be added
      len - length of the string to be added
      type - header type that is affected by the current change ( can be 0 )
Returns :
      the created lump structure for adding to the SIP message. Can be further used to chain together different types of lumps in the message attached list of lumps. NULL is returned in case of internal error.
*/

struct lump* insert_new_lump_after(struct lump* after,                                            
                char* new_hdr, unsigned int len, enum _hdr_types_t type);                                        
struct lump* insert_new_lump_before(struct lump* before, char* new_hdr,
                unsigned int len,enum _hdr_types_t type);


If the developer's desire is just to add a particular string to the SIP message, a new anchor lump must be created, that will then have to be provided as the first parameter to insert_new_lump_after/insert_new_lump_before.
For creating a new anchor lump, data_lump.h also exports

/*
Parameters :
      msg - the SIP message that will be affected by the lump anchor
      offset - the offset in the SIP message where the anchor will be placed
      len - not currently used ( should be 0 )
      type - header type that is affected by the current change ( can be 0 )
Returns:
      the created lump structure for adding to the SIP message. Can be further used to chain together different types of lumps in the message attached list of lumps. NULL is returned in case of internal error.
*/

struct lump* anchor_lump(struct sip_msg* msg, unsigned int offset,
                int unsigned len, enum _hdr_types_t type)


Example of adding a new SIP header at the end of the SIP message headers :

      /* make sure we detect all headers */
      if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
            LM_ERR("error while parsing message\n");
            return -1;
      }

      /* add the anchor at the very end of the SIP headers */
      anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, 0);
      if (anchor == NULL) {
            LM_ERR(“Failed to create lump anchor\n”);
            return -1;
      }
      len =  sizeof(“MY_HDR: MY_VAL\r\n”) -1;
      new_hdr=pkg_malloc(len);
      if (!new_hdr) {
            LM_ERR(“No more pkg mem\n”);
            return -1;
      }

      memcpy(new_hdr,”MY_HDR: MY_VAL\r\n”,len);
      if (insert_new_lump_after(anchor, new_hdr, len, 0) == 0) {
            LM_ERR("can't insert lump\n");
            pkg_free(new_hdr);
            return -1;
      }

      /* job done, the PKG new_hdr mem will be free internally when the lump will be applied */
      return 0;

If we want to replace a particular part of a SIP message, the operation can be split in two steps, first deleting the part we don't need anymore by calling del_lump, and then using the returned lump to add a new lump after it.
Example of replacing the content of the RPID header :

      /* first parse the header to figure out where it actually starts in the SIP message */
      if( parse_headers(msg,HDR_RPID_F,0)<0 || msg->rpid == NULL ){
            LM_DBG(“No rpid header – nothing to delete \n”);
            return 0;
      }

      /* delete just the contents of the RPID header */
      del =  del_lump(msg, msg->rpid->body.s-msg->buf, msg->rpid->body.len,HDR_RPID_T);
      if ( del == NULL) {
            LM_ERR(“Failed to delete RPID header \n”);
            return -1;
      }

      len =  sizeof(“sip:new_rpid@my_domain.com\r\n”) -1;
      new_rpid=pkg_malloc(len);
      if (!new_rpid) {
            LM_ERR(“No more pkg mem\n”);
            return -1;
      }
      memcpy(new_rpid,“sip:new_rpid@my_domain.com\r\n”,len);

      if(insert_new_lump_after(del,new_rpid,len,HDR_RPID_T)==NULL) {
            LM_ERR("Failed to insert new callid\n");
            pkg_free(new_rpid);
            return -1;
      }
5.2  SIP Reply Lumps

When used in the case of a SIP request, these lumps will operate on the SIP reply that will be internally generated when rejecting a request from within OpenSIPS ( if the Request if forwarded instead of rejected at OpenSIPS level, these lumps will have no effect ). Since the reply will be internally generated by OpenSIPS, the Reply Lumps can only add new content.
data_lump_rpl.h exposes

/*
Parameters :
      msg - the SIP Request that the reply will be generated for
      s - the string to be added to the reply
      len - the length of the string to be added
      flags - Since the reply will be generated by OpenSIPS, it is important to mark your lump if it should be added to the Reply headers or to the Reply body. Relevant flags for these cases are LUMP_RPL_HDR and LUMP_RPL_BODY.
Returns :
      the created lump structure for adding to the SIP reply. Can be further used to chain together lumps in the message attached list of lumps. NULL is returned in case of internal error.
*/

struct lump_rpl* add_lump_rpl(struct sip_msg *msg, char *s, int len, int flags);


Example of Adding contact header to the internally generated reply :

      static char ct[CT_LEN] = “Contact: opensips@my_domain.com\r\n”;

      /* we are adding a lump to the headers, so we pass the  LUMP_RPL_HDR flag
      also , our buffer is located in a static buffer, thus no need for the core to allocate memory for this lump, we also pass the LUMP_RPL_NODUP flag */

      if (add_lump_rpl(msg, ct, CT_LEN, LUMP_RPL_HDR |  LUMP_RPL_NODUP)==0) {
            LM_ERR("unable to add lump\n");
            return -1;
      }

6.  Extending OpenSIPS core Config File

OpenSIPS uses flex and bison in order to parse the configuration file and then build the entire action tree that a SIP message will go through once it is read from network level.
When it comes to extending the OpenSIPS configuration file directly in the core, the developer can either choose to add a new core parameter, or a new core function.

6.1  Adding a core parameter

In the following step by step tutorial, we will follow the implementation of the udp_workers core parameter, which is an integer controlling the number of OpenSIPS processes per UDP interface.
First of all, we will have to add the variable that will hold the value of our new core parameter.
In our case, in globals.h we have added

extern int udp_workers_no;


Adding the variable here will make it visible in both the OpenSIPS core and the OpenSIPS modules. main.c will hold the actual variable :

/* Default value in case the parameter is not set from the script */
int udp_workers_no = 8;


Now, under cfg.lex, we need to instruct the lexer to recognize our new token:

/* Default value in case the parameter is not set from the script */
UDP_WORKERS udp_workers


Next, we will have to modify the grammar in order to accept our new parameter. In cfg.y , first we re-specify the lexer token:

%token UDP_WORKERS


Finally, we set the parsing rules for the new token:

| UDP_WORKERS EQUAL NUMBER { udp_workers_no=$3; }
| UDP_WORKERS EQUAL error { yyerror("number expected"); }


Our variable will have a numeric variable, anything else will trigger and error in parsing the OpenSIPS script.

6.2  Adding a core function

In the following step by step tutorial, we will follow the implementation of the xlog core function, which is used to print information to the logging facility.

Note that xlog can receive either a single parameter (the string to be printed), or two parameters (the log level and then the string to be printed).

First, we extend the lexer file with the new word. Under cfg.lex, we have:

XLOG     "xlog"


Next, the grammar must be extended. In cfg.y, we have :

%token XLOG
...
...
        | XLOG LPAREN STRING RPAREN {
                mk_action1($$, XLOG_T, STR_ST, $3); }
        | XLOG LPAREN folded_string RPAREN {
                mk_action1($$, XLOG_T, STR_ST, $3); }
        | XLOG LPAREN STRING COMMA STRING RPAREN {
                mk_action2($$, XLOG_T, STR_ST, STR_ST, $3, $5); }
        | XLOG LPAREN STRING COMMA folded_string RPAREN {
                mk_action2($$, XLOG_T, STR_ST, STR_ST, $3, $5); }


Note the different ways of invoking xlog() we defined: the basic version with 1 parameter, the 2-parameter version and alternatives which allow it to receive multi-line strings. Notice that XLOG_T is a new enum value which we will define in route_struct.h.
From the grammar, we will start building the actions. In route.c, we will define the fixup part of the function, where all the sanity checks and parameter parsing should be done. The fixup part will be invoked only once, at script parsing.

            case XLOG_T:
                s.s = (char*)t->elem[1].u.data;
                if (s.s == NULL) {
                    /* commands have only one parameter */
                    s.s = (char *)t->elem[0].u.data;
                    s.len = strlen(s.s);
                    if(s.len==0)
                    {
                        LM_ERR("param is empty string!\n");
                        return E_CFG;
                    }

                    if(pv_parse_format(&s ,&model) || model==NULL)
                    {
                        LM_ERR("wrong format [%s] for value param!\n", s.s);
                        ret=E_BUG;
                        goto error;
                    }

                    t->elem[0].u.data = (void*)model;
                    t->elem[0].type = SCRIPTVAR_ELEM_ST;
                } else {
                    /* there are two parameters */


In action.c we will have to add the actual code that needs to get executed when the function gets called from the OpenSIPS script at runtime:

case XLOG_T:
            script_trace("core", "xlog", msg, a->file, a->line) ;
            if (a->elem[1].u.data != NULL) {
                if (a->elem[1].type != SCRIPTVAR_ELEM_ST)
                {
                    LM_ALERT("BUG in xlog() type %d\n", a->elem[1].type);
                    ret=E_BUG;
                    break;
                }
                if (a->elem[0].type != STR_ST)
                {
                    LM_ALERT("BUG in xlog() type %d\n", a->elem[0].type);
                    ret=E_BUG;
                    break;
                }
                ret = xlog_2(msg,a->elem[0].u.data, a->elem[1].u.data);
                if (ret < 0)
                {
                    LM_ERR("error while printing xlog message\n");
                    break;
                }
            } else {
6.3  Adding a core Pseudo-Variable

All the OpenSIPS core pseudo-variables are defined in pvar.c :

static pv_export_t _pv_names_table[] = {            
      {{"avp", (sizeof("avp")-1)}, PVT_AVP, pv_get_avp, pv_set_avp,
            pv_parse_avp_name, pv_parse_index, 0, 0},
      {{"hdr", (sizeof("hdr")-1)}, PVT_HDR, pv_get_hdr, 0, pv_parse_hdr_name,
            pv_parse_index, 0, 0},              
      {{"hdrcnt", (sizeof("hdrcnt")-1)}, PVT_HDRCNT, pv_get_hdrcnt, 0, pv_parse_hdr_name, 0, 0, 0},
      {{"var", (sizeof("var")-1)}, PVT_SCRIPTVAR, pv_get_scriptvar,
            pv_set_scriptvar, pv_parse_scriptvar_name, 0, 0, 0},
      {{"ai", (sizeof("ai")-1)}, /* */            
            PVT_PAI_URI, pv_get_pai, 0,          
            0, 0, 0, 0},  
      {{"au", (sizeof("au")-1)}, /* */
            PVT_AUTH_USERNAME, pv_get_authattr, 0,
            0, 0, pv_init_iname, 1},
...
...
...


The general syntax of an OpenSIPS pseudo-variable, along with the pv_export_t structure to be used in OpenSIPS ( both in core and in modules ) to expose new PVARs are shown below :

/*! \brief
 * PV spec format:
 * - $class_name
 * - $class_name(inner_name)
 * - $(class_name[index])
 * - $(class_name(inner_name)[index])
 * - $(class_name{transformation})
 * - $(class_name(inner_name){transformation})
 * - $(class_name[index]{transformation})            
 * - $(class_name(inner_name)[index]{transformation})
 */
           
typedef struct _pv_export {
        str name;                      /*!< class name of PV */
        pv_type_t type;                /*!< type of PV */
        pv_getf_t  getf;               /*!< function to get the value */
        pv_setf_t  setf;               /*!< function to set the value */
        pv_parse_name_f parse_name;    /*!< function to parse the inner name */
        pv_parse_index_f parse_index;  /*!< function to parse the index of PV */
        pv_init_param_f init_param;    /*!< function to init the PV spec */
        int iparam;                    /*!< parameter for the init function */
} pv_export_t;  


Further on we will follow the implementation of the $ru pseudo variable, which offers read/write access to the SIP message Request-URI.
First, PVT_RURI was added in pvar.h in enum _pv_type.

Afterwards, the following was added in _pv_names_table :

        {{"ru", (sizeof("ru")-1)}, /* */            
                PVT_RURI, pv_get_ruri, pv_set_ruri,  
                0, 0, 0, 0},  

Our new pvar will be accessible from script by using $ru. Read access from the script will lead to pv_get_ruri getting called, while write requests to $ru will make a call to pv_set_ruri.
Since the $ru pvar does not support indexing by concept ( the SIP message has one and only one Request-URI ), there is no need to add a parsing or an indexing function for our pseudo variable. Also, no special initialization is needed for our pseudo variable case, since we will operate directly on the currently processed SIP message in the script.

/*
Parameters :
      msg - the message context to evaluate the current pvar
      param - the parameter provided for evaluating the pvar
      res - the output value of our pvar
Returns :
      0 in case of success, negative in case of error
*/
     
static int pv_get_ruri(struct sip_msg *msg, pv_param_t *param,
            pv_value_t *res)
{
      if(msg==NULL || res==NULL)
            return -1;

      if(msg->first_line.type == SIP_REPLY)   /* REPLY doesn't have a ruri */
            return pv_get_null(msg, param, res);

      if(msg->parsed_uri_ok==0 /* R-URI not parsed*/ && parse_sip_msg_uri(msg)<0) {
            LM_ERR("failed to parse the R-URI\n");
            return pv_get_null(msg, param, res);
      }

      if (msg->new_uri.s!=NULL)
            return pv_get_strval(msg, param, res, &msg->new_uri);

      return pv_get_strval(msg, param, res, &msg->first_line.u.request.uri);
}


For all read access on the PVARs from contexts where the PVAR does not have any meaningful value (eg. Request-URI from a Reply Context), make sure to use pv_get_null to signal this to the script writer.


/*
Parameters :
      msg - the SIP message to apply the changes to
      param - the parameter provided for evaluating the pvar
      op - further indication on the type of write access to be done
      val - value to be pushed to our pvar
Returns :
      0 in case of success, negative in case of error
*/

int pv_set_ruri(struct sip_msg* msg, pv_param_t *param,
                int op, pv_value_t *val)
{
      if(msg==NULL || param==NULL || val==NULL) {
            LM_ERR("bad parameters\n");
            return -1;
      }

      /* type checking, we can only push strings to R-URI */
      if(!(val->flags&PV_VAL_STR)) {
            LM_ERR("str value required to set R-URI\n");
            goto error;
      }

      /* populate the message R-URI with the string value from the provided val */
      if (set_ruri( msg, &val->rs)!=0) {
            LM_ERR("failed to set RURI\n");
            goto error;
      }

      return 0;
error:
      return -1;
}

7.  Adding Transformations

Transformations are functions which can operate directly on any OpenSIPS pseudo-variable. A transformation takes as input the value of the pseudo-variable and processes it, outputting a transformed version.
Some transformation examples:

  • the "uri" transformations (which work on SIP URIs, and they allow extracting various useful information, such as the URI's username or domain parts, various parameters, etc.)
  • the "s" class of transformations (which work on generic strings, and provide various useful methods as fetching the length of the string, searching the first occurrence of a character inside the string, etc.)
  • ... many others! – see http://www.opensips.org/Documentation/Script-Tran-3-1


# example of usage
$var(tutorial)  = “OpenSIPSDevel”;
xlog("Our variable has $(var(tutorial){s.len}) characters\n");
# ... which will print "Our variable has 13 characters\n"

Note that transformations can be chained together, so make sure to take this into account when implementing new ones!


$var(our_uri) = “sip:vlad@opensips.org;
xlog("The username of our URI has $(var(our_uri){uri.user}{s.len}) characters\n");

In our examples, uri and s are the so called classes of transformations, while user and len are the actual operations within the class.
We will further follow the implementation of the uri class of transformation, and then focus on the user function.
Adding new classes of transformations is done in transformations.c, by extending the core_trans[] array:

static trans_export_t core_trans[] = {
...
    {str_init("uri"), tr_parse_uri, tr_eval_uri},
...

Notice that we supply a parsing function and an evaluation function.
The parsing function will be invoked with the full name of the transformation, so you can parse it and establish the actual function (transformation) to be invoked:

int tr_parse_uri(str* in, trans_t *t)
{
    char *p;
    str name;
    tr_param_t *tp = NULL;

    if(in==NULL || in->s==NULL || t==NULL)
        return -1;
    p = in->s;
    name.s = in->s;
...


After we've decided what actually needs to be done and did all the parsing, the evaluation function, tr_eval_uri() will do all the weightlifting at runtime, as it will evaluate our transformation and return the new transformation output.
In our case, the function parses the provided string into a SIP URI, and then populates the output pv_value_t accordingly to the subtype provided :

/* make a PKG copy of the input */
_tr_uri.s = (char*)pkg_malloc((val->rs.len+1)*sizeof(char));
...
memcpy(_tr_uri.s, val->rs.s, val->rs.len);
_tr_uri.s[_tr_uri.len] = '\0';
...
/* parse uri -- params only when requested */
        if(parse_uri(_tr_uri.s, _tr_uri.len, &_tr_parsed_uri)!=0)
        {
            LM_ERR("invalid uri [%.*s]\n", val->rs.len,
                    val->rs.s);
...
/* zero out the output val */
memset(val, 0, sizeof(pv_value_t));
/* the output pvar will be a string */
val->flags = PV_VAL_STR;

switch(subtype) {
      case TR_URI_USER:
            val->rs = (_tr_parsed_uri.user.s)?_tr_parsed_uri.user:_tr_empty;
            break;
...

8.  Locking API

OpenSIPS has its own locking API, and it is recommended to use it instead of the system exposed locks, since they offer greater flexibility - depending on the use case and the menuconfig provided compilation flags, the OpenSIPS generic locks can be switched to:

  • architecture specific locks
    • with futexes
    • with adaptive waiting (yield execution instead of blocking)
    • with busy-waiting
  • SysV locks
  • pthread mutexes
  • POSIX semaphores
  • umutexes (FreeBSD)


The Locking API offers two distinct functionalities, one for using single lock elements, and another for operating on entire sets of locks.

8.1  Single Lock API

The API can be used by including “locking.h” . The OpenSIPS generic lock is defined by the gen_lock_t structure.
Allocating a new lock is done by calling lock_alloc :

/*
Returns :
      A shared memory allocated lock, or NULL in case of an error.
*/

gen_lock_t *lock_alloc(void);


Since the locks usually have to be reachable by all processes fighting on some resource, the locks are allocated by default in shared memory. Also, note that it is not necessary to always allocate the lock separately - if the lock is embedded within a structure which is allocated in SHM, the effect is the same.
Before any operation on the lock, it must be initialized :

/*
Parameters :
      lock - the lock instance to be initialized
Returns :
      The initialized lock in case of success, or NULL in case of error.
*/

gen_lock_t* lock_init(gen_lock_t* lock);


In order to acquire a lock, one must use the lock_get function :

/*
Parameters :
      lock - the lock to be acquired
*/

void lock_get(gen_lock_t *lock);


The function will block if the lock is acquired by another process, and will only return once the lock has been acquired by the current process.
For releasing a lock, lock_release should be used :

/*
Parameters :
      lock - the lock to be released.
*/

void lock_release(gen_lock_t *lock);


Once a lock is no longer needed, one must first destroy the lock, and then the lock can be safely deallocated.

/*
Parameters :
      lock - the lock to be destroyed
*/

void lock_destroy(gen_lock_t *lock);
/*
Parameters :
      lock - the lock to be deallocated
*/

void lock_dealloc(gen_lock_t *lock);


Here is a code snippet showing the typical code used when dealing with single lock instances :


gen_lock_t *my_lock;

int init_function(void)
{
        /* … */

        my_lock = lock_alloc();
        if (my_lock == NULL) {
                LM_ERR(“Failed to allocate lock \n”);
                return -1;
        }

        if (lock_init(my_lock) == NULL) {
                LM_ERR(“Failed to init lock \n”);
                return -1;
        }

        /* … */
        return 0;
}


int do_work(void)
{

        /* … */
        lock_get(my_lock)


        /* critical region protected by our lock
        generally recommended to keep critical regions short
        I/O operations to be avoided in such critical regions */


        lock_release(my_lock)
        /* … */
}

void destroy_function(void) {

        /* … */

        lock_destroy(my_lock);
        lock_dealloc(my_lock);

        /* … */
}
8.2  Lock Set API

Operating on an entire array of locks can become very useful when dealing with structures like hashes, where you would need a lock per each hash entry.
The API can be used by including locking.h . The OpenSIPS generic array of locks is defined by the gen_lock_set_t structure, and working with it is very similar in concept to operating a single lock entry.
Allocating a new lock set is done by calling lock_set_alloc :

/*
Returns :
      A shared memory allocated lock set, or NULL in case of an error.
*/

gen_lock_set_t *lock_set_alloc(void);


Since the locks usually have to be reachable by all processes fighting on some resource, the locks are allocated by default in shared memory. Also, note that it is not necessary to always allocate the lock set separately - if the lock set is embedded within a structure which is allocated in SHM, the effect is the same.
Before any operation on the lock set, it must be initialized :

/*
Parameters :
      lock - the lock set instance to be initialized
Returns :
      The initialized lock in case of success, or NULL in case of error.
*/

gen_lock_set_t* lock_set_init(gen_lock_set_t* lock);


In order to acquire a lock in a lock set, one must use the lock_set_get function :

/*
Parameters :
      lock - the lock to be acquired
      entry - the entry in the lock set that needs to be acquired
*/

void lock_set_get(gen_lock_set_t *lock,int entry);


The function will block if the lock is acquired by another process, and will only return once the lock has been acquired by the current process.
For releasing a lock, lock_set_release should be used :

/*
Parameters :
      lock - the lock to be released.
      entry - the entry in the lock set that needs to be released
*/

void lock_set_release(gen_lock_set_t *lock,int entry);


Once a lock set is no longer needed, one must first destroy the lock set, and then the lock set can be safely deallocated.

/*
Parameters :
      lock - the lock set to be destroyed
*/

void lock_set_destroy(gen_lock_set_t *lock);
/*
Parameters :
      lock - the lock set to be deallocated
*/

void lock_set_dealloc(gen_lock_set_t *lock);


Here is a code snippet showing the typical code used when dealing with single lock instances :

gen_lock_set_t *my_lock;

int init_function(void)
{
        /* … */

        /* allocate lock set with 32 entries */
        my_lock = lock_set_alloc(32);
        if (my_lock == NULL) {
                LM_ERR(“Failed to allocate lock set \n”);
                return -1;
        }

        if (lock_set_init(my_lock) == NULL) {
                LM_ERR(“Failed to init lock set \n”);
                return -1;
        }

        /* … */
        return 0;
}

int do_work(void)
{

        /* … */

        /* acquire entry 5 in the lock set */
        lock_set_get(my_lock,5)

        /* also acquire entry 21 in the lock set */
        lock_set_get(my_lock,21);

        /* critical region protected by our lock
        generally recommended to keep critical regions short
        I/O operations to be avoided in such critical regions */


        lock_set_release(my_lock,21);
        lock_set_release(my_lock,5);
        /* … */
}

void destroy_function(void)
{
        /* … */

        lock_set_destroy(my_lock);
        lock_set_dealloc(my_lock);

        /* … */
}
8.3  Readers-Writers Locking API

A readers-writer lock is like a mutex, in that it controls access to a shared resource, allowing concurrent access to multiple threads for reading but restricting access to a single thread for writing to the resource.
This can prove very useful when having a use case where all the OpenSIPS processes need read-only access to a resource, but you need to have an MI command to reload that resource (e.g. from a database). In such scenarios, using a readers-writers lock can improve performance by a considerable margin.
The API can be used by including rw_locking.h . The OpenSIPS generic lock is defined by the rw_lock_t structure.

Allocating a new readers-writers lock into shared memory and initializing it is done by calling lock_init_rw :

/*
Returns :
      A shared memory allocated rw lock, or NULL in case of an error.
*/

inline static rw_lock_t * lock_init_rw(void);


In order to acquire the lock for reading purpose, one should use lock_start_read :

/*
Parameters :
      lock - the lock to be acquired
*/

void lock_start_read(rw_lock_t * lock);


In case there is currently a write access ongoing, the lock will block until the write is done. Otherwise, the lock will be acquired immediately.
After the read is finished, you must call lock_stop_read :

/*
Parameters :
      lock - the lock to be released
*/

void lock_stop_read(rw_lock_t * lock);


For requesting a write access, you can use lock_start_write :

/*
Parameters :
      lock - the lock to be acquired
*/

void lock_start_write(rw_lock_t * lock);


In case there are other ongoing writes, the op will block until the other writes complete. Otherwise, the lock will block until all the existing readers finish reading.
After the write operation is finished, call lock_stop_write :

/*
Parameters :
      lock - the lock to be release
*/

void lock_stop_write(rw_lock_t * lock);


Upon calling the above function, readers will again be allowed in the critical section.
For destroying and deallocating a rw lock, use lock_destroy_rw :

/*
Parameters :
      lock - the lock to be destroyed
*/

void lock_destroy_rw(rw_lock_t * lock);


Taking reader/writer locking to a new level, for scenarios with N x guaranteed-to-be readers and 1 x reader which may occasionally want to transition into a writer role after grabbing the read lock (depending on some condition), we can avoid forcing our uncertain "1 x reader" to be a simple writer (and always hogging down all other readers) into being a "switchable reader", thanks to the below API functions:

/*
Parameters :
      lock - the lock to acquire
*/

void lock_start_sw_read(rw_lock_t * lock);


Our single, undecided reader may sometimes switch into writing mode using:

/*
Parameters :
      lock - the lock to acquire
      write_status - the previous "write" status of the lock
*/

void lock_switch_write(rw_lock_t * lock, int write_status); # this is actually a macro, so "write_status" is an output variable :)


After switching to write, switching back to read is mandatory before being able to release the lock:

/*
Parameters :
      lock - the lock to acquire
      write_status - the previous "write" status of the lock
*/

void lock_switch_read(rw_lock_t * lock, int previous_write_status);


Finally, we release the "switchable reading" lock:

/*
Parameters :
      lock - the lock to acquire
      write_status - the previous "write" status of the lock
*/

void lock_stop_sw_read(rw_lock_t * lock);

NB: the "switchable reading" API is only useful when all below conditions are true:

  • you are doing a lot of lock_start_read(), from lots of processes
  • from a very limited number of processes, you must do a lock_start_read() which cannot transition into a writer role using lock_stop_read() + lock_start_write(), because temporarily exiting the critical region is unacceptable. At the same time, always doing a lock_start_write() from these processes would cause unnecessary overhead, since the writer transition is very unlikely to happen.

... in all other reader/writer cases, just use the basic R/W locking API.

9.  Timer API

OpenSIPS exposes an API for recurrent job scheduling, with second and microsecond precision.
The OpenSIPS timer architecture involves the following types of processes (which may be listed at runtime by running opensips-cli -x mi ps):

  • "time_keeper" - a process which keeps track of the time since startup, using two global counters of second and microsecond precision. It also
  • "timer" - a process responsible for timer job scheduling. It does not run the jobs themselves, it simply dispatches them for execution to other processes
  • "SIP receiver" and "TCP receiver" - SIP workers, responsible for SIP message processing. Yet any of them may also receive an above-mentioned high-priority timer job sent by the "timer" worker, which they will immediately execute, disregarding any pending SIP messages on the network
  • "Timer handler" - for the cases where the SIP workers are so busy processing traffic (e.g. they are all stuck in some slow DB queries) that timer jobs are at risk of getting delayed, this process is meant to save the day and ensure the timer jobs execute on time

timer.h exposes all the relevant functionality for scheduling recurring jobs in OpenSIPS. To register a new timer function with second precision, use:

/*
Parameters:
        label – opaque string containing a short function description (for displaying/logging purposes)
        f – the actual function to be called
        param – parameter to be provided to the timer function
        interval – the interval, in seconds, that the function needs to be called at
        flags – the nature of the job: is it real-time (must be skipped if it cannot run on time) or critical (must never skip a single run, despite delays)

Returns:
        0 in case of success, negative code in case of internal error.
*/

int register_timer(char *label, timer_function f, void* param,
      unsigned int interval, unsigned short flags);
/*
The timer job prototype:

Parameters:
      ticks – represents the current number of seconds since OpenSIPS startup
      param - the parameter provided at job registration
*/

typedef void (timer_function)(unsigned int ticks, void* param);


To register a microsecond-precision job, you can use:

/*
Parameters:
        label – opaque string containing a short function description (for displaying/logging purposes)
        f – the actual function to be called
        param – parameter to be provided to the timer function
        interval – the interval, in microseconds, that the function needs to be called at
        flags – the nature of the job: is it real-time (must be skipped if it cannot run on time) or critical (must never skip a single run, despite delays)

Returns:
        0 in case of success, negative code in case of internal error
*/

int register_utimer(char *label, utimer_function f, void* param,
      unsigned int interval, unsigned short flags);


Important to note here that all the above timer related functions MUST be called in the context of the "attendant" process, before forking any other workers (so either from your module's mod_init() callback or directly from the core, before forking).

Below is a code snippet exemplifying how the dialog module registers two timer functions:

  • one with second precision, responsible for cleaning up empty profile structures
  • one with microsecond precision, responsible for replicating profile counters to other cluster nodes
    if (register_timer("dialog-repl-profiles-timer", clean_profiles, NULL,
        repl_prof_timer_check, TIMER_FLAG_DELAY_ON_DELAY) < 0) {
        LM_ERR("failed to register profiles utimer\n");
        return -1;
    }

    ...

    if (register_utimer("dialog-repl-profiles-utimer", broadcast_profiles, NULL,
        repl_prof_utimer * 1000, TIMER_FLAG_DELAY_ON_DELAY) < 0) {
        LM_ERR("failed to register profiles utimer\n");
        return -1;
    }


Additionally, the timer.h API exposes the time elapsed since OpenSIPS startup:

/*
Returns :
      the number of seconds elapsed since OpenSIPS start
*/

unsigned int get_ticks(void);

/*
Returns:
      the number of microseconds elapsed since OpenSIPS start
*/

utime_t get_uticks(void);

10.  Management Interface API

The Management Interface is the abstract layer that is commonly used to control and monitor OpenSIPS. The MI Interface supports multiple actual back-ends (e.g. FIFO, Datagram, JSON-RPC, XML-RPC). Thanks to its modularity and clear separation between structuring of the data (logic) and representation of the data structures (transport) layers, the developer is only left to define the functions which (de)structure the data, and then it is up to the OpenSIPS script writer to choose what transport she will actually use for controlling OpenSIPS.
The MI interface heavily uses JSON in order to interface with its transport layer:

  • the interface will provide as input a JSON (mi_item_t) to the user-defined MI function, with its required input fields
  • an MI function must also return a JSON, which will then be converted by the transport layer to the appropriate representation


Further on, we will focus on the core MI functions, with a specific focus on the log_level MI function. Note that modules may also export MI functions (and commonly do so) - see the Modules Development MI functions topic for more information on that.
The structures commonly used for exporting MI functions are found in mi/mi.h :

typedef struct mi_export_ {
    /* the name of the function users will invoke it from their transport of choice */
    char *name;

    /* short description of the usage of this function */
    char *help;

    /* flags for this function.  The current options are :
         - MI_ASYNC_RPL_FLAG - the function has an asynchronous behavior (e.g: MI functions that send out SIP messages and do not wait for their reply)
         - MI_NO_INPUT_FLAG - the function does not receive any parameters
    */

    unsigned int flags;

    /* an initialization function to be called by OpenSIPS (one time) */
    mi_child_init_f *init_f;

    /* the possible combinations of arguments which may be supplied to this function, along with their handlers */
    mi_recipe_t recipes[MAX_MI_RECIPES];
} mi_export_t;

/* Example of core MI exported function */
static mi_export_t mi_core_cmds[] = {
...
    { "log_level", "gets/sets the per process or global log level in OpenSIPS",
        0, 0, {
        {w_log_level,   {0}},
        {w_log_level_1, {"level", 0}},
        {w_log_level_2, {"level", "pid", 0}},
        {EMPTY_MI_RECIPE}
        }
    },
...
};


/* For exporting the populated array of MI functions
Parameters :
      mod_name : the name of the module exporting these functions
      mis : the array of exported MI functions
Returns :
      0 on success, negative in case of error
*/

int register_mi_mod( char *mod_name, mi_export_t *mis);

/* Example of usage */
if (register_mi_mod( "core", mi_core_cmds) < 0) {
      LM_ERR("unable to register core MI cmds\n");
      return -1;
}


The structures commonly used for implementing MI functions can be found in mi/mi.h and mi/item.h :

/*
Parameters:
      params : the JSON tree which includes the input arguments
      async_hdl : if the function has async capabilities, this is its async handler

Returns:
      An mi_response_t JSON structure with the requested data or execution result
*/

typedef mi_response_t *(mi_cmd_f)(const mi_params_t *params,
                                        struct mi_handler *async_hdl);

/* Both mi_item_t and mi_response_t point to the same cJSON struct, see lib/cJSON.h */
typedef struct cJSON
{
    /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
    struct cJSON *next;
    struct cJSON *prev;
    /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
    struct cJSON *child;

    /* The type of the item, as above. */
    int type;

    /* The item's string, if type==cJSON_String  and type == cJSON_Raw */
    char *valuestring;
    /* The item's number, if type==cJSON_Number */
    int valueint;
    /* The item's number, if type==cJSON_Number */
    double valuedouble;

    /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
    char *string;
} cJSON;


For building the output JSON, mi/item.h exposes the following functions:

/* Use for creating a new output reply tree
Parameters :
      code : success code for this tree ( >=200<300 for success, anything else for errors )
      reason : string reasons representation for the code
      reason_len : length of the reason parameter
Returns :
      A new mi_root tree, or NULL in case of error. Note that this function will allocate the node in PKG and it typically has to be returned - the freeing will be done in the MI core, after the output tree is written by the transport module */

struct mi_root *init_mi_tree(unsigned int code, char *reason, int reason_len);

/* Adding a new child node to our tree - typically first called to mi_root->node.kids
Parameters :
      parent : the parent node for our newly added node
      flags : Current options are :
                   MI_DUP_NAME : the name of this node needs to be duplicated in PKG
                   MI_DUP_VALUE : the value of the current node needs to be duplicated in PKG
      name : the name of the current node
      name_len : length of the node's name
      value : the value of the current node
      value_len : length of the node's value
*/

struct mi_node *add_mi_node_child(struct mi_node *parent, int flags,
        char *name, int name_len, char *value, int value_len);
/* Adding a new sibling node to one of our nodes
Parameters :
      brother : the brother node for our newly added node
      flags : Current options are :
                   MI_DUP_NAME : the name of this node needs to be duplicated in PKG
                   MI_DUP_VALUE : the value of the current node needs to be duplicated in PKG
      name : the name of the current node
      name_len : length of the node's name
      value : the value of the current node
      value_len : length of the node's value
*/

struct mi_node *add_mi_node_sibling(struct mi_node *brother, int flags,
        char *name, int name_len, char *value, int value_len);
/* Adding a new attribute to one of our nodes
      node : the node we will be adding the key-value attribute to
      flags : Current options are :
                   MI_DUP_NAME : the name of this attribute needs to be duplicated in PKG
                   MI_DUP_VALUE : the value of the current attribute needs to be duplicated in PKG
      name : the name of the current attribute
      name_len : length of the node's attribute name
      value : the value of the current value
      value_len : length of the node's attribute value
*/

struct mi_attr *add_mi_attr(struct mi_node *node, int flags,                                      
        char *name, int name_len, char *value, int value_len)


Further on, we will follow the implementation of the debug MI function. If called with no parameters, the function will return the current debug level in OpenSIPS. If called with one integer parameter, then the function will set the current debug level to the provided parameter.

struct mi_root *mi_debug(struct mi_root *cmd, void *param)
{
      struct mi_root *rpl_tree;
      struct mi_node *node;
      char *p;
      int len;
      int new_debug;

      /* check the kids member of our root node -
      if the input root node has kids, our command was called with parameters */

      node = cmd->node.kids;
      if (node!=NULL) {
            /* take the node's value and convert it to int, to make sure the parameter is valid */
            if (str2sint( &node->value, &new_debug) < 0)
                  /* if failed to convert to int, still return a RPL tree with an >=400 code and reason */
                  return init_mi_tree( 400, MI_SSTR(MI_BAD_PARM));
      } else
            new_debug = *debug;

      /* all is good so far, initialize a new output ROOT tree which has a 200 OK code & reason */
      rpl_tree = init_mi_tree( 200, MI_SSTR(MI_OK));
      if (rpl_tree==0)
              return 0;

      p = sint2str((long)new_debug, &len);
      /* add a new node to our output tree, which the current debug level */
      node = add_mi_node_child( &rpl_tree->node, MI_DUP_VALUE,
             MI_SSTR("DEBUG"),p, len);
      if (node==0) {
              free_mi_tree(rpl_tree);
              return 0;
      }

      /* if all was successful, overwrite the actual debug level, and return our tree */
      *debug = new_debug;
      return rpl_tree;
}


For more generic information on the MI Interface as well as some examples used for running MI commands with the opensipsctl utility, see the MI Interface documentation page.

11.  Statistics API

OpenSIPS exposes a statistics API that can be used both from the core or the modules. The statistics are essentially counters that will be internally incremented/decremented by OpenSIPS and that can be fetched by the outside world ( via the MI interface ) for understanding the OpenSIPS load / health status / etc.
The advantages of using the OpenSIPS Statistics API instead of regular counters is :

  • easily fetched from the MI Interface
  • on supported architectures, the statistics do not use an explicit lock ( the consistency is ensured by employing assembly code ), thus you will get better performance


The most important structures used for extending statistics are exported by statistics.h :

typedef struct stat_export_ {
        char* name;                /* null terminated statistic name */
        unsigned short flags;      /* flags */
        stat_var** stat_pointer;   /* pointer to the variable's mem location *
                                    * NOTE - it's in shm mem */

} stat_export_t;


For example, the core stats exported by OpenSIPS are defined in the following array :

stat_var* rcv_reqs;
stat_var* rcv_rpls;
stat_var* fwd_reqs;
stat_var* fwd_rpls;
stat_var* drp_reqs;
stat_var* drp_rpls;
stat_var* err_reqs;
stat_var* err_rpls;
stat_var* bad_URIs;
stat_var* unsupported_methods;
stat_var* bad_msg_hdr;

stat_export_t core_stats[] = {
        {"rcv_requests" ,         0,  &rcv_reqs              },
        {"rcv_replies" ,          0,  &rcv_rpls              },
        {"fwd_requests" ,         0,  &fwd_reqs              },
        {"fwd_replies" ,          0,  &fwd_rpls              },
        {"drop_requests" ,        0,  &drp_reqs              },
        {"drop_replies" ,         0,  &drp_rpls              },
        {"err_requests" ,         0,  &err_reqs              },
        {"err_replies" ,          0,  &err_rpls              },
        {"bad_URIs_rcvd",         0,  &bad_URIs              },
        {"unsupported_methods",   0,  &unsupported_methods   },
        {"bad_msg_hdr",           0,  &bad_msg_hdr           },
        {"timestamp",  STAT_IS_FUNC, (stat_var**)get_ticks   },
        {0,0,0}
};


As note from the above structure, statistics can either be a simple counter ( eg. rcv_requests ), but it can also be a function. Statistics function might come in handy when the developer needs to do extra processing on the raw counters before providing the final output.


After defining your array of statistics that you want to export, one should use the following for exporting the stats to be accessible by all

/*
Parameters :
      module - a string describing the module the current statistics belong to. Will be used when fetching the statistics via MI
      stats - the statistics to be registered
Returns :
      0 in case of success, negative in case of error
*/

int register_module_stats(char *module, stat_export_t *stats;


Note that register_module_stats will export the statistics, and also allocate them in SHM memory, for them to be accessible by all OpenSIPS processes.

Important to note here that all the above statistics related functions MUST be called in the context of the attendant process before forking is done.


At runtime, the developer has access to the following functions for operating on statistics :

/*
Parameters :
      var : the statistics to be updated
      n : the value ( if positive -> stat will be increment. negative -> stat will be decremented )
*/

void update_stat(stat_var* var, int n);

/*
Parameters :
      var : the statistics to be reseted
*/

void reset_stat(stat_var* var);

/*
Parameters :
      var : the statistics to be fetched
Returns :
      statistic value
*/

unsigned long get_stat_val(stat_var* var)

All statistics related code should be guarded by #ifdef STATISTICS , since the statistics are not a mandatory part of the OpenSIPS core ( they can be disabled from within menuconfig ).


For fetching the mynewstat statistic exported by the mynewmod module, one can use the opensipsctl like this :
opensipsctl fifo get_statistics mynewmod mynewstat
For fetching all the statistics exported by the mynewmod module, you can use
opensipsctl fifo get_statistics mynewmod:

12.  SQL Database API

OpenSIPS exposes a SQL database API that the module developers can use for operating the most common SQL queries. Advantages here are :

  • writing back-end independent code, since the DB API is decoupled from the actual modules implementing the back-end specific code
  • the ability to expose SQL-like capabilities to back-ends who are not internally SQL ( eg. the db_flatstore modules operates directly with flat-text files, yet the developer can insert into the file as if he was inserting into a regular SQL database


db/db.h exposes most of the database related functions. At startup, the developer will have just the database URL where he needs to connect. By calling db_bind_mod , the OpenSIPS DB API will try to automatically locate the actual DB module that support that specific back-end, and will return all the needed functions for operating on the back-end.

/**                                        
 * \brief Bind database module functions                            
 *                                                                      
 * This function is special, it's only purpose is to call find_export function in
 * the core and find the addresses of all other database related functions. The
 * db_func_t callback given as parameter is updated with the found addresses.
 *                                                            
 * This function must be called before any other database API call!
 *                                                              
 * The database URL is of the form "mysql://username:password@host:port/database" or
 * "mysql" (database module name).
 * In the case of a database connection URL, this function looks only at the first
 * token (the database protocol). In the example above that would be "mysql":
 * \see db_func_t                                                            
 * \param mod database connection URL or a database module name
 * \param dbf database module callbacks to be further used                  
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
                                     
int db_bind_mod(const str* mod, db_func_t* dbf);

typedef struct db_func {
      unsigned int           cap;           /* Capability vector of the database transport */
      db_use_table_f         use_table;     /* Specify table name */
      db_init_f              init;          /* Initialize database connection */
      db_close_f             close;         /* Close database connection */
      db_query_f             query;         /* query a table */
      db_fetch_result_f      fetch_result;  /* fetch result */
      db_raw_query_f         raw_query;     /* Raw query - SQL */
      db_free_result_f       free_result;   /* Free a query result */
      db_insert_f            insert;        /* Insert into table */
      db_delete_f            delete;        /* Delete from table */
      db_update_f            update;        /* Update table */
      db_replace_f           replace;       /* Replace row in a table */
      db_last_inserted_id_f  last_inserted_id;  /* Retrieve the last inserted ID
                                                    in a table */

      db_insert_update_f insert_update;     /* Insert into table, update on duplicate key */
} db_func_t;

/* Example of usage below */
db_func_t sql_functions;
db_url = str_init("mysql://root:vlad@localhost/opensips");

if (db_bind_mod(db_url, &sql_functions) < 0){
      /* most likely the db_mysql modules was not loaded, or it was loaded after our module */
      LM_ERR("Unable to bind to a database driver\n");
      return -1;
}
 

After successfully binding to the module, the developer must also make sure that the URL provided from the script writer point of a back-end which also supports the capabilities that will be further used ( eg. when operating on a flat text file, the db_last_inserted_id_f function will not be populated, and thus if the C code calls that function, the module will crash ). This is done by using the DB_CAPABILITY macro :

/**
 * Returns true if all the capabilities in cpv are supported by module
 * represented by dbf, false otherwise
 */

#define DB_CAPABILITY(dbf, cpv) (((dbf).cap & (cpv)) == (cpv))

/**
 * Represents the capabilities that a database driver supports.
 */

typedef enum db_cap {
        DB_CAP_QUERY =     1 << 0,  /**< driver can perform queries                                     */
        DB_CAP_RAW_QUERY = 1 << 1,  /**< driver can perform raw queries                                 */
        DB_CAP_INSERT =    1 << 2,  /**< driver can insert data                                         */
        DB_CAP_DELETE =    1 << 3,  /**< driver can delete data                                         */
        DB_CAP_UPDATE =    1 << 4,  /**< driver can update data                                         */
        DB_CAP_REPLACE =   1 << 5,  /**< driver can replace (also known as INSERT OR UPDATE) data       */
        DB_CAP_FETCH   =   1 << 6,  /**< driver supports fetch result queries                           */
        DB_CAP_LAST_INSERTED_ID = 1 << 7,  /**< driver can return the ID of the last insert operation   */
        DB_CAP_INSERT_UPDATE = 1 << 8, /**< driver can insert data into database and update on duplicate */
        DB_CAP_MULTIPLE_INSERT = 1 << 9 /**< driver can insert multiple rows at once */
} db_cap_t;


/**
 * All database capabilities except raw_query, replace, insert_update and
 * last_inserted_id which should be checked separately when needed
 */

#define DB_CAP_ALL (DB_CAP_QUERY | DB_CAP_INSERT | DB_CAP_DELETE | DB_CAP_UPDATE)

/* Example of usage below */
if (!DB_CAPABILITY(sql_functions, DB_CAP_ALL)) {
      LM_CRIT("Database modules does not "
            "provide all functions needed by our module\n");
      return -1;
}


Now that we have binded to the needed module and also made sure it supports our needed capabilities, we can go ahead and connect to the back-end, by invoking the init function from the binded functions :

/**
 * \brief Initialize database connection and obtain the connection handle.
 *
 * This function initialize the database API and open a new database
 * connection. This function must be called after db_bind_mod but before any
 * other database API function is called.
 *
 * The function takes one parameter, the parameter must contain the database
 * connection URL. The URL is of the form
 * mysql://username:password\@host:port/database where:
 *
 * username: Username to use when logging into database (optional).
 * password: password if it was set (optional)
 * host:     Hostname or IP address of the host where database server lives (mandatory)
 * port:     Port number of the server if the port differs from default value (optional)
 * database: If the database server supports multiple databases, you must specify the
 * name of the database (optional).
 * \see bind_dbmod
 * \param _sqlurl database connection URL
 * \return returns a pointer to the db_con_t representing the connection if it was
 * successful, otherwise 0 is returned
 */

typedef db_con_t* (*db_init_f) (const str* _sqlurl);

/* Example of usage below */
static db_con_t* db_connection;

if ((db_connection = sql_functions.init(db_url)) == NULL) {
      LM_ERR("Failed to connect to the database \n");
      return -1;
}


Connection sharing between multiple processes does not work for the majority of back-end specific connectors ( eg. MySQL, Postgres, etc ). Due to this fact, the developers MUST make sure to create a separate database connection for each process that will eventually need one - in the context of Module development, the connections need to be opened in the child_init function.

The output of the init() function will be the handler to be further used for all database interactions. When the connection is not needed anymore, the close method should be called :

/**
 * \brief Close a database connection and free all memory used.
 *
 * The function closes previously open connection and frees all previously
 * allocated memory. The function db_close must be the very last function called.
 * \param _h db_con_t structure representing the database connection
 */

typedef void (*db_close_f) (db_con_t* _h);


Before running any queries on the back-end, common practice dictates that the used tables should be versioned, in order to ensure that the user is not running your code on top of an older database structure.
db/db.h exposes db_check_table_version for this purpose, that checks the version table in the default OpenSIPS database structure :

/*              
Parameters :
      dbf - the functions to be used for running the version query
      dbh - the connection to run the version query
      table - str containing the table name we want to check for version
      version - the version we expect to find
Returns :
      0 means table version was successfully validated, negative in case of error ( internal error or older version found )
 */

int db_check_table_version(db_func_t* dbf, db_con_t* dbh, const str* table, const unsigned int version);


Before running a query through the API, we need to choose the table that the query will run on :

/**
 * \brief Specify table name that will be used for subsequent operations.
 *
 * The function db_use_table takes a table name and stores it db_con_t structure.
 * All subsequent operations (insert, delete, update, query) are performed on
 * that table.
 * \param _h database connection handle
 * \param _t table name
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */

typedef int (*db_use_table_f)(db_con_t* _h, const str * _t);


All queries must be preceded by a call to the use_table function. OpenSIPS internally does connection pooling - in case multiple module request connections to the same database, the connection will be shared between all those modules. Thus, in the context of a process, the same connection might be used by different modules - never assume a connection is dedicated to a single module.

For running a SELECT query, you should use the query function. Prototype is :

/**
 * \brief Query table for specified rows.
 *
 * This function implements the SELECT SQL directive.
 * If _k and _v parameters are NULL and _n is zero, you will get the whole table.
 *
 * if _c is NULL and _nc is zero, you will get all table columns in the result.
 * _r will point to a dynamically allocated structure, it is necessary to call
 * db_free_result function once you are finished with the result.
 *
 * If _op is 0, equal (=) will be used for all key-value pairs comparisons.
 *
 * Strings in the result are not duplicated, they will be discarded if you call
 * db_free_result, make a copy yourself if you need to keep it after db_free_result.
 *
 * You must call db_free_result before you can call db_query again!
 * \see db_free_result
 *
 * \param _h database connection handle
 * \param _k array of column names that will be compared and their values must match
 * \param _op array of operators to be used with key-value pairs
 * \param _v array of values, columns specified in _k parameter must match these values
 * \param _c array of column names that you are interested in
 * \param _n number of key-value pairs to match in _k and _v parameters
 * \param _nc number of columns in _c parameter
 * \param _o order by statement for query
 * \param _r address of variable where pointer to the result will be stored
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */

typedef int (*db_query_f) (const db_con_t* _h, const db_key_t* _k, const db_op_t* _op,
                                const db_val_t* _v, const db_key_t* _c, const int _n, const int _nc,
                                const db_key_t _o, db_res_t** _r);


Upon a successful select query call, the developer will have to manipulate the output db_res_t in order to use the results of his select query. Below are the used structures for interpreting a query result set.

/**
 * This type represents a result returned by db_query function (see below). The
 * result can consist of zero or more rows (see db_row_t description).
 *
 * Note: A variable of type db_res_t returned by db_query function uses dynamically
 * allocated memory, don't forget to call db_free_result if you don't need the
 * variable anymore. You will encounter memory leaks if you fail to do this!
 *
 * In addition to zero or more rows, each db_res_t object contains also an array
 * of db_key_t objects. The objects represent keys (names of columns). *
 */

typedef struct db_res {
        struct {
                db_key_t* names;   /**< Column names                    */
                db_type_t* types;  /**< Column types                    */
                int n;             /**< Number of columns               */
        } col;
        struct db_row* rows;   /**< Rows                            */
        int n;                 /**< Number of rows in current fetch */
        int res_rows;          /**< Number of total rows in query   */
        int last_row;          /**< Last row                        */
} db_res_t;

/**
 * Structure holding the result of a query table function.
 * It represents one row in a database table. In other words, the row is an
 * array of db_val_t variables, where each db_val_t variable represents exactly
 * one cell in the table.
 */

typedef struct db_row {
        db_val_t* values;  /**< Columns in the row */
        int n;             /**< Number of columns in the row */
} db_row_t;

/**
 * This structure represents a value in the database. Several datatypes are
 * recognized and converted by the database API. These datatypes are automatically
 * recognized, converted from internal database representation and stored in the
 * variable of corresponding type.
 *
 * Module that want to use this values needs to copy them to another memory
 * location, because after the call to free_result there are not more available.
 *
 * If the structure holds a pointer to a string value that needs to be freed
 * because the module allocated new memory for it then the free flag must
 * be set to a non-zero value. A free flag of zero means that the string
 * data must be freed internally by the database driver.
 */

typedef struct {
        db_type_t type; /**< Type of the value                              */
        int nul;                /**< Means that the column in database has no value */
        int free;               /**< Means that the value should be freed */
        /** Column value structure that holds the actual data in a union.  */
        union {
                int           int_val;    /**< integer value              */
                long long     bigint_val; /**< big integer value          */
                double        double_val; /**< double value               */
                time_t        time_val;   /**< unix time_t value          */
                const char*   string_val; /**< zero terminated string     */
                str           str_val;    /**< str type string value      */
                str           blob_val;   /**< binary object data         */
                unsigned int  bitmap_val; /**< Bitmap data type           */
        } val;
} db_val_t;


Many macros are in place in order to help writing faster and easier to read code :

/* Macros below work on result sets ( db_res_t )
/** Return the column names */

#define RES_NAMES(re) ((re)->col.names)
/** Return the column types */
#define RES_TYPES(re) ((re)->col.types)
/** Return the number of columns */
#define RES_COL_N(re) ((re)->col.n)
/** Return the result rows */
#define RES_ROWS(re)  ((re)->rows)
/** Return the number of current result rows */
#define RES_ROW_N(re) ((re)->n)
/** Return the last row of the result */
#define RES_LAST_ROW(re)  ((re)->last_row)
/** Return the number of total result rows */
#define RES_NUM_ROWS(re) ((re)->res_rows)

/* Macros below work on rows */
/** Return the columns in the row */
#define ROW_VALUES(rw) ((rw)->values)
/** Return the number of colums */
#define ROW_N(rw)      ((rw)->n)

/* Macros below work on values */
/**
 * Use this macro if you need to set/get the type of the value.
 */

#define VAL_TYPE(dv)   ((dv)->type)
/**
 * Use this macro if you need to set/get the null flag. A non-zero flag means that
 * the corresponding cell in the database contains no data (a NULL value in MySQL
 * terminology).
 */

#define VAL_NULL(dv)   ((dv)->nul)
/**
 * Use this macro if you need to access the integer value in the db_val_t structure.
 */

#define VAL_INT(dv)    ((dv)->val.int_val)
/**
 * Use this macro if you need to access the str structure in the db_val_t structure.
 */

#define VAL_STR(dv)    ((dv)->val.str_val)


Find below an example of a full select query from start to finish :

/* we will work on 'mytable' table with just two columns, keyname and value.
The select query we will run is 'select value from mytable where keyname='abc';'
*/

db_key_t key;
db_val_t val;
db_key_t col;
db_res_t* db_res = NULL;
db_row_t * rows;
db_val_t * values;

#define KEY_COL "keyname"
#define VALUE_COL "value"
str key_column = str_init(KEY_COL);
str value_column = str_init(VALUE_COL);
str db_table = str_init("mytable");

val.type = DB_STR;
val.nul = 0;
val.val.str_val.s = "abc";
val.val.str_val.len = 3;

key = &key_column;
col = &value_column;

if (sql_functions.use_table(db_handle, &db_table) < 0) {
      LM_ERR("sql use_table failed\n");
      return -1;
}

if(sql_functions.query(db_handle, &key, NULL, &val, &col, 1, 1, NULL, &db_res) < 0) {
       LM_ERR("failed to query database\n");
       return -1;
}

nr_rows = RES_ROW_N(db_res);
rows = RES_ROWS(db_res);


if (nr_rows <= 0) {
      LM_DBG("no rows found\n");
      sql_functions.free_result(db_handle, db_res);
      return -1;
}

for (i=0;i<nr_rows;i++) {
      values = ROW_VALUES(rows + i);
      if (VAL_NULL(values)) {
            LM_WARN("Column value should not be null - skipping \n");
            continue;
      }

      LM_DBG("We have feteched %s\n",VAL_STRING(values));
      /* do further rows processing here */
}

sql_functions.free_result(db_handle, db_res);
return 0;
 


As can be seen from the example above, any successful call to the query SQL function must be followed by the freeing of the returned result set, which is done by calling the free_result API function :

/**
 * \brief Free a result allocated by db_query.
 *
 * This function frees all memory allocated previously in db_query. Its
 * neccessary to call this function on a db_res_t structure if you don't need the
 * structure anymore. You must call this function before you call db_query again!
 * \param _h database connection handle
 * \param _r pointer to db_res_t structure to destroy
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */

typedef int (*db_free_result_f) (db_con_t* _h, db_res_t* _r);


Sometimes, especially when querying large tables, it is not desirable to fetch all the rows in one chunk, since that might lead to the filling of the OpenSIPS private memory.
For such scenarios, using the fetch_result API function is highly recommended

/**                                                              
 * \brief Fetch a number of rows from a result.                                
 *
 * The function fetches a number of rows from a database result. If the number
 * of wanted rows is zero, the function returns anything with a result of zero.
 * \param _h structure representing database connection
 * \param _r structure for the result
 * \param _n the number of rows that should be fetched            
 * \return returns 0 if everything is OK, otherwise returns value < 0        
 */

typedef int (*db_fetch_result_f) (const db_con_t* _h, db_res_t** _r, const int _n);


Find below a full example of using fetch_result :

        /* check if our used DB driver supports fetching a limited number of rows */
        if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
                /* run our query as usual, but DO NOT provide a result set pointer ( last parameter 0 ) */
                if ( dr_dbf->query( db_hdl, 0, 0, 0, columns, 0, db_cols, 0, 0 ) < 0) {
                        LM_ERR("DB query failed\n");
                        goto error;
                }
                /* estimate how many rows we can fit into our current PKG memory */
                no_rows = estimate_available_rows( 4+32+15+4+32+4+128+4+32+4, db_cols);
                if (no_rows==0) no_rows = 10;
                /* try to fetch our rows */
                if(dr_dbf->fetch_result(db_hdl, &res, no_rows )<0) {
                        LM_ERR("Error fetching rows\n");
                        goto error;
                }
        } else {
                /* no fetching rows support - fallback to full rows loading */
                if ( dr_dbf->query(db_hdl,0,0,0,columns,0,db_cols,0,&res) < 0) {
                        LM_ERR("DB query failed\n");
                        goto error;
                }
        }

        do {
                for(i=0; i < RES_ROW_N(res); i++) {
                        row = RES_ROWS(res) + i;
                        /* start processing our loaded rows */
                }

                if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
                        /* any more rows to fetch ? */
                        if(dr_dbf->fetch_result(db_hdl, &res, no_rows)<0) {
                                LM_ERR( "fetching rows (1)\n");
                                goto error;
                        }
                        /* success in fetching more rows - continue the loop */
                } else {
                        /* we were not supporting fetching rows in the first place, processed everything */
                        break;
                }
        } while(RES_ROW_N(res)>0);

        dr_dbf->free_result(db_hdl, res);
 


Inserting rows in a table can be done by calling the insert API function :

/**
 * \brief Insert a row into the specified table.
 *
 * This function implements INSERT SQL directive, you can insert one or more
 * rows in a table using this function.
 * \param _h database connection handle
 * \param _k array of keys (column names)
 * \param _v array of values for keys specified in _k parameter
 * \param _n number of keys-value pairs int _k and _v parameters
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */

typedef int (*db_insert_f) (const db_con_t* _h, const db_key_t* _k,
                                const db_val_t* _v, const int _n);
 


Deleting rows from a table is accomplished by calling the delete API function :

/**
 * \brief Delete a row from the specified table.
 *
 * This function implements DELETE SQL directive, it is possible to delete one or
 * more rows from a table.
 * If _k is NULL and _v is NULL and _n is zero, all rows are deleted, the
 * resulting table will be empty.
 * If _o is NULL, the equal operator "=" will be used for the comparison.
 *
 * \param _h database connection handle
 * \param _k array of keys (column names) that will be matched
 * \param _o array of operators to be used with key-value pairs
 * \param _v array of values that the row must match to be deleted
 * \param _n number of keys-value parameters in _k and _v parameters
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */

typedef int (*db_delete_f) (const db_con_t* _h, const db_key_t* _k, const db_op_t* _o,
                                const db_val_t* _v, const int _n);


Updating rows in a table can be done by calling the update API function :

/**
 * \brief Update some rows in the specified table.
 *
 * The function implements UPDATE SQL directive. It is possible to modify one
 * or more rows in a table using this function.
 * \param _h database connection handle
 * \param _k array of keys (column names) that will be matched
 * \param _o array of operators to be used with key-value pairs
 * \param _v array of values that the row must match to be modified
 * \param _uk array of keys (column names) that will be modified
 * \param _uv new values for keys specified in _k parameter
 * \param _n number of key-value pairs in _k and _v parameters
 * \param _un number of key-value pairs in _uk and _uv parameters
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */

typedef int (*db_update_f) (const db_con_t* _h, const db_key_t* _k, const db_op_t* _o,
                                const db_val_t* _v, const db_key_t* _uk, const db_val_t* _uv,
                                const int _n, const int _un);


Replacing rows in a database table can be done with the replace function from the API :

/**
 * \brief Insert a row and replace if one already exists.
 *
 * The function implements the REPLACE SQL directive. It is possible to insert
 * a row and replace if one already exists. The old row will be deleted before
 * the insertion of the new data.
 * \param _h structure representing database connection
 * \param _k key names
 * \param _v values of the keys
 * \param _n number of key=value pairs
 * \return returns 0 if everything is OK, otherwise returns value < 0
*/

typedef int (*db_replace_f) (const db_con_t* handle, const db_key_t* keys,
                                const db_val_t* vals, const int n);


Sometimes, for optimizing database operations, it is useful to know the auto-increment primary key value upon inserting / updating a record. For such cases, the last_inserted_id API call can be used :

/**
 * \brief Retrieve the last inserted ID in a table.
 *
 * The function returns the value generated for an AUTO_INCREMENT column by the
 * previous INSERT or UPDATE  statement. Use this function after you have
 * performed an INSERT statement into a table that contains an AUTO_INCREMENT
 * field.
 * \param _h structure representing database connection
 * \return returns the ID as integer or returns 0 if the previous statement
 * does not use an AUTO_INCREMENT value.
 */

typedef int (*db_last_inserted_id_f) (const db_con_t* _h);


Also, when we want to insert a row into a table, and update the row in case of duplicate key errors, we should use the insert_update API call :

/**
 * \brief Insert a row into specified table, update on duplicate key.
 *
 * The function implements the INSERT ON DUPLICATE KEY UPDATE SQL directive.
 * It is possible to insert a row and update if one already exists.
 * The old row will not deleted before the insertion of the new data.
 * \param _h structure representing database connection
 * \param _k key names
 * \param _v values of the keys
 * \param _n number of key=value pairs
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */

typedef int (*db_insert_update_f) (const db_con_t* _h, const db_key_t* _k,
                                const db_val_t* _v, const int _n);


For running all other database queries, one should use the raw_query API function.

/**
 * \brief Raw SQL query.
 *
 * This function can be used to do database specific queries. Please
 * use this function only if needed, as this creates portability issues
 * for the different databases. Also keep in mind that you need to
 * escape all external data sources that you use. You could use the
 * escape_common and unescape_common functions in the core for this task.
 * \see escape_common
 * \see unescape_common
 * \param _h structure representing database connection
 * \param _s the SQL query
 * \param _r structure for the result
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */

typedef int (*db_raw_query_f) (const db_con_t* _h, const str* _s, db_res_t** _r);

13.  NoSQL API

14.  Event Interface API

15.  BIN Interface API

The Binary Internal Interface is an OpenSIPS core interface which offers an efficient way for communication between individual OpenSIPS instances.
This is especially useful in scenarios where realtime data (such as dialogs) cannot be simply stored in a database anymore, because failover would require entire minutes to complete. This issue can be solved with the new internal binary interface by replicating all the events related to the runtime data (creation / updating / deletion) to a backup OpenSIPS instance.
The BIN interface functionality is exported by the bin_interface.h file.
Using the interface has two steps :

  • creating and sending the new event from the Active OpenSIPS server
  • receiving and processing the event in the Backup OpenSIPS server

For creating and sending a new event, the following methods are to be used :

/**
 * bin_init - begins the construction of a new binary packet (header part):
 *
 * +-------------------+------------------------------------------------------+
 * |  8-byte HEADER    |                 BODY                max 65535 bytes  |
 * +-------------------+------------------------------------------------------+
 * | PK_MARKER |  CRC  | LEN | MOD_NAME | CMD | LEN | FIELD | LEN | FIELD |...|
 * +-------------------+------------------------------------------------------+
 *
 * @param: { LEN, MOD_NAME } + CMD
 */

int bin_init(str *mod_name, int cmd_type)

/*
 * copies the given string at the 'cpos' position in the buffer
 * allows null strings (NULL content or NULL param)
 *
 * @return: 0 on success
 */

int bin_push_str(const str *info)

/*
 * adds a new integer value at the 'cpos' position in the buffer
 *
 * @return: 0 on success
 */

int bin_push_int(int info)

/**
 * bin_send - computes the checksum of the current packet and then
 * sends the packet over UDP to the @dest destination
 *
 * @return: number of bytes sent, or -1 on error
 */

int bin_send(union sockaddr_union *dest)


On the receiving end, the developer must first register a callback that will be trigger when receiving special types of BIN message, by using the following :

/**
 * bin_register_cb - registers a module handler for specific packets
 * @mod_name: used to classify the incoming packets
 * @cb:       the handler function, called once for each matched packet
 *
 * @return:   0 on success
 */

int bin_register_cb(char *mod_name, void (*cb)(int))


The callback will only be triggered for the mod_name class of BIN packages, and also the callback will receive the packet type as well, in order to be able to differentiate between multiple types of events ( eg. create, update, delete, etc ).
Afterwards, you should use the pop methods for extracting the contents of the package :

/*
 * pops an str from the current position in the buffer
 * @info:   pointer to store the result
 *
 * @return: 0 on success
 *
 * Note: The pointer returned in @info str is only valid for the duration of
 *       the callback. Don't forget to copy the info into a safe buffer!
 */

int bin_pop_str(str *info)

/*
 * pops an integer value from the current position in the buffer
 * @info:   pointer to store the result
 *
 * @return: 0 on success
 */

int bin_pop_int(void *info)


See the main page dedicated to the Binary Interface on how to then configure OpenSIPS listeners for the binary packages.

16.  Module Development

16.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 dynamically 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 required 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 comments ) 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 */
        };
16.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.

16.3  Initializing the module

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

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.

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 ).

16.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)();
16.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 */
}
16.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 values after fixup.
Again, mod_fix.h provides various 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 strictly 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.

16.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.

16.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

16.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.

16.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 necessarily 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.

17.  Module APIs

Within OpenSIPS, one modules might need to access the functionality of another module ( one very common example are modules desiring to do operations on a per dialog basis, thus needing part of the dialog module functionality ). Instead of directly accessing the functionality from within the target module, OpenSIPS heavily 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 that indicate various behavior.
The module that needs to operate with the above API should first call ( within its mod_init ) the function to bind to the needed module's API, and then operate with the received structure.
Find below the most heavily used module APIs in OpenSIPS.

17.1  TM module
17.2  RR Module

The RR ( Record Route ) module API is exported by the modules/rr/api.h file.
From a general functionality point of view, the RR module is the one responsible for controlling the Record-Route part for requests, and then routing the sequential requests based on the Route headers. The RR module is the simplest module that provides very simple dialog-aware functionality, by allowing the storage of information ( parameters ) in the Record-Route headers, which can be later retrieved from the Route headers.
First, you will have to bind to the RR module's API and get the structure which you will further use. The binding function is :

/*
Parameters : rrb is the API output to be further used

Returns : 0 in case of success and -1 in case of failure
*/

inline static int load_rr_api( struct rr_binds *rrb );

The rr_binds structure is exemplified below :

struct rr_binds {
        add_rr_param_t      add_rr_param;
        check_route_param_t check_route_param;
        is_direction_t      is_direction;
        get_route_param_t   get_route_param;
        register_rrcb_t     register_rrcb;
        get_remote_target_t get_remote_target;
        get_route_set_t     get_route_set;
        /* whether or not the append_fromtag parameter is enabled in the RR module */
        int                 append_fromtag;
        /* the number of routes removed within the loose routing process */
        int*                removed_routes;
        /* the type of routing done, when comparing the previous and the next hop
        Both can be either strict or loose routers, thus here we have 4 different options :
        ROUTING_LL - loose to loose routing
        ROUTING_SL - strict to loose routing
        ROUTING_SS - strict to strict routing
        ROUTING_LS - loose to strict routing
        */

        int*                routing_type;

        loose_route_t       loose_route;
        record_route_t      record_route;
};


Find below the API function signatures along with their usage :

/* Adds a parameter to the requests's Record-Route URI. The API supports the use case
   where the Record-Routed header will be further added.
   The function is to be used for marking certain dialogs that can
   be identified from the sequential requests - since the Route
   headers in the sequential requests will also contain our added
   params, which we'll be able to fetch with get_route_param ( see below )

   The function returns 0 on success. Otherwise, -1 is returned.

   Parameters :
     * struct sip_msg* msg - request that will has the parameter
       “param” added to its Record-Route header.
     * str* param - parameter to be added to the Record-Route
       header - it must be in “;name=value” format.
*/

typedef  int (*add_rr_param_t)(struct sip_msg* msg, str* param);


/*    The function checks for the request “msg” if the URI parameters
   of the local Route header (corresponding to the local server)
   matches the given regular expression “re”. It MUST be call
   after the loose_route was done.

   The function returns 0 on success. Otherwise, -1 is returned.

   * struct sip_msg* msg - request that will has the Route
       header parameters checked.
   * regex_t* re - compiled regular expression to be checked
       against the Route header parameters.
*/

int (*check_route_param_t)(struct sip_msg* msg, regex_t* rem);


/*    The function checks the flow direction of the request “msg”. As
   for checking it's used the “ftag” Route header parameter, the
   append_fromtag (see Section 1.4.1, “append_fromtag (integer)”
   module parameter must be enables. Also this must be call only
   after the loose_route is done.

   The function returns 0 if the “dir” is the same with the
   request's flow direction. Otherwise, -1 is returned.

   Meaning of the parameters is as follows:
     * struct sip_msg* msg - request that will have the direction
       checked.
     * int direction - direction to be checked against. It may be
       RR_FLOW_UPSTREAM ( from callee to caller ) or
       RR_FLOW_DOWNSTREAM ( from caller to callee ).
*/

typedef  int (*is_direction_t)(struct sip_msg* msg, int direction);


/*
   The function search in to the “msg”'s Route header parameters
   the parameter called “name” and returns its value into “val”.
   It must be call only after the loose_route is done.

   The function returns 0 if parameter was found (even if it has
   no value). Otherwise, -1 is returned.

   Meaning of the parameters is as follows:
     * struct sip_msg* msg - request that will have the Route
       header parameter searched.
     * str *name - contains the Route header parameter to be
       searched.
     * str *val - returns the value of the searched Route header
       parameter if found. It might be empty string if the
       parameter had no value.
*/

typedef  int (*get_route_param_t)(struct sip_msg*, str*, str*);


/*
   The function register a new callback (along with its
   parameter). The callback will be called when a loose route will
   successfully be performed for the local address.

   The function returns 0 on success. Otherwise, -1 is returned.

    Meaning of the parameters is as follows:
     * rr_cb_t func - callback function to be registered.
     * void *param - parameter to be passed to the callback
       function.
     * short prior - parameter to set the priority. The callbacks
        will be executed in order from small to big priority - thus
        to be used for ordering callbacks that depend on each other.

*/

typedef int (*register_rrcb_t)( rr_cb_t func, void *param, short prior);


/* Function to be registered as callback within the RR API :
     * struct sip_msg* req - request that is currently being processed
     * str *rr_param - the parameters in our server's Route header
     * str *param - the custom parameter provided at the callback registration
*/

typedef void (rr_cb_t) (struct sip_msg* req, str *rr_param, void *param);


/*
   Function used to fetch the far-end remote target for the current message.
   Depending on the type routing done ( see the '''routing_type''' API member )
   the remote target can be either in the initial Request URI, in the current
   Request-URI or in the last route header. The API function take care to
   correctly identify which scenario is correct.
   The API function MUST be called after loose_route() was called.

   The function returns the str pointer with the remote target, or NULL in case of error.

   Meaning of the parameters is as follows:
        -* struct sip_msg* msg - request that the remote target will be extracted from
*/

typedef  str* (*get_remote_target_t)(struct sip_msg* msg);


/*
    Function used to fetch the route set from the current SIP message.
    The function takes into account the actual loose_route() done, and properly discards
    the proxy's own Route headers from the SIP message. Thus, the function must be called
    after loose_route() was done.

    The function will return an array of str structures, or NULL in case of error. The
    nr_routes parameter will indicate the size of the returned array

    Meaning of the parameters is as follows:
        -* struct sip_msg* msg - request that the remote target will be extracted from
        -* int* nr_routes - the size of the returned array
*/

typedef  str* (*get_route_set_t)(struct sip_msg*,int *nr_routes);


/*
    Function to be used when for routing a request according to the Route headers present
    in it and to the type of Routing ( loose vs strict ) that needs to be used.

    The function will return 0 in case of success ( request is successfully routed ). Otherwise,
    -1 is returned.

   Meaning of the parameters is as follows:
        -* struct sip_msg* msg - request to be routed

*/

typedef  int (*loose_route_t)(struct sip_msg* msg);


/*
    Function to be used when record-routing an initial request. The function will add
    one or two Record-Route headers , depending if there are any interface changes and
    if r2 is enabled. Also, if any parameters are provided, they will be added to all the
    Record-Route headers that the function internally adds.

    Returns 0 in case of success. Otherwise, -1 will be returned.

    Meaning of the parameters is as follows:
        -* struct sip_msg* msg - request to be record routed
        -* str* params - parameters to be added to the Record-Route headers
*/

typedef  int (*record_route_t)(struct sip_msg* msg, str* params);
 


See below an example of binding to the RR API from another module, registering a callback and then checking the direction of the sequential request and also checking for the existence of a certain parameter.

...
#include "../rr/api.h"
...
struct rr_binds my_rrb;
...
...
int mod_init(void) {
        ...
        ...
        /* load the RR API */
        if (load_rr_api( &my_rrb )!=0) {
            LM_ERR("can't load RR API\n");
            goto error;
        }

        if (!my_rrb.append_fromtag) {
            LM_ERR("The append_fromtag parameter is not set, but we need it for detecting the direction of requests \n");
            goto error;
        }
        ...
        ...
        /* register a RR callback */
        if (my_rrb.register_rrcb(my_callback,0,0))!=0) {
            LM_ERR("can't register RR callback\n");
            goto error;
        }
        ...
        ...
}

void my_callback(struct sip_msg* msg,str* rr_param,void *param)
{
        str name = str_init("ftag");
        str val;

        LM_INFO("Received a new sequential request from %s\n",
        my_rrb.is_direction( msg, RR_FLOW_UPSTREAM)?"callee":"caller");

        if (my_rrb.get_route_param(msg,&name,&val) == 0) {
                LM_INFO("We have the ftag parameter with value [%.*s]\n",val.len,val.s);
        }
}


17.3  Dialog Module

The Dialog module API is exported by the modules/dialog/dlg_load.h file.
The dialog module provides dialog awareness to the OpenSIPS proxy. Its functionality is to keep track of the current dialogs and to offer information about them (like how many dialogs are active).
Aside from tracking, the dialog module offers functionalities like flags and attributes per dialog (persistent data across dialog), dialog profiling and dialog termination (on timeout base or external triggered).
First, you will have to bind to the RR module's API and get the structure which you will further use. The binding function is :

/*
Parameters : dlgb is the API output to be further used
Returns the 0 in case of success and -1 in case of failure
*/

static inline int load_dlg_api( struct dlg_binds *dlgb );
 


The dlg_binds structure is exemplified below :

struct dlg_binds {
        register_dlgcb_f     register_dlgcb;
        create_dlg_f         create_dlg;
        get_dlg_f            get_dlg;
        add_profiles_f       add_profiles;
        search_dlg_profile_f search_profile;
        set_dlg_profile_f    set_profile;
        unset_dlg_profile_f  unset_profile;
        get_profile_size_f   get_profile_size;
        store_dlg_value_f    store_dlg_value;
        fetch_dlg_value_f    fetch_dlg_value;
        terminate_dlg_f      terminate_dlg;

        match_dialog_f       match_dialog;
        validate_dialog_f    validate_dialog;
        fix_route_dialog_f   fix_route_dialog;
};


Find below the API function signatures along with their usage :

/*
To be used to register a new dialog based callback.
The function returns 0 on success. Otherwise, -1 is returned.

Parameters :
        * struct dlg_cell *dlg : the dialog that the registered callback belongs to
        * int cb_types : The type of registered callback ( can be a bitmask of multiple callback types). The options are as follows :
                        * DLGCB_LOADED - callback will get called when a new dialog is loaded into memory at startup ( from a database ) or at runtime ( from a database via external MI call or via the binary replication interface ). This callback must be registered alone, and the dlg_cell* provided at registration time must be NULL ( since it's a global cb type, not associated to any particular dialog )
                        * DLGCB_CREATED - callback will get called when a new dialog is created. The callback will be called when the dialog is fully initialized ( create_dialog() was called either from script or from the API and the Transaction associated to the initial invite is fully initialized as well ). This callback must be registered alone, and the dlg_cell* provided at registration time must be NULL ( since it's a global cb type, not associated to any particular dialog )
                        * DLGCB_FAILED - callback will get called when a particular dialog fails to establish ( 3xx, 4xx, 5xx or 6xx reply received and relayed ). This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_CONFIRMED - callback will get called when a particular dialog gets established ( 2xx reply received ). This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_REQ_WITHIN - callback will get called when a sequential request is matched as belonging to a particular dialog ( either via loose_route() or match_dialog() calling from the script). This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_TERMINATED - callback will get called when a dialog gets terminated. Reasons here are BYE routing or external Termination ( MI, API, etc ). This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_EXPIRED - callback will get called when a dialog lives past its assigned timeout ( see the $DLG_timeout script pvar ). This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_EARLY - callback will get called when the first provisional reply ( 1xx ) is received for the registered dialog. This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_RESPONSE_FWDED - callback will get called when a reply is forwarded during the initial state of the dialog setup ( usually provisional 180, 183, etc ). This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_RESPONSE_WITHIN - callback will get called for all replies forwarded by OpenSIPS for sequential requests belonging to the current dialog. This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_MI_CONTEXT - callback will get called when the 'dlg_list_ctx' MI function is called. Useful when modules bind to the dialog module API want to append nodes to the dlg_list_ctx MI response tree. This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_DESTROY - callback will get called when the dialog is getting ready to be destroyed. At the time of the callback calling, the dialog is unlinked from the main hash, but it is not freed yet. This is a per dialog callback, so the first dlg parameter MUST be provided.
                        * DLGCB_SAVED - callback will get called when the dialog information is synchronized to the database ( either initial insertion, updating various fields in the DB or removing the dialog from the DB ). This is a per dialog callback, so the first dlg parameter MUST be provided.
        func - the actual callback function that will be executed.
        param - parameter that will be sent to the callback when called
        free_func - function to free the callback parameter at destroy time.
*/

typedef int (*register_dlgcb_f)(struct dlg_cell* dlg, int cb_types,
                dialog_cb func, void *param, param_free_cb free_func);
/* callback function prototype */
typedef void (dialog_cb) (struct dlg_cell* dlg, int type,
                struct dlg_cb_params * params);
/* function to free the callback param */
typedef void (param_free_cb) (void *param);


/*
        Creates the dialog structure for the current initial INVITE message and will keep track of the call for the rest of it's lifetime.
        Returns 0 in case of success. Otherwise, -1 is returned.
        Parameters :
                msg : the initial INVITE sip message
                flags : flags altering the behavior of the create dialog. Options here are :
                        * DLG_FLAG_BYEONTIMEOUT - dialog will be terminated from the middle, by OpenSIPS, when the dialog lifetime is exceeded.
                        * DLG_FLAG_PING_CALLER - ping the caller with OPTIONS messages to detect if the call is still up
                        * DLG_FLAG_PING_CALLEE - ping the callee with OPTIONS messages to detect if the call is still up
*/

typedef int (*create_dlg_f)(struct sip_msg *req,int flags);


/*
        Returns the current dialog pointer. In case of no created dialog or other internal errors, NULL is returned.
*/

typedef struct dlg_cell *(*get_dlg_f) (void);


/*
        Parses and creates the provided profile definitions.
        Returns 0 in case of success. Otherwise, -1 is returned.
        Parameters :
                profiles - the NULL terminated string containing one or multiple profile definitions ( separated by ';' )
                has_value - whether our profiles will contain values with counters, or they will be just stand-alone counters
*/

typedef int (*add_profiles_f)(char* profiles, unsigned int has_value);


/*
        Looks up and returns the profile definition associated to the provided name. In case the profile is not found, or of internal error, NULL is returned.
        Parameters :
                name : str containing the name of a single profile definition
*/

typedef struct dlg_profile_table* (*search_dlg_profile_f)(str *name);


/*
        Sets the current dialog to belong in the provided profile definition.
        Returns 0 in case of success, and -1 otherwise.
        Parameters :
                msg : the SIP message currently being processed
                value : the value the dialog will be associated with within the provided profile
                profile : the main profile the dialog will be linked to
                is_replicated : whether or not this dialog was originated on the current machine, or we received it via replication mechanisms. Controls whether the cachedb counters should be increased for the current dialog profile or not.
*/

typedef int (*set_dlg_profile_f)(struct sip_msg *msg, str *value,
                        struct dlg_profile_table *profile, char is_replicated);


/*
        The opposite of the set_dlg_profile_f API function.
        Returns 0 in case of success, and -1 otherwise.
        Parameters :
                msg : the SIP message currently being processed
                value : the value the dialog will be de-associated with within the provided profile
                profile : the main profile the dialog will be un-linked from

*/

typedef int (*unset_dlg_profile_f)(struct sip_msg *msg, str *value,
                         struct dlg_profile_table *profile);


/*
        Returns the number of dialogs belonging to the current profile.
        Parameters :
                profile : the profile definition
                value : the value to filter the profile. Can be missing, the size of all individual values within a profile will be returned.
*/

typedef unsigned int (*get_profile_size_f)(struct dlg_profile_table *profile,
                                                                                str *value);


/*
        Stores an opaque key-value mapping inside the dialog structure, which can be fetched at a later time based on the current dialog. If the key already exists, it will be overwritten.
        Returns 0 in case of success. Otherwise, -1 is returned.
        Parameters :
                dlg : the dialog pointer to link the key to
                name : name of the key to store within the provided dialog
                val : the value to be mapped to the provided key
*/

typedef int (*store_dlg_value_f)(struct dlg_cell *dlg,
                str *name, str *val);


/*
        Fetch a previously stored value within the provided dialog.
        Returns 0 in case of success. Otherwise, -1 is returned.
        Parameters :
                dlg : the dialog pointer to fetch the key from
                name : the name of the key to fetch
                val : output parameter, the value of the key will be stored here
                val_has_buf - whether we have a buffer allocated for fetching the key's value or not. If 0, the dialog module will return a static buffer that it reuses for all key values.
*/

typedef int (*fetch_dlg_value_f)(struct dlg_cell *dlg,
                str *name, str *val, int val_has_buf);


/*
        Terminates an ongoing dialog ( by sending BYE messages both ways )
        Returns 0 in case of success. Otherwise, -1 is returned.
        Parameters :
                h_entry : The hash bucket ID for the dialog that we want to terminate
                h_id : The ID of the dialog element within our hash bucket
                reason : An opaque string describing the reason for terminating the dialog. Can be later fetched from the script by the user
*/

typedef int (*terminate_dlg_f)(unsigned int h_entry, unsigned int h_id,str *reason);


See below a simple example of binding to the Dialog API from another module and running a couple of dialog related operations.

...
#include "../dialog/dlg_load.h"
...
...
struct dlg_binds my_dlgb;
...
...
int mod_init(void) {
        ...
        ...
        /* load the dialog API */
        if (load_dlg_api(&my_dlgb)!=0) {
                LM_ERR("failed to find dialog API - is dialog module loaded?\n");
                goto error;
        }

        /* make sure we get notified of all upcoming created dialogs */
        if (my_dlgb.register_dlgcb(NULL,DLGCB_CREATED,new_created_dialog_cb,NULL,NULL) != 0 ) {
                LM_ERR("Failed to register initial dlg callback \n");
                goto error;
        }

        ...
        ...
}

void new_created_dialog_cb(struct dlg_cell *did, int type,
                struct dlg_cb_params * params)
{
        time_t curtime;
        str create_time_key = str_init("created_at");
        str create_time;

        time(&curtime);
        LM_INFO("Dialog was created ! \n");

        if ( my_dlgb->register_dlgcb(did,
        DLGCB_CONFIRMED, dialog_confirmed_cb, NULL, NULL) != 0) {
                LM_ERR("Failed to register CB for dialog establishment \n");
                return;
        }

        if ( my_dlgb->register_dlgcb(did,
        DLGCB_REQ_WITHIN, dialog_sequential_cb, NULL, NULL) != 0) {
                LM_ERR("Failed to register CB for dialog sequential requests \n");
                return;
        }

        create_time.s = ctime(&curtime);
        create_time.len = strlen(create_time.s);

        if ( my_dlgb.store_dlg_value(did,&create_time_key,&create_time) != 0) {
                LM_ERR("Failed to store our create string key \n");
                return;
        }

}

void dialog_confirmed_cb(struct dlg_cell *did, int type,
                struct dlg_cb_params * params)
{
        str create_time_key = str_init("created_at");
        str create_time;

        if ( my_dlgb.fetch_dlg_value(did,&create_time_key,&create_time,0) != 0 ) {
                LM_ERR("Failed to fetch our create string key \n");
                return;
        }

        LM_INFO("The dialog was created at %.*s and is now established \n",create_time.len,create_time.s);
}

void dialog_sequential_cb(struct dlg_cell *did, int type,
                struct dlg_cb_params * params)
{
        struct sip_msg *msg = msg;
        str term_reason = "we HATE options from callee :D";

        if (msg->first_line.u.request.method_value == METHOD_OPTIONS && params->dir == DLG_DIR_UPSTREAM) {
                LM_INFO("Received OPTIONS sequential from callee. Terminating DLG because %.*s\n",
                term_reason.len,term_reason.s);

                if (my_dlgb.terminate_dlg(did->h_entry,did->h_id,&term_reason) != 0) {
                        LM_ERR("Failed to terminate the dialog \n");
                }
        }
}

18.  Video Tutorial

A full video tutorial ( 7 video sessions of 1-2 hours ) going through the OpenSIPS development process can be found here , along with some source code examples used in the video tutorial.


Page last modified on October 15, 2022, at 04:50 AM