使用OpenSER構建電話通信系統——第五章(3)

注:以下文章如需轉載,請註明所屬作者,轉載地址,謝謝!

Openserctl shell腳本

Openserctl工具是安裝在/usr/sbin上的shell腳本。被用來使用命令行的方式來對OpenSER進行管理。可以用來進行:

l         啓動,終止,重啓OpenSER

l         展示,授權,撤銷ACLs

l         添加,刪除,列出別名

l         添加,刪除,配置AVP

l         管理LCR(low cost routes)

l         管理RPID

l         添加,刪除,列出訂閱者

l         添加,刪除,展示usrloc表“in-ram”

l         監控OpenSER

我們將在下面的章節中學習一些它的選項.下面是openserctl help命令的輸出信息:

/etc/openser# openserctl help

database engine 'MYSQL' loaded

Control engine 'FIFO' loaded

/usr/sbin/openserctl 1.2 - $Revision: 1.3 $

Existing commands:

-- command 'start|stop|restart'

restart ............................ restart OpenSER

start .............................. start OpenSER

stop ............................... stop OpenSER

-- command 'acl' - manage access control lists (acl)

acl show [<username>] .............. show user membership

acl grant <username> <group> ....... grant user membership (*)

acl revoke <username> [<group>] .... grant user membership(s) (*)

-- command 'alias_db' - manage database aliases

alias_db show <alias> .............. show alias details

alias_db list <sip-id> ............. list aliases for uri

alias_db add <alias> <sip-id> ...... add an alias (*)

alias_db rm <alias> ................ remove an alias (*)

alias_db help ...................... help message

- <alias> must be an AoR (username@domain)"

- <sip-id> must be an AoR (username@domain)"

-- command 'avp' - manage AVPs

avp list [-T table] [-u <sip-id|uuid>]

[-a attribute] [-v value] [-t type] ... list AVPs

avp add [-T table] <sip-id|uuid>

<attribute> <type> <value> ............ add AVP (*)

avp rm [-T table] [-u <sip-id|uuid>]

[-a attribute] [-v value] [-t type] ... remove AVP (*)

avp help .................................. help message

- -T - table name

- -u - SIP id or unique id

- -a - AVP name

- -v - AVP value

- -t - AVP name and type (0 (str:str), 1 (str:int),

2 (int:str), 3 (int:int))

- <sip-id> must be an AoR (username@domain)

- <uuid> must be a string but not AoR

-- command 'db' - database operations

db exec <query> ..................... execute SQL query

db show <table> ..................... display table content

-- command 'lcr' - manage least cost routes (lcr)

* lcr *

* IP addresses must be entered in dotted quad format e.g. 1.2.3.4 *

* <uri_scheme> and <transport> must be entered in integer or text,*

* e.g. transport '2' is identical to transport 'tcp'. *

* scheme: 1=sip, 2=sips; transport: 1=udp, 2=tcp, 3=tls *

* Examples: lcr addgw_grp usa 1 *

* lcr addgw level3 1.2.3.4 5080 sip tcp 1 *

* lcr addroute +1 % 1 1 *

lcr show ....................................................................

............. show routes, gateways and groups

lcr reload ..................................................................

............. reload lcr gateways

lcr addgw_grp <grp_name> ....................................................

.............. add gateway group, autocreate grp_id

lcr addgw_grp <grp_name> <grp_id> ...........................................

............... add gateway group with grp_id

lcr rmgw_grp <grp_id> ......................................................

............... delete the gw_grp

lcr addgw <gw_name> <ip> <port> <scheme> <transport> <grp_id> ...............

............... add a gateway

lcr addgw <gw_name> <ip> <port> <scheme> <transport> <grp_id> <prefix> ......

............... add a gateway with prefix

lcr addgw <gw_name> <ip> <port> <scheme> <transport> <grp_id> <prefix> <strip>

............... add a gateway with prefix and strip

lcr rmgw <gw_name> .........................................................

............... delete a gateway

lcr addroute <prefix> <from> <grp_id> <prio> ................................

.............. add a route

lcr rmroute <prefix> <from> <grp_id> <prio> ................................

.............. delete a route

-- command 'rpid' - manage Remote-Party-ID (RPID)

