仿Drouting在OpenSER中加个Nrouting模块
在《实时检测,自动实现话务转接:OpenSER的Drouting模块应用》中,我们简单讲述了drouting,其实在实际应用中,我可能偏load balancer或dispatcher,毕竟drouting真的要用好的话,也比较难,但有时需要自定义相关的路由,在OpenSER体系中,脚本才是我们最重要的工具,可出错,或折腾来折腾去等很多的问题就跟着出现了,所以我们实现了一个Nrouting模块,用于按主叫,来源Ip,被叫三者进行主叫改号改地址,被叫改号改地址等相关操作。
表结构:
前端界面:
opensips模块代码(kamailio差不多):
\
int nr_mi_param =0;
////////////////////////////////////////
static str db_url = {NULL,0}; //默认数据库连接,如果指定了系统默认的,那么就用系统默认的,切记,一定一定要用Postgresql
static str use_domain = {"0",1};
//////////////////////////////////////
static int fixup_av_mgm(void** param);
static int fixup_nway_qop(void** param);
static int fixup_nway_alg(void** param);
/////
static int mod_init(void);
static void mod_destroy(void);
static int child_init(int rank);
static int nr_route_function(struct sip_msg* msg, char* param, char* value); //路由
static int nr_auth_function(struct sip_msg* msg, char* param, char* value); //认证
static int nr_challenge_function(struct sip_msg *msg, str *realm, qop_type_t qop, intptr_t algmask); //挑战
static int nr_route_media_function(struct sip_msg* msg, char* param, char* value); //从媒体服务器过来
static int nr_mi_function(struct mi_root* cmd_root, void* param, unsigned int param_no);
mi_response_t *nr_reload_cmd(const mi_params_t *params,
struct mi_handler *async_hdl);
static cmd_export_t cmds[] = {
{"nr_route", (cmd_function)nr_route_function, {
{CMD_PARAM_STR|CMD_PARAM_OPT,0,0}, {0,0,0}},REQUEST_ROUTE},
{"nr_auth", (cmd_function)nr_auth_function, {
{CMD_PARAM_STR, 0, 0},
{0,0,0}},
REQUEST_ROUTE},
{"nr_challenge", (cmd_function)nr_challenge_function, {
{CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* realm */
{CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_nway_qop, 0},/* qop */
{CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_nway_alg, 0},/* alg */
{0,0,0}},REQUEST_ROUTE},
{"nr_route_media", (cmd_function)nr_route_media_function, {
{CMD_PARAM_STR|CMD_PARAM_OPT,0,0}, {0,0,0}},REQUEST_ROUTE},
{0,0,{{0,0,0}},0}
};
static const param_export_t params[] = {
{"db_url", STR_PARAM, &db_url.s },
{"use_domain", STR_PARAM, &use_domain.s },
{0, 0, 0}
};
struct module_exports exports = {
"nrouting",
MOD_TYPE_DEFAULT,
MODULE_VERSION,
DEFAULT_DLFLAGS,
0,
0,
cmds, /* module commands */
NULL, /* Exported async functions */
params, /* Exported parameters */
NULL, /* exported statistics */
NULL, /* exported MI functions */
NULL, /* exported pseudo-variables */
NULL, /* exported transformations */
0, /* extra processes */
0, /* module pre-initialization function */
mod_init, /* module initialization function */
0, /* response function */
mod_destroy, /* destroy function */
child_init, /* child initialization function */
NULL /* reload confirm function */
};
static int child_init(int rank)
{
return 0;
}
static int mod_init(void)
{
LM_WARN("nrouting: module initialized\n");
return 1;
}
static void mod_destroy(void)
{
LM_INFO("nrouting: module destroyed\n");
}
static int nr_route_function(struct sip_msg* msg, char* param, char* value)
{
LM_WARN("nr_route_function called with param: %s, value: %s\n", param, value);
return 1;
}
static int nr_auth_function(struct sip_msg* msg, char* domain, char* value){
return 1;
}
static int fixup_nway_qop(void** param){
return 0;
}
static int fixup_nway_alg(void** param){
return 0;
}
//challenge
static char* generate_secret_key() {
char *key = (char *)pkg_malloc(33); // 32 bytes + null terminator
if (!key) {
return NULL;
}
// 初始化随机数生成器
srand(time(NULL));
// 生成32字节的随机数据
for (int i = 0; i < 16; i++) {
unsigned int random_value = rand();
sprintf(&key[i * 2], "%02x", random_value & 0xFF);
}
key[32] = '\0'; // null terminator
return key;
}
// 生成nonce
char* generate_nonce(nway_challenge_t *n) {
char *nonce = (char *)malloc(256);
if (!nonce) {
return NULL;
}
// 使用当前时间和一个随机数生成nonce
srand(time(NULL));
int random_value = rand();
snprintf(nonce, 256, "%ld:%d:%s", time(NULL), random_value, n->secret_key);
return nonce;
}
//////////////////////////
static int nr_challenge_function(struct sip_msg* msg, str *realm, qop_type_t qop, intptr_t algmask){
return 1;
}
static int nr_route_media_function(struct sip_msg* msg, char* param, char* value){
return 1;
}
mi_response_t *nr_reload_cmd(const mi_params_t *params,
struct mi_handler *async_hdl)
{
return init_mi_result_ok();
}
路由方式:
#### 加载宁卫专有模块
loadmodule "nrouting.so"
modparam("nrouting","use_domain","0")
route{
...
if (is_method("INVITE") ) {
if (nr_route_media()){
t_relay();
}else if (nr_route()){
t_relay();
}else{
xlog("L_INFO","呼叫来源于非媒体服务,也非网关,进行相关验证!\n");
if (nr_auth("")){
t_relay();
}else{
xlog("L_INFO","非法呼叫$fu!\n");
}
}
}
那么以上方式再结合其实一些功能模块,对于OpenSER来说,就可以更好地控制和使用。
以上模块均结合golang实现相关的逻辑,在golang中解析我们的sip消息:
type SIPMessage struct {
Method string
URI string
Version string
Headers map[string][]string
}
func NwayParseSIPMessage(message string) SIPMessage {
lines := strings.Split(message, "\n")
requestLine := strings.Fields(lines[0])
sipMessage := SIPMessage{
Method: requestLine[0],
URI: requestLine[1],
Version: requestLine[2],
Headers: make(map[string][]string),
}
for _, line := range lines[1:] {
if line == "" {
continue
}
header := strings.SplitN(line, ":", 2)
if len(header) == 2 {
key := strings.TrimSpace(header[0])
value := strings.TrimSpace(header[1])
sipMessage.Headers[key] = append(sipMessage.Headers[key], value)
}
}
return sipMessage
}
这样在开发时,相对省时省力也性能比较高效。