OpenSIPS 3.1 开发手册(二)

https://www.opensips.org/Documentation/Development-Manual

目录

5.  改变SIP 消息

5.1  SIP 消息Lump

5.2  SIP 应答Lumps

6.  扩展OpenSIPS 核心配置文件

6.1 添加核心参数

6.2  添加核心函数

6.3  添加核心伪变量

7.  添加Transformation


5.  改变SIP 消息

        在OpenSIPS 内,改变SIP消息的标准方法是使用lumps 系统

        lump 系统的工作方式非常类似Linux 环境里的diff/patch工具:开发者对SIP消息执行增删操作。lump 存储在一个链表中,仅应用于OpenSIPS 脚本完全执行之后,SIP消息转发之前。因此,对SIP消息所作的更改在进一步检查时,不会立刻反应在SIP消息上(比如说:在脚本中添加一个新的头域,然后检查头域实例是否存在)。

        在讨论它们对SIP消息的影响时,lump可以分为以下几种类型:
 

5.1  SIP 消息Lump

        这类lump操作当前SIP消息上下文。

        从操作视角看,它们分为两种类型:


Delete Lumps

data_lump.h 

/*
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);


实例:删除RPID 头域:

      /* 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 

/*
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);


      如果开发者的意愿只是向SIP消息添加一个特定的字符串,那么必须创建一个新的锚点(anchor )lump ,然后把它作为nsert_new_lump_after/insert_new_lump_before的第一个参数。

        创建新锚点lunp的方法也在data_lump.h 中声明:

/*
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)


        下面示例在SIP消息头域列表的尾部添加一个新的SIP头域:

      /* 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;

        如果要替换SIP消息中的特定部分内容,那么操作可以分为两步,先调用del_lump删除不需要需要的部分,然后利用返回的lamp,在它之后添加一个新的lamp。

        下面代码示例,替换RPID 头域内容:

      /* 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 应答Lumps

        当用于SIP请求的场景中是,这些lump将操作SIP应答消息,在OpenSIPS拒绝请求时,在OpenSIPS 内部生成应答消息(如果OpenSIPS 把请求转发出去,而不是拒绝,那么这些lump不会生效)。由于应答消息是OpenSIPS内部生成的,那么应答lump就只能添加新的内容。接口文件data_lump_rpl.h

/*
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);


        示例:在内部生成的应答消息中添加contact头域:

      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.  扩展OpenSIPS 核心配置文件

        OpenSIPS 使用flex和bison解析配置文件,然后构建一棵响应树,从网络层读取SIP消息之后,遍历执行树中的节点。

        直接扩展OpenSIPS 核心配置文件时,开发者可以选择添加新的核心参数,或添加新的核心函数。
 

6.1 添加核心参数

        在接下来的分步教程中,我们将实现一个核心参数udp_workers ,它是一个整型值,控制每个UDP接口的OpenSIPS 进程数量。
        首先,我们必须添加一个变量来保存新的核心参数。我们在globals.h里添加声明:

extern int udp_workers_no;

添加在这里的变量,对OpenSIPS核心及模块都是可见的。main.c 里实际定义这个变量:

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

现在,在cfg.lex里,我们需要指示lexer识别新的标记:

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

接下来,我们必须修改cfg.y 里的语法,才能接受新的参数,我们先重新定义lexer标记:

%token UDP_WORKERS

最后,为新标记设置解析规则:

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

我们设置一个数值类型的变量,其它任何类型在解析OpenSIPS 脚本时都会触发一个错误。
 

6.2  添加核心函数

        在接下来的分步教程中,我们将实现一个核心函数xlog ,可以用它将日志信息输出到日志设备中。


       注意: xlog可以接收一个参数(待打印的字符串),也可以接收两个参数(日志等级和待打印字符串)。


        首先,我们扩展lexer文件,添加新的关键字。在cfg.lex下添加:

XLOG     "xlog"


接下来,扩展语法。在cfg.y里添加:

%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); }

        注意里面定义了调用xlog() 的不同方式:基础版本带一个参数;两个参数的版本;还有多行输入的版本。注意: XLOG_T i是一个新的枚举值,它将在 route_struct.h里定义。

        从语法开始,我们构建其响应动作。在 route.c里,我们将定义函数的fixup部分,所有完整性检查及参数解析都将在这里完成。fixup部分只会在脚本解析时调用一次:
 

            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 */

action.c 里,我们必须添加运行时从OpenSIPS 脚本调用函数时所执行的实质代码:

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  添加核心伪变量

        所有的OpenSIPS核心伪变量都定义在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},
...
...
...


        接下来说说OpenSIPS 伪变量的通用语法,以及OpenSIPS里(核心及模块)所使用的pv_export_t结构体,我们用它来定义新的伪变量:

/*! \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;  


        接下来,我们将实现一个伪变量 $ru,它提供SIP消息的Request-URI的读写途径:

首先,在pvar.h里添加PVT_RURI,添加在 enum _pv_type里,

然后,在_pv_names_table里添加以下内容:

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

        脚本里,将通过$ru访问新的伪变量。脚本中读取变量将调用 pv_get_ruri,而写变量将调用pv_set_ruri

        由于$ru伪变量不支持索引的概念(SIP消息中有且仅有一个Request-URI),所以不需要为它添加索引解析函数。此外,它也不需要初始化函数,因为我们将对脚本当前处理的SIP消息直接操作。

/*
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);
}

    对于所有上下文中没有任何实际意义的伪变量访问(比如说,在处理应答消息的上下文中获取Request-URI),务必使用pv_get_null 来通知脚本。


/*
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.  添加Transformation

        Transformation是指可以直接操作OpenSIPS 伪变量的函数。它读取伪变量作为入参并进行处理,然后输出转换后的版本。

        以理中几个transformation 实例:
 

  • "uri" transformation,作用于SIP URI,它们可以提取各种有用的信息,比如说URI的 username 或domain ,参数等等
  • "s"类 transformation,作用于字符串,提供各种有用的方法,比如说计算字符串长度,查找字符等等 
  • ... 其它,很多很多! – 请参考 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"

    注意:transformation可以链接在一起,所有实现新功能时一定要考虑这一点!


$var(our_uri) = “sip:[email protected]”;
xlog("The username of our URI has $(var(our_uri){uri.user}{s.len}) characters\n");


        在上面的例子中, uri 和s 是so级别的transformation类,user 和len才是实际的操作。

        接下来,我们将实现一下uri transformation类,聚焦于user 函数。
        添加新的 transformation类是在transformations.c里完成的,扩展core_trans[]数组:


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

        注意这里提供了parsing 函数和evaluation 函数。
        将使用transformation的全名来调用parsing 解析函数,因此,你可以解析它并构建实质的执行函数:


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


        在完成所有解析动作,确定实际上需要做什么之后, tr_eval_uri()将在运行时执行所有动作,它将评估transformation,并返回新的transformation输出。

        在我们的案例中,解析函数所字符串解析为SIP URI,然后根据提供的子类型填充输出的pv_value_t:
 

/* 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;
...

猜你喜欢

转载自blog.csdn.net/yetyongjin/article/details/106552887
3.1