rpid add <username> <rpid> ......... add rpid for a user (*)

rpid rm <username> ................. set rpid to NULL for a user (*)

rpid show <username> ............... show rpid of a user

-- command 'speeddial' - manage speed dials (short numbers)

speeddial show <speeddial-id> ....... show speeddial details

speeddial list <sip-id> ............. list speeddial for uri

speeddial add <sip-id> <sd-id> <new-uri> [<desc>] ...

........................... add a speedial (*)

speeddial rm <sip-id> <sd-id> ....... remove a speeddial (*)

speeddial help ...................... help message

- <speeddial-id>, <sd-id> must be an AoR (username@domain)

- <sip-id> must be an AoR (username@domain)

- <new-uri> must be a SIP AoR (sip:username@domain)

- <desc> a description for speeddial

-- command 'add|mail|passwd|rm' - manage subscribers

add <username> <password> <email> .. add a new subscriber (*)

passwd <username> <passwd> ......... change user's password (*)

rm <username> ...................... delete a user (*)

mail <username> .................... send an email to a user

-- command 'cisco_restart' - restart CISCO phone (NOTIFY)

cisco_restart <uri> ................ restart phone configured for <uri>

-- command 'online' - dump online users from memory

online ............................. display online users

-- command 'monitor' - show internal status

monitor ............................ show server's internal status

-- command 'ping' - ping a SIP URI (OPTIONS)

ping <uri> ......................... ping <uri> with SIP OPTIONS

-- command 'ul|alias' - manage user location or aliases

ul show [<username>]................ show in-RAM online users

ul rm <username> [<contact URI>].... delete user's UsrLoc entries

ul add <username> <uri> ............ introduce a permanent UrLoc entry

ul add <username> <uri> <expires> .. introduce a temporary UrLoc entry

-- command 'fifo'

fifo ............................... send raw FIFO command

Openserctl資源文件(Openserctl Resource File)

在版本1.1中,我們介紹一個叫做openserctlrc的資源文件。這個腳本可以在/etc/openser中找到。openserctl工具對其進行語法解析然後使用其配置數據庫認證和一些進行溝通需要的參數。通常,它使用FIFO機制來向OpenSER守護進程發送命令。

|      安全起見,改變默認的訪問數據庫的用戶名和密碼是很重要的    |

Openserctlrc文件(Opneserctlrc File)

To show the file, issue a command:

cat /etc/openser/openserctlrc

# $Id: openserctlrc,v 1.2 2006/07/05 19:37:20 miconda Exp $

#

# openser control tool resource file

#

# here you can set variables used in the openserctl

## your SIP domain

SIP_DOMAIN=voffice.com.br

## database type: MYSQL or PGSQL, by defaulte none is loaded

DBENGINE=MYSQL

## database host

DBHOST=localhost

## database name

DBNAME=openser

## database read/write user

DBRWUSER=openser

## database read only user

DBROUSER=openserro

## password for database read only user

DBROPW=openserro

## database super user

DBROOTUSER="root"

## type of aliases used: DB - database aliases; UL - usrloc aliases

## - default: none

ALIASES_TYPE="DB"

## control engine: FIFO or UNIXSOCK

## - default FIFO

CTLENGINE="FIFO"

## path to FIFO file

OSER_FIFO="FIFO"

## check ACL names; default on (1); off (0)

VERIFY_ACL=1

## ACL names - if VERIFY_ACL is set, only the ACL names from below list

## are accepted

ACL_GROUPS="local ld int voicemail free-pstn"

## verbose - debug purposes - default '0'

VERBOSE=1

## do (1) or don't (0) store plaintext passwords

## in the subscriber table - default '1'

# STORE_PLAINTEXT_PW=0

使用帶有認證功能的OpenSER(Using OpenSER with Authentication)

現在,讓我們以一種實踐的方式來實現認證。

步驟1:按照上面章節中描述的內容來修改openser.cfg文件。

步驟2:使用命令/etc/init.d/openser restart 來重啓OpenSER。

步驟3:使用被openserctl使用的默認的參數來配置openserctlrc。

# $Id: openserctlrc 1827 2007-03-12 15:22:53Z bogdan_iancu $

#

# openser control tool resource file

#

# here you can set variables used in the openserctl

## your SIP domain

SIP_DOMAIN=voffice.com.br

## database type: MYSQL or PGSQL, by defaulte none is loaded

DBENGINE=MYSQL

## database host

DBHOST=localhost

## database name

DBNAME=openser

## database read/write user

DBRWUSER=openser

## database read only user

DBROUSER=openserro

## password for database read only user

DBROPW=openserro

## database super user

DBROOTUSER="root"

## type of aliases used: DB - database aliases; UL - usrloc aliases

## - default: none

ALIASES_TYPE="DB"

## control engine: FIFO or UNIXSOCK

## - default FIFO

CTLENGINE="FIFO"

## path to FIFO file

OSER_FIFO="/tmp/openser_fifo"

## check ACL names; default on (1); off (0)

VERIFY_ACL=1

## ACL names - if VERIFY_ACL is set, only the ACL names from below list

## are accepted

ACL_GROUPS="local ld int voicemail free-pstn"

## presence of serweb tables - default "no"

HAS_SERWEB="yes"

## verbose - debug purposes - default '0'

VERBOSE=1

## do (1) or don't (0) store plaintext passwords

## in the subscriber table - default '1'

STORE_PLAINTEXT_PW=1

步驟4:使用openserctl工具來配置兩個用戶帳戶。

/sbin/openserctl add 1000 password [email protected]

/sbin/openserctl add 1001 password [email protected]

|      如果你碰到“複製關鍵字(Duplicate Keys)”的問題,請檢查你是否  |

|      安裝了SerWEB表。如果你已經裝了,那麼只要將HAS_SERWEB設      |

|      爲“yes”即可。                                                                                          |

|      當你被要求密碼時,使用openserrw                                                               |

你可以使用openserctl的rm命令來刪除用戶,使用openserctl的passwd命令來修改密碼。

步驟5:使用ngrep工具來觀察SIP消息:

#ngrep -p -q -W byline port 5060 >register.pkt

步驟6:使用用戶名和密碼註冊雙方話機:

步驟7:驗證話機是否註冊上,使用下面的命令:

#openserctl ul show

步驟8:你可以使用下面的命令來驗證哪些用戶在線:

#openserctl online

步驟9:你可以使用下面的命令來ping一個用戶:

#openserctl ping 1000

步驟10:使用ngrep工具來驗證認證信息

步驟11:從一個用戶向另外一個用戶打通電話

步驟12:使用下面命令驗證在register.pkt文件中的認證

#pg register.pkt

增強型腳本(Enhancing the Script)


由SIP代理處理的通話可以被分爲:

l         內域

l         帶外內域

l         帶內內域

l         帶外到帶外

讓我們來描述一些目前我們腳本的一些問題:

問題#1:目前,我們沒有檢查從其他域過來的帶外通話的標識。這就使得我們的服務器成爲了比較開放的中繼。因此,任何人都可以使用我們的服務器了隱藏他們的標識。

問題#2:我們的腳本不接受來自另一個域的來電

問題#3:一個用戶可以使用另一個用戶的證書來僞造INVITE請求中的FROM頭域。

問題#4:一個用戶可以使用另一個用戶的證書來僞造REGISTER請求中的TO頭域。

問題#5:此腳本不會準備對多域(multiple domains)進行管理。

管理多域(Managing Multiple Domains)

到現在爲止,我們已經使用uri==myself這一行指令來對請求進行了校驗。然而,這條指令僅僅校驗了本地名字和地址。如果我們需要管理多域,我們必須使用域模塊和它的is_from_local()和is_uri_host_local()函數。

就像我之前說的那樣,域模塊導出了兩個函數,這兩個函數將在我們的腳本中使用。第一個是is_from_local(),用來校驗FROM頭域是否包含我們代理管理着的域。第二個函數是is_uri_host_local(),用來替代uri==myself指令。這些函數的優點是他們可以檢查MySQL表中的域。然後你就可以在你的配置中處理多域了。

|      這個函數需要所有的被管理的域都要被插入數據庫中           |

|      對於使用這個功能資源的用戶來說,一個相當普遍的           |

|      錯誤就是在對話機進行註冊之前,忘記將域插入到MySQL  |

