上一次是將 routing 訊息直接填寫到 opensips.cfg 設定檔,但有另一個方式可以達到這個功能,就是使用 dynamic routing,因為 dynamic routing 是將設定好的 routing 資訊寫到 DB 裡面,再以 MI command(dr_reload) 在不停止 opensips 的狀況下,重新載入 routing 資訊。
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 負責。
DB tables
跟 dynamic routing 有關的 3 個 DB tables
- dr_groups
從 opensips.conf 中呼叫 dr 的 table
- dr_gateways
route endpoints,也就是要填 asterisk 的 ips
- dr_rules
存放 inbound DID 或 default routes 的 rules
填寫 table 資料
dr_groups
在這裡我們不管 inbound 的 username 與 domain,所以設定為 * ,最重要的是 groupid 0 這個資訊,這個 groupid 在後面用來設定 dynamic routes。
mysql> use opensips;
mysql> INSERT INTO dr_groups(username,domain,groupid,description) VALUES(".*",".*","0","INBOUND");
dr_gateway
address 的地方填寫 gateway ip:port,strip 欄位決定要去除電話號碼的幾位,probe_mode 這個欄位設定為 2 代表我們要 enable probe,使用一個 active gw 的列表。
mysql> INSERT INTO dr_gateways(type,gwid,address,strip,probe_mode,description) VALUES("0","1","192.168.1.5:5060","1","2","asterisk 5");
mysql> INSERT INTO dr_gateways(type,gwid,address,strip,probe_mode,description) VALUES("0","2","192.168.1.17:5060","0","2","asterisk 17");
dr_rules
dr_rules 設定撥號規則,groupid 填 0 就是剛剛填寫的 dr_groups "INBOUND",prefix 1 是電話號碼的前置碼,也可以填上完整的 DID 號碼。
gwlist 欄位是參考到 dr_gateways 的 gwid 資料
mysql> INSERT INTO dr_rules(groupid,prefix,priority,gwlist,description) VALUES("0","1","0","1","My Number");
mysql> INSERT INTO dr_rules(groupid,prefix,priority,gwlist,description) VALUES("0","2","0","2","My Number 2");
修改 opensips.conf
modules 裡面要加上
loadmodule "drouting.so"
loadmodule "db_mysql.so"
modparam("drouting", "db_url", "mysql://opensips:opensipsrw@localhost/opensips")
modparam("drouting", "probing_interval", 60)
modparam("drouting", "probing_from", "sip:probe@URI")
modparam("drouting", "probing_method", "OPTIONS")
modparam("drouting", "probing_reply_codes", "501, 403, 404")
modparam("drouting", "use_domain", 1)
在 #### INITIAL REQUESTS 這一行的後面,加上封包判斷,INVITE/CANCEL 時,就呼叫 route[gw]。
do_routing("0") 就是呼叫 groupid 為 0 的路由
if (method == "INVITE") {
setflag(1);
record_route();
xlog("INBOUND CALL,$dd,$ru,$ci,$fn,$fu");
route(gw);
exit;
} else if ( is_method("CANCEL") ) {
xlog("!!CANCEL\n");
setflag(ACC_DO);
setflag(ACC_FAILED);
xlog("CANCEL CALL,$dd,$ru,$ci,$fn,$fu");
route(gw);
exit;
}
route[gw] {
if (!do_routing("0")) {
xlog("do_routing: No rules matching the URI\n");
send_reply("503","No rules matching the URI");
exit;
}
if (is_method("INVITE")) {
t_on_failure("GW_FAILOVER");
}
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");
}
}
opensips.cfg
完整的 opensips.cfg
####### 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"
####### 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
modparam("drouting", "probing_interval", 60)
modparam("drouting", "probing_from", "sip:probe@URI")
modparam("drouting", "probing_method", "OPTIONS")
modparam("drouting", "probing_reply_codes", "501, 403, 404")
modparam("drouting", "use_domain", 1)
#### 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 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
xlog("initial requests\n");
if ( is_method("INVITE") ) {
xlog("!!INVITE\n");
setflag(1);
record_route();
xlog("INBOUND CALL,$dd,$ru,$ci,$fn,$fu");
route(gw);
exit;
} else if ( is_method("CANCEL") ) {
xlog("!!CANCEL\n");
setflag(ACC_DO);
setflag(ACC_FAILED);
xlog("CANCEL CALL,$dd,$ru,$ci,$fn,$fu");
route(gw);
exit;
}
if ( !isflagset(IS_TRUNK) ) {
## accept new calls only from trunks
send_reply("403","Not from trunk");
exit;
}
# CANCEL processing
if (is_method("CANCEL")) {
xlog("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;
}
# 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("0") ) {
send_reply("404","No Route found");
exit;
}
t_on_failure("GW_FAILOVER");
route(RELAY);
}
route[RELAY] {
if (!t_relay()) {
sl_reply_error();
};
exit;
}
route[gw] {
if (!do_routing("0")) {
xlog("do_routing: No rules matching the URI\n");
send_reply("503","No rules matching the URI");
exit;
}
if (is_method("INVITE")) {
t_on_failure("GW_FAILOVER");
}
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
用這個指令,重新啟動 opensips
service opensips restart
如果 opensips 已經啟動了,但修改了 DB table 裡面的 routing 資訊,就可以用這個指令讓 opensips 重新由 DB 載入 routing 資訊。
opensipsctl fifo dr_reload
測試
在 192.168.1.5 的分機,撥打 2000 到 opensips (192.168.1.24) 時,opensips 會自動將電話轉送到 192.168.1.17 的號碼 2000
在 192.168.1.17 的分機,撥打 1106 時,opensips 會自動將第一碼 1 去掉,電話轉送到 192.168.1.5 的號碼 106。
參考網頁
OpenSIPS Dynamic Routing