目录
前言
1、模块开发介绍
1.1 可选的5个export结构定义
1.2 定义模块的export结构
2、模块开发实战:呼叫超频控制模块
2.1 代码实现
2.2 加载模块并添加路由处理
2.3 模块源码下载
小结
提示:本篇有较多代码,电脑浏览效果更佳
前言
OpenSIPS支持丰富的拓展模块,可以根据不同的场景需求搭配不同的模块来实现。但当遇到定制化很强的复杂业务,现有模块不能满足需求的情况,或者需要自定义功能时,就可以考虑进行模块开发了。
1、模块开发介绍
OpenSIPS模块开发本身并不复杂,主要涉及到5个可选的export结构和最关键的module_exports结构,已及模块初始化和销毁的函数。
1.1 可选的5个export结构定义
这些结构都在sr_module.h中定义。
cmd_export_ 模块导出函数结构定义:
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 */
};
(手机查看代码显示不全可左滑)
该结构主要用于定义可以在脚本中调用的函数,其中flag标志位用于说明该函数可以在哪个类型的路由块中调用,取值是下列宏中的一个或多个算术或“|”得到的值。
#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 */
#define ALL_ROUTES \
(REQUEST_ROUTE|FAILURE_ROUTE|ONREPLY_ROUTE|BRANCH_ROUTE| \
ERROR_ROUTE|LOCAL_ROUTE|STARTUP_ROUTE|TIMER_ROUTE|EVENT_ROUTE)
具体路由块介绍请参考OpenSIPS实战(三):路由脚本介绍与实战。
acmd_export_ 模块导出异步函数结构定义,与cmd_export_功能和用法相同,只是执行方式为异步,不阻塞当前请求的路由。(这里就不给出具体定义了)
param_export_ 定义模块导出参数结构:
struct param_export_ {
char* name; /* null terminated param. name */
modparam_t type; /* param. type */
void* param_pointer; /* pointer to the param. memory location */
};
定义的导出参数可以在脚本文件中,紧随模块加载配置之后,对这些参数进行赋值。
proc_export_ 结构定义:
struct proc_export_ {
char *name;
mod_proc_wrapper pre_fork_function;
mod_proc_wrapper post_fork_function;
mod_proc function;
unsigned int no;
unsigned int flags;
};
如果模块需要独立进程来运行的,就需要创建这个结构对象,主要定义进程运行的函数。
dep_export_ 结构定义:
typedef struct dep_export_ {
module_dependency_t md[MAX_MOD_DEPS];
modparam_dependency_t mpd[];
} dep_export_t;
用于定义该模块需要依赖于那些OpenSIPS模块才能运行。
除了这几个export结构外,还有其他几个,如mi_export_(用于定义MI命令)和stat_export_(用于统计信息)这里不做过多介绍。
1.2 定义模块的export结构
struct module_exports{
char* name; /* 模块名 */
enum module_type type; /* 模块类型 */
char *version; /* 模块版本号 */
char *compile_flags; /* 编译时的参数 */
unsigned int dlflags; /* dlopen参数 */
dep_export_t *deps; /* 依赖的模块定义 */
cmd_export_t* cmds; /* 模块导出的函数 */
acmd_export_t* acmds;
param_export_t* params; /* 模块导出的参数 */
stat_export_t* stats; /* 状态统计参数 */
mi_export_t* mi_cmds; /* MI命令操作定义 */
pv_export_t* items; /* 模块导出的伪变量 */
proc_export_t* procs; /* 模块额外的进程,
有些模块是单独进程运行的,如db_mysql,mi_fifo */
init_function init_f; /* 初始化模块时调用的函数 */
response_function response_f; /* 用处不详,一般置为0 */
destroy_function destroy_f; /* 销毁模块时调用的函数 */
child_init_function init_child_f; /* 所有子进程创建后
调用的函数 */
};
模块定义结构定义了很多成员,使用时并不是所有成员都需要定义,现在如果不明白每个成员用来做什么不要紧,只要了解有哪些成员就好,后面结合实例再来理解。
这里特别介绍下cmd_export_定义的模块导出函数cmd_function,函数原型定义如下:
typedef int (*cmd_function)(struct sip_msg*, char*, char*, char*, char*, char*, char*);
模块函数只接收字符串参数(这也是为什么需要同时定义fixup_function的原因),参数个数是可变的,最多支持6个参数,每个模块函数都默认传入sip_msg参数,调用时不需要指定。cmd_function返回值小于0表示失败,返回值等于0会使脚本执行结束;返回值大于0表示成功。
2、模块开发实战:呼叫超频控制模块
为了更好的理解本篇内容,这里以一个模块开发过程来讲解。但是我不想简单的开发一个只能打印“hello world”这样的模块,太敷衍了,也起不到示例的作用。所以我想写一个既简单又能玩起来的模块。思来想去我觉得可以开发一个针对主被叫呼叫频率进行控制的模块,然后花了一个下午,把这个模块开发调试了下,有效代码才100行。下面就来看看这个模块的实现。
2.1 代码实现
首先看下导出结构的定义
static unsigned int max_frequency;
static unsigned int of_interval;
static int CheckFrequency(struct sip_msg *msg, unsigned int );
static cmd_export_t commands[] = {
{"check_frequency", (cmd_function)CheckFrequency, 1,
fixup_uint_null, 0, REQUEST_ROUTE},
{0, 0, 0, 0, 0, 0}
};
static param_export_t parameters[] = {
{"max_frequency", INT_PARAM, &max_frequency},
{"time_interval", INT_PARAM, &of_interval},
{0, 0, 0}
};
static dep_export_t deps = {
{ /* OpenSIPS module dependencies */
{ MOD_TYPE_CACHEDB, "cachedb_local", DEP_ABORT},
{ MOD_TYPE_NULL, NULL, 0 },
},
{ /* modparam dependencies */
{ NULL, NULL },
},
};
commands[]: 提供一个名为“check_frequency”的导出函数,该函数根据参数指定检查主叫是否呼叫超频或者被叫是否被呼叫超频,模块中实现函数为CheckFrequency( )。该函数接收一个unsigned int参数指定检查方(主被叫),但在脚本中调用传入的参数都是字符串的,所以需要fixup_uint_null( )(OpenSIPS已经实现了该函数)函数修复。REQUEST_ROUTE指定该函数只能在请求路由(route)中调用。
parameters[]:提供两个导出参数,名为“max_frequency”的用于设置指定时间间隔内最大呼叫数,参数类型为整型,存储在max_frequency变量中;“time_interval”指定计算超频的时间间隔,参数类型为整型,存储在of_interval变量中。即在指定时间间隔(time_interval)内呼叫超过了这个最大呼叫数(max_frequency)就算超频。
deps:指定模块依赖于cachedb_local模块,如果OpenSIPS启动加载该模块时,cachedb_local模块没有被加载,就会报错并退出(abort)。
下面是模块结构对象exports的定义
struct module_exports exports = {
"over_frequency", // module name
MOD_TYPE_DEFAULT,// class of this module
MODULE_VERSION, // module version
DEFAULT_DLFLAGS, // dlopen flags
&deps, // OpenSIPS module dependencies
commands, // exported functions
0, // exported async functions
parameters, // exported parameters
NULL,
NULL, // exported MI functions
NULL, // exported pseudo-variables
NULL, // extra processes
mod_init, // module init function
// (before fork. kids will inherit)
NULL, // reply processing function
mod_destroy, // destroy function
NULL // child init function
};
模块结构传入了前面定义的commands、parameters、deps作为对应结构成员的值,最前面三个结构成员使用默认值就可以,没有用到的字段都置为NULL,模块的初始化和销毁函数应该始终都定义并赋值。这里mod_init和mod_destroy函数实现看后文。
模块初始化mod_init( )实现,主要是初始化了cachedb_local,用于在内存中存储号码呼叫的统计信息。cached_loacl模块使用哈希表实现,缓存基于共享内存,所以缓存内容在多进程间共享:
/* local cache */
cachedb_funcs overf_cdbf;
cachedb_con *overf_cache;
static int
mod_init(void)
{
/* 初始化本地缓存,缓存是进程间共享的 */
str local_url = str_init("local://");
if (cachedb_bind_mod(&local_url, &overf_cdbf) < 0) {
LM_ERR("cannot bind functions for cachedb_url %.*s\n",
local_url.len, local_url.s);
return -1;
}
overf_cache = overf_cdbf.init(&local_url);
if (!overf_cache) {
LM_ERR("cannot connect to cachedb_url %.*s\n",
local_url.len, local_url.s);
return -1;
}
return 0;
}
模块销毁函数mod_destroy( )的实现,主要是销毁cachedb_local,释放资源:
static void
mod_destroy(void)
{
if (overf_cache != NULL)
overf_cdbf.destroy(overf_cache);
}
模块最核心功能CheckFrequency( )函数的实现:
enum {
DIRE_CALLER = 1U << 0,
DIRE_CALLEE = 1U << 1
};
static int
CheckFrequency(struct sip_msg *msg, unsigned int direction)
{
struct sip_uri *uri = NULL;
int val;
/* 根据参数觉得对哪边做超频控制 */
if (direction & DIRE_CALLER ){
uri = parse_from_uri(msg);
}else if (direction & DIRE_CALLEE ){
uri = parse_to_uri(msg);
}
if (!uri || !uri->user.s || !uri->user.len){
LM_ERR("get call number failed,"
"direction:%d\n", direction);
return -1;
}
if (overf_cdbf.add(overf_cache, &uri->user, 1,
of_interval, &val) < 0){
LM_ERR("local cache add failed!!!\n");
return -1;
}
if (val > max_frequency)
return -1;
return 1;
}
CheckFrequency函数首先根据direction参数,确定要检查的是主叫还是被叫超频,并获得对应的号码。然后以号码为key将对应value值加1,设置key超时时间为of_interval,并返回自增后的值保存到val变量。最后检查当前值val是否大于设置的max_frequency值,如果大于则认为是超频,返回-1。
overf_cdbf.add操作首先检查key是否存在,不存在则插入键值并设置键超时。如果key存在则检查key是否超时,如果超时则删掉该key(cached_loacl使用链表存储重复key值的哈希表实现)项,以overf_cdbf.add传入的新值重新创建key项,并设置超时。如果key存在且没有超时,则value += 1(1是传入的第三个参数)。
这样呼叫超频控制模块就实现完成了。
最后在modues目录下新建over_frequency目录,源文件放到这个目录,编辑Makefile:
include ../../Makefile.defs
auto_gen=
NAME=over_frequency.so
LIBS=
include ../../Makefile.modules
最后在over_frequency目录下编译,将生成的over_frequency.so动态库文件复制到模块安装目录,然后就可以开始测试(玩)了。
2.2 加载模块并添加路由处理
这里的示例配置同一主叫10分钟(600秒)内不能呼叫超过10次,否则返回“403 Over Frequency”并结束呼叫。time_interval的单位是秒。check_frequency函数参数1表示检查主叫,2表示检查被叫。
loadmodule "cachedb_local.so"
loadmodule "over_frequency.so"
modparam("over_frequency", "max_frequency", 10)
modparam("over_frequency", "time_interval", 600)
route{
...
if (is_method("INVITE")) {
if (!check_frequency("1")){
send_reply("403","Over Frequency");
exit;
}
}
...
}
经过测试,结果如预期那样运行。10分钟内如果主叫呼叫了10次,再发起呼叫返回“403 Over Frequency”,从计时开始的10分钟后可以恢复正常呼叫,同时从再一次呼叫发起时间起重新计时,如果超过限制将继续禁止发起呼叫。
该模块实现还很简陋,没有考虑内存占用量、key的删除和时间段精确控制、单单用号码作为key导致可能的主被叫统计混在一起等,以及更多的业务需求场景,甚至可能OpenSIPS已经实现了这个功能。但是通过这个模块的开发,已经清楚的展示的OpenSIPS模块开发的过程,而且贴合实际应用,也是一个可玩性较强的模块。已经达到了这本篇文章的目的。
2.2 模块代码下载
公众号回复:呼叫超频控制模块。我看到后会回复下载链接。
小结
模块开发属于OpenSIPS使用的较高级阶段,对于业务比较复杂的系统必将多多少少需要进行模块开发,满足定制化要求。模块开发中可以复用很多OpenSIPS已经实现了的工具代码,来提高开发效率。遇到不清楚的用法可以参考其他模块的实现,开源项目就是这样方便,有不明白的地方可以翻源码看实现。
(全文完)
更多参考官方文档
http://www.opensips.org/Documentation/Development-Manual#toc32