OpenSIPS 3.1 開發手冊(七)--新模塊開發

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

目錄

16.  模塊開發

16.1  引言

16.2  編譯模塊

16.3  初始化模塊

16.4  銷燬模塊

16.5  添加模塊參數

16.6  添加模塊函數

16.7  添加模塊MI函數

16.8  添加模塊統計

16.9  添加模塊僞變量

16.10  添加模塊專用進程


16.  模塊開發

16.1  引言

        根據OpenSIPS 的模塊化架構,添加新特性(新的參數,腳本函數,MI接口函數)的最簡單方式是把它合入一個新的OpenSIPS模塊
        一個OpenSIPS 模塊實際上就是一個動態庫(so文件),在OpenSIPS 啓動過程中可以動態加載,如果從OpenSIPS 腳本中加載模塊,那麼要用loadmodule 指令。

loadmodule "mynewmod.so"


        加載新模塊時,OpenSIPS核心會查找類型爲struct module_exportsexports 變量。這個結構體變量是開發新OpenSIPS模塊時最重要的變量。

        struct module_exports{
              char* name;                     /*!< null terminated module name */
              char *version;                  /*!< module version */
              char *compile_flags;            /*!< compile flags used on the module */
              unsigned int dlflags;           /*!< flags for dlopen */

              cmd_export_t* cmds;             /*!< null terminated array of the exported
                                           commands */
              param_export_t* params;         /*!< null terminated array of the exported
                                           module parameters */

              stat_export_t* stats;           /*!< null terminated array of the exported
                                           module statistics */

              mi_export_t* mi_cmds;           /*!< null terminated array of the exported
                                           MI functions */

              pv_export_t* items;             /*!< null terminated array of the exported
                                           module items (pseudo-variables) */

              proc_export_t* procs;           /*!< null terminated array of the additional
                                           processes reqired by the module */

              init_function init_f;           /*!< Initialization function */
              response_function response_f;   /*!< function used for responses,
                                           returns yes or no; can be null */
              destroy_function destroy_f;     /*!< function called when the module should
                                           be "destroyed", e.g: on opensips exit */
              child_init_function init_child_f;/*!< function called by all processes
                                            after the fork */
        };


        module_exports內容及註釋非常清晰。

        接下來,我們將逐個討論module_exports 的每個成員,解釋它們在構建新模塊中的用法。先看一個純粹的實例,dialog 模塊裏的exports 變量:

        struct module_exports exports= {
              "dialog",        /* module's name */
              MODULE_VERSION,
              DEFAULT_DLFLAGS, /* dlopen flags */
              cmds,            /* exported functions */
              mod_params,      /* param exports */
              mod_stats,       /* exported statistics */
              mi_cmds,         /* exported MI functions */
              mod_items,       /* exported pseudo-variables */
              0,               /* extra processes */
              mod_init,        /* module initialization function */
              0,               /* reply processing function */
              mod_destroy,
              child_init       /* per-child init function */
        };

16.2  編譯模塊

        接下來,我們將遵循構建新模塊的各種選項,構建一個名爲ournewmod的新模塊。

        在OpenSIPS的modules/目錄下創建一個ournewmod 目錄,然後,需要爲我們的模塊寫一個Makefile,放在ournewmod 目錄下。如果模塊沒有外部庫依賴,那麼最基本的Makefile內容是這樣的:

# $Id$
#
# WARNING: do not run this directly, it should be run by the master Makefile

include ../../Makefile.defs
auto_gen=
NAME=ournewmod.so
LIBS=

include ../../Makefile.modules


        如果模塊有外部庫依賴,需要把它們鏈接進模塊的Makefile。例如cachedb_memcached模塊:

include ../../Makefile.defs
auto_gen=
NAME=cachedb_memcached.so
DEFS+=-I$(LOCALBASE)/include
LIBS=-L$(LOCALBASE)/lib -lmemcached

include ../../Makefile.modules

    如果新模塊依賴外部庫,那麼不得將它放入缺省編譯範圍!


 

接下來,必須編輯Makefile.conf.template 文件(位於源碼根目錄下),它指定模塊是否缺省編譯,還有它的依賴關係。

Makefile.conf.template 裏添加一行,格式如下:

modulename= Module Description | module dependency

 

        此外,我們還需要修改Makefile.conf.template裏的exclude_modules 列表,把新模塊的名字加進去,這樣,缺省就不會編譯這個模塊。
 

16.3  初始化模塊

        在初始化新模塊的上下文中,有兩類函數可以幫助我們:

mod_init

        這個函數必須在我們的module_exports exports 結構體變量中指定,對應成員init_f 。

        它是在單進程上下文中執行的( attendant 進程),執行時間點在OpenSIPS 配置文件完整解析之後(包含了相關模塊參數),所有助手API(共享內存、鎖、定時器進程,等等)也是在這個點初始化的。

        這個函數的目的是檢查OpenSIPS 腳本配置模塊的完整性,初始化需要的數據結構,等等。此外,一些關鍵資源(比如說定時器),只能在模塊的mod_init()函數中初始化。
函數原型是:

/* MUST return 0 in case of success, anything else in case of error */
typedef int (*init_function)(void);

    由於這個函數是在單進程上下文中調用的,那麼在OpenSIPS fork之後,每個OpenSIPS 進程都將收到attendat 進程資源的副本。因此,不要在mod_init函數裏初始化OpenSIPS 單個進程私有的數據結構和連接。


 

child_init

         這個函數必須在我們的module_exports exports 結構體變量中指定,對應成員init_child_f 。

        它運行在所有OpenSIPS 進程上下文中,觸發時間點是新進程剛被fork出來時。

        這個的目的是建立各種連接(db、cachedb,等等),這些資源每個OpenSIPS 進程的需求是不同的,所以需要獨立初始化它們。函數原型:

/* MUST return 0 in case of success, anything else in case of error */
typedef int (*child_init_function)(int rank);


        這個函數接收一個整型參數,指示當前調用函數的OpenSIPS進程的類型。以下是所有可用選項:

#define PROC_MAIN      0  /* Main opensips process */
#define PROC_TIMER    -1  /* Timer attendant process */
#define PROC_MODULE   -2  /* Extra process requested by modules */
#define PROC_TCP_MAIN -4  /* TCP main process */
#define PROC_BIN      -8  /* Any binary interface listener */


 

        rank 參數爲正值表示當前操作是在OpenSIPS的監聽器(UDP、TCP或SCTP)上下文裏發生的。


    如果我們初始化時必須執行耗時操作(比如說從數據加載大量數據),那麼應當把它放在child_init() 裏,而不是放在mod_init()裏。這會加速OpenSIPS 的啓動過程,儘快進入消息處理(至少需要處理的消息可能不會完全依賴於我們模塊內部的數據填充)。


16.4  銷燬模塊

        這個函數必須在我們的module_exports exports 結構體變量中指定,對應成員destroy_function 。

        它運行在單進程上下文中( attendant 進程),時間點是OpenSIPS 關機處理時。

        這俱函數的目的是清理OpenSIPS 所使用的各種資源(共享內在、DB連接,等等)。此外,destroy_function 是執行模塊狀態持久化的良好時機,以便下次啓動時恢復狀態(比如說,dialog 模塊在析構函數中所所有SIP dialog信息保存在DB中)。函數原型:

typedef void (*destroy_function)();

16.5  添加模塊參數

        模塊添加新參數是由模塊裏的exports 變量裏的params 成員完成的。OpenSIPS啓動時,將解析腳本,根據OpenSIPS 腳本的參數配置設置內部變量。參數定義如下( param_export_t ) :

struct param_export_ {
        char* name;             /*!< null terminated param. name */
        modparam_t type;        /*!< param. type */
        void* param_pointer;    /*!< pointer to the param. memory location */
};


The OpenSIPS modules can export both string and integer parameters.

       OpenSIPS 模塊既可以導出字符串參數,也可以導出整型參數。

        以下示例將分別演示。注意: param_export_t結構體不接收任何的長度參數指示模塊導出的參數數量,結構體必須以一個全0的行結束。

int enable_stats = 0;
static str db_url = {NULL,0};

static param_export_t mod_params[]={
        { "enable_stats",          INT_PARAM, &enable_stats         },
        { "db_url",                STR_PARAM, &db_url.s             },
        { 0,0,0 }
}

 

OpenSIPS 腳本中爲ournewmod 模塊設置這些參數的用法:

loadmodule "ournewmod.so"

modparam("ournewmod","enable_stats", 1)
modparam("ournewmod","db_url","mysql://vlad:mypw@localhost/opensips")


        此外,OpenSIPS 還支持在腳本中設置某個特定參數時觸發一個內部函數。如果所提供參數需要轉換爲內部格式,或者一個參數可以多次設置,那麼這一機制將針非常有利。

        找一個現成的實例說明一下,請參考下面的代碼片段,NoSQL URL 可以重複設置多次,建立多個後端連接:
 

static param_export_t params[]={
        { "cachedb_url",                 STR_PARAM|USE_FUNC_PARAM, (void *)&set_connection},
        {0,0,0}
};

int set_connection(unsigned int type, void *val)
{
        LM_INFO("Our parameter has been set : value is %s\n",(char *)val);
        /* continue processing, eg : add our new parameter to a list to be further processed */
}

16.6  添加模塊函數

        新模塊添加函數是由exports 結構裏的cmds 成員完成的。導出的函數結構定義:

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


        和exports 結構裏的params 很相似,cmds 成員也必須以NULL結尾。

       OpenSIPS 啓動時,嘗試定位每個調用函數的位置,不論它是核心提供的,還是外圍模塊提供的。

       


函數重載很簡單,你需要在 cmds結構中列出同名條目就行,但要區別param_no字段。


        模塊導出的腳本函數原型:

typedef  int (*cmd_function)(struct sip_msg*, char*, char*, char*, char*, char*, char*);


        如你所見,所有OpenSIPS模塊函數只接受字符串參數,一個函數最多支持六個參數。當前正在處理的SIP消息作爲C函數的第一個參數傳入,但它對腳本編寫者是透明的(它只提供參數索引1到5)。


    cmd_export_ 結構體中的flags成員決定了OpenSIPS 腳本中可以調用函數的地方。


以下是當前的定義選項:

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


        通過上述值的位掩碼提供多類型的路由是支持的。

        這裏需要理解的一個非常重要的概念是fixup_function。這個函數只在腳本最開始解析時調用一次,它是一種優化,可以進一步解析所提供的參數,以提高運行時效率。

        這裏只提供一些fixup函數使用案例:

  • 由於所有的模塊函數參數都是字符串,有些場景,我們的模塊需要整型參數。可以用fixup function把字符串轉換爲整型值
  • 如果我們接受它,那麼我們的函數可以接受僞變量作爲參數。這時,可以用fixup函數查找僞變量空間,在運行時,我們僅需要當前SIP消息的上下文中評估pvar的值


        接下來,我們將利用load_balancer 模塊的lb_is_destination 的實現,來深入理解這一概念。函數定義如下:

        {"lb_is_destination",(cmd_function)w_lb_is_dst4,     4,    fixup_is_dst,
                0, REQUEST_ROUTE|FAILURE_ROUTE|ONREPLY_ROUTE|BRANCH_ROUTE|LOCAL_ROUTE},

函數接收4個參數。文檔描述的用法是lb_is_destination(ip,port,group,active) :

  • ip - 字符串或pvar,攜帶待檢查的IP地址
  • port - 字符串或pvar,攜帶待檢查的端口,如果爲空,將跳過所有端口檢查
  • group - 整數或pvar,攜帶待檢查的 load_balancer組ID
  • active - 整數。如果1,我們只接受活躍目的地的檢查


瞭解這些後,fixup_is_dst 實現如下 :

static int fixup_is_dst(void** param, int param_no)
{
        if (param_no==1) {
                /* the ip to test */
                return fixup_pvar(param);
        } else if (param_no==2) {
                /* the port to test */
                if (*param==NULL) {
                        return 0;
                } else if ( *((char*)*param)==0 ) {
                        pkg_free(*param);
                        *param = NULL;
                        return 0;
                }
                return fixup_pvar(param);
        } else if (param_no==3) {
                /* the group to check in */
                return fixup_igp(param);
        } else if (param_no==4) {
                /*  active only check ? */
                return fixup_uint(param);
        } else {
                LM_CRIT("bug - too many params (%d) in lb_is_dst()\n",param_no);
                return -1;
        }
}


        將爲提供的每個參數調用fixup 函數,其中 param_no 參數描述正在解析的參數索引(從1開始,因爲實際上第一個參數是當前處理的SIP消息,佔用索引0)。

        上述fixup函數將用它們各自處理後的輸出替換在主函數中接收的參數。因此,主函數不會接收到任何腳本中的純文本參數,而是收到解析後的pvar值,或者是轉換後的數字。以下是w_lb_is_dst4 在fixup之後的處理代碼:

