免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1384 | 回复: 0
打印 上一主题 下一主题

使用OpenSER构建电话通信系统——第七章(1) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-08-10 23:19 |只看该作者 |倒序浏览

               
               
               
                注:以下文章如需转载,请注明所属作者,转载地址,谢谢!第七章:与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
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP