2014/9/9

opensips - connect to gateway

如果整個 SIP 環境要跟傳統電話交換機連接,必須要有 voice gateway 處理這個問題,而 opensips 要做的,就是提供 SIP 界面跟其他 voice gateway 整合。

我們設想一個情境,有兩個 edge 端分別用 asterisk 當作 voice gateway,這兩個端點之間,可以直接互相設定 sip trunk。但最好的方式,是在中間增加一個 opensips,充當 SIP Proxy Server 的功能,只針對受話號碼的 prefix 形式來判斷,要將電話話務轉送給那一個 gateway 處理。

在中間的 SIP Proxy Server 單純地只處理 SIP 的封包, 最重要的就是 INVITE,在轉送話務後,讓兩個端點直接對送 RTP 資料。

osipsconfig

先前我們使用 opensips 承擔了 registration server 的工作,但在這個情境中,並不需要註冊的功能,所以我們先使用 osipsconfig 產生新的設定檔。

我們選擇了 Trunking Script,並只勾選 USE_DIALPLAN, USE_DIALOG 這兩個項目。

為了要對 script 除錯,我們可以使用 xlog() 這個函數,當 opensips 收到 SIP 封包,就會執行 routing script,並將 xlog 的資訊列印到 log file 裡面。

xlog 的第一個參數可有有無,可填上以下的 log level,將來就能直接在 opensips.cfg 的 debug=3 裡面決定 log 的資訊等級。

L_ALERT - log level -3
L_CRIT - log level -2
L_ERR - log level -1
L_WARN - log level 1
L_NOTICE - log level 2
L_INFO - log level 3
L_DBG - log level 4

獨立的 log file

opensips使用syslog服務,預設安裝的情況下,log內容會寫入 /var/log/message 這個文件,如果希望使用獨立的log文件,可用以下的指令設定。

touch /var/log/opensips.log
vi /etc/rsyslog.conf –> 增加一行:local0.* /var/log/opensips.log
/etc/init.d/rsyslog restart

針對 opensips.log 檔案,我們再用 logrotate 避免 log file 太大。

> vi /etc/logrotate.d/opensips.logrotate
/var/log/opensips.log {
   missingok
   rotate 5
   daily
   create 0640 root root
}

senario

asterisk: 192.168.1.5
asterisk: 192.168.1.17
opensips: 192.168.1.24

兩個 asterisk 分別設定 siptrunk 指定 host 為 192.168.1.24,另外再以電話號碼 prefix 來區分,prefix 為 2 的分機是由 192.168.1.17 負責,prefix 為 1 是由 192.168.1.5 負責。

osipsconfig - Trunking Script

我們只單純需要 opensips 扮演 SIP Proxy 的角色,因此就先在 osipsconfig 的 Trunking Script,只勾選 USE_DIALPLAN, USE_DIALOG 兩個選項,然後就 Generate Trunking Script 產生設定檔,檔名的形式為 opensips_trunking_2014-5-26_10:6:51.cfg,中間的日期及時分秒就是產生檔案的時間。

把該設定檔替換為 opensips.cfg,然後就能再進行下一步的設定修改,設定檔分為以下三個部份。

global configuration

要修改 listen=udp:192.168.1.24:5060,並增加一行 db_default_url="mysql://opensips:opensipsrw@localhost/opensips" 。

####### Global Parameters #########

debug=3
log_stderror=no
log_facility=LOG_LOCAL0

fork=yes
children=4

/* uncomment the following lines to enable debugging */
#debug=6
#fork=no
#log_stderror=yes

/* uncomment the next line to enable the auto temporary blacklisting of 
   not available destinations (default disabled) */
#disable_dns_blacklist=no

/* uncomment the next line to enable IPv6 lookup after IPv4 dns 
   lookup failures (default disabled) */
#dns_try_ipv6=yes

/* comment the next line to enable the auto discovery of local aliases
   based on revers DNS on IPs */
auto_aliases=no