|      數據庫。                                                                             |

替代路線(Alternative Routes)

爲了簡化我們的腳本,我們將創建幾個替代路線。我們已經看到腳本可能會變得非常複雜和難懂。爲了避免這種情況的發生,我們已經創建了一些和子函數類似的替代路線。使用替代路線,允許我們將代碼分片以加強它的可讀性。

註冊請求(Register Requests)(route[2])

註冊請求路線負責處理所有的註冊請求。這段代碼對用戶進行認證並保存UAC的位置信息。

route[2] {

#

# -- Register request handler --

#

if (is_uri_host_local()) {

if (!www_authorize("", "subscriber")) {

www_challenge("", "1");

exit;

};

if (!check_to()) {

sl_send_reply("401", "Unauthorized");

exit;

};

save("location");

exit;

} else if {

sl_send_reply("401", "Unauthorized");

};

}

非註冊請求(Non-Register Requests)(route[3])

非註冊請求路線處理所有其他的請求。請求需要再次進行認證。我們已經決定把請求分成下面幾個部分:

l         帶內到帶內(route[10])

l         帶內到帶外(route[11])

l         帶外到帶內(route[12])

l         帶外到帶外(route[13])

通常,你會允許帶認證的帶內到帶內的請求。這時比較普通的情況。帶內到帶外和帶外到帶內被用來處理內域請求。大多數的VoIP服務提供商不允許內域之間的交互,因爲這回減少其潛在收入。帶外到帶外更是不會被允許的。大多數情況下,這種交互被認爲是一種安全漏洞。

route[3] {

#

# -- non-register requests handler --

#

# Verify the source (FROM)

if (is_from_local()){

# From an internal domain -> check the credentials and the FROM

if (!proxy_authorize("","subscriber")) {

proxy_challenge("","1");

exit;

} else if (!check_from()) {

sl_send_reply("403", "Forbidden, use From=ID");

exit;

};

consume_credentials();

# Verify aliases

lookup("aliases");

# Verify the destination (URI)

if (is_uri_host_local()) {

# -- Inbound to Inbound

route(10);

} else {

# -- Inbound to outbound

route(11);

};

} else {

#Verify aliases, if found replace R-URI.

lookup("aliases");   # Verify the destination (URI)

if (is_uri_host_local()) {

#-- Outbound to inbound

route(12);

} else {

# -- Outbound to outbound

route(13);

};

};

}

管理我們域中發起的通話(Managing Calls Coming from Our Domain)

我們的腳本openser.cfg現在使用源地址和目的地址來區分通話。使用函數if(is_uri_host_local())來決定目的地,使用if(is_from_local())來決定源地址。

對於帶內發起的通話,我們會首先檢查標識並刪除其證書以避免發送之。我們在檢查目的地和前轉請求前會對定義的別名進行解析。

如果通話目的地是我們管理域中之一(使用is_uri_host_local()檢查),我們將其送至route(10)否則如果是一個外部域則將其送至route(11)。

帶內到帶內——route[10](Inbound-to-Inbound------route[10])

帶內目的地會被用戶位置數據庫處理。

route[10] {

#from an internal domain -> inbound

#Native SIP destinations are handled using the location table

append_hf("P-hint: inbound->inbound \r\n");

if (!lookup("location")) {

sl_send_reply("404", "Not Found");

exit;

};

route(1);

}

帶內到帶外——route[11](Inbound-to-outbound-----route[11])

我們將使用DNS搜索來將通話路由到外部的目的地.

route[11] {

# from an internal domain -> outbound

# Simply route the call outbound using DNS search

append_hf("P-hint: inbound->outbound \r\n");

route(1);

}

帶外到帶內——route[12](Outbound-to-Inbound------route[12])

我們允許通話從外部域到我們自己的話機。這個配置會讓許多垃圾信息傳到我們的話機上,但是實際情況卻不是這樣。我相信這樣做的收益遠大於風險。以後到底會怎樣,誰也不知道。對於我來說比較合理的是,開放的接收通話就應該像開放的接收傳統通話,開放的接收emails一樣。

route[12] {

# From an external domain -> inbound

# Verify aliases, if found replace R-URI.

lookup("aliases");

if (!lookup("location")) {

sl_send_reply("404", "Not Found");

exit;

};

route(1);

}

