目录
前言
1、lumps system简介
2、lumps system函数接口
2.1 创建指定被操作lumps的函数
2.2 创建用于新插入或替换lumps的函数
3、修改Contact头域与lump操作图解
3.1 使用lumps实现FixContact函数
3.2 图解FixContact函数的lumps操作过程
小结
前言
SIP消息交互的过程中,经常需要对sip消息进行修改,如修改/添加SIP头域,修改SDP内容等,在使用过程中多少也会遇到需要开发来修改信令实现功能的情况。在OpenSIPS中对SIP消息执行更改的标准机制是使用所谓的块系统(lumps system)。
1、lumps system简介
OpenSIPS的lumps system效率很高,这也是opensips性能高效的一个原因之一。这些要修改的块(lumps)存储在一个列表中,并且只在OpenSIPS脚本执行完毕之后并且在SIP消息被转发之前应用到消息中(重新组包)。因此,对SIP消息所做的更改在进一步检查时不会立即反映在SIP消息中。
lumps system虽然设计巧妙,但是使用起来不容易理解,我也是用过几次之后,专门翻了下代码实现来研究,才慢慢理解lumps操作的。
Lumps分为两种,SIP消息块(SIP Message Lumps)和SIP响应块(SIP Reply Lumps)。这里主要介绍常会用到的SIP消息块操作。将“lumps”直接翻译为“块”对于后文的叙述而言容易造成意义不清,后面的叙述将都使用lumps来说明。
2、lumps system函数接口
SIP消息块这种类型的lumps用于操作当前SIP消息。从操作的角度来看,SIP消息块也分为两类:删除lumps(Delete Lumps)和添加lumps(Add Lumps)。但从函数接口角度来分又可以分为两类:创建指定被操作lumps的函数和创建插入或替换lumps的函数。
下面从函数接口角度对几个常用的具有代表性的lumps操作函数进行分类介绍。
2.1 创建指定被操作lumps的函数
del_lump函数接口:
/* 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);
del_lump函数用于指定在当前sip_msg buffer中要删除的消息块的内存位置和删除长度。del_lump函数创建一个lump对象,指定删除的lump在sip_msg buffer中的位移(offset)以及删除长度(len)、lump类型LUMP_DEL(函数内部指定,不是参数type指定,下同)等,并将lump对象插入到sip_msg中的lump链表中,最后返回该lump对象的指针。
anchor_lump函数接口:
/* 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 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, enum _hdr_types_t type)
anchor_lump函数创建(malloc)一个lump对象,用于保存该lump插入点在sip_msg buffer中的位移(offset),lump操作类型设置为LUMP_NOP等,并将该lump对象插入到sip_msg中的lump链表,最后返回该lump对象的指针。
2.2 创建用于新插入或替换lumps的函数
insert_new_lump_after和insert_new_lump_before接口:
/* 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);
insert_new_lump_after和insert_new_lump_before这两个函数分别用于创建用于新插入或替换原有内容的lumps。
insert_new_lump_after函数传入一个指定了新插入(或删除)位置的lumpafter(del_lump和anchor_lump函数返回的lump),并创建一个新的lump tmp,lump tmp用于保存新插入(或替换)内容的buffer指针以及buffer长度,lumps操作类型设置为LUMP_ADD,最后将after->after指向tmp,并返回tmp对象。
insert_new_lump_before函数操作和insert_new_lump_after函数基本相同,只是最后将传入的指定了新插入(或删除)位置的lump tmp赋值给了before->before。
这两个函数实现上和功能上区别不大,大部分场景这两个函数调用效果都是一样的,唯一的区别只是在消息重新组包的时候,insert_new_lump_before创建的lump比insert_new_lump_after创建的lump先被执行。
lumps system提供的所有接口都声明在data_lump.h头文件中,这里只介绍了常用的几个。
下面举例讲解lumps操作。
3、使用lump实现FixContact函数
以下图场景为例,OpenSIPS收到的SIP消息中,Contact头域中URI地址是无效的“o8kvkeft9oak.invalid”字符串:
如果需要从OpenSIPS上修复这个问题,就需要用到lumps system。
3.1 FixContact函数实现
该函数可以实现为模块导出函数,在脚本中调用。实际上nat_traversal模块已经实现同名且功能相同的导出函数,这里做了修改,以更好的说明。
FixContact函数实现
static int FixContact(struct sip_msg *msg) { str newip; unsigned short newport; contact_t* contact; struct lump* anchor; struct sip_uri uri; int len, offset; char *addr_buf; /* 从消息中解析出Contact头域 */ if ((parse_headers(msg, HDR_CONTACT_F, 0) == -1) || !msg->contact) { LM_ERR("parse the Contact header failed\n"); return -1; } if (!msg->contact->parsed && parse_contact(msg->contact) < 0) { LM_ERR("cannot parse the Contact header\n"); return -1; } contact = ((contact_body_t*)msg->contact->parsed)->contacts; if (contact == NULL) { LM_ERR("Contact header is NULL\n"); return -1; } if (parse_uri(contact->uri.s, contact->uri.len, &uri) < 0 || uri.host.len <= 0) { LM_ERR("cannot parse the Contact URI\n"); return -1; } /* 获得消息源IP地址 */ newip.s = ip_addr2a(&msg->rcv.src_ip); newip.len = strlen(newip.s); newport = msg->rcv.src_port; len = newip.len + 10; addr_buf = pkg_malloc(len); if (addr_buf == NULL) { LM_ERR("out of memory\n"); return -1; } /* 创建指定要被替换的lump */ offset = uri.host.s - msg->buf; anchor = del_lump(msg, offset, uri.host.len, 0); if (!anchor) { pkg_free(addr_buf); return -1; } if(msg->rcv.src_ip.af == AF_INET6) { len = sprintf(addr_buf, "[%.*s]:%d", newip.len, newip.s, newport); } else { len = sprintf(addr_buf, "gf%.*s:%d", newip.len, newip.s, newport); } /* 创建用于替换的lump */ if (insert_new_lump_after(anchor, addr_buf, len, 0) == 0) { pkg_free(addr_buf); return -1; } return 1; }
如果在脚本逻辑对应位置中调用了该函数,则当前消息将会在转发修正这个问题,也就是contact URI中的“o8kvkeft9oak.invalid”被替换为了UAC的源地址,然后再转发给下一跳服务器。
3.2 图解FixContact函数lumps操作过程
第一步,FixContact函数从消息包中解析获得Contact头域对象,然后再解析Contact头域,获得头域中发URI对象。
第二步,计算contact URI在于消息 buffer中的偏移量,并计算要替换的字符串长度,调用del_lump函数创建指定删除字符串的lump,并保存返回的lump指针对象anchor。
第三步,使用正确的IP:Port填充addr_buf,然后调用insert_new_lump_after函数,传入anchor,addr_buf地址和addr_buf长度。
最后在消息转发前,会基于msg消息的lump链表重新进行组包,才最终将修改应用到消息中。
这里图解lumps只展示了一个概略的组包流程,实际代码实现包含很多实现细节,这里都没有展示,毕竟讲解lumps system源码实现不是本篇的目的(lumps system的源码实现还是值得学习的,如果哪天我有心情有时间也许会写一篇源码实现的介绍呢~)。
小结
OpenSIPS的lumps system从实现的很巧妙,代码应用上也经常会用到。如果需要了解更多实现细节可以去查看lumps实现源码和消息组包源码。如果需要知道其他lumps函数的使用方法也可以去查看OpenSIPS模块的源码,看是怎么使用的,毕竟这方面文档太少。
(全文完)
更多查看官方文档
http://www.opensips.org/Documentation/Development-Manual#toc11