Openser中文网

专注于VoIP,Opensips,Kamailio等技术,QQ群:QQ群:293697898

仿Drouting在OpenSER中加个Nrouting模块


tags:opensips kamailio 模块开发 路由 创建时间:2024-06-25 14:59:30

在《实时检测,自动实现话务转接:OpenSER的Drouting模块应用》中,我们简单讲述了drouting,其实在实际应用中,我可能偏load balancer或dispatcher,毕竟drouting真的要用好的话,也比较难,但有时需要自定义相关的路由,在OpenSER体系中,脚本才是我们最重要的工具,可出错,或折腾来折腾去等很多的问题就跟着出现了,所以我们实现了一个Nrouting模块,用于按主叫,来源Ip,被叫三者进行主叫改号改地址,被叫改号改地址等相关操作。 表结构:

前端界面:

opensips模块代码(kamailio差不多):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../../globals.h"  //其中定义了extern char *db_default_url;
#include "../../sr_module.h"
#include "../../mem/mem.h"
#include "../../mem/shm_mem.h"
#include "../../dprint.h"
#include "../../mod_fix.h"
#include "../rr/api.h"
#include "../../lib/digest_auth/digest_auth.h"
#include "../../parser/digest/digest.h"
#include "../../parser/parse_from.h"
#include "../../parser/parse_to.h"
#include "../../parser/parse_uri.h"
#include "../../parser/msg_parser.h"
#include "../../parser/parse_authenticate.h"

#include "nrouting.h"
#include "libnwayrouting.h"
#define HLP1 "nr_route_media:check from media server,or rewrite ruri/furi/turi\n" \
    "nr_route:check from gateway rewrite ruri/furi/turi\n"\
    "nr_auth:check auth\n"

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
}

这样在开发时,相对省时省力也性能比较高效。



如果在有疑问或需要沟通的地方,可以QQ:1354608370 或 加FreeSWITCH+Kamailio+Opensips QQ群: 293697898 沟通交流!