帶外到帶外——route[13](Outbound-to-Outbound------route[13])

我們不想做一個開放的中繼器來對外部消息進行中繼。如果下面的配置沒有進行,那麼其他人就能夠使用我們的代理來匿名的路由通話。

route[13] {

#From an external domain outbound

#we are not accepting these calls

sl_send_reply("403", "Forbidden");

exit;

}

函數check_to()和check_from()(The Functions check_to() and check_from())

當操作SIP代理時,你應該確認的是一個有效的帳戶不會被一個沒有進行認證的用戶所使用。Check_to()和check_from()函數被用來將SIP用戶通認證用戶做映射。SIP用戶在FROM和TO頭域中,而auth用戶僅僅用來進行認證(Authorize頭域)並有自己的密碼。在當前的例子中,此函數檢查SIP用戶同auth用戶是否相同。這就能夠避免一個用戶使用另一個用戶的認證信息。這些函數通過URI_DB模塊進行使能。

使用別名(Using Aliases)

有些情況下,你想要允許一個用戶擁有多個地址,譬如和一個主地址相關的電話號碼。你可以使用別名來實現這個目的。

爲了添加別名,使用下面命令:

#openserctl alias add [email protected] sip:[email protected]

database engine 'MYSQL' loaded

Control engine 'FIFO' loaded

MySql password for user 'openser@localhost':

lookup("aliases");

函數lookup(“aliases”)檢查數據庫中的別名表,如果一個註冊者被找到,就將其別名翻譯成正規的地址(訂閱列表中的地址)。這個特性還能夠被用來將DIDs重定向到最終用戶。還有一個Alias_db模塊。它不是從內存而是數據庫中直接對別名進行搜索。犧牲了一點性能的好處就是,它能夠簡化數據庫中別名的直接準備。

處理CANCEL請求和重傳(Handling CANCEL requests and retransmissions)

按照RFC3261,cancel請求消息和INVITE請求按照同一種方式進行路由。下面的腳本檢查CANCEL請求是否與現存的事務相匹配,並且關注所有的必要的路由。有時候,我們需要進行一些與現存事務相關的重傳。在這種情況下,函數t_check_trans()將處理之並退出腳本。

#CANCEL processing

if (is_method("CANCEL"))

{

if (t_check_trans())

t_relay();

exit;

}

t_check_trans();

帶有上面所有資源的完整腳本(Full Script with All the Resources Above)

# ------------------ module loading ----------------------------------

#set module path

mpath="//lib/openser/modules/"

# Uncomment this if you want to use SQL database

loadmodule "mysql.so"

loadmodule "sl.so"

loadmodule "tm.so"

loadmodule "rr.so"

loadmodule "maxfwd.so"

loadmodule "usrloc.so"

loadmodule "registrar.so"

loadmodule "textops.so"

loadmodule "mi_fifo.so"

loadmodule "uri.so"

loadmodule "uri_db.so"

loadmodule "domain.so"

# Uncomment this if you want digest authentication

# mysql.so must be loaded !

loadmodule "auth.so"

loadmodule "auth_db.so"

# ----------------- setting module-specific parameters ---------------

# -- mi_fifo params --

modparam("mi_fifo", "fifo_name", "/tmp/openser_fifo")

# -- usrloc params --

#modparam("usrloc", "db_mode", 0)

# Uncomment this if you want to use SQL database

# for persistent storage and comment the previous line

modparam("usrloc", "db_mode", 2)

# -- auth params --

# Uncomment if you are using auth module

#

modparam("auth_db", "calculate_ha1", 0)

#

# If you set "calculate_ha1" parameter to yes,

# uncomment also the following parameter)

#

#modparam("auth_db", "password_column", "password")

# -- rr params --

# add value to ;lr param to make some broken UAs happy

modparam("rr", "enable_full_lr", 1)

# ------------------------- request routing logic -------------------

# main routing logic  route{

# initial sanity checks -- messages with

# max_forwards==0, or excessively long requests

if (!mf_process_maxfwd_header("10")) {

sl_send_reply("483","Too Many Hops");

exit;

};   if (msg:len >= 2048 ) {

sl_send_reply("513", "Message too big");

exit;

};

