- 论坛徽章:
- 0
|
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!第七章:与PSTN的连通(Connectivity to the PSTN)
在前两章中,我们已经使用认证(authentication)和数据库为OpenSER处理通话做好了准备。SerMyAdmin用来处理数据记录。然而,你仍然不能打给普通电话,因为你没有连上PSTN。现在的挑战是如何将通话由PSTN路由进来和如何将通话路由到PSTN(Public
Switched Telephone Network)
![]()
为了能将通话路由到PSTN,你需要一个叫做SIP
PSTN网关(SIP PSTN Gateway)的设备。在市面上,有很多生产这种设备的厂家,诸如Cisco,AudioCodes,Nortel,Quintum等等。你也可以使用Asterisk
PBX完成这个工作。Asterisk是一个你绝对能够承担的起的网关,而且与上面提到的各个厂商的设备兼容。它完全是开源的,也是按照GPL许可的。
这一章的结束,你将能够:
l
将OpenSER与SIP网关连上
l
将认证应用到带内通话
l
使ACLs防止PSTN网关不被没有经过认证的用户使用
l
使用LCR(Least
Cost Route)模块路由你的通话
l
使用SerMyAdmin来管理授信主机(Trusted
Hosts),网关(Gateways)和路由器(Routes)
在这一章中,你将学会如何将通话打到PSTN。我们将介绍三个新的模块(LCR,PERMISSIONS,还有GROUP),他们将帮助你路由这些通话并保证他们的安全。你可以在互联网上轻易的找到关于regexps的指南。如果你对regular
expressions或regexps不熟悉,http://www.visibone.com/regular-expressions/这条廉洁可以作为参考。
我们在哪儿?(Where Are We?)
VoIP服务提供商的方案中有很多的部件。为了避免迷失,我们将在每一章中展示下面的这张图片。在这一章中,我们将利用SIP代理部件和PSTN网关一起工作。
![]()
本章之后,我们的VoIP提供者将能够使用SIP网关将通话打给PSTN。
发往网关的请求(Requests Sent to the Gateway)
在标明给网关的请求中,我们必须验证该用户属于哪一组(group),以查看其是否被允许使用PSTN。
![]()
要达到这个目的,我们要使用到‘group’模块。这个暴露了函数is_user_in(“credentials”,
“group”)用来检查用户是否属于指定组。在上面的例子中,我们已经创建了3个组:local代表本地通话,ld代表长途,int代表国际长途。脚本中,我们使用正则表达式(regular
expressions)来检查通话是属于上面介绍的三种中的哪一种。
你必须将这些组插进叫做group的MySQL表中才能使用它。你可以很容易的插入,删除,显示组成员(group
membership):
Openserctl acl show
[]
Openserctl acl grant
Openserctl acl revoke
[]
使用SerMyAdmin来管理你的表也是可能的。要添加和删除组,你可以浏览“User
Groups”部分,在那里,你可以添加,删除组(groups)。
要改变用户的组成员,你可以在下面显示的用户菜单菜单中进行编辑:
使用检查栏选择用户属于的这儿显示的指点的组。
来自网关的请求(Requests Coming From the Gateway)
现在,我们要使用PERMISSIONS模块来对来自没有摘要认证过程的PSTN网关的通话进行授权。
我们将使用的allow_trusted()函数有PERMISSIONS模块曝露出来。许可模块可以被用来授权(authorize)REGISTER,REFER,和INVITE请求。我们可以用permissions.allow,permissions.deny,register.allow,和register.deny文件来对其进行管理和调节。然而这个模块中使用的allow_trusted()函数要将请求的源IP地址同我们数据库中的授信表中的数据进行比较检查。
当通话到来时,函数allow_trusted要试着去找到一条符合该请求的规则。该规则包含下面的域,,。
如果下面的规则存在一条,则接受该请求:
l
Ip地址和请求的源IP地址相同
l
传输层协议是“any”或是同请求的传输层协议相符
l
正则表达式为空或符合请求
对于网关来说,不注册到SIP代理上是很正常的事。因此,来自网关的请求不应该接收“407
Proxy Authentication Required”响应。在我们目前的脚本中,所有来自我们域的INVITE请求都要求他们带有凭据。然而,如果像从网关发出的请求,没有凭据发出,从而通话将会失败。所有,为了修正这一点,我们使用allow_trusted()函数检查源IP地址的方式,而不是去检查凭据。
| 不要忘了将授信的IP地址插入我们MySQL数据库的授信表中, |
| 这样我们的脚本才能工作。 |
你可以使用SerMyAdmin来查看,更新授信主机列表(trusted
hosts list)。使用如下显示的授信主机菜单:
![]()
要添加新的主机,只需简单的点击“New Trusted Host”菜单选项。
![]()
使用函数rewritehostport()前转通话到PSTN网关。
![]()
这个脚本的名字为openser.pstn。可以在http://www.sermyadmin.org/openser找到。一份拷贝展示如下。之前的脚本修改处使用高亮显示。
# ------------------ module loading
----------------------------------
#set module path
mpath="//lib/openser/modules/"
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 "uri.so"
loadmodule "uri_db.so"
loadmodule "domain.so"
loadmodule "permissions.so"
loadmodule "group.so"
loadmodule "mi_fifo.so"
# Uncomment this if you want digest authentication
# mysql.so must be loaded !
loadmodule "auth.so"
loadmodule "auth_db.so"
# ----------------- setting module-specific parameters
---------------
modparam("mi_fifo", "fifo_name",
"/tmp/openser_fifo")
modparam("usrloc", "db_mode", 2)
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column",
"password")
modparam("rr", "enable_full_lr", 1)
modparam("auth_db|permissions|uri_db|usrloc","db_url",
"mysql://
openser:openserrw@localhost/openser")
modparam("permissions", "db_mode", 1)
modparam("permissions", "trusted_table",
"trusted")
# -------------------------
request routing logic -------------------
# main routing logic
route{
#
# -- 1 -- Request Validation
#
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;
};
#
# -- 2 -- Routing Preprocessing
#
## Record-route all except Register
if (!method=="REGISTER")
record_route();
##Loose_route packets
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;
};
t_check_trans();
#
# -- 3 -- Determine Request Target
#
if (method=="REGISTER") {
route(2);
} else {
route(3);
};
}
route[1] {
#
# -- 4 -- Forward request
to target
#
## Forward statefully
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("403",
"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(!allow_trusted()){
if (!proxy_authorize("","subscriber"))
{
proxy_challenge("","1");
exit;
} else if (!check_from()) {
sl_send_reply("403", "Forbidden, use From=ID");
exit;
};
}
else {
log("Request bypassed the
auth.using allow_trusted");
};
consume_credentials();
#Verify
aliases, if found replace R-URI.
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[4] {
# routing to the
public network
rewritehostport("10.1.30.45");
route(1);
}
route[10] {
#from an internal
domain -> inbound
#Native SIP
destinations are handled using the location table
#Gateway destinations
are handled by regular expressions
append_hf("P-hint: inbound->inbound \r\n");
if
(uri=~"^sip:[2-9][0-9]{6}@") {
if
(is_user_in("credentials","local")) {
route(4);
exit;
} else {
sl_send_reply("403", "No permissions for local
calls");
exit;
};
};
if
(uri=~"^sip:1[2-9][1-9]{9}@") {
if
(is_user_in("credentials","ld")) {
route(4);
exit;
} else {
sl_send_reply("403", "No permissions for long
distance");
exit;
};
};
if
(uri=~"^sip:011[0-9]*@") {
if
(is_user_in("credentials","int")) {
route(4);
exit;
} else {
sl_send_reply("403", "No permissions for
international calls");
};
};
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;
}
openser.cfg检查(openser.cfg Inspection)
PERMISSIONS模块曝露了一些重要的函数以对到我们SIP代理的访问进行控制。其中之一是allow_trusted(),这个函数允许我们控制网关使用IP地址访问代理,而不是使用认证凭据。授信表(trusted
table)是授信地址的存储库。你应该将每个网关的IP地址和传输层协议插入这个数据库。这就使得来自网关的请求避免了标准摘要认证的进行。
PERMISSIONS模块还有些标准的许可和拒绝文件。我们这次不使用这些特性。为了不要日志中的这些信息,请将PERMISSIONS模块的config文件夹下的文件拷贝到/etc/openser文件夹下。
cp /usr/src/openser-1.2.2/modules/permissions/config/*
/etc/openser
在许可文件中,基于正则表达式来过滤请求是可能的,这可以改进环境的安全性。检查用例文件是不是符合正确的句法。
group.so模块用来检查用户的组成员资格。这个叫做ACL(Access
Control List)。你可以使用openserctl工具(如下)来添加,删除或是显示用户ACLs。
loadmodule “permissions.so”
loadmodule “group.so”
下面的第一行告诉模块到哪去寻找传递需要凭据的数据库。第二行提示模块使用数据库上的缓存(cache)访问以增加性能。
modparam("auth_db|permissions|uri_db|usrloc","db_url",
"mysql://openser:openserrw@localhost/openser")
modparam("permissions", "db_mode", 1)
当的代理服务器收到INVITE请求后,通常的行为是向UAC请求凭据。然而,PSTN网关通常并不对认证进行响应。因此,你需要采取特殊的处理过程。函数allow_trusted()将INVITE请求的源IP地址同数据库中授信表进行对比检查。如果符合,则接受之。如果不符合,则再请求凭据。
if(!allow_trusted()){
if
(!proxy_authorize("","subscriber")) {
proxy_challenge("","0");
exit;
} else if
(!check_from()) {`
sl_send_reply("403","Forbidden, use FROM=ID");
exit;
};
};
| 网关的IP地址插入数据库是很重要的 |
你可以使用诸如SerMyAdmin或是phpMyAdmin的工具来维护数据库。这样可比手动在MySQL的CLI(Command
line interface)操作容易的多。
在授信表中,插入的是网关的IP地址,传输层协议(udp,tcp,tls,any)和正则表达式。
下面是我们按照正则表达式进行通话的路由:
if (uri=~"^sip:[2-9][0-9]{6}@") {
if
(is_user_in("credentials","local")) {
route(4);
exit;
} else {
sl_send_reply("403", "No permissions for local
calls");
exit;
};
};
if (uri=~"^sip:1[2-9][0-9]{9}@") {
if
(is_user_in("credentials","ld")) {
route(4);
exit;
} else {
sl_send_reply("403", "No permissions for long
distance");
exit;
};
};
if (uri=~"^sip:011[0-9]*@") {
if
(is_user_in("credentials","int")) {
route(4);
exit;
} else {
sl_send_reply("403", "No permissions for
internat. calls");
exit;
};
};
本地通话(local call)使用数字7分辨并且以2到9之间的数开头(“^sip:[2-9][0-9]{6}@”)。长途号码符合这条正则
“^sip:1[2-9][0-9]{9}@”,号码以1开头,后面紧接这2-9之间的数,加起来一共9个数字,这样的号码被认为是长途号码。最后,国际长途号码以011+国家编码+地区编码+电话号码作为前缀。所有的情况下,脚本都会被抛到路由4(route
4)
| 将ACL数据插入数据库,对于脚本的正常工作很重要 |
你可以使用openserctl工具,SerMyAdmin或是phpMyAdmin来完成。
最后,我们让路由块4(routing
block 4)来处理PSTN的目的地。函数rewritehostport()用来改变URI的主机部分,也就是说当你使用t_relay()中继请求时,此请求将被发往网关。
route[4] {
##--
## PSTN gateway
handling
##--
rewritehostport("10.1.30.45");
route(1);
}
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/74590/showart_2023887.html |
|