Login | Register

Documentation

Documentation -> Development Manual 3.6 -> Extending the Configuration File

This page has been visited 159 times.


Pages for other versions: Older versions:


Extending the Configuration 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.

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.

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 {

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 pseudovariable, 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 pseudovariable. Also, no special initialization is needed for our psedovariable 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 doesnt 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;
}


Page last modified on December 11, 2019, at 06:51 PM