# we record-route all messages -- to make sure that

# subsequent messages will go through our proxy; that's

# particularly good if upstream and downstream entities

# use different transport protocol

if (!method=="REGISTER")

record_route();

# subsequent messages withing a dialog should take the

# path determined by record-routing

if (loose_route()) {

# mark routing logic in request

append_hf("P-hint: rr-enforced\r\n");

route(1);

};   #CANCEL processing

if (is_method("CANCEL")) {

if (t_check_trans()) t_relay();

exit;

}

if (method=="REGISTER") {

route(2);

} else {

route(3);

};

}            route[1] {

# send it out now; use stateful forwarding as it works reliably

# even for UDP2TCP

if (!t_relay()) {

sl_reply_error();

};

exit;

}

route[2] {

#

# -- Register request handler --

#

if (is_uri_host_local()) {

if (!www_authorize("", "subscriber")) {

www_challenge("", "1");

exit;

};

if (!check_to()) {

sl_send_reply("40=3", "Forbidden");

exit;

};   save("location");

exit;

} else if {

sl_send_reply("403", "Forbidden");

};

}            route[3] {

#

# -- INVITE request handler --

#

if (is_from_local()){

# From an internal domain -> check the credentials and the FROM

if (!proxy_authorize("","subscriber")) {

proxy_challenge("","1");

exit;

} else if (!check_from()) {

sl_send_reply("403", "Forbidden, use From=ID");

exit;

};

consume_credentials();

# Verify aliases

lookup("aliases");

if (is_uri_host_local()) {

# -- Inbound to Inbound  route(10);

} else {

# -- Inbound to outbound

route(11);

};

} else {

# From an external domain -> do not check credentials

#Verify aliases, if found replace R-URI.

lookup("aliases");

if (is_uri_host_local()) {

#-- Outbound to inbound

route(12);

} else {

# -- Outbound to outbound

route(13);

};

};

}            route[10] {

#from an internal domain -> inbound

#Native SIP destinations are handled using the location table

append_hf("P-hint: inbound->inbound \r\n");

if (!lookup("location")) {

sl_send_reply("404", "Not Found");

exit;

};

route(1);

}

route[11] {

# from an internal domain -> outbound

# Simply route the call outbound using DNS search

append_hf("P-hint: inbound->outbound \r\n");

route(1);

}

route[12] {

# From an external domain -> inbound

# Verify aliases, if found replace R-URI.

lookup("aliases");

if (!lookup("location")) {

sl_send_reply("404", "Not Found");

exit;

};

route(1);

}

route[13] {

#From an external domain outbound

#we are not accepting these calls

append_hf("P-hint: outbound->inbound \r\n");

sl_send_reply("403", "Forbidden");

exit;

}

實驗——加強安全性(Lab——Enhancing the Security)

步驟1:試着用新的配置註冊你的話機。你會注意到在你的話機註冊時出現了一個錯誤。

步驟2:上面的配置目前使用的是domain.so模塊。現在,爲了認證,域必須在MySQL數據庫中的域表中。

爲了增加一個域,使用openserctl工具。

openserctl domain add your-ip-address

openserctl domain add your-domain

對於每一個域重複上面的過程。

步驟3:再次嘗試註冊話機。現在註冊過程將正常。

實驗——使用別名(Lab——Using Aliases)

步驟1:爲訂閱者1000添加別名

#openserctl alias add john@youripordomain sip:1000@youripordomain

database engine 'MYSQL' loaded

Control engine 'FIFO' loaded

MySql password for user 'openser@localhost':

|             使用openserrw作爲密碼                                             |

步驟2:從註冊爲1001的軟電話打給John

通話完整麼?爲什麼?

概要(Summary)

在這一章中,你已經學會了如何將MySQL整合進OpenSER中。現在,我們的腳本可以認證用戶,檢查TO和FROM頭域,按照帶內和帶外來處理通話了。重要的是要牢記域必須被插進數據庫中,因爲要支持多域。如果你改變了你的域或IP地址,請記着更新你的數據庫。

http://blog.chinaunix.net/space.php?uid=2555411&do=blog&cuid=2016255
發佈了10 篇原創文章 · 獲贊 4 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章