listen=udp:192.168.1.24:5060   # CUSTOMIZE ME


disable_tcp=yes

disable_tls=yes

db_default_url="mysql://opensips:opensipsrw@localhost/opensips"

module configuration

修改 module path
mpath="/usr/lib64/opensips/modules/"

然後注意 db_url 的密碼的地方,根據自己的設定修改密碼
modparam("drouting", "db_url",
"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

####### Modules Section ########

#set module path
mpath="/usr/lib64/opensips/modules/"


#### SIGNALING module
loadmodule "signaling.so"

#### StateLess module
loadmodule "sl.so"

#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timer", 5)
modparam("tm", "fr_inv_timer", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)

#### Record Route Module
loadmodule "rr.so"
/* do not append from tag to the RR (no need for this script) */
modparam("rr", "append_fromtag", 0)

#### MAX ForWarD module
loadmodule "maxfwd.so"

#### SIP MSG OPerations module
loadmodule "sipmsgops.so"

#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)

#### URI module
loadmodule "uri.so"
modparam("uri", "use_uri_table", 0)

#### MYSQL module
loadmodule "db_mysql.so"

#### AVPOPS module
loadmodule "avpops.so"

####  DYNAMIC ROUTING module
loadmodule "drouting.so"
modparam("drouting", "db_url",
    "mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

####  PERMISSIONS module
loadmodule "permissions.so"
modparam("permissions", "db_url",
    "mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

#### ACCounting module
loadmodule "acc.so"
/* what special events should be accounted ? */
modparam("acc", "early_media", 0)
modparam("acc", "report_cancels", 0)
/* by default we do not adjust the direct of the sequential requests.
   if you enable this parameter, be sure the enable "append_fromtag"
   in "rr" module */
modparam("acc", "detect_direction", 0)
modparam("acc", "failed_transaction_flag", "ACC_FAILED")
/* account triggers (flags) */
modparam("acc", "log_flag", "ACC_DO")
modparam("acc", "log_missed_flag", "ACC_MISSED")


#### DIALOG module
loadmodule "dialog.so"
modparam("dialog", "dlg_match_mode", 1)
modparam("dialog", "default_timeout", 21600)  # 6 hours timeout
modparam("dialog", "db_mode", 2)
modparam("dialog", "db_url",
    "mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

####  DIALPLAN module
loadmodule "dialplan.so"
modparam("dialplan", "db_url",
    "mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

routing configuration

把 if ( check_source_address("1","$avp(trunk_attrs)") ) { ... } 這個部份加上 # 註解掉。

把 if ( !isflagset(IS_TRUNK) ) { ... } 這個部份加上 # 註解掉。

在 record_routing 的前面,增加一段 URI 的檢查

    # 增加 URI 的檢查
    if ( uri == myself ) {
        if(is_method("INVITE") && !has_totag() && uri=~"sip:.*") {
            route(home);
        }
    }

增加subroute: route[home],就是以 prefix 號碼來決定要不要 rewritehostport,如果機器的 port 不是預設的 5060,而是 5070,就改成 192.168.1.17:5070。

# 增加 route[home]
route[home] {
    if (uri=~"^sip:2[0-9]{3}@") {
        # uri 開頭為 2,共 4 碼
        rewritehostport("192.168.1.17");
    } else if(uri=~"^sip:[1][0-9].*") {
        # uri 開頭為 1
        rewritehostport("192.168.1.5");
    }
    route(RELAY);
}

完整的 script 內容如下

####### Routing Logic ########

# main request routing logic

route{

    if (!mf_process_maxfwd_header("10")) {
        sl_send_reply("483","Too Many Hops");
        exit;
    }

#    if ( check_source_address("1","$avp(trunk_attrs)") ) {
#        # request comes from trunks
#        setflag(IS_TRUNK);
#    } else if ( is_from_gw() ) {
#        # request comes from GWs
#    } else {
#        send_reply("403","Forbidden");
#        exit;
#    }

    if (has_totag()) {
        # sequential request withing a dialog should
        # take the path determined by record-routing
        if (loose_route()) {

            # validate the sequential request against dialog
            if ( $DLG_status!=NULL && !validate_dialog() ) {
                xlog("In-Dialog $rm from $si (callid=$ci) is not valid according to dialog\n");
                ## exit;
            }

            if (is_method("BYE")) {
                setflag(ACC_DO); # do accounting ...
                setflag(ACC_FAILED); # ... even if the transaction fails
            } else if (is_method("INVITE")) {
                # even if in most of the cases is useless, do RR for
                # re-INVITEs alos, as some buggy clients do change route set
                # during the dialog.
                record_route();
            }

            # route it out to whatever destination was set by loose_route()
            # in $du (destination URI).
            route(RELAY);
        } else {
            if ( is_method("ACK") ) {
                if ( t_check_trans() ) {
                    # non loose-route, but stateful ACK; must be an ACK after 
                    # a 487 or e.g. 404 from upstream server
                    t_relay();
                    exit;
                } else {
                    # ACK without matching transaction ->
                    # ignore and discard
                    exit;
                }
            }
            sl_send_reply("404","Not here");
        }
        exit;
    }

    #### INITIAL REQUESTS

#    if ( !isflagset(IS_TRUNK) ) {
#        ## accept new calls only from trunks
#        send_reply("403","Not from trunk");
#        exit;
#    }

    # CANCEL processing
    if (is_method("CANCEL")) {
        if (t_check_trans())
            t_relay();
        exit;
    } else if (!is_method("INVITE")) {
        send_reply("405","Method Not Allowed");
        exit;
    }

    if ($rU==NULL) {
        # request with no Username in RURI
        sl_send_reply("484","Address Incomplete");
        exit;
    }

    t_check_trans();

    # preloaded route checking
    if (loose_route()) {
        xlog("L_ERR",
        "Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
        if (!is_method("ACK"))
            sl_send_reply("403","Preload Route denied");
        exit;
    }

    # 增加 URI 的檢查
    if ( uri == myself ) {
        if(is_method("INVITE") && !has_totag() && uri=~"sip:.*") {
            route(home);
        }
    }

    # record routing
    record_route();

    setflag(ACC_DO); # do accounting


    # create dialog with timeout
    if ( !create_dialog("B") ) {
        send_reply("500","Internal Server Error");
            exit;
    }





    # apply transformations from dialplan table
    dp_translate("0","$rU/$rU");

    # route calls based on prefix
    if ( !do_routing("1") ) {
        send_reply("404","No Route found");
        exit;
    }

    t_on_failure("GW_FAILOVER");

    route(RELAY);
}


route[RELAY] {
    if (!t_relay()) {
        sl_reply_error();
    };
    exit;
}

# 增加 route[home]
route[home] {
    if (uri=~"^sip:2[0-9]{3}@") {
        # uri 開頭為 2,共 3 碼
        rewritehostport("192.168.1.17");
    } else if(uri=~"^sip:[1][0-9].*") {
        # uri 開頭為 1
        rewritehostport("192.168.1.5");
    }
    route(RELAY);
}

failure_route[GW_FAILOVER] {
    if (t_was_cancelled()) {
        exit;
    }

    # detect failure and redirect to next available GW
    if (t_check_status("(408)|([56][0-9][0-9])")) {
        xlog("Failed GW $rd detected \n");

        if ( use_next_gw() ) {
            t_on_failure("GW_FAILOVER");
            t_relay();
            exit;
        }

        send_reply("500","All GW are down");
    }
}


local_route {
    if (is_method("BYE") && $DLG_dir=="UPSTREAM") {

        acc_log_request("200 Dialog Timeout");

    }
}

小結

這個方式,很單純地直接修改 opensips.cfg,並直接將 routing 的條件寫在設定檔中,接下來應該要嘗試使用 dynamic routing module。