static int w_lb_is_dst4(struct sip_msg *msg,char *ip,char *port,char *grp,
                        char *active)
{
        int ret, group;

        if (fixup_get_ivalue(msg, (gparam_p)grp, &group) != 0) {
                LM_ERR("Invalid lb group pseudo variable!\n");
                return -1;
        }

        ret = lb_is_dst(*curr_data, msg, (pv_spec_t*)ip, (pv_spec_t*)port,
                        group, (int)(long)active);


        如你所見,輸入的字符串參數在fixup之後,轉換爲它對應的值。
        此外,mod_fix.h 裏提供了各種訪問fixup結果的函數。在上例中,調用fixup_get_ivalue獲取整數值(既可以從純文本中提取,也可以從解析的僞變量空間中提取)。此外,注意active參數是怎樣直接轉換爲long的, 由於這個參數只接收純文本的整數,fixup直接幫我們轉換了。


    模塊中爲腳本導出函數的返回代碼非常重要。正值表示成功,負值表示失敗。返回0將在函數終止後停止腳本執行T。如果沒有絕對必要,不要返回0。


16.7  添加模塊MI函數

        模塊添加新的MI函數,由exports 結構體中的mi_cmds 成員完成。


    exports 構體中的mi_cmds 成員所指定的MI函數,將會被模塊接口自動註冊。


16.8  添加模塊統計

        模塊添加統計接口,由exports 結構體中的stats 成員完成。


    exports 構體中的mi_cmds 成員所指定的stats 函數,將會被模塊接口自動註冊。如果我們的新模塊命名爲mynewmod ,導出一個統計接口,命名爲mycustomstat ,那麼我們就能用opensipsctl 獲取統計信息:
opensipsctl fifo get_statistics mynewmod mycustomstat


16.9  添加模塊僞變量

        模塊添加統計接口,由exports 結構體中的items 成員完成。
 

16.10  添加模塊專用進程

        對於某些案例,我們的模塊可能需要與非SIP的外部實體通信。對於這樣的案例,我們會需要一個(或多個)進程來專門負責這類通信。RTPProxy 就是一個典型實例(它與外部的RTP Proxy引擎通信),還有mi_fifo和mi_datagram (從FIFO或UDP socket讀取MI命令)。

        exports 結構體中的procs 成員可以完成這事。proc_export_t 結構體描述額外請求的進程:
 

struct proc_export_ {
        char *name;                             /* name of the new task */
        mod_proc_wrapper pre_fork_function;     /* function to be run before the fork */
        mod_proc_wrapper post_fork_function;    /* function to be run after the fork */
        mod_proc function;                      /* actual function that will be run in the context of the new process */
        unsigned int no;                        /* number of processes that will be forked to run the above function */
        unsigned int flags;                     /* flags for our new processes - only PROC_FLAG_INITCHILD makes sense here*/
};

typedef void (*mod_proc)(int no);
typedef int (*mod_proc_wrapper)();

    新進程裏執行的函數必須永遠執行。一旦函數終止執行,整個OpenSIPS 都將退出。



        pre_fork_function和post_fork_function 這兩個函數爲主進程派生進程提供各種輔助工具。

 


    這兩個輔助函數都在OpenSIPS attendant 進程的上下文中執行。



        結構體中的no成員指示OpenSIPS需要fork出幾個進程來處理指定的事務。當需要處理的工作量很大時,它就派上用場了,在你的模塊邏輯裏,爲進程函數提供no參數,工作量將分散在fork出來的進程中。

        OpenSIPS 爲特定功能fork出的進程數,不一定是靜態的。 

        以下是一個實例,看看MI數據報是如何處理fork進程的。進程fork數量的缺省值是MI_CHILD_NO,但是它可以通過children_count 參數配置,代碼如下:

static proc_export_t mi_procs[] = {
        {"MI Datagram",  pre_datagram_process,  post_datagram_process,
                        datagram_process, MI_CHILD_NO, PROC_FLAG_INITCHILD },
        {0,0,0,0,0,0}
};


static param_export_t mi_params[] = {
        {"children_count",      INT_PARAM,    &mi_procs[0].no           },


        結構體中的flags 成員值,可以是0或PROC_FLAG_INITCHILD。如果是PROC_FLAG_INITCHILD,那麼,所有可加載模塊的child_init函數也將在新派生的進程中執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章