茅山之僵尸道长txt:使用OpenSER构建VoIP通话

来源:百度文库 编辑:中财网 时间:2024/04/29 22:10:04
使用OpenSER构建VoIP通话 使用OpenSER构建电话通信系统——第一章(1)
 
 
 
前言:openser已经成为opensips项目,但是本书的内容绝大部分是适合于学习相关知识的。所以还是针对原书原封不动的做的翻译。 使用OpenSER构建电话通信系统Building Telephony Systems with OpenSER 第一章:SIP介绍(Introduction to SIP)会话初始化协议是互联网工程任务组(IETF)制定的协议标准,在多个RFC(Request for Comments)文档中被进行了描述说明。RFC3261是最近的一个RFC,一般称它为SIP版本2。SIP是一个应用层的协议,用来建立,修改,终止会话(sessions)或是多媒体通话(multimedia calls)。这些会话可以会议(conferences),e-learning,网络电话和一些相似的应用。它是同HTTP协议相类似的文本协议并且被设计用来发起,保持,关闭用户之间的交互会话。目前SIP已经是VoIP领域被广泛使用的协议之一了,市场上几乎每一台IP电话都会支持它。本章结束的时候你将能够:l         描述SIP是什么l         描述SIP是干什么的l         描述SIP的框架l         解释SIP主要部件的意义l         理解并比较主要SIP消息l         描述INVITE和REGISTER请求消息头部的处理过程 在建立和关闭多媒体通话的过程中,SIP协议支持五种要素。l         用户定位(User location)l         用户参数协商(User parameters negotiation)l         用户可用性(User availability)l         通话建立(Call establishment)l         通话管理(Call management) SIP协议被设计成多媒体框架的一部分,而这种多媒体框架包括RVSP,RTP,RTSP还有SDP等其他协议。然而,SIP却并不依靠其他这些协议工作。 SIP基础(SIP Basics)SIP在工作的方式上与HTTP协议相类似。SIP的地址像是e-mail的地址。SIP代理中使用的一个比较有趣的特性就是“别名(alias)”,也就是说你可以有多个SIP地址,譬如:       johndoe@sipA.com       +554845678901@sipA.com       45678901@sipA.com在SIP的体系结构中,有多个用户代理和提供不同服务的服务器。SIP使用点对点(peer-to-peer)的分布模型来和服务器进行消息的交互。服务器只进行消息(signaling)的处理,而用户代理的客户端和服务端既可以处理消息也可以处理媒体。下面的图描述了这样的一个体系:
在SIP模型中,用户代理,通常是一台SIP话机与它的SIP代理进行交互,从上图可以看到,外呼代理(outgoing proxy)将使用INVITE消息向外发出通话请求。外呼代理将观察这通通话是否是被定向到外部的域名。然后它将向DNS服务器发出请求将目标域名解析为对应的IP地址。然后再将通话请求发送给DomainB对应的SIP代理。呼入代理(incoming proxy)将在地址列表(location table)中查询agentB的IP地址。如果在地址列表这个地址与之前在注册过程中的IP地址对应,那么呼入代理就可以定位这个地址了。现在就可以使用这个地址将通话请求发送到agentB了。agentB收到这个SIP消息后(INVITE),就拥有了可以与agentA建立RTP会话(通常是音频方面的会话)所需要的信息。使用BYE消息可以终止这个会话。SIP代理在VoIP提供者里的作用/上下文(SIP Proxy in the Context of a VoIP Provider)通常VoIP服务的提供者们并不会实现像上幅图那样的纯粹的SIP四边形结构,他们不会允许你向一个外部的域名发送通话请求,因为如果这样,那么将影响他们的收入(revenue stream)。取而代之的是一个接近三角形的SIP网络结构。(如下图所示)
 SIP工作原理(SIP Operation Theory)在上图中,你可以看到SIP体系结构中的主要的构成部件。所有的SIP消息都会经过SIP代理服务器。另一方面,由RTP协议承载的媒体流则是从一端直接流向另一端。我们将在下面的列表中简要的对其中的一些构成部件进行解释。
l        用户代理客户端(UAC user agent client)——发起SIP消息的客户端或终端l        用户代理服务端(UAS user agent server)——对接收到的从用户代理客户端发起的SIP消息进行相应的服务端。l        用户代理(UA user agent)——SIP终端(IP电话,电话适配器(ATA),软电话(softphone))l        代理服务器(Proxy Server)——从用户代理接收请求,并且如果指定的被请求的终端不在其域中时,会将请求发送给另外的SIP代理。l        重定向服务器(Redirect Server)——接收请求,但是不直接发送给被叫用户,而是向主叫方发送目的地址的信息。l        定位服务器(Location Server)——向代理服务器和重定向服务器提供被叫者的联系地址。通常,物理上,代理服务器,重定向服务器和定位服务器存在于同一台电脑和软件中。SIP注册过程(SIP Registration Process) 
  SIP协议中使用了一个构件叫做注册服务器。它不仅能够接收REGISTER消息请求,还能够将收到的消息包中的信息保存到管理对应域名的定位服务器上面。SIP协议具有发现能力;换句话说,就是如果一个用户要与另外一个用户开始会话,那么SIP协议必须要发现这个用户能够到达的主机存在。由于定位服务器可以收到请求消息并找到向什么地方发送,所以这个发现过程由定位服务器来完成。而这则是基于管理每个域的定位服务器维护着一个定位数据库的事实来实现的。注册服务器不仅可以接收客户端的IP地址,还能够接收其他类型的消息。比如,能够收到服务器上面的CPL(Call Processing Language)脚本。在一台话机能够接收一通通话之前,它需要在定位数据库中有注册信息。在这个数据库中我们要拥有所有电话的各自的相关的IP地址。在我们的例子中,你将看到SIP用户8590@voffice.com.br注册到200.180.1.1上面的过程。RFC3665定义实现了一个最小的功能集合,这是使得SIP进行IP网络交互时的最好实践。下面的图就是根据RFC3665中的描述所画出的注册事务处理流程。
 按照rfc3665中所说,与注册一个用户代理的过程相关的有五个基本的流程,如下所述:1.    一个新的成功的注册(A successful new registration)——用户代理在发送Register请求后,将收到认证过程的挑战。我们将在阐述验证过程的章节中看到这个过程的细节。 2.    联系列表的更新(An update of the contact list)——由于不再是新的注册,消息中已经包含了摘要(digest),那么不会返回401消息。为了改变联系列表,用户代理仅仅需要发送一条在CONTACT头中带有新的联系信息的注册信息即可。 3.    请求获得当前的联系列表——在这种情况下,用户代理将把发送消息中的CONTACT头置空,表明用户希望向服务器询问当前的联系列表。在回复的200OK消息中,SIP服务器将把当前的联系列表放在其CONTACT的头中。 4.    取消注册(Cancellation of a registration)——用户代理在发送的消息中将EXPIRES头置成0,并且将CONTACT头设置为“*”表示将此过程应用到所有存在的联系信息。 5.    不成功的注册(Unsuccessful Registration)——用户代理客户端(UAC)发送一条Register请求消息,收到一条“401 Unauthorized”消息,事实上,这个过程同成功注册过程相同。但是接下来,它进行哈希运算尝试进行认证。而服务器检测到的是一个无效的密码,继续发送“401 Unauthorized”消息。这个过程一直重复直到重复次数超过在UAC设置的最大值。  作为SIP代理运转的服务器(Server Operating as a SIP Proxy)在SIP代理模式下,所有的IP消息都要经过SIP代理。这种行为在向诸如计费(billing)的过程中帮助很大,而且迄今为止,这也是一种最普遍的选择。但是它的缺点就是在会话建立过程中的所有的SIP交互中,服务器造成的额外开销也是客观的。要记住的是,即使服务器作为SIP代理在工作时,RTP包也总是直接从一端传送到另一端,而不会经过服务器。
 作为SIP重定向运转的服务器(Server Operating as a SIP Redirect)SIP代理可以运行在SIP重定向模式。在这种模式下,SIP服务器的处理量是相当巨大的,因为它不需要保持事务处理的状态。在对INVITE消息进行初始化后,仅仅向UAC回复一条“302 Moved Temporarily”消息就可以离开SIP对话(dialog)了。在这种模式下的SIP代理,即使只是利用非常少的资源也可以每小时传送上百万的通话。当你需要的规模很大并且不需要对通话计费的情况下,这种模式通常会被使用。  基本消息(Basic Messages)
在SIP环境中会被发送的基本的消息有:
大多数时间内,你会使用到REGISTER,INVITE,BYE还有CANCEL。而另外一些消息会被用在其他的特性当中。举例来说,INFO被用在DTMF relay和通话中消息信息(mid-call signaling information)。PUBLISH,NOTIFY和SUBSCRIBE则用来支持列席系统(presence systems)。REFER用来进行通话转接(call transfer)而MESSAGE则应用于一些聊天应用程序中。更新的消息也会随着协议标准化进程而随之出现。像HTTP协议一样,这些消息的响应也会以文本形式出现。其中一些最终的响应消息被列在下图当中:
SIP对话流程(SIP Dialog Flow)
这一节将通过一个简单的例子来介绍一些基本的SIP操作。先让我们来诊视下图展示的两个用户代理之间的消息顺序。你可以看到伴随这RFC3665描述的会话建立过程还有几个其它的流程。
我们在这些消息上标上了序号。在这个例子中用户A使用IP电话向网络上的另外一台IP电话发出通话请求。为了完成通话,使用了两个SIP代理。用户A使用称为SIP URI的SIP标识向用户发出通话。URI就像是一个电子邮件地址,比如sip:userA@sip.com。一种可靠安全的SIP URI也可以被使用,譬如sips:userA@sip.com。使用SIPS建立的通话将会在主叫和被叫之间使用安全可靠的传输机制(TLS-Transport Layer Security)事务(transaction)的建立始于用户A向用户B发送INVITE请求消息。INVITE请求中包含一些特定头域。这些头域被称之为属性,为消息提供了额外的一些信息。包括唯一的标识,目的地,还有关于会话(session)的信息。
 第一行是消息的方法名(the method name)。接下来是列出的头域。这个例子包含了所需要的头的最小集合。我们将在下面简要的描述这些头域。l        VIA:它包含了用户A等待发出请求对应响应的所在地址。还包含了一个叫做“branch”的参数,这个参数用来唯一的标识这个事务(transaction)。VIA头将最近从“SIP跳”(SIP hop)定义为IP,传输,和指定事务参数(The VIA header defines the last SIP hop as IP,transport,and transaction-specific parameters)。VIA专门用来路由响应消息。请求经过的每一个代理都会增加一个VIA头。而对于响应消息而言,相对于再次向定位服务器或是DNS服务器进行定位请求,使用VIA头进行路由将更加容易。l        TO :它包含了名字(显示名(display name))和最初选择的目的地的SIP URI(这里是sip:userB@sip.com)。TO头域不被用来路由消息包。l        FROM:它包含了名字和表明主叫ID(caller ID)的SIP URI(这里是sip:userA@sip.com)。这个头域有一个tag参数,而这个参数包含了被IP电话添加进URI的一个随机字符串。是被用来进行辨识唯一性的。tag参数被用在TO和FROM头域中。作为一种普遍的机制用来标识对话(dialog),对话是CALL-ID和两个tag的结合,而这两个tag分别来自参与对话的双方。Tags在并行派生(parallel forking)中作用显著。l        CALL -ID:它包含了一个作为这通通话全局性的唯一的标识,而这个唯一标识是有一个随机字符串,来自IP电话的主机名或是IP地址结合而成的。TO,FROM的tag和CALL-ID的结合完整的定义了一个端到端的SIP关系,这种关系就是我们所知道的SIP对话(SIP dialog)l        CSEQ:CSEQ或者称之为命令序列(command sequence)包含了一个整数和一个方法名。CSEQ数对于每一个在SIP对话中的新请求都会递增,是一个传统的序列数。l        CONTACT:它包含一个代表直接路由可以联系到用户A的SIP URI,通常是有一个用户名和FQDN(fully qualified domain name)。有时候域名没有被注册,所以,IP地址也是允许使用的。VIA头告诉其他的组件向什么地方发送响应消息,而CONTACT则告诉其他组件向什么地方发送将来的请求消息。l        MAX-FORWARDS:它被用来限制请求在到达最终目的地的路径中被允许的最大跳数(hops)。由一个整数构成,而这个整数在每一跳中将会递减。l        CONTENT-TYPE:它包含了对内容消息的描述。l        CONTENT-LENGTH:它用来告知内容消息的字节数。会话的一些细节,像媒体类型和编码方式并不是使用SIP进行描述的。而是使用叫做会话描述协议(SDP RFC2327)来进行描述。SDP消息由SIP消息承载,就像是一封电子邮件的附件一样。 过程如下: 话机开始并不知道用户B和负责域B的服务器的位置。因此,它向负责sipA域的服务器发送INVITE消息请求。发送地址在用户A的话机中进行设置或通过DHCP发现。服务器sipA.com也就是我们知道的域sipA.com的SIP代理服务器。 1. 在这个例子中,代理服务器收到INVITE请求消息并发送“100 trying”响应消息给用户A,表明代理服务器已经收到了INVITE消息并正在转发这个请求。SIP的响应消息使用一个三个数字组成的数字码和一条描述语句说明响应的类型。并拥有和INVITE请求一样的TO,FROM,CALL-ID和CSEQ等头域,以及VIA和其“branch”参数。这就使得用户A的话机同发出的INVITE请求联系在一起。 2. 代理A定位代理B的方法是向DNS服务器(SRV 记录)进行查询以找到负责sipB的SIP域的服务器地址并将INVITE请求转发给它。在向代理B(译者注:这里作者写的是proxyA,但是应该是B)发送INVITE消息前,代理A将其自己的地址通过VIA头添加进INVITE,这就使得用户A的话机同INVITE请求的响应消息联系在了一起。 3. 代理B收到INVITE请求,返回“100 Trying”消息响应,表明其正在处理这个请求。 4. 代理B查询自己的位置数据库以找到用户B的地址,然后将自己的地址也通过VIA头域添加进INVITE消息发送给用户B的IP地址。 5. 用户B的话机收到INVITE消息后开始振铃。话机为了要表明这种情况(振铃),发送回“180 Ringing”响应消息。 6. 这个消息以相反的方向路由通过那两个代理服务器。每一个代理利用VIA头域来决定向哪里发送响应消息并从顶部将其自己的VIA头去除。结果就是,180 Ringing消息不需要任何的DNS查询,不需要定位服务的响应,也不需要任何的状态处理就能够返回到用户那里。这样的话,每一个代理服务器都能够看到由INVITE开始的所有消息。 7. 当用户A的话机收到“180 Ringing” 响应消息后开始“回铃”,表明另一端的用户正在振铃。一些话机是通过显示一些信息进行表示的。 8. 在这个例子中,用户B对对方发起的通话进行了响应。当用户B响应时,话机发送”200 Ok“响应消息以表明通话被接起。“200 Ok”的消息体中包含了会话的描述信息,这些信息包括指定了编码方式,端口号,以及从属于会话的所有事情。作这项工作的就是SDP协议。结果就是,在从A到B(INVITE)和从B到A(200 OK)的两个阶段,双方交换了一些信息,以一种简单的“请求/响应”的模式协商了在这通通话中所需的资源和所需要的能力要求。如果用户B不想得到这通通话或是此刻处于忙线中,200 Ok将不会发出,取代它的是描述这种状况(这里是486 Busy Here)的消息。
第一行是响应码和描述信息(OK)。接下来是头域行。VIA,TO,FROM,CALL-ID和CSEQ是从INVITE请求中拷贝的。有三个VIA头,一个是用户A添加的,另一个是代理A添加的,最后一个则是代理B添加的。用户B的SIP话机在对话的双方加入了一个TAG参数,这个参数在这通通话的以后的请求和响应消息中都将出现。CONTACT头域中包含了URI信息,这个URI信息是用户B能够直接被联系到他们自己的IP话机的地址。CONTENT-TYPE和CONTENT-LENGTH头域先给出了关于SDP头的一些信息。而SDP头则包含了用来建立RTP会话的媒体相关的参数。1. 在这个例子中,“200 Ok”消息通过两个代理服务器被送回给用户A,之后用华A的话机停止“回铃”表明通话被接起。 2. 最后用户A向用户B的话机发送ACK消息确认收到了“200 Ok”消息。在这里,ACK避开了两个代理服务器直接发送给用户B。ACK是SIP中唯一不需要进行响应的消息请求。两端在INVITE的过程中从CONTACT消息中了解双方的地址信息。也结束了INVITE/200 OK/ACK的过程,这个过程也就是我们所熟知的SIP三次握手。 3. 这个时候两个用户之间开始进行会话,他们以用SDP协议协商好的方式来发送媒体包。通常这些包是端对端进行传送的。在会话中,通话方可以通过发送一个新的INVITE请求来改变会话的一些特性。这叫做re-invite。如果re-invite不被接受,那么“488 Not Acceptable Here”响应就会被发出,但是会话不会因此而失败。 4. 要结束会话的时候,用户B产生BYE消息来中断通话。这个消息绕过两个代理服务器直接路由回用户A的软电话上。 5. 用户A发出“200 OK”响应消息以确认收到了BYE消息请求,从而结束会话。这里,不会发出ACK。ACK只在INVITE请求过程中出现。 有些情况下,在整个会话过程中,对于代理服务器来说,能够待在消息传输的中间位置来观察两端的所有消息交互是很重要的。如果代理服务器想在INVITE请求初始化完成后还待在此路径中,可以在请求消息中添加RECORD-ROUTE头。用户B的话机得到了这个消息,之后在其消息中也会带有这个头,并且会将消息发送回代理。记录路由(Record Routing)在大多数的方案中都会被使用。 REGISTER请求是代理B用来定位用户B的方法。当话机初始化的时候或是在通常的时间间隔中,软电话B向在域sipB中的一个服务器(SIP REGISTRAR)发送REGISTER请求。注册服务器(REGISTER)将URI与一个IP地址联系在一起,这种绑定被存储在定位服务器上面的数据库里。通常,注册服务器,定位服务器,和代理服务器在同一台物理机器上,并使用相同的软件。OpenSER就能够扮演这三种角色。一个URI只能够在一个特定的时间内由一个单独的机器注册。 SIP事务和对话(SIP Transactions and Dialogs)理解“事务”(transaction)和“对话”(dialog)的区别是非常重要的。事务发生在一个用户代理客户端和另一个用户代理服务端之间,包含从第一请求到最后一个响应的所有消息。中间的阶段性的响应消息可以是以1开头的三位数字码(比如180 Ringing),最终的响应消息则是以2开头的三位数字码(比如 200 OK)。事务包括的范围由SIP消息中VIA头形成“堆栈”来决定。因此,用户代理在初始化invite后才不需要依靠DNS或位置表进行消息路由。对话(Dialog)通常是从INVITE事务开始,由BYE事务结束。一个对话由CALL-ID头唯一标识。TO tag,FROM tag和CALL-ID的结合来完整的定义。按照rfc3665的描述,有11个基本的会话建立流程。其列出的并不一定是完整的,但是覆盖了最好的例子。前两个流程在这一章节中进行了阐述——“成功建立会话Successful Session Establishment”和“通过两个代理建立会话Session Establishment Through Two Proxies”。其中的一些流程的描述你将在其他阐述呼叫前传(call forwarding)(譬如:“不接听导致没能成功建立Unsuccessful with no Answer”和“忙音导致建立失败Unsuccessful Busy”)的章节中看到。 RTP协议(The RTP Protocol)
译者注:应为Real Time Transport Protocol实时传输协议是负责诸如音频和视频等数据的实时传输的。它标准化于RFC3550。使用UDP协议进行传输承载。为了能够被实时传输,音频或视频必须经过一定的编码打包。最基本的,该协议允许使用如下的一些特性对进出的数据包的媒体传输的时间和内容要求进行指定:l        序号l        时间戳l        无重传机制的包的前转l        源识别l        内容识别l        同步与RTP相伴的协议叫做RTCP(Real Time Control Protocol),被用作对RTP包进行监控。它可以度量延迟和抖动。 编码(Codecs)
RTP协议描述的内容通常会由一种编码方式进行编码。每一种编码方式都有一种指定的用途。一些有压缩算法而另外一些则不需要。比较普遍的是使用不需要压缩的G.711编码方式。一个信道64Kbps的带宽要求需要一个高速的网络,通常在局域网(LANs)中比较常见。但是在广域网(WAN)中对于一个单独的声音信道来说,64Kbps的带宽购买起来比较昂贵。这时候诸如G.729和GSM等可以将声音包压缩至8Kbps的编码方法则可以节省很多的带宽。由Global IP sound公司发明的iLBC编码方式则可以掩盖网络中由丢包造成的影响。在丢包率带到7%的情况下,使用iLBC仍然能够维持一个比较好的声音质量。所以对于你的VoIP提供商支持的编码方式你必须进行明智的选择。DTMF-Relay
在一些情况下,RTP协议被用来承载诸如DTMF的信号信息。RFC2833中描述了一种方法,这种方法就是将DTMF作为RTP协议中的命名事件(named events)进行传输。在用户代理客户端和用户代理服务端之间使用同一种方法进行DTMF传输是非常重要的。实时控制协议(Real Time Control Protocol-RTCP)
RTCP可以对接受的质量进行反馈。它为RTP媒体流提供带外控制信息。诸如抖动(Jitter),往返时延(RTT-Round Trip Time),传输延迟(latency)和丢包等的数据可以使用RTCP进行搜集。RTCP通常用来对声音质量进行报告。会话描述协议(Session Description Protocol-SDP)
SDP协议在RFC4566中被进行了详细的描述。它是在用户代理之间进行会话参数协商之用的。媒体的细节,传输的地址还有其他与媒体相关的一些信息都使用SDP协议在用户代理之间进行交互。通常,INVTIE消息中包含了SDP“供给消息”,而200 Ok则包含了“回答消息”。这些消息会在下面的图中进行展示。在下图中,你可以观察到GSM编码方式被“供给”,但是另外一台话机却并不支持该编码,那么然后它将使用它本身支持的编码方式进行“回答”,在这个例子中它支持的是G.711 ulaw(PCMU)和G.729编码方式。 会话的“rtpmap:101”就是在RFC2833中描述的DTMF-relay信息。INVITE (SDP Offer)
200 OK(SDP Answer)
 SIP协议与OSI七层模型(The SIP Protocol and the OSI Model)
理解声音相关协议的每一个协议对于OSI模型是属于哪一层也是相当重要的。
VoIP服务提供商的整体框图(The VoIP Provider “Big Picture”)在我们开始深入的挖掘SIP代理之前,了解VoIP提供商的解决方案中的所有部件是非常重要的。服务提供者通常由多个服务器(servers)和多个服务(services)组成。这里说的服务可以根据规模的大小来决定是被安装在一台单独的服务器上还是安装在多台机器上面。本书将在前几个章节中按照这张图从左到右来描述每一个部件。所有的章节中都将使用这张图来帮助你来了解你所处的位置何在。SIP 代理(SIP Proxy)
SIP代理是我们解决方案中的核心部件。用来负责用户的注册和维护位置数据库(映射IP和SIP地址)。所有的SIP路由和消息都会被SIP代理处理,它也负责一些用户端级别的服务譬如呼叫前转,白/黑名单,快速拨号等。这个部件从不处理媒体(RTP包),所有与媒体相关的包都从用户代理客户端,服务器和PSDN网关直接路由。用户,管理和供给入口(User,Administration,and Provisioning Portal)
用户管理和供给入口是一个重要的部件。在入口当中,用户订阅服务并且应该能够购买信用量,修改密码和验证他/她的账号。另一方面,管理者应该能够删除用户,改变用户信用级别,承认,删除权限。对于管理员来说,“供给(Provisioning)”过程使得用户代理如IP电话,模拟话机适配器还有软电话的自动安装过程更加容易。PSDN 网关(PSDN网关)
为了能和公共的交换电话网络交互,PSTN网关是需要的。通常,这个网关是使用E1或T1中继线的PSTN的接口。在这个领域中使用最广泛的是来自Cisco,AudoCodes和Quintum公司的网关。Asterisk也占据了一定的市场份额,因为它的每个端口的价格要比竞争对手们便宜75%。如何评估一个网关的好坏,要检查它对SIP协议扩展的支持程度,譬如对RFC3515(REFER),RFC3891(Replaces)还有RFC3892(Referred by)。这些协议使得我们能够在SIP代理背后进行呼叫转移;如果网关中不支持他们,进行呼叫转移是不太可能的事。媒体服务器(Media Server)
因为SIP代理从不处理媒体,所以如IVRs,语音邮箱,电话会议等和媒体相关的服务要能够在媒体服务中得到实现。由iptel开发的SEMS SIP Express媒体服务器具有一些很好的特性,如conference,voicemail和announcements等。我们要再一次提到Asterisk,因为它也能够用来提供这些服务。穿透NAT的媒体代理或RTP代理(Media Proxy or RTP Proxy for Nat Traversal)
任何SIP服务提供者必须要为他的客户提供NAT穿透的解决方案。媒体代理就是一座帮助处在对称式防火墙(symmetric firewall)之后的用户能够访问SIP服务提供者进行RTP连接的桥梁。如果没有了这些代理,服务提供者可能会流失35%的用户。你可以使用这些部件来实现一个通用的NAT穿透技术。媒体代理还可以帮助你进行记帐纠错,比如,因为某种原因,没有收到BYE消息而导致了SIP对话没有结束,结果记帐发生了错误等情况。RADIUS记账(RADIUS Accounting)
拥有一台安装了RADIUS的服务器是对通话进行记账的基本条件。SIP服务提供者对账单记录是最关心的。OpenSER可以被配置将一些记账信息发送到一台RADIUS服务器(比如Radiator或FreeRADIUS)上。SIP通话账单也可以被记录到数据库中。但是,这样将产生两条记录,而这两条记录需要手工进行核对。用CDRTool计费(CDRTool Rating)
RADIUS服务器可以记录关于通话时长的一些信息,但是却不能记录每通通话的资费信息。将资费信息应用到通话上是需要技巧的。这里我们使用AG项目组(cdrtool.agprojects.com)开发的被称为CDRTool的GPL工具。它负责将资费应用到通话上面。监控工具(Monitoring Tools)
最后我们需要一些监控,故障检修和测试的工具来帮助我们定位并解决在SIP服务器上发生的一些问题。首当其冲的当然是协议分析工具,在余下的章节中我们将能够看到如何使用ngrep,ethereal和tethereal。OpenSER有一个被称为SIP trace的模块,我们也将使用到。哪儿能够找到更多信息
SIP协议最好的参考资料就是RFC3261。阅读RFC确实有点沉闷(不过如果你有点失眠这确实是一种好办法)。你可以在http://www.ietf.org/rfc/rfc3261.txt上找到它。也可以在哥伦比亚大学的网站上找到好的SIP教程如:http://www.cs.columbia.edu/~coms6181/slides/11/sip_long.pdf。也可以在http://www.cs.columbia.edu/sip/上找到许多关于SIP的信息。一个非常好的教程在iptel的网站上:http://www.iptel.org/files/sip_tutorial.pdf。下面是一个叫做SIP应用者(SIP implementors)的邮件列表,你可以在上面就SIP的知识进行提问:https://lists.cs.columbia.edu/mailman/listinfo/sip-implementors。概要(Summary)
这一章你已经学到了什么是SIP协议以及SIP协议的功能。也知道了诸如SIP代理,SIP注册服务器,用户代理客户端,用户代理服务端,PSTN网关等SIP协议中的部件。还看到了SIP的体系结构,主要的消息和处理过程。以及去什么地方可以找到更多相关的信息。
  第二章:SIP快速路由器(The SIP Express Router)上一章节中我们讨论了VoIP提供商的“整体框图”(the big picture)。通常,一个VoIP提供商由几个部件构成。这些部件根据规模的大小或驻留在同一台机器上或分布于多个机器上面。其中的一个部件就是SIP代理,在我们的例子中代理服务器运行的是OpenSER软件。就像它的名字一样,描述SER的最好的东西就是SIP路由器(a SIP Router)。它能够对SIP的头域进行操作并能够以极高的速度对SIP包进行路由。第三方的模块给了SER极高的灵活性来完成一些原本没有的功能,诸如NAT穿透,IMS,负载均衡等其他功能。在这一章,我们将向你展示SIP快速路由器的能力和框架。在本章的末尾,你将能够:l        解释SIP快速路由器(SER)到底是什么l        在两个开源项目SER和OpenSER中作出选择l        描述对它们的使用方案l        辨别出openser.cfg文件中的不同的区段l        描述SIP消息的处理过程l        辨别松散路由和严格路由l        辨别SIP和SDP我们在哪儿?(Where Are We?)VoIP提供商的解决有很多部件。为了能够对各个部件的联系保持一个整体的把握,我们将在每一个章节中展示下面这张图片。在这一章中,我们的主要的讨论围绕SIP代理部件来进行。
 SIP快速路由器是什么?(What is the SIP Express Router?)SIP快速路由器是一套兼容IETF RFC3261 sip协议的开源的SIP代理服务器。它的目的是兼容并包尽可能多的应用。
只需要起一个单独的服务,SER就可以以它“短小精悍”的特点最快速的对请求进行前转,并能够处理成千上万的用户。它被大量的VoIP的提供商所使用,也被使用在处理能力相对较弱的嵌入式IP PBX上面。它和其他一些设备的互操作性也使得它成为实际的标准。用哪个软件,SER还是OpenSER?(What software to use,SER or OpenSER?)SER最开始是由德国柏林的FhG Fokus研究院所开发,发布的时候是遵从GPL许可的。它的核心开发人员是Andrei Pelinescu-Onciul,Bogdan-Andrei Iancu,Daniel Constantin Mierla,Jan Janak,还有Jiri Kuthan。之后,其他的一些人也为此作出了贡献,他们是Juha Heinamen(RADIUS, ENUM, DOMAIN, URI), Greg Fausak (POSTGRES), Maxim Sobolev(NATHELPER), Adrian Georgescu (MEDIAPROXY), Elena Ramona Modroiu(XLOG, DIAMETER, AVPOPS, SPEEDDIAL), Miklos Tirpak (Permissions),等其他人。OpenSER是SER项目的衍生品。2004年FhG Fokus进行了SER项目的副产品的开发创立了iptel.org。2005IPtel的商业变种被卖给了TEKELEC。核心开发团队一分为二。他们中的三位成员去了iptel.org(Andrei Pelinescu-Onciul, Jan Janak, and Jiri Kuthan),另外两名成员则离开FhG,创建了一家叫做Voice-System的公司,他们也是2005年开始的OpenSER项目的主要维护人员。 这本书始于2005后半年,基于SER项目。那个时候,我对使用SER进行NAT穿透的解决方案很感兴趣。Asterisk的可伸缩性对于(host??)SIP提供商不是足够的好,所以我转而投向SER的研究中。它的文档真的很难懂,于是乎我开始撰写自己的文档来对SIP提供商的管理者们进行培训。在电子书完成后,我发现SER项目已经停止维护了。大部分的代码还停留在2003年。经过一点研究我找到了OpenSER项目。它似乎更有活力,它有更加新的模块,更频繁发行的版本。我于是在非常短的时间内将所有的东西转向了OpenSER。我不想陷入SER VS OpenSER的争论中。这样的争论是毫无意义的。现在的事实就是,这本书是为OpenSER而写的。OpenSER为第三方应用程序提供了一个灵活的可插入模型。应用程序可以被很容易的创建并插入服务器中。这种可插入模型给予了一些新的模块的开发,譬如                 RADIUS, DIAMETER, ENUM,PRESENCE 还有SMS。更新的模块每个月都会被添加进来。你可以在http://www.openser.org/docs/modules/1.2.x上查看OpenSER 1.2.x支持的模块。它的高效和健壮使得OpenSER能够被用来为数百万的用户提供服务。在最近的2007 3月14号的性能报告中,OpenSER 1.2.x能够处理相当于400万用户的注册请求。TM(事务模块)能够每小时处理2800万通通话。完整的报告可以在http://www.openser.org/docs/openser-performance-tests/上面找到。OpenSER不仅仅被服务提供商所使用。它还可以构造SIP应用。目前有一些SIP防火墙(SIP firewall),会话边缘处理器(Session Border Controller),和负载均衡的代码都是从OpenSER项目中借鉴的。LINKSYS选择OpenSER作为它的一款PBX的平台,可能就是因为它的资源耗用少但性能高的特性吧。OpenSER灵活,移植性好并且可以扩展。用ANSI C开发的它能够被轻易的移植到任何平台上。使用C语言能够很容易的创建出新的模块用于扩展。近来,编程中的一些新的层次被添加了进来。使用呼叫处理语言(Call Processing Language)简化路由脚本,使用Perl实时的对请求进行处理都成为可能。WeSIP是一种应用程序的编程接口,它允许你使用Java和Servlets创建SIP应用服务对OpenSER服务器进行扩展。可以在www.wesip.com上查看WeSIP。使用方案(Usage Scenarios)OpenSER主要用来作为SIP代理和注册服务器。但是,它也可以被用于其他的一些应用当中,比如代理分发器(Proxy dispatches),Jabber网关(Jabber Gateway),与媒体网关和RTP代理合作来进行NAT穿透等。支持IPv4和IPv6并且能够支持多域。OpenSER可以被应用在Linux,Solaris,还有FreeBSD等平台。OpenSER本身的创建是为了当作SIP代理服务器使用的。然而,利用它的新的模块,如今,OpenSER能够被用在如下的一些方案中:Modules
 Functionality
  
DISPATCHER,PATH
 Load balancing
  
MEDIAPROXY,RTPPROXY,NATHELPER
 Nat Traversal
  
PRESENCE
 Presence Server
  
IMC XMPP
 Instant Messaging
    让我们看看OpenSER的大多的使用场景吧。在所有这些场景中,OpenSER就像胶水一样,将所有的SIP部件粘在一起。l        VoIP服务提供商l        即时消息服务提供商l        SIP负载均衡l        嵌入式IP PBXl        NAT穿透l        SIP.EDUOpenSER 框架(OpenSER Architecture)
 核心和模块(Core and Modules)OpenSER建造在一套核心之上,这套核心负责基本的功能实现以及对SIP消息进行处理。模块则负责OpenSER的大半的功能实现。模块和在脚本中使用的命令和参数一起将他们的功能性曝露在OpenSER当中。在一个叫做openser.cfg的文件中我们对OpenSER进行配置。这个配置文件控制着哪个模块被加载以及他们对应的参数。所有的SIP流程也都在此文件中定义的一些流程块中被控制。Openser.cfg是OpenSER的主要的配置文件。Openser.cfg文件中的各个区段(Sections of the File openser.cfg)Openser.cfg文件有七个区段:l        全局定义(Global definitions):文件的这一部分包含了OpenSER的几个工作参数,包括SIP服务的监听ip端口对和debug等级。l        模块(Modules):包含了外部库的列表,这些外部库是核心所没有的但却是能够展现其功能的。模块的加载使用loadmodule。l        模块配置(Modules configuratio):模块有一些参数是需要被合适的设置的。这些参数可以使用modparam(modulename, parametername,parametervale)进行配置。l        主路由块(Main routing block):主路由块是进行SIP消息处理的开始之处。它控制着所有收到的消息的处理。l        次要路由块(Secondary routing blocks):管理员可以使用route()命令来定义新的路由块。这些路由块就像是OpenSER脚本中的子程序一样。l        处理响应路由块(Reply routing blocks):响应路由块白被用来处理响应消息,通常是200 ok。l        处理出错路由块(Failure routing blocks):处理出错路由块用来处理一些出错情况如线路繁忙(busy)或是超时(timeout)。注:这个文件的细节将在4,5,6,7,8,9章节中详细进行描述。会话,对话和事务(Sessions,Dialogs,and Transactions)理解一些在OpenSER处理过程中使用的SIP概念是很重要的:l        SIP事务(SIP transaction):包括一条sip消息或任何重发的和对他们的直接响应消息(如,REGISTER和200 OK)l        SIP对话(SIP dialog):两个SIP实体之间存在一段时间的关系。如,两个UAC之间由INVITE消息到BYE消息这段时间建立的对话)l        SIP会话(SIP session):在两个SIP实体之间的一通媒体流(音频/视频/文本)openser.cfg消息处理openser.cfg是为了处理收到的sip消息而执行的一段脚本。例如:如果用户A想要和用户B进行通话,它就要向B发送INVITE消息。这个消息在主路由块中被处理。这个处理过程一直要延续到它找到t_relay()(前转)或是s1_send_reply(发送出错信息)或是最终在块的末尾使用exit()命令丢弃该消息。billing SIP代理——期望的行为(SIP Proxy—Expected Behavior)按照RFC3261中描述的SIP代理的基本的处理过程是非常重要的。如果不能很好的理解,将很难去配置代理服务器。每一个代理在将请求消息发送到下一个部件时会进行路由抉择,并对请求消息作些修改。响应消息将沿着请求消息走的路线原路路由回同样的一组代理。代理服务器既可以运作在有状态模式下也可以以无状态的模式运行。当SIP代理服务器只是被当作一个简单的SIP包前转器(forwarder)工作时,它只是按照请求消息的要求将消息包前转到一个单独的部件上。无状态模式工作的代理会丢弃它所前转的消息的任何信息。而这个特性限制则限制了对错误的处理和对费用的记录。如果OpenSER知道200 Ok是和一个特定的INVITE相对应,那么我们就说它此时工作在有状态模式下。这意味和你现在可以在onreply_route()块中来对响应消息进行管理。而无状态下的消息处理过程不会有上下文的处理方式。无状态处理过程通常被用在类似负载均衡的应用中;在脚本中使用forward()命令来处理。当你需要更加复杂的资源如计费,呼叫前转,voicemail等时,有状态处理方式是你所需要的。每个事务都将在内存中被维护,并且出错信息,响应信息和重传信息都将和这些事务有所联系。有状态事务由TM(transaction module)处理,通常使用t_relay()命令。一个经常会被误解的概念是:所谓处理过程的有状态指的是事务而不是对话。因此,一条INVITE请求消息的有状态处理过程的结束是到收到200 OK响应为止,而不是收到BYE。状态操作(Stateful Operation)
这只是对有状态操作的简单描述。你会在RFC3261中找到更加完整和详尽的描述。openser.cfg和上图有些相似的地方。有些过程是手工完成的,如检查Max-forwards头域,而其他一些过程则在一条命令中即可完成。更好的说明就是,当你调用t_relay()时,所有的就像描述中的前转请求处理过程都将自动完成。当处在有状态模式下是,代理只是一个简单的SIP事务处理器而且下面的这些处理步骤都是需要的:l        验证请求l        预处理路由信息l        决定请求的目的地l        前转请求到达目标l        处理所有响应消息有状态代理为每一个得到的请求创建一个新的服务器事务(server transaction)。该请求的任何重传都会被该服务器事务所处理。举个例子:对于每一个经过我们SIP代理服务器的请求,我们都会:第一步:请求验证l        检查消息大小,避免缓存溢出l        检查Max-forwards头域以检测出是否发生回环(loops)第二步:路由信息的预处理l        如果有record-route头,处理之第三步:决定请求的目的地l        目的地在定位数据库中么?(对于注册用户来说)l        可以路由到目的地么?(网关目的地)l        能够到达外部的域么?(外部地址)第四步:请求前转l        调用t_relay()函数,OpenSER将在有状态的模式下处理所有的工作任务。第五步:响应消息的处理过程l        通常这个过程会被OpenSER自动完成。有时候你可以使用onreply_route[]区段对一些响应作出处理。譬如:在“忙则呼叫前转”(call forward on busy)的情形中,我们可以使用486响应消息将通话转向voicemail服务器。严格路由和松散路由之间的区别(Differences between Strict Routing and Loose Routing)在路由SIP消息的过程中,有松散和严格两种不同的方法。松散路由是SIP版本2中的新方法。当使用松散路由时,R-URI从来都不会被改变,这种方法和之前的旧方法保持向后兼容。(严格路由RFC2543)严格路由的问题是开始对话前的初始化请求消息时指定所有的代理集合的过程。这种处理过程抛弃了包含在得到的R-URI中的信息。带有带外代理(outbound-proxy)的UA的行为是不确定的。如果其中的一个部件发生错误那么整个系统都将会出错。
 解决办法就是松散路由。这种方法将目标从路由信息中分离。允许每一个目的地来路由消息包,并且有机制能够保证和严格路由向后兼容。参数;lr的使用就是对松散路由的支持象征。
当SIP服务器收到一个消息,它可以决定它自己是否处在中间位置。也就是说,如果SIP服务器不愿意待在中间位置,那么它就将信息传递给用户代理的UA让他们能够能够连接起来。然后消息就将在两个用户代理间进行处理。而如果它想要待在中间,那么就应该使用函数record_route()插入ROUTE头。理解SIP和RTP(Understanding SIP and RTP)在理解接下来的子区段之前,你应该要懂得一些关于SIP和RTP的知识。首先,SIP是一个使用INVITE,BYE,和CANCEL方法来控制通话的信号协议。在INVITE请求消息中的关于会话(音频/视频/文本)消息包含在SIP协议中,使用的是叫做SDP(Session Description Protocol)的协议来包含的这些信息。包含在SDP中的信息描述了两个用户代理间的一个或是更多的媒体流的配置情况。代理服务器从不参与到媒体流中,因此他对于媒体是什么都不用了解的。换句话说,无论UA和网关指定的媒体是什么格式,它都支持。但是有时候,B2BUA(back to back user agent)如媒体代理,可以安装在同一个服务器上来处理RTP音频(也就是NAT穿透机制)。SDP协议是供给/应答模型(Offer/Answer model)。SDP供给信息嵌入在INVITE请求消息中而应答信息在在200 OK的响应消息中。举例:摘自Ethereal:
 上图中的包是一个INVITE请求消息。该请求消息中嵌入了描述会话信息的SDP包。我们可以看到这是eyeBeam软电话产生的INVITE消息。它提供使用G.729编码格式,并使用UDP的8558端口(为了安全起见,我隐藏了IP地址)。属性rtpmap:101 telephone-event/8000描述了使用的DTMF前转方法(RFC2833)。另一个设备,在这个例子中是一个网关,在200 Ok的回复中对“供给”进行了应答。
 概要(Summary)这一章中,我们学到了什么是OpenSER以及它的主要特性。现在,你可以识别出openser.cfg配置文件和它的一些配置块,如全局定义(global definitions),加载模块(load modules),模块的参数,主路由块,路由块,响应路由块和出错处理路由块。每一个代理接收的请求都按照openser.cfg脚本中的配置来进行处理。脚本的内容的组织几乎和SIP有状态代理处理过程相一致。通常OpenSER作为松散路由器来运行(SIP版本2)。最后我们介绍了SIP和SDP的概念。 
 使用OpenSER构建电话通信系统——第三章(1)
 
 
   
 注:以下文章如需转载,请注明所属作者,转载地址,谢谢!这一章前半部分的内容是介绍系统安装和openser安装的,图片较多,而本blog文章中嵌入图片太过费时,所以直接将pdf文档链接公布如下,请直接下载。 使用OpenSER构建电话通信系统——第三章(1) OpenSER v1.2 目录结构在安装完成后,OpenSER将创建文件安放架构。了解这个架构对于定位系统存储在哪一个主文件夹中是很重要的。你需要这些消息来更新或删除软件。配置文件(etc/openser)openser-1:/etc/openser# ls -l   total 12   -rw-r--r-- 1 root root 1804 2007-09-10 14:02 dictionary.radius   -rw-r--r-- 1 root root 4077 2007-09-10 14:05 openser.cfg   -rw-r--r-- 1 root root 1203 2007-09-10 14:02 openserctlrccd 模块(/lib/openser/modules)openser-1:/lib/openser/modules# ls   acc.so            domain.so       msilo.so        sms.so   alias_db.so       enum.so         mysql.so        speeddial.so   auth_db.so        exec.so         nathelper.so    sst.so   auth_diameter.so  flatstore.so    options.so      statistics.so   auth_radius.so    gflags.so       path.so         textops.so   auth.so           group_radius.so pdt.so          tm.so   avpops.so         group.so        permissions.so uac_redirect.so   avp_radius.so     imc.so          pike.so         uac.so   dbtext.so         lcr.so          registrar.so    uri_db.so   dialog.so         mangler.so      rr.so           uri.so   dispatcher.so     maxfwd.so       seas.so         usrloc.so   diversion.so      mediaproxy.so   siptrace.so     xlog.so   domainpolicy.so   mi_fifo.so      sl.socd /lib/openser/modules二进制文件(/sbin)openser-1:/sbin# ls -l op*    -rwxr-xr-x 1 root root 2172235 2007-09-10 14:02 openser    -rwxr-xr-x 1 root root   41862 2007-09-10 14:02 openserctl    -rwxr-xr-x 1 root root   38107 2007-09-10 14:02 openser_mysql.sh    -rwxr-xr-x 1 root root   13562 2007-09-10 14:02 openserunixcd /sbin 日志文件(Log Files)初始化日志可以在syslog文件中查看到(/var/log/syslog):Sep 10 14:25:56 openser-1 openser: init_tcp: using epoll_lt as the io watch method (auto detected) Sep 10 14:25:56 openser-1 /sbin/openser[7791]: INFO: statistics manager successfully initialized Sep 10 14:25:56 openser-1 /sbin/openser[7791]: StateLess module - initializing Sep 10 14:25:56 openser-1 /sbin/openser[7791]: TM - initializing... Sep 10 14:25:56 openser-1 /sbin/openser[7791]: Maxfwd module- initializing Sep 10 14:25:56 openser-1 /sbin/openser[7791]: INFO:ul_init_locks: locks array size 512 Sep 10 14:25:56 openser-1 /sbin/openser[7791]: TextOPS - initializing Sep 10 14:25:56 openser-1 /sbin/openser[7791]: INFO: udp_init: SO_RCVBUF is initially 109568 Sep 10 14:25:56 openser-1 /sbin/openser[7791]: INFO: udp_init: SO_RCVBUF is finally 262142 Sep 10 14:25:56 openser-1 /sbin/openser[7791]: INFO: udp_init: SO_RCVBUF is initially 109568 Sep 10 14:25:56 openser-1 /sbin/openser[7791]: INFO: udp_init: SO_RCVBUF is finally 262142 Sep 10 14:25:56 openser-1 /sbin/openser[7792]: INFO:mi_fifo:mi_child_ init(1): extra fifo listener processes created 启动选项(Startup Options)OpenSER可以使用初始化脚本或使用openserctl工具进行启动。如果你使用的是初始化脚本启动的openser,那么你也只能使用初始化脚本来终止其运行。同样的道理适用于使用openserctl工具。使用初始化脚本启动,终止和重启OpenSER。/etc/init/d/openser start|stop|restart使用openserctl工具脚本启动,终止和重启OpenSER。/etc/init/d/openserctl start|stop|restartOpenSER的执行有几个启动选项。展现于下面的这些选项允许你改变守护进程(DAEMON)的配置。最有用的几个如下:l        “-c”用来查看配置文件l        “-D -E dddddd”用来查看模块加载(不要用于[production??],它只是绑定第一个接口)还有许多其他的选项允许你调整你的配置。对于每一个选项,在你填入的配置文件中都能找到对应的核心参数。Usage: openser -l address [-p port] [-l address [-p port]...] [options] Options:     -f file      Configuration file (default //etc/openser/openser.cfg)     -c           Check configuration file for errors     -C           Similar to '-c' but in addition checks the flags of                   exported functions from included route blocks     -l address   Listen on the specified address/interface (multiple -l               mean listening on more addresses).  The address format               is [proto:]addr[:port], where proto=udp|tcp and               addr= host|ip_address|interface_name. E.g: -l locahost,               -l udp:127.0.0.1:5080, -l eth0:5062 The default               behavior is to listen on all the interfaces. -n processes Number of child processes to fork per interface               (default: 8) -r           Use dns to check if is necessary to add a "received="               field to a via -R           Same as '-r' but use reverse dns;               (to use both use '-rR') -v           Turn on "via:" host checking when forwarding replies -d           Debugging mode (multiple -d increase the level) -D           Do not fork into daemon mode -E           Log to stderr -T           Disable tcp -N processes Number of tcp child processes (default: equal to '-n') -W method    poll method -V           Version number -h           This help message -b nr        Maximum receive buffer size which will not be exceeded               by auto-probing procedure even if  OS allows -m nr        Size of shared memory allocated in Megabytes -w dir       Change the working directory to "dir" (default "/") -t dir       Chroot to "dir" -u uid       Change uid -g gid       Change gid -P file      Create a pid file -G file      Create a pgid file -x socket    Create a unix domain socket 概要(Summary)在这一章中你已经学会如何安装OpenSER和为了OpenSER的安装如何准备Linux系统。我们已经下载和编译了OpenSER和MySQL模块。在安装完后,我们又介绍了如何使用OpenSER初始化文件在引导的时候启动OpenSER。 第四章:OpenSER标准配置(OpenSER Standard Configuration)OpenSER的标准配置文件安装在/etc/openser/openser.cfg。它是OpenSER的最简单的配置文件之一。而且它还是一个可以开始进行解释OpenSER功能的理想脚本。和一些基本的模块,参数和函数一样,还有一些区段是你应该熟悉了解的。这个章节结束后,你将能够:l        识别出openser.cfg配置文件的各个区段l        识别出标准配置的限制l        使用ngrep工具跟踪SIP事务l        使用XLOG模块记录路由过程l        使用append_hf命令标记ngrep工具跟踪的包标准配置是一个好的出发点。它拥有最小化的功能集合,不支持验证,所以你可以不使用密码就可以连接你的SIP话机。不管怎样,你都可以从一部话机呼向另一部,我们将在之后测试之。我们在哪?(Where Are We?)我们要再一次提到的是,VoIP服务提供商的解决方案中有许多的部件。为了失去对整体的把握,我们会在大多数的章节中展示这幅图片。在这一章中,我们仍然会与标准配置的SIP代理部件打交道。
分析标准配置(Analyzing the Standard Configuration)下面是OpenSER 1.2.2版本的标准配置的展示。在这一节,我们将开始描述标准配置中的每一行的命令和函数。## $Id: openser.cfg 1676 2007-02-21 13:16:34Z bogdan_iancu $ # #simple quick-start config script #Please refer to the Core CookBook at http://www.openser.org/dokuwiki/ doku.php #for a explanation of possible statements, functions and parameters. # # ----------- global configuration parameters ------------------------ debug=3            # debug level (cmd line: -dddddddddd) fork=yes log_stderror=no    # (cmd line: -E) children=4 port=5060 #uncomment the following lines for TLS support #disable_tls = 0 #listen = tls:your_IP:5061 #tls_verify_server = 1 #tls_verify_client = 1 #tls_require_client_certificate = 0 #tls_method = TLSv1 #tls_certificate = "//etc/openser/tls/user/user-cert.pem" #tls_private_key = "//etc/openser/tls/user/user-privkey.pem" #tls_ca_list = "//etc/openser/tls/user/user-calist.pem" # ------------------ 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" # 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", yes) ## If you set "calculate_ha1" parameter to yes (which true in thisconfig),# 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);          };          if (!uri==myself) {                  # mark routing logic in request                  append_hf("P-hint: outbound\r\n");                  # if you have some interdomain connections via TLS                  #if(uri=~"@tls_domain1.net") {                  #       t_relay("tls:domain1.net");                  #       exit;                 #} else if(uri=~"@tls_domain2.net") {                 #        t_relay("tls:domain2.net");                 #        exit;                 #}                 route(1);         };         # if the request is for other domain use UsrLoc         # (in case, it does not work, use the following command         # with proper names and addresses in it)         if (uri==myself) {                 if (method=="REGISTER") {                          # Uncomment this if you want to use digest                            authentication                          #if (!www_authorize("openser.org",                                "subscriber")) {                          #        www_challenge("openser.org", "0");                          #        exit;                          #};                          save("location");                          exit;                 };                 lookup("aliases");                 if (!uri==myself) {                          append_hf("P-hint: outbound alias\r\n");                          route(1);                 };                 # native SIP destinations are handled using our                    USRLOC DB                 if (!lookup("location")) {                          sl_send_reply("404", "Not Found");                          exit;                 };                 append_hf("P-hint: usrloc applied\r\n");         };         route(1); } route[1] {         # send it out now; use stateful forwarding as it works         # reliably even for UDP2TCP         if (!t_relay()) {                 sl_reply_error();         };        exit;}这个标准的配置是最简单的可以使用的配置。我们将以它作为出发点,然后再在接下来的章节中循序渐进的包含新的命令和函数。使用这个配置,客户端能够进行注册(不需要认证)并且UACs能够互相之间进行沟通。注册服务器,定位服务器和代理服务器使用最小配置进行工作。下面我们将解释一些脚本中的引用。debug=3 # debug level (cmd line: -dddddddddd)设置日志级别(Set log level):它是一个-3到4之间的一个数。默认为2。数越大,那么写道日志中的信息就越多。不过如果将其设为4,那么系统的性能可能会变得很差。日志级别是:l        L_ALERT(-3)——这个级别只被用来报告需要立即响应的错误。l        L_CRIT(-2)——这个级别只被用来报告造成危险状况的错误。l        L_ERR(-1)——这个级别用来报告数据处理过程中不会造成系统故障的错误。l        L_WARN(1)——这个级别用来写入一些警告消息。l        L_NOTICE(2)——这个级别用来报告不寻常的状况。l        L_INFO(3)——这个解别用来写入一些提示信息的消息。l        L_DBG(4)——这个级别用来写入用来调试的消息。fork=yesfork参数用来定义OpenSER进程是运行在前台还是后台。运行在后台则设置fork=yes。有时候你会发现如果在前台运行对于定位脚本错误是非常有用的。如果fork被禁用的话,OpenSER将不能够同时监听多个接口,TCP/TLS支持功能将被自动禁用。在单进程模式下,只有一个UDP接口被接受。log_stderror=no # (cmd line: -E)如果被设为yes,服务器将打印调试信息到标准错误输出。如果设为no,syslog会被使用。Children=4“children”核心参数告诉OpenSER每个创建处理接入请求的进程的接口有多少孩子进程。四个进程对于大多数系统来书都是很好的一个出发点。这个参数只适用于UDP接口,对于TCP没有影响。Port=5060如果在监听参数中没有被指明,那么这个参数就是默认使用的端口号。mpath="/lib/openser/modules/"设置模块搜索路径。这可以被用来简化模块的加载。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"上面的这些行是OpenSER加载外部的模块。这个时候,只有需要的那些模块被加载了。其余附加的功能需要加载其他诸如RADIUS和MYSQL的模块。所有这些模块都有一个README文件来描述其功能。modparam("mi_fifo", "fifo_name", "/tmp/openser_fifo")创建FIFO文件及其名称用来监听和读取外部命令。modparam("usrloc", "db_mode", 0)modparam核心命令配置了相应的模块。上面的usrloc模块负责定位服务。当一个用户注册时,它用来保存定位信息,也就是我们知道的变量db_mode表明的相对于位置的AOR(Address of Record)。为0表示是内存。所以如果你关闭了服务器,那么你将丢失所有的注册记录。表的位置靠的就是db_mode变量。将db_mode设置为0表明这个数据将被保存到数据库中。换句话说,如果OpenSER关闭,所有记录丢失。modparam("rr", "enable_full_lr", 1)上面的语句设置rr(Record Routing)模块的enable_full_1r变量为1。它告诉OpenSER要支持那些不对record_route头域进行管理的SIP客户端。如果设为1,;lr=on将被使用而不是;lr。Route {这是SIP请求的路由逻辑的开始。“块(block)”起始于“{”。在这个块中,SIP请求将被处理。通过下面的语句你将看清大概:# initial sanity checks -- messages with# max_forwards==0, or excessively long requestsif (!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;};当一个请求进入主路由块时,要进行一些检查。首先检查的是前转(forward)的最大次数。为了避免回环(loop),我们使用mf_process_maxfwd_header()函数来检查包到底传递了多少SIP跳(SIP hops)。如果回环(loop)被发现,那么脚本则使用s1_send_reply()函数发送“483 too many hops”消息。Msg:len是OpenSER核心用来返回SIP请求长度(单位byte)的函数。这是一个标准的用来检查消息大小限制方法。if (!method=="REGISTER")        record_route();如果不是REGISTER方法,OpenSER将进行record-route。这条消息告诉SIP服务器要处在两个UAC请求的路径当中。record_route()函数只是简单的增加了一个新的record-route头域。# Subsequent messages within 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);        };loose_route()函数是用来试着看看请求有没有使用record-route头域进行路由的。这个函数标识的请求将使用最上面的record-route头域的内容进行路由。如果请求是在同一个对话中,我们将进入if,并前转该包。我们只是简单的前转该请求。我们将调用次级路由块route(1)做这件事,在该块中t_relay()函数将被调用。按照record-route头域(rr-enforced),append_hf函数将添加一个表明请求被处理的提示。if (!uri==myself) {        # mark routing logic in request        append_hf("P-hint: outbound\r\n");        # if you have some interdomain connections via TLS        #if(uri=~"@tls_domain1.net") {        #       t_relay("tls:domain1.net");        #       exit;        #} else if(uri=~"@tls_domain2.net") {        #       t_relay("tls:domain2.net");        #       exit;        #}        route(1);};上面的代码将处理一个我们的代理不处理的域中的请求,if(!uri==myself),使用调用t_relay的route(1)来前转该请求。 默认的情况下,代理只是一个开放式的中继器。在下面的章节中我们将讨论怎样改进带外通话(outbound call)的处理过程。将请求前转至其他的代理是很重要的;然而,还应该进行一些合适的标识检查。现在,我们将处理指向被我们的SIP代理处理的域的请求。if (uri==myself) {        if (method=="REGISTER") {                # Uncomment this if you want to use digest                  authentication                #if (!www_authorize("openser.org",                  "subscriber")) {                #        www_challenge("openser.org", "0");                #        exit;                #};                save("location");                exit;        };如果是REGISTER请求,使用save(“location”)将AOR保存至位置表(location table)中。理解两个概念至关重要。验证被取消(www_authorize被注释掉)和因为我们没有把数据库安装在SIP代理上,所以定位数据库不是持续起作用的。lookup("aliases");if (!uri==myself) {        append_hf("P-hint: outbound alias\r\n");        route(1);};别名(alias)是可选的URI(比如,8590@voffice.com.br可以是原有URI flavio@voffice.com.br的别名)。lookup(“aliases”)函数寻找在请求中表示的URI的典型的URI。如果该URI被找到,则在操作进行前用其取代R-URI。最终的URI既可以被放置在我们域的里面,也可以是外面。如果在外面,那么系统只是简单的将包前转到负责该域的SIP代理上。如果在外面则开始请求的处理。if (!lookup("location")) {        sl_send_reply("404", "Not Found");        exit;};append_hf("P-hint: usrloc applied\r\n");lookup(“location”)函数将试着恢复R-URI的AOR。如果AOR被定位到(UA注册了)那么UA的ip地址将替代R-URI。如果没有找到,我们只是简单的发回错误消息“404 Not Found”。route(1);如果找到AOR我们将使用route(1)结束;route[1] {         # send it out now; use stateful forwarding as it worksreliably         # even for UDP2TCP         if (!t_relay()) {                 sl_reply_error();         };         exit;}最后,路由块被调用。t_relay()函数前转基于请求URI的有状态的请求。域部分将使用NAPTR,SRV和A records等来进行解析。这个函数是由TRANSACTION模块(tm.so)提供的,负责发送请求并处理重发和响应。如果请求不能被成功的被发送到目的地,错误信息将由t_relay()函数自动产生。如果错误产生,s1_reply_error()将发送响应信息给UA。Using标准配置(Using the Standard Configuration)在这个实验中,我们将使用协议分析机来抓取一个完整的SIP通话。我们将分析消息头和消息流程。你可以创建一个PC和带有两个UAC的实验环境。UAC可以是软电话,ATA,或是IP电话。
|          调整以满足你的实验需要。                                                         ||                 1:使用ngrep开始抓取包。如果你没有安装ngrep,              ||                        使用下面的命令安装:                                                                     ||                        apt-get install ngrep                                                                            ||                 2:使用下面命令抓取包:                                                                              ||                        ngrep -p -q -W byline port 5060 > test.txt                         ||                 3:配置UAC(软电话,IP电话,或ATA)。                                       |使用下面的描述来配置第一个UAC:sip proxy 10.1.x.y – IP of your proxy user: 1000 password: 1000 使用下面的描述来配置第二个UAC:sip proxy 10.1.x.y – IP of your proxy user: 1001password: 1001 在完成配置后,你需要注册IP话机。不是所有的设备都会做自动注册的操作。1.    使用下面的命令检查电话是否注册上:                      openserctl u1 show2.    用第一个UAC拨打1001。第二个UAC会振铃。 3.    验证抓取的包没有相对于INVITE请求的“407- Proxy authentication required”错误和相对于REGISTER请求的“401- Unauthorize”错误。这证明了不需要验证。 4.    你可以使用如下命令查看抓包: more test.txt使用OpenSER构建电话通信系统——第四章(2)
 
 
 
  路由基础(Routing Basics)探明如何路由SIP包并不是一件容易的事。我们将在这一节中说明通过代理服务器路由SIP包的一些基本概念。第一个重要的基本概念包含事务和对话。事务和对话(Transactions and Dialogs)一个事务通常由一个请求开始,由一个响应码(a response code)结束。VIA头域中的branch参数用来标识一个事务。对话可以是开始于一个INVITE事务,结束于一个BYE事务。一个对话由FROM,TO和CALL-ID头域的结合所标识。并不是所有的SIP方法都可以启动一个对话,REGISTER和MESSAGE方法就不行。
初始和接续请求(Initial and Sequential Requests)了解初始和接续请求消息之间的区别是很重要的。对于初始请求,你必须决定如何使用发现机制来进行路由,通常它是基于DNS或是位置表(a location table)。初始请求使用VIA头域来记录路由信息,如果你打开record-routing,那么也可以使用ROUTE头域来进行记录。在一个事务中,SIP包将被路由回到之前经过的返回到的每一个代理的VIA头域记录的地址。随后的请求则使用CONTACT头域中的地址进行路由。然而,如果你打开record-routing,接下来的请求则会使用被发现的路由集合路由回去。你可以用TO头域中的TAG参数来分辨初始和接续请求。事务的上下文中的路由过程(Routing in a Context of a Transaction)在一个事务中所有的请求都使用VIA头域来进行路由。所以,在到达最终的目的地之前,所有的响应都会经过代理。如果你使用函数t_relay()来路由请求,那么SIP代理会处在有状态模式下,则你可以使用区段onreply_route[]和failure_route[]来处理响应和失败。对话的上下文中的路由过程(Routing in the Context of a Dialog)同一个对话中的接续请求将直接使用CONTACT头域来进行点到点(peer-to-peer)的路由。不过大部分时间你会希望一些接续事务,例如BYE,可以通过代理,这样就可以进行计费和对话控制。你可以打开record-routing来完成这项工作。这样做将指示脚本来进行record-routes.
 之后,你可以使用之前记录过的路由信息,也就是我们知道的路由集合(route set),来进行接续请求的前转。这是最通常的配置,也是默认的配置文件中的配置。
 实验——跟踪一个完整的对话(Lab------Tracking a Complete Dialog)在这个实验中,我们将使用一个简化了的脚本来理解路由的概念。我们用函数appenc_hf()来添加一个头域到包中以标记包被脚本处理的位置信息。
步骤1:使用下面的脚本(openser.chapter4-2)。重启OpenSER并且重新注册电话。route{    # All messages, except for REGISTER will pass here    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: (1)rr-enforced\r\n");           route(1);    };    # We will route only intra-domain requests    if (!uri==myself) {           exit();    };    # main routing of intra-domain requests    if (uri==myself) {           if (method=="REGISTER") {                  save("location");                  exit;           };           # native SIP destinations are handled using our USRLOC DB           if (!lookup("location")) {                  sl_send_reply("404", "Not Found");                  exit;           };           append_hf("P-hint: (2)usrloc applied\r\n");    };    route(1); } route[1] {    # send it out now; use stateful forwarding    t_on_reply("1");    t_on_failure("1");        if (!t_relay()) {           sl_reply_error();    };    exit; } onreply_route[1] {    append_hf("P-hint: (3)passed thru onreply_route[1]\r\n"); } failure_route[1] {    append_hf("P-hint: (4)passed thru failure_route[1]\r\n"); } 步骤2:使用ngrep抓取请求和响应包,并存入文件ngrep -p -q -W byline port 5060 >rr-stateful步骤3:由1000到1001发起一通通话(任何注册的话机都可以)步骤4:用CTRL-C来终止ngrep的运行使用文本编辑器来检查包并观察P-Hint头域。他们应该要和实验开始时的那幅图中的一样才是。实验——无状态运行(Lab-------Running Stateless)如果你使用函数forward()来替代函数t_relay(),那么SIP代理将运行在无状态的模式下。所有的工作还在进行,但是你将不能处理响应消息。代理在这时候将无法将同一个事务中请求和响应关联上。响应仍将使用VIA头域来处理。步骤1:使用forward()函数替代t_relay()函数。Replace:if (!t_relay()) {           sl_reply_error(); }; By:forward()步骤2:重启OpenSER,重新注册话机步骤3:使用ngrep抓取请求和响应到一个文件中ngrep -p -q -W byline port 5060 > rr-stateless步骤4:1000打给1001一通电话步骤5:结束通话后,终止ngrep步骤6:使用文本编辑器查看rr-stateless文件。你将注意到响应消息中没有了P-Hint头域。这表明他们没有被区段onreply_route处理。所以,如果你使用无状态模式,除了将消息前转至目的地外,你将无法对回复的消息进行任何处理。实验——关闭record-route(Lab--------Disabling record-route)在这个实验中,我们将停止记录路由。对话中的接续请求将绕过SIP代理,直接由一台话机传到另外一台上。当然,使用了CONTACT头域的消息。步骤1:将负责记录路由的那一行注释掉#if (!method=="REGISTER") record_route();步骤2:重启OpenSER并重新注册话机步骤3:使用ngrep来抓取请求和响应消息存入文件ngrep –p –q –W byline port 5060 >norr-stateless步骤4:1000打给1001一通电话步骤5:结束通话后,终止ngrep步骤6:使用文本编辑器查看norr-stateless文件。现在,你将注意到,无法看到BYE和ACK请求。因为他们此时直接由一端传到了另一端。如果你想对通话计费,那么你是不想要SIP代理进行这个实验的行为的。概要(Summary)在这一章中,你已经学到了openser.cfg配置文件中每个区段的一些状态。这是最简单的一个配置文件。在下一章中,我们将增加脚本的功能和复杂度。这一章只是作为开发更高级脚本的一个起点。即使它比较简单,但是这样的一个脚本也已经可以让你能够连接两台话机,并能够互相通话了。
 
 
 
   
 使用OpenSER构建电话通信系统——第五章(1)
 
 
 
 
第五章:用MySQL添加认证(Adding Authentication with MySQL)
在这一章中我们将学到如何使用几种数据库后端来对SIP请求进行鉴定并提供诸如位置和别名表等数据的持续性。最主要的是,我们将使用MySQL来做每一件事。这一章分为两个部分。第一个部分,我们将学到如何实现认证,第二部分我们将学到如何处理不同方向的通话。这一章的最后,你将能够:l        配置MySQL来对SIP设备进行认证l        使用openserctl工具来实现基本的操作,如添加和删除用户l        改变脚本openser.cfg来配置MySQL认证l        实现订阅列表的连续性l        实现位置列表的连续性l        重启服务器但是不丢失位置记录l        正确的处理inbound-to-inbound,inbound-to-outbound,outbound-to-inbound,和outbound-to-outbound会话l        正确的处理CANCEL请求我们在哪儿?(Where Are We?)
现在,我们仍然将集中精力于SIP代理上。然而,我们将包含一个新的部件,数据库。OpenSER可以使用MySQL和PostgreSQL。在这本书中,我们选择的是MySQL。到目前为止,它是OpenSER使用最多的数据库。AUTH_DB模块(The AUTH_DB Module)
以数据库为基础的认证是由AUTH_DB模块实现的。其他类型的认证,如radius和diameter可以分别使用AUTH_RADIUS和AUTH_DIAMETER来实现。AUTH_DB和诸如MySQL和PostgreSQL等数据库模块一起工作。AUTH_DB有一些参数没有在脚本中明确的声明。让我们看看AUTH_DB模块的默认参数。  由ATUH_DB模块会导出两个函数。www_authorize(realm, table)这个函数在REGISTER的认证中被使用,和RFC2617保持一致。proxy_authorize(realm, table)这个函数按照RFC2617对non-REGISTER请求的证书进行验证。如果验证成功,则该证书将被标记为已认证。如果你的服务器是请求的终点,那么你必须使用www_authorize函数。而当请求的最终目的地不是你的服务器,那么则使用proxy_authorize函数,然后你要对请求进行前转,这时,服务器实际上是作为代理在工作。www_authorize和proxy_authorize的不同使用之处就在于请求的终点是否是你的服务器(REGISTER)。REGISTER认证顺序(The REGISTER Authentication Sequence)
脚本应该对REGISTER和INVITE消息进行认证。在修改openser.cfg脚本之前,让我们先展示这是如何发生的。当OpenSER收到REGISTER消息时,它先检查其是否有Authorize头域。如果没有找到,它会请求UAC的证书并退出。当UAC收到请求后,向其再次发送REGISTER消息,此时的消息中带有Authorize头域。
 注册过程(ngrep抓的包)(Register Sequence(Packets Captured by ngrep))
注册过程可以从下面的抓包中看到:U 192.168.1.119:29040 -> 192.168.1.155:5060REGISTER sip:192.168.1.155 SIP/2.0.Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-13517a5a8218ff45-1--d87543-;rport.Max-Forwards: 70.Contact: .To: "1000".From: "1000";tag=0d10cc75.Call-ID:e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..CSeq: 1 REGISTER.WWW-Authenticate: Digest realm="192.168.1.155", nonce="46263864b3abb96a423a7ccf052fa68d4ad5192f".Server: OpenSER (1.2.0-notls (i386/linux)).Content-Length: 0.U 192.168.1.119:29040 -> 192.168.1.155:5060REGISTER sip:192.168.1.155 SIP/2.0.Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-da776d09bd6fcb65-1--d87543-;rport.Max-Forwards: 70.Contact: .To: "1000".From: "1000";tag=0d10cc75.Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..CSeq: 2 REGISTER.Expires: 3600.Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO.User-Agent: X-Lite release 1003l stamp 30942.Content-Length: 0.U 192.168.1.155:5060 -> 192.168.1.119:29040SIP/2.0 401 Unauthorized.Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-13517a5a8218ff45-1--d87543-;rport=29040.To: "1000";tag=329cfeaa6ded039da25ff8cbb8668bd2.41bb.From: "1000";tag=0d10cc75.Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..CSeq: 1 REGISTER.WWW-Authenticate: Digest realm="192.168.1.155",nonce="46263864b3abb96a423a7ccf052fa68d4ad5192f".Server: OpenSER (1.2.0-notls (i386/linux)).Content-Length: 0.U 192.168.1.119:29040 -> 192.168.1.155:5060REGISTER sip:192.168.1.155 SIP/2.0.Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-da776d09bd6fcb65-1--d87543-;rport.Max-Forwards: 70.Contact: .To: "1000".From: "1000";tag=0d10cc75.Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..CSeq: 2 REGISTER.Expires: 3600.Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO.User-Agent: X-Lite release 1003l stamp 30942.Authorization: Digest username="1000",realm="192.168.1.155",nonce="46263864b3abb96a423a7ccf052fa68d4ad5192f",uri="sip:192.168.1.155",response="d7b33793a123a69ec12c8fc87abd4c03",algorithm=MD5.Content-Length: 0.U 192.168.1.155:5060 -> 192.168.1.119:29040SIP/2.0 200 OK.Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-da776d09bd6fcb65-1--d87543-;rport=29040.To: "1000";tag=329cfeaa6ded039da25ff8cbb8668bd2.c577.From: "1000";tag=0d10cc75.Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..CSeq: 2 REGISTER.Contact: ;expires=3600.Server: OpenSER (1.2.0-notls (i386/linux)).Content-Length: 0.注册过程的代码片段(Register Sequence Code Snippet)
现在让我们看看这个过程在openser.cfg脚本中是如何写的吧:if (method=="REGISTER") {# Uncomment this if you want to use digest authenticationif (!www_authorize("", "subscriber")) {www_challenge("", "0");exit;};save("location");exit;}在上面的过程中,第一次传的REGISTER包没有被www_authorize函数认证。然后,www_challenge语句被调用。它发送了“401 Unauthorized”包,这个包按照摘要认证方法(digest authentication scheme)包含了认证请求。UAC第二次传的REGISTER包添加了Authorize头域,然后,save(”location”)函数被调用用来保存AOR到MySQL的位置表。INVITE认证过程(The INVITE Authentication Sequence)
相对的则是一通普通通话的INVITE认证过程。代理服务器总是会对第一个INVITE请求进行响应,使用的是”407 Proxy Authentication Required”消息。这个消息包含了Authorize头域,含有关于摘要认证的信息,如realm和nonce。一旦UAC收到这个消息,它就会立即发送一条新的INVITE消息。现在,Authorize中包含了可以使用MD5算法进行计算的摘要,包括username,password,realm,和nonce。如果在服务器上使用与之相同的参数进行计算的摘要和UAC传递的摘要相符时,用户得到认证。
 INVITE过程的抓包(INVITE Sequence Packet Capture)
我们用ngrep抓了一套INVITE认证过程的包。这个过程能够帮助你理解上面的图。为了避免列的过长,我们没有将SDP头的内容一起附上。U 192.168.1.169:5060 -> 192.168.1.155:5060INVITE sip:1000@192.168.1.155 SIP/2.0.Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.From: ;tag=a83bebd75be1d88e.To: .Contact: .Supported: replaces.Call-ID: 8acb7ed7fc07c369@192.168.1.169.CSeq: 39392 INVITE.User-Agent: TMS320V5000 TI50002.0.8.3.Max-Forwards: 70.Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE.Content-Type: application/sdp.Content-Length: 386.(sdp header striped off).U 192.168.1.155:5060 -> 192.168.1.169:5060SIP/2.0 407 Proxy Authentication Required.Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.From: ;tag=a83bebd75be1d88e.To: ;tag=329cfeaa6ded039da25ff8cbb8668bd2.b550.Call-ID: 8acb7ed7fc07c369@192.168.1.169.CSeq: 39392 INVITE.Proxy-Authenticate: Digest realm="192.168.1.155", nonce="4626420b4b162ef84a1a1d3966704d380194bb78".Server: OpenSER (1.2.0-notls (i386/linux)).Content-Length: 0.U 192.168.1.169:5060 -> 192.168.1.155:5060ACK sip:1000@192.168.1.155 SIP/2.0.Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.From: ;tag=a83bebd75be1d88e.To: ;tag=329cfeaa6ded039da25ff8cbb8668bd2.b550.Contact: .Call-ID: 8acb7ed7fc07c369@192.168.1.169.CSeq: 39392 ACK.User-Agent: TMS320V5000 TI50002.0.8.3.Max-Forwards: 70.Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE.Content-Length: 0.U 192.168.1.169:5060 -> 192.168.1.155:5060INVITE sip:1000@192.168.1.155 SIP/2.0.Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKcdb4add5db72d493.From: ;tag=a83bebd75be1d88e.To: .Contact: .Supported: replaces.Proxy-Authorization: Digest username="1001", realm="192.168.1.155", algorithm=MD5, uri="sip:1000@192.168.1.155", nonce="4626420b4b162ef84a1a1d3966704d380194bb78", response="06736c6d7631858bb1cbb0c86fb939d9".Call-ID: 8acb7ed7fc07c369@192.168.1.169.CSeq: 39393 INVITE.User-Agent: TMS320V5000 TI50002.0.8.3.Max-Forwards: 70.Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE.Content-Type: application/sdp.Content-Length: 386.(sdp header striped off)INVITE Code SnippetIn the code below, the SIP proxy will challenge the user for credentials on any request different from REGISTER. We consume the credentials after authentication, for security reasons, to avoid sending encrypted material ahead. if (!proxy_authorize("","subscriber")) { proxy_challenge("","0");exit; }; consume_credentials(); lookup("aliases"); if (!uri==myself) { append_hf("P-hint: outbound alias\r\n"); route(1); }; # native SIP destinations are handled using our USRLOC DB if (!lookup("location")) { sl_send_reply("404", "Not Found"); exit; }; append_hf("P-hint: usrloc applied\r\n"); }; route(1);摘要认证(Digest Authentication)
摘要认证的基础是RFC2617中的”HTTP Basic and Digest Access Authentication”。我们这一章节的目的是介绍一个带有摘要认证的系统的要素。它不是对于SIP的所有可能的安全性问题的回答,但确实是一种可以保护在网络传输中的用户名和密码的好方法。
 摘要方法是一中简单的请求响应机制(challenge-response mechanism)。使用nonce值向UA发出请求。一个有效的响应包含有所有参数的一个校验和(checksum)。因此,密码从不以简单的文本形式进行传输。WWW-Authenticate响应头(WWW-Authenticate Response Header)
如果服务器没有收到带有有效Authorize头域的REGISTER或INVITE请求,那么它会返回一个带有WWW-Authenticate头域的”401 unauthorized”消息。这个头包含一个realm和一个nonce。认证请求头(The Authorization Request Header)
我们希望用户能够再试一次,但是要带上Authorize头域。该头要包含用户名,realm和nonce(由server提供),uri,一个32位的十六进制数,还有一种认证方法(此例中是MD5)。这种响应是用户使用一种特定的算法产生的校验和。
QOP——保护质量(QOP ------- Quality of Protection)
qop参数表明了用户应用到该消息上的保护的质量。如果出现了这个参数,那么它的值要是服务器支持的所有可选值之一。这些选择在WWW-Authenticate头域中表明。这些值会影响摘要的计算。之所以有这条指令,是为了和RFC2809的最小实现保持兼容。你可以在两个函数中对该参数进行配置,分别是www_challenge(realm, qop)和proxy_challenge(realm, qop)。如果被配成1,那么服务器就会要求有qop参数。总是使用qop=1会帮助你避免’replay”攻击。然而,一些用户对于qop会不兼容。对于摘要认证的详细描述见RFC2617。
 
  使用OpenSER构建电话通信系统——第五章(2)
 
 
 
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!安装 MySQL支持为了允许持续性,换句话说,为了能够将用户的证书保存在数据库中,也就是说,为了避免这些数据在断电和重启的情况下被毁,OpenSER需要配置使用如MySQL的数据库。在你继续之前,确认你已经安装了MySQL并且已经编译并安装了OpenSER MySQL模块是非常重要的。在第三章,我们在编译OpenSER的时候加入了对MySQL的支持。检查/lib/openser/modules文件夹看看有没有mysql.so模块。在你可以使用带有MySQL的OpenSER之前,还有一些任务需要去做。步骤1:确认mysql.so模块在指定的文件夹中是否存在:ls /lib/openser/mokules/mysql.so如果该模块不存在,那么请重新编译OpenSER以支持MySQL。步骤2:使用openser_mysql.sh shell脚本创建MySQL表。这个脚本将使用下面的参数来创建MySQL表:DBNAME="openser"DBHOST="localhost"DBRWUSER="openser"DBRWPW="openserrw"DBROUSER="openserro"DBROPW="openserro"DBROOTUSER="root"cd/sbin./openser_mysql.sh createMySQL password for root:Enter password:Enter password:creating database openser ...Core OpenSER tables succesfully created.Install presence related tables ?(y/n):ycreating presence tables into openser ...Presence tables succesfully created.Install extra tables - imc,cpl,siptrace,domainpolicy ?(y/n):ycreating extra tables into openser ...Extra tables succesfully created.Install SERWEB related tables ?(y/n):nDomain (realm) for the default user 'admin': voffice.com.br需要密码才能够访问该数据库。这时候,密码是空的。该脚本会询问密码两次;两次都要按回车键。此脚本也会询问域(realm);要告诉管理者用户的域。步骤3:配置OpenSER使用MySQL。按照下面文件的高亮部分进行修改:# ------------------ module loading ----------------------------------#set module pathmpath="//lib/openser/modules/"# Uncomment this if you want to use SQL databaseloadmodule "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"# 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 linemodparam("usrloc", "db_mode", 2)# -- auth params --# Uncomment if you are using auth module#modparam("auth_db", "calculate_ha1", yes)## If you set "calculate_ha1" parameter to yes (which true in this config),# uncomment also the following parameter)#modparam("auth_db", "password_column", "password")# -- rr params --# add value to ;lr param to make some broken UAs happymodparam("rr", "enable_full_lr", 1)# ------------------------- request routing logic -------------------# main routing logicroute{ # 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); }; if (!uri==myself) { # mark routing logic in request append_hf("P-hint: outbound\r\n"); # if you have some interdomain connections via TLS #if(uri=~"@tls_domain1.net") { # t_relay("tls:domain1.net");  # exit; #} else if(uri=~"@tls_domain2.net") { # t_relay("tls:domain2.net"); # exit; #} route(1); };   # if the request is for other domain use UsrLoc # (in case, it does not work, use the following command # with proper names and addresses in it) if (uri==myself) { if (method=="REGISTER") { # Uncomment this if you want to use digest. if (!www_authorize("", "subscriber")) { www_challenge("", "0"); exit; }; save("location"); exit; }; if (!proxy_authorize("","subscriber")) { proxy_challenge("","0"); exit; }; consume_credentials(); lookup("aliases"); if (!uri==myself) { append_hf("P-hint: outbound alias\r\n"); route(1); };             # native SIP destinations are handled using our USRLOC DB if (!lookup("location")) { sl_send_reply("404", "Not Found"); exit; }; append_hf("P-hint: usrloc applied\r\n"); }; route(1);}route[1] { # send it out now; use stateful forwarding as it worksreliably # even for UDP2TCP if (!t_relay()) { sl_reply_error(); }; exit;}Openser.cfg文件分析(openser.cfg File Analysis)现在,配置已经做好准备来对REGISTER事务进行认证了。我们可以将AOR保存在位置数据库中来实现持续性。这就允许我们可以重启服务器而不会丢失AOR记录和影响UACs。另一个重要的方面是,OpenSER现在要对REGISTER请求进行认证。之后我们要对INVITE请求的认证进行实现。现在需要的是UACs的注册认证。loadmodule "mysql.so"loadmodule "auth.so"loadmodule "auth_db.so"MySQL支持可以通过在要加载的模块列表中添加包含mysql.so语句来轻松实现。MySQL模块要在其他模块之前加载。有些模块,例如uri_db,需要依靠MySQL来加载。认证的能力是由auth.so和auth_db.so模块提供的。这些模块被用来使能认证功能。模块uri_db列出了一些认证函数。modparam("auth_db", "calculate_ha1", 1)modparam("usrloc", "db_mode", 2)参数calculate_hal告诉auth_db模块要使用明文密码。我们将使用明文密码来与SerMyAdmin兼容。db_mode参数告诉usrloc模块存储或获取数据库中的AOR记录。if (method=="REGISTER") { # Uncomment this if you want to use digest auth. if (!www_authorize("", "subscriber")) { www_challenge("", "0"); exit; }; save("location");exit; } else if (method=="INVITE") { if (!proxy_authorize("","subscriber")) { proxy_challenge("","0"); exit; }; consume_credentials(); };在上面的代码片段中,我们检查了INVITE和REGISTER方法的认证过程。如果是REGISTER方法,并且证书是正确的,那么www_authorize返回true。在认证完成后,系统保存该UAC的位置信息数据。第一个参数指定了用户被进行认证的域(realm)。Realm通常是域名(domain name)或主机名(host name)。第二个参数告诉OpenSER到底要寻找那一个MySQL表。www_challenge("","0");如果包中不含有Authorize头域,我们则向UAC发送”401 unauthorized”消息。它告诉UAC重新传送包含证书的请求消息。www_challenge命令有两个参数。第一个是UAC用来计算摘要的域(realm)。第二个参数则会影响发送给UAC消息中的qop参数的使用。1表明在摘要中要包含qop。有些话机可能不支持qop。你可以在这种状况下试试将参数设为0。consume_credentials();我们不想冒险将摘要证书发送到服务器上。因此,我们使用函数consume_credentials()函数在将请求中继前移除Authorize头域。if (!proxy_authorize("","subscriber")) {我们使用proxy_authorize()函数来对认证头进行检查。如果我们不检查证书我们会被认为是一个比较开放的中继器。它的参数与www_authorize的参数相同。
 
   使用OpenSER构建电话通信系统——第五章(3)
 
 
 
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!
Openserctl shell脚本
Openserctl工具是安装在/usr/sbin上的shell脚本。被用来使用命令行的方式来对OpenSER进行管理。可以用来进行:l         启动,终止,重启OpenSERl         展示,授权,撤销ACLsl         添加,删除,列出别名l         添加,删除,配置AVPl         管理LCR(low cost routes)l         管理RPIDl         添加,删除,列出订阅者l         添加,删除,展示usrloc表“in-ram”l         监控OpenSER我们将在下面的章节中学习一些它的选项.下面是openserctl help命令的输出信息:/etc/openser# openserctl helpdatabase engine 'MYSQL' loadedControl engine 'FIFO' loaded/usr/sbin/openserctl 1.2 - $Revision: 1.3 $Existing commands:-- command 'start|stop|restart'restart ............................ restart OpenSERstart .............................. start OpenSERstop ............................... stop OpenSER-- command 'acl' - manage access control lists (acl)acl show [] .............. show user membershipacl grant ....... grant user membership (*)acl revoke [] .... grant user membership(s) (*)-- command 'alias_db' - manage database aliasesalias_db show .............. show alias detailsalias_db list ............. list aliases for urialias_db add ...... add an alias (*)alias_db rm ................ remove an alias (*)alias_db help ...................... help message- must be an AoR (username@domain)"- must be an AoR (username@domain)"-- command 'avp' - manage AVPsavp list [-T table] [-u ][-a attribute] [-v value] [-t type] ... list AVPsavp add [-T table] ............ add AVP (*)avp rm [-T table] [-u ][-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))- must be an AoR (username@domain)- must be a string but not AoR-- command 'db' - database operationsdb exec ..................... execute SQL querydb show ..................... 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 ** and 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 groupslcr reload ............................................................................... reload lcr gatewayslcr addgw_grp .................................................................. add gateway group, autocreate grp_idlcr addgw_grp .......................................................... add gateway group with grp_idlcr rmgw_grp ..................................................................... delete the gw_grplcr addgw .............................. add a gatewaylcr addgw ..................... add a gateway with prefixlcr addgw ............... add a gateway with prefix and striplcr rmgw ........................................................................ delete a gatewaylcr addroute .............................................. add a routelcr rmroute .............................................. delete a route-- command 'rpid' - manage Remote-Party-ID (RPID)rpid add ......... add rpid for a user (*)rpid rm ................. set rpid to NULL for a user (*)rpid show ............... show rpid of a user-- command 'speeddial' - manage speed dials (short numbers)speeddial show ....... show speeddial detailsspeeddial list ............. list speeddial for urispeeddial add [] .............................. add a speedial (*)speeddial rm ....... remove a speeddial (*)speeddial help ...................... help message- , must be an AoR (username@domain)- must be an AoR (username@domain)- must be a SIP AoR (sip:username@domain)- a description for speeddial-- command 'add|mail|passwd|rm' - manage subscribersadd .. add a new subscriber (*)passwd ......... change user's password (*)rm ...................... delete a user (*)mail .................... send an email to a user-- command 'cisco_restart' - restart CISCO phone (NOTIFY)cisco_restart ................ restart phone configured for -- command 'online' - dump online users from memoryonline ............................. display online users-- command 'monitor' - show internal statusmonitor ............................ show server's internal status-- command 'ping' - ping a SIP URI (OPTIONS)ping ......................... ping with SIP OPTIONS-- command 'ul|alias' - manage user location or aliasesul show []................ show in-RAM online usersul rm [].... delete user's UsrLoc entriesul add ............ introduce a permanent UrLoc entryul add .. introduce a temporary UrLoc entry-- command 'fifo'fifo ............................... send raw FIFO commandOpenserctl资源文件(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 domainSIP_DOMAIN=voffice.com.br## database type: MYSQL or PGSQL, by defaulte none is loadedDBENGINE=MYSQL## database hostDBHOST=localhost## database nameDBNAME=openser## database read/write userDBRWUSER=openser## database read only userDBROUSER=openserro## password for database read only userDBROPW=openserro## database super userDBROOTUSER="root"## type of aliases used: DB - database aliases; UL - usrloc aliases## - default: noneALIASES_TYPE="DB"## control engine: FIFO or UNIXSOCK## - default FIFOCTLENGINE="FIFO"## path to FIFO fileOSER_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 acceptedACL_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 domainSIP_DOMAIN=voffice.com.br## database type: MYSQL or PGSQL, by defaulte none is loadedDBENGINE=MYSQL## database hostDBHOST=localhost## database nameDBNAME=openser## database read/write userDBRWUSER=openser## database read only userDBROUSER=openserro## password for database read only userDBROPW=openserro## database super userDBROOTUSER="root"## type of aliases used: DB - database aliases; UL - usrloc aliases## - default: noneALIASES_TYPE="DB"## control engine: FIFO or UNIXSOCK## - default FIFOCTLENGINE="FIFO"## path to FIFO fileOSER_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 acceptedACL_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 1000@voffice.com.br/sbin/openserctl add 1001 password 1001@voffice.com.br|      如果你碰到“复制关键字(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 FROMif (!proxy_authorize("","subscriber")) {proxy_challenge("","1");exit;} else if (!check_from()) {sl_send_reply("403", "Forbidden, use From=ID");exit;};consume_credentials();# Verify aliaseslookup("aliases");# Verify the destination (URI)if (is_uri_host_local()) {# -- Inbound to Inboundroute(10);} else {# -- Inbound to outboundroute(11);};} else {#Verify aliases, if found replace R-URI.lookup("aliases");   # Verify the destination (URI)if (is_uri_host_local()) {#-- Outbound to inboundroute(12);} else {# -- Outbound to outboundroute(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 tableappend_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 searchappend_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 callssl_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 flavio@asteriskguide.com sip:1000@asteriskguide.comdatabase engine 'MYSQL' loadedControl engine 'FIFO' loadedMySql 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 processingif (is_method("CANCEL")){if (t_check_trans())t_relay();exit;}t_check_trans();带有上面所有资源的完整脚本(Full Script with All the Resources Above)
# ------------------ module loading ----------------------------------#set module pathmpath="//lib/openser/modules/"# Uncomment this if you want to use SQL databaseloadmodule "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 linemodparam("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 happymodparam("rr", "enable_full_lr", 1)# ------------------------- request routing logic -------------------# main routing logic  route{# initial sanity checks -- messages with# max_forwards==0, or excessively long requestsif (!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 protocolif (!method=="REGISTER")record_route();# subsequent messages withing a dialog should take the# path determined by record-routingif (loose_route()) {# mark routing logic in requestappend_hf("P-hint: rr-enforced\r\n");route(1);};   #CANCEL processingif (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 UDP2TCPif (!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 FROMif (!proxy_authorize("","subscriber")) {proxy_challenge("","1");exit;} else if (!check_from()) {sl_send_reply("403", "Forbidden, use From=ID");exit;};consume_credentials();# Verify aliaseslookup("aliases");if (is_uri_host_local()) {# -- Inbound to Inbound  route(10);} else {# -- Inbound to outboundroute(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 inboundroute(12);} else {# -- Outbound to outboundroute(13);};};}            route[10] {#from an internal domain -> inbound#Native SIP destinations are handled using the location tableappend_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 searchappend_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 callsappend_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-addressopenserctl domain add your-domain对于每一个域重复上面的过程。步骤3:再次尝试注册话机。现在注册过程将正常。实验——使用别名(Lab——Using Aliases)
步骤1:为订阅者1000添加别名#openserctl alias add john@youripordomain sip:1000@youripordomaindatabase engine 'MYSQL' loadedControl engine 'FIFO' loadedMySql password for user 'openser@localhost':|             使用openserrw作为密码                                             |步骤2:从注册为1001的软电话打给John通话完整么?为什么?概要(Summary)
在这一章中,你已经学会了如何将MySQL整合进OpenSER中。现在,我们的脚本可以认证用户,检查TO和FROM头域,按照带内和带外来处理通话了。重要的是要牢记域必须被插进数据库中,因为要支持多域。如果你改变了你的域或IP地址,请记着更新你的数据库。
 
   使用OpenSER构建电话通信系统——第六章(1)
 
 
 
 注:以下文章如需转载,请注明所属作者,转载地址,谢谢! 第六章:使用SerMyAdmin构建用户入口在上一章中,我们用MySQL数据库实现了认证。现在,我们将需要一个工具来帮助用户和管理员们。显然,这个工具必须要比openserctl容易操作。手动对成百上千的用户进行管理是很困难的,所以用户预置工具在其过程中就变得相当重要。在这一章中,将看到SerMyAdmin工具是如何来被创建来帮助构建用户和管理员入口的。本章末,你将能够:l         知道为什么需要一个用户入口进行管理l         安装SerMyAdmin和它的依赖l         配置诸如管理者和用户访问的资源l         添加和删除域l         用你公司的颜色和logo来定制入口SerMyAdmin这段材料本来是写给SerWeb。SerWeb原本是为SER项目开发的。不幸的是,SerWeb和最新版本的OpenSER不兼容。我们没有选择SerWeb的另外一个重要的原因是它被认为有漏洞。OpenSER的一些网页接口缺少很多选项。其中我们曾发现的一种工具是OpenSER administrator。这个工具正在使用”Ruby on Rails”开发。似乎这是一个非常好的OpenSER服务器管理工具,但是它却不允许与SerWeb相同的方式来提供用户接口,并且其缺少多域(multi-domain)支持。OpenSER administrator能够在http://sourceforge.net/projects/openseradmin找到。既然构建OpenSER入口的工具找不到,那么我们决定使用JAVA建造我们自己的工具,命名为SerMyAdmin。我们现在已经准备好使用这样的工具来构建本书。它是按照GPLv2授权的在Grails上开发(Groovy on rails)。可以在http://sourceforge.net/projects/sermyadmin上下载。你这儿看到的是一套独立的工具。在我们的轨迹路线中,我们打算将SerMyAdmin整合进行Liferay入口。使用类似Liferay之类的内容管理系统将使你构建入口的工作更加容易。SerMyAdmin项目可以在sermyadmin.sourceforge.net上找到。它的想法是改进OpenSER数据库的管理。SerMyAdmin在GPLv2下许可。
 实验——安装SerMyAdmin(Lab——Installing SerMyAdmin)SerMyAdmin使用Grails框架,所以它需要一个应用服务器。你可以自行进行选择,如IBM WebSphere,JBoss,Jetty,Tomcat等等。在这本书中,我们使用Apache Tomcat,因为它是免费的容易安装。因为我们要使用到一些Java1.5的特性,我们需要Sun的Java JDK,而不是免费的可供选择的GCJ。步骤1:为SerMyAdmin创建管理者:mysql –u rootuse openserINSERT INTO 'subscriber' ( 'id' , 'username' , 'domain' , 'password', 'first_name' , 'last_name' , 'email_address' , 'datetime_created' ,'ha1' , 'ha1b' , 'timezone' , 'rpid' , 'version' , 'password_hash' ,'auth_username' , 'class' , 'domain_id' , 'role_id' )VALUES (NULL , 'admin', 'openser.org', 'senha', 'Admin', 'Admin', 'admin@openser.org', '0000-00-00 00:00:00', '1', '1', '1', '1', '1', NULL ,'admin@openser.org', NULL , '1', '3');步骤2:下一步我们要做的是更新我们的源列表以使用contrib仓库和non-free包。我们的/etc/apt/sources.list文件应该看起来是下面这个样子:# /etc/apt/souces.listdeb http://ftp.br.debian.org/debian/ etch main contrib non-freedeb-src http://ftp.br.debian.org/debian/ etch main contrib non-freedeb http://security.debian.org/ etch/updates main contrib non-freedeb-src http://security.debian.org/ etch/updates main contrib non-free/etc/apt/sources.list注意到我们只是在我们的仓库定义后面添加了关键字contrib和non-free。步骤3:使用下面的命令来更新包列表。openser:~# apt-get update步骤4:运行下面的命令安装Sun’s Java1.5openser:~# apt-get install sun-java5-jdk步骤5:确认你正在使用Sun’s Java。请运行下面的命令以告诉Debian你想要使用Sun’s Java作为你的默认的Java应用。openser:~# update-java-alternatives -s java-1.5.0-sun步骤6:到目前为止,如果每一步都顺利完成,那么你应该运行下面的命令以得到一个类似下面的输出。openser:~# java -versionjava version "1.5.0_14"Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_14-b03)Java HotSpot(TM) Client VM (build 1.5.0_14-b03, mixed mode, sharing)步骤7:安装Tomcat。你可以在http://tomcat.apache.org/download-60.cgi.获得Tomcat。为了安装Tomcat,只需要运行下面的命令:openser:/usr/local/etc/openser# cd /usr/localopenser:/usr/local# wget http://mirrors.uol.com.br/pub/apache/tomcat/tomcat-6/v6.0.16/bin/apache-tomcat-6.0.16.tar.gzopenser:/usr/local# tar zxvf apache-tomcat-6.0.16.tar.gzopenser:/usr/local# ln -s apache-tomcat-6.0.16 tomcat6步骤8:为了在你的服务器初始化中启动Tomcat,请复制下面的脚本到/etc/init.d/tomcat6。#! /bin/bash –e#### BEGIN INIT INFO# Provides: Apache’s Tomcat 6.0# Required-Start: $local_fs $remote_fs $network# Required-Stop: $local_fs $remote_fs $network# Default-Start: 2 3 4 5# Default-Stop: S 0 1 6# Short-Description: Tomcat 6.0 Servlet engine# Description: Apache’s Tomcat Servlet Engine### END INIT INFO## Author: Guilherme Loch Góes #set -ePATH=/bin:/usr/bin:/sbin:/usr/sbin:CATALINA_HOME=/usr/local/tomcat6CATALINA_BIN=$CATALINA_HOME/bintest -x $DAEMON || exit 0. /lib/lsb/init-functionscase "$1" instart)echo "Starting Tomcat 6" "Tomcat6"$CATALINA_BIN/startup.shlog_end_msg $?;;stop)echo "Stopping Tomcat6" "Tomcat6"$CATALINA_BIN/shutdown.shlog_end_msg $?;;force-reload|restart)$0 stop$0 start;;*)echo "Usage: /etc/init.d/tomcat6 {start|stop|restart}"exit 1;;esacexit 0告诉Debian在系统启动时运行你的脚本;我们使用下面的命令实现之:openser: chmod 755 /etc/init.d/tomcat6openser:/etc/init.d# update-rc.d tomcat6 defaults 99步骤10:为了确认每一步都运行正确,重启服务器,并且在你的浏览器中打开URL http://localhost:8080;如果一切顺利的话,你会看到Tomcat的开始页面。步骤11:为Tomcat安装MySQL驱动使得SerMyAdmin可以访问你的数据库。这个驱动可以在http://dev.mysql.com/downloads/connector/j/5.1.html上找到。你可以下载驱动然后解包,之后将connector复制到Tomcat的共享库文件夹下,如下所示:openser:/usr/src# tar zxf mysql-connector-java-5.1.5.tar.gzopenser:/usr/src# cp mysql-connector-java-5.1.5/mysql-connector-java-5.1.5-bin.jar /usr/local/tomcat6/lib步骤12:为SerMyAdmin声明数据源以使其链接到OpenSER的数据库上。你可以在/usr/local/tomcat6/conf/context.xml上做些修改以实现之:文件看起来是下面这个样子:在上面的文件中,请按照你的环境修改高亮显示的参数。SerMyAdmin可以安装在其他的机器上,而不仅仅是拥有数据库的服务器上。当规模需要的时候可以这样做。在Debian上默认安装后的MySQL只接收本地请求,所以你应该修改文件/etc/mysql/my.cnf,让MySQL能够接收来自外部主机的请求。步骤13:创建一个用户(可以参照文件context.xml)。这个用户需要访问该数据库的权限。请运行下面的命令:openser:/var/lib/tomcat5.5/conf# mysql -u root –pEnter password:Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 14Server version: 5.0.32-Debian_7etch5-log Debian etch distributionType 'help;' or '\h' for help. Type '\c' to clear the buffer.mysql> grant all privileges on openser.* to sermyadmin@'%' identified by 'secret';Query OK, 0 rows affected (0.00 sec)步骤14:我们已经接近完成。下一步工作就是部署SerMyAdmin WAR文件了。请下载并复制文件serMyAdmin.war到Tomcat的 webapps文件夹下。重启以激活更改。openser:/usr/src# cp serMyAdmin-0.4.war /usr/local/tomcat6/webapps/serMyAdmin.waropenser:/usr/src# invoke-rc.d tomcat6 restart不用担心数据库的修改;SerMyAdmin将自动的为你进行处理以适应其改变。步骤15:配置Debian的MTA(Message Transfer Agent)以允许SerMyAdmin发送一封确认邮件给新用户。运行下面的命令配置Exim4(Debian默认的MTA)。询问你公司的邮件管理员。openser:/# apt-get install exim4openser:/# dpkg-reconfigure exim4-config映入眼帘的是一个以对话框为基础的配置菜单;在这个菜单上,要着重注意两个选项:邮件配置的通用类型(General type of mail configuration),这个选项应该被配成Internet Site,这样我们就可以直接使用SMTP来发送和接收邮件;转发邮件的域,这个选项应该被配成从SerMyAdmin发出的邮件你想要其表现的来自何处的域。步骤16:定制文件 /usr/local/apache-tomcat-6.0.16/webapps/serMyAdmin-0.3/WEB-INF/spring/resource.xml,这个文件包含指定使用哪个email服务器来发送邮件的参数和希望如何显示这些邮件的出处的参数。下面是这个文件的一个例子:localhostadmin@sermyadmin.org第一个要改变的参数是我们用来发送邮件的服务器。第二个则是那些邮件要显示的从何而来的参数。再次重启Tomcat,此时我们已经准备好要开始了。当你将浏览器指向http://:8080/serMyAdmin,你将看到登录页面,正如我们在本章开始时呈现的那样。基本任务(Basic Tasks)现在你能够使用SerMyAdmin来做很多工作了。在这一章中我们将向你展示如何创建和管理新的用户和组。下一章,我们将使用SerMyAdmin做些其他工作,如管理信任列表和LCR模块。注册一个新用户(Registering a New User)只需简单的在登录页面上的Register按钮点击一下就可以注册一个新的用户了。
 填上下面这些,Username,Password,Domain,Email,First Name,Last Name,Caller Id和确认码(confirmation code)。按下屏幕的最下方的Create按钮。用户将被加入数据库。系统管理员和该用户都将收到一封关于注册的电子邮件。在用户可以打电话前,管理员必须要同意该用户。同意新用户(Approving a New User)按照下面的步骤一步一步做来同意一个新用户:步骤1:用admin@localhost帐号登录,密码是在OpenSER安装时创建的openserrw。安装时已经为每一个用户创建了叫做Role的新的属性。这个属性的目的是用来区分普通用户,域管理员和通用管理员的。Admin用户自动被设置为通用管理员(Global Administrator)。这个新的属性将帮助我们提供多域的支持。步骤2:选择菜单中Registered Users条目。 
 在上面的屏中,选择你想要添加用户,选中对应复用框。按下Approve按钮添加用户;该用户将从register_user表中移除,而被转移到订阅列表中,然后他将能够注册到OpenSER并可以打电话。
步骤3:现在用户会显示在你的用户列表中了。点击菜单目录中的用户检查之。
 用户管理(User Management)你能够查看,添加,编辑和删除用户菜单上的用户了。当你点击它时,所有的用户都会显示在你的系统上。
 要添加新用户的话,必须要点击“New User”链接。点后你将转到下面的页面上:
你要完全的填满上一张图片中的空档,并点击“Create”链接,然后该用户会被添加到订阅表(subscriber table)中。之后会显示下面的图片:
 在这个页面中,你可以点击“Edit”来编辑插入的信息,点击“Delete“来删除该用户。在“User List“页面中,你可以基于username,domain,和email对用户进行搜索;只要填上所需的条件,然后点击”Search“链接即可。下面的页面中,我们使用用户名jdoe来对用户进行搜索。点击”Search“;页面将转移到符合你所输条件的用户列表(user list)上去。
 域管理(Domain Management)你可以像管理你的用户那样来管理你的域。点击“Domains”得到一张域列表(domain list)。你可以在其中进行诸如添加一个新的域,删除一个已经存在的域等类似的操作。这里要注意的很重要的一点就是,SerMyAdmin不允许没有域的用户存在,所以当你删掉一个域后,你也就删除掉了所有属于该域的所有用户。接口定制(Interface Customization)网站的布局,SerMyAdmin使用SiteMesh框架,所以按照你自己的品位来定制SerMyAdmin的显示效果是非常简单的。SiteMesh显示的页面是基于一份模板,这份模板可以在openser:/usr/local/apache-tomcat-6.0.16/webapps/serMyAdmin-0.3/WEB-INF/grails-app/view/layouts中找到。在这里,你将找到main.gsp和notLoggedIn.gsp文件,这些文件是用来控制页面如何显示的Groovy服务器页面(Groovy Server Pages)。SiteMesh使用HTML元标记来选择要使用哪个布局;这些标记应该可以在每个页面的头元素(head element)中找到,如果一个页面在头元素中含有标记,那么SiteMesh将使用main.gsp布局显示之。你可以按照你的意愿对main.gsp和notLoggedIn.gsp进行修改,但是要了解的很重要的一点是将使用这个布局来保持页面的head和body标志。另外一件要知道的事情是被用来整理页面碎片;这些页面碎片是GSP文件,它们的文件名以下划线打头。为了使用你自己的logo来替代SerMyAdmin的,只需要将你的logo放到/usr/local/apache-tomcat-6.0.16/webapps/serMyAdmin-0.3/images,并对布局文件中的指向logo_voffice.png的标志进行修改,如下:在上面我们仅仅是通过改变粗体显示的参数就用我们自己的logo替代了SerMyAdmin的logo。你也可以通过修改CSS文件来改变SerMyAdmin的显示效果和感觉,CSS文件可以在/usr/local/apache-tomcat-6.0.16/webapps/serMyAdmin-0.3/css中找到;在main.css中,我们将能够发现每一个改变SerMyAdmin行为的类(class)。例子:如果我们使用下面的参数来改变这个文件中的背景类(background class):body { background: #00f; color: #333; font: 8px verdana, arial, helvetica, sans-serif;}我们将能够在最后得到如下的页面:
这个页面不是世界上最好看的,但是你可以按照这个例子的方法来是它看起来更好一些。概要(Summary)在这一章中,你已经学到了为什么拥有一个用户和管理入口是如此的重要。它是软件中你应该放大部分精力在其上的部分。但是一些VoIP服务提供商并没有分配必要的时间和资源在构建入口这项重要的工作上。OpenSER是一个令人惊讶的SIP代理,但是SIP代理也只是VoIP服务提供商的部件的一员而已。没有好的管理和用户接口,VoIP提供项目很容易失败。SerMyAdmin就是我们在此方面对你项目的一个贡献。它是利用JAVA语言,使用Groovy on Rails架构进行开发,按照GPL版本2进行许可的系统。你已然已经学会了如何安装,管理用户和域和如何定制显示效果。这个工具可以做的事情还很多,在下一章我们将向你展示其更多的功能。
 
   使用OpenSER构建电话通信系统——第七章(1)
 
 
 
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!第七章:与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); }
 
  使用OpenSER构建电话通信系统——第七章(2)
 
 
 
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!
实验——用Asterisk做PSTN网关(Lab——Using Asterisk as a PSTN Gateway)
使用本章提供的框架,写一个脚本将打给PSTN的通话传给PSTN网关。你可以使用phpMyAdmin或是MySQL命令行向数据库插入数据。步骤1——使用SerMyAdmin向授信表中添加网关地址:
如果需要或是如果你觉得方便,可以使用MySQL命令行接口来完成同样的事情:#mysql –u openser –p -- enter your mysql password -- mysql> use openser; mysql> INSERT INTO trusted ( src_ip, proto, from_pattern ) VALUES ( '10.1.30.22', 'any', '^sip:.*$'); 上面的记录告诉OpenSER脚本要允许符合下面条件的请求:来自IP地址10.1.30.22,可以是任何的传输层协议,与正则表达式“^sip:.*$”相符合。如果你不想重新加载OpenSER,你可以使用下面的命令。#openserctl fifo trusted_reload步骤2——在域表中包含你的服务域。(如果之前你没有做过)openserctl domain add sermyadmin.org你也可以使用SerMyAdmin来做这件事。
 步骤3——在组中包含用户(local,ld和int)#openserctl acl grant 1000@sermyadmin.org local #openserctl acl grant 1000@sermyadmin.org ld #openserctl acl grant 1000@sermyadmin.org int #openserctl acl grant 1001@sermyadmin.org local 要使用SerMyAdmin,转向下面这一屏:
 步骤4——配置Asterisk做网关。对于OpenSER来说,两个非常流行的网关是Asterisk和Cisco AS5300。其他厂商的网关也可以使用;检查他们的文档说明作为配置的指导。让我们看看怎样配置带两个FXO的Cisco 2601和带一个E1 PSTN卡Asterisk。|    警告:防止将SIP包直接发送给网关是很重要的。SIP网关        ||    应该在gateway的前面,并且要有防火墙防止用户直接将SIP    ||    请求发送给网关。                                                                             |步骤5——建立Asterisk服务器或是Cisco网关我们现在认为Asterisk网关的PSTN方面已经配置好了。让我们来改变我们网关的SIP配置(sip.conf)和它的拨号方案(extensions.conf)。我们将配置Asterisk来处理从PSTN来的或是打给PSTN的通话。Asterisk的一些基本知识是需要的。下面是最简单的一个能够让Asterisk与OpenSER进行交互的配置。请按照你的布局对这个脚本进行调整。|           警告:只允许来自你的SIP服务器的SIP包传到你的           ||           asterisk服务器。不允许来自其他地方的SIP包。你            ||           可以用IP Tables来达到目的,如果你还是有疑问,请         |
       |           资讯Linux安全专家。                                                         |Asterisk网关(sip.conf)
[general] context=sipincoming #calls incoming from the SIP proxy to be terminated in the PSTN lines [sipproxy] #calls incoming from the PSTN to be forwarded to clients behind the SIP #proxy type=peer host=10.1.30.22 Asterisk (extensions.conf) [general] [globals] [sipincoming] exten=>_[0-9].,1,Dial(Zap/g1/${EXTEN:1}) exten=>_[0-9].,2,hangup() [sipoutgoing] # If you have a digital interface use the lines below exten=_[0-9].,1,Answer() exten=_[0-9].,2,dial(SIP/${EXTEN}@sipproxy) exten=_[0-9].,3,Hangup() #If you have analog FXO interfaces use the lines below. exten=s,1,Answer() exten=s,2,dial(SIP/${EXTEN}@sipproxy) exten=s,3,Hangup() Cisco 2601 网关(Cisco 2601 Gateway)下面的解释可以帮的上忙,但是要完成这个配置,Cisco网关的一些知识也是需要的。Cisco网关的通话路由是由指令拨号点(instruction dial peer)完成的。由拨号点voice1和2两条POTS(plain old telephone system)线路的说明可以看到,任何以9打头,其他数字跟随的拨号通话都将会被前转到PSTN的1/0口或1/1口。而在拨号点voice123 voip线路的说明中,以1-9开始,任何数字跟随的拨号通话都将被直接转到IP地址为10.1.3.22的SIP代理上。voice class codec 1 codec preference 2 g711ulaw!interface Ethernet0/0  ip address 10.1.30.38 255.255.0.0  half-duplex!ip classlessip route 0.0.0.0 0.0.0.0 10.1.0.1no ip http serverip pim bidir-enable!voice-port 1/0!voice-port 1/1!mgcp profile default!! The dial-peer pots commands will handle the calls coming from SIP!dial-peers. Any call matching 9 followed by any number of digits willbe !forwarded to the PSTN with the 9 striped.dial-peer voice 1 pots  destination-pattern 9T          port 1/0!dial-peer voice 2 pots  destination-pattern 9T    port 1/1!!The dial-peer voip commands will handle the calls coming from thepots !dial peers (PSTN). You can prefix a number (80 in this example)and send the DID number ahead.!dial-peer voice 123 voip  destination-pattern ....T prefix 80  forward all  session protocol sipv2  session target ipv4:10.1.30.22  dtmf-relay sip-notify步骤6——测试配置,打出和接受通话使用LCR(Using LCR[Least Cost Routes])
上面的配置是好的,如果你只有一些网关,那么路由通常是不用改变的。然而,大多数的SIP服务提供者会频繁的改变路由。除此之外,他们中的大多数都有很多的网关并且会链接到更大的VoIP服务提供商那里。如果每次路由发生改变,那么修改脚本是比较麻烦的。那么LCR模块就有用武之地了。它允许你可以将路由器和网关插入数据库并且可以动态的更改他们以适应你所需求的系统要求。想象一下下面这种情况:你有两个大的服务商,一个在欧洲,一个在亚洲。现在,你想要将本地和长途通话送到你自己的网关,到荷兰,德国和法国的通话送到欧洲的服务提供商,到日本和澳大利亚的通话送到亚洲的服务提供商。如果亚洲的服务停了,那么要求能够将通话转向你自己的网关。LCR模块(The LCR Module)
LCR模块有两种应用功能。最重要的是将一个请求连续的前转到一个或多个网关(load_gws()和next_gw())。这些函数用来将通话送到网关上,当然你也可以使用failure_route和next_gw()将故障转移到其他网关上。你可以给网关分配优先级以决定到底哪一个网关会被优先选择。你也可以使用LCR模块中的load_contacts()和next_contacts()函数前转基于q值的contacts。配置图表(Configuration Diagram)要实现上面的这幅图中的内容,我们需要了解三张表:lcr,gw和gw_grp。VoIP服务提供商拨号方案(VoIP Provider Dial Plan)
是时候开始为我们的VoIP服务提供商仔细的作一份拨号方案的时候了。我们将实现E.164编号方案(numbering scheme),将通话送到正确的网关上。当客户拨打本地号码时,我们在使用load_gw()函数选择网关之前,先使用正则表达式将其号码转换成它们相应的E.164地址。除了订阅者,这个服务提供商不会接受其他用户的任何非E.164格式的号码。我们假设用户接口能够帮用户拨出E.164号码。
 LCR表(The LCR Table)
在lcr表中,我们将实现路由。lcr的各个字段描述如下。本地通话和长途通话将以号码+1305作为前缀。
  l        Prefix——这个前缀要和URI(phone number)的用户部分(user part)相符合l        From_uri——From_uri可能会包含需要匹配的URI。有时候当你想要用主叫用户信息来进行路由的时候使用这个字段。l        Grp_id——Grp_id标识网关组(gateway group)l        Priority——网关优先级(Gateway priority)网关表(The Gateways Table)
 
 l         gw_name——网关名称(Gateway name)l         grp_id——网关组标识(Gateway group identification)l         ip_addr——网关IP地址(IP address of the gateway(对于1.2.x一下的版本,使用反转的十进制记法))l         port——(UDP/TCP端口)l         uri_scheme——sip(1),sips(2)l         transport——udp(1),tcp(2),tls(3)l         strip——删除的字符的数量l         prefix——发往网关前应用的前缀网关组表(The Gateway Groups Table)
网关组表只是用来进行管理之用。与网关的名字息息相关。添加,删除和显示LCR和网关(Adding,Removing,and Showing LCR and Gateways)
你可以使用openserctl或SerMyAdmin来添加,删除,和吸纳是LCR表和网关表。你也可以手动进行该项工作。|                 如果你正使用OpenSer1.0.x或1.1.x,需要观察,这个字段的                 ||                 IP地址要是反转的十进制记法。                                                                           |要将IP地址转换成十进制反转记法。需要做如下计算:l         IP Address=a.b.c.dl         反转十进制记法的IP地址 = d*224+c*216+b*28+a如果你不想计算,那么就使用openserctl工具。它对于所有的版本都工作良好。在OpenSER的1.2.x版本中,你可以将IP地址以通常的十进制点记法的格式直接插入数据库。Openserctl LCR相关的命令(Openserctl LCR-Related Commands) 小贴士(Notes):
l         IP地址必须以点记法的格式键入,如1.2.3.4l         必须以整数或文本的方式键入:n         Transport ‘2’ 等同于 transport ‘tcp’n         Scheme:1=sip,2=sips;transport:1=udp,2=tcp,3=tls例子(Examples):
lcr addgw_grp usa 1lcr addgw level3 1.2.3.4 5080 sip tcp 1lcr addroute +1 ' ' 1 1
 
   使用OpenSER构建电话通信系统——第七章(3)
 
 
 
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!
实验——使用LCR特性(Lab——using the LCR Feature)让我们使用LCR来执行一个简单的实验。对于这个实验,我们需要一个OpenSER服务器,两个网关和一个IP电话。你可以使用asterisk作为网关和虚拟机很容易的模拟这个实验环境。步骤1:使用网关构建LAB。配置一台名为usa1的网关来接收以+1作为前缀的通话,另配置一台名为br1的网关来接收以+55作为前缀的通话。在网关中,你可以在将通话发送到PSTN之前添加前缀或删除一个数字。可以使用strip()和prefix()核心函数来加前缀和删除数字。要了解更多的细节,请登录www.openser.org。步骤2:从http://www.sermyadmin.org/openser/openser.lcr下载配置文件并复制到openser.cfg。cd /etc/openserwget http://www.sermyadmin.org/openser/openser.lcrcp openser.lcr openser.cfgThe script can be seen below with the modifications highlighted.# ------------------ module loading ----------------------------------#set module pathmpath="//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"loadmodule "lcr.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 logicroute{# # -- 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()) { # marca a logica de roteamento no pedido append_hf("P-hint: roteado por loose_route\r\n"); route(1); };   # # -- 3 -- Determine Request Target # if (method=="REGISTER") { route(2); } else { route(3); };}  route[1] { # # -- 4 -- Forward request to target # ## Forward statefully t_on_failure("1"); 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 if (!load_gws()) { sl_send_reply("503", "Unable to load gateways"); exit; }   if(!next_gw()){ sl_send_reply("503", "Unable to find a gateway"); exit; } route(1); exit;}  route[10] { #from an internal domain -> inbound #Native SIP destinations are handled using the location table #Gateway destinations are handled by regular expressions #In our example we will normalize the number to e164 +1305XXXXXX #to facilitate the posterior billing. append_hf("P-hint: inbound->inbound \r\n"); if (uri=~"^sip:[2-9][0-9]{6}@") { if (is_user_in("credentials","local")) { # Assuming your country is USA (+1) and area code (305) prefix("+1305"); 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")) { prefix("+"); 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")) { strip(2); prefix("+"); 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;}failure_route[1] { if(!next_gw()) { t_reply("503", "Service not available, no more gateways"); exit;} t_on_failure("1"); t_relay();步骤3:使用ngrep抓包并确保包都能够到达正确的目的地。步骤4:按照下面的表,使用openserctl lcr命令添加路由和网关lcr 网关组(lcr Gateway Groups)你可以使用SerMyAdmin来插入网关组。
Lcr 网关(lcr Gateways)
Lcr 路由(lcr Routes)
 步骤5:对任何以+1或+55开头的号码进行测试步骤6:关闭网关br1,并在此测试+55的通话。通话此时应该走另一个可供选择的网关,因为现在的br1已经关闭了。保证re-INVITES的安全(Securing re-INVITES)
既然我们连上了PSTN,那么对于安全性的考虑就很重要了。Re-INVITES在松散路由区段中被处理。这些re-INVITES不会被要求他们的凭据。为了增强安全性,需要添加下面脚本的内容到你的loose_route区段中。如果是接续请求(has_totag()),需要其拥有ROUTE头。如果它没有该头(由函数loose_route()检查得到),我们将丢弃请求并给出错误“404,not here”。如果你有疑问,请查看文件openser.章节7-3。if (has_totag()) { # sequential request withing a dialog should # take the path determined by record-routing if (loose_route()) { #Check authentication of re-invites if(method=="INVITE" && (!allow_trusted())) { if (!proxy_authorize("","subscriber")) { proxy_challenge("","1"); exit; } else if (!check_from()) {sl_send_reply("403", "Forbidden, use From=ID"); exit; }; }; route(1); } else { sl_send_reply("404","Not here"); } exit;}黑名单和“473/Filtered Destination”消息(Blacklists and “473/Filtered Destination messages”)
DNS黑名单是用来进行DNS故障转移的特性。如果你发送一通通话给一个网关,并且这个网关不能被访问或是返回5××或6××错误码,OpenSER使用了一个叫做“dns blacklist”的资源,并将你的网关插入黑名单。在能够继续发送前,你的网关在此黑名单将持续4分钟(这个数字可以在编译时在blacklists.h中被修改)。当网关在黑名单中时,如果你试图继续向该网关发送通话,那么你将收到“473/Filtered Destination”消息。要关闭该特性(默认是打开的),可以使用下面的语句:disable_dns_blacklist=yes你也可以创建你自己的永久的网关黑名单或暂时的服务之外的名单。概要(Summary)
在本章中,你已经学会如何配置OpenSER来将通话前转到网关。注意安全性是很重要的。使用permissions模块你能够允许网关避开摘要认证(digest authentication)并允许他们只验证IP地址。组模块对于控制由UAC到来的访问尤其重要。确认re-INVITES的凭据也是很有趣的工作。从你连上PSTN的那一刻起,就要特别注意通行欺骗(toll fraud)。我建议你找一个安全专家定期检查你的环境。还要经常的对你的通话记录进行分析以检查出不正常的通话活动。
 
   使用OpenSER构建电话通信系统——第八章(1)
 
 
 
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!
第八章:通话前转和语音邮件(call forward and voice mail)
通话前转是VoIP服务提供商提供的重要的特性。它的实现有两种方式,一种是派生(forking),一种是重定向。当使用派生时,新的通话分支将会被创建,在首选的目的地失败时(busy或timeout),它会向新的目的地发送新的INVITE消息。而使用重定向,代理只是会向发起者发送应答信息,并将新的重定向的通话地址告诉发起者。本章结束你将能够:l         描述诸如派生和重定向的概念l         实现通话前转l         实现忙线时的通话前转l         使用AVP资源存储通话前转数据l         使用失败路由块(failure route)前转没有应答和忙线的通话在本材料中,我们只适用派生,因为它比重定向更加安全,而且还能够允许我们对通话进行计费。重定向的方法对于VoIP服务提供者来说几乎是无用的,因为代理不在通话的路径当中,在这种情况下根本无法计费。让我们来检验一下我们的进度。VoIP服务提供商的解决方案中有许多部件。为了避免失去对整体的把握,我们在每一章中都会展示下面的这幅图。在这一章中,我们将同媒体服务器合作(采用Asterisk Voice Mail)。还有许多其他的媒体服务器可供选择,如SEMS(SIP Express Media Server www.iptel.org/sems),Yate,FreeSwitch等等。我们之所以选择Asterisk是因为它非常流行。“前转”对于在某些情况下将通话送到媒体服务器是非常重要的特性。本章我们会介绍语音邮件(voicemail)的使用。媒体服务器还可以被很多应用使用,如:IVRs,播放提示信息,播放语音文本,和声音识别等。请记住,SIP代理从不会去处理媒体,所以在需要对媒体进行的处理的情况下,你需要媒体服务器。
 本章学习完后,VoIP服务提供商将能够使用连续派生将无应答和忙线的通话发送到语音邮箱服务器上。通话前转(Call Forwarding)
本章中,我们将实现三种通话前转。此前转对于语音邮箱的操作很重要。l         盲转(Blind call forwarding)——所有打给某一电话号码的通话的INVITE请求都会被立即重新指向存储在user_preferences表中的电话号码。SIP路由器将派生出一路指向新地址的通话。配置了前转的话机无论是否注册上,都不会振铃。l         忙线前转(Forward on busy)——在这种情况下,我们将使用failure_route特性来拦截“486 Busy”消息,并创建一个新的通话将INVITE请求发送到最终的目的地。l         无应答前转(Forward on no answer)——如果话机对于INVITE请求的回复是“408 Request Timeout”,那么OpenSER也会使用failure_route特性来拦截该消息并创建新的通话将INVITE消息发送到最终的目的地。所有的呼叫前转的目的地址都被存储在叫做user_preferences的表中。我们在这一章将介绍如AVPs(Attribute-Value Pairs)和伪变量(pseudo-variables)的一些新的概念。AVPs是OpenSER的核心使用。AVPOPS(Attribute-Value Pairs Operations)模块提供了几个操作AVPs(如,同SIP请求和数据库进行交互,使用字符串和正则表达式进行操作。)的函数。 伪变量(Pseudo-Variables)
伪变量是系统变量,你可以在你的脚本中将其作为参数或内部函数(inside functions)使用。在脚本执行之前,这些变量会被相应的值替换掉。有些模块可以接受伪变量,如:l         ACCl         AVPOPSl         TEXTOPSl         UACl         XLOG伪变量的名字总是以“$”开头。如果你想要在你的脚本中使用$字符,那么你必须像这样进行换码——$$。有一个伪变量的预定义集合。OpenSER1.1中使用的OpenSER伪变量如下:$ar
 Auth realm
 
$au
 Auth username
 
$br
 Request's first branch
 
$bR
 All Request's branches
 
$ci
 Call-ID header
 
$cl
 Content length
 
$cs
 Cseq
 
$ct
 Contact Header
 
$cT
 Content Type
 
$dd
 Domain of destination URI
 
$di
 Diversion header URI
 
$dp
 Port of destination URI
 
$dP
 Transport protocol of destination URI
 
$ds
 Destination set
 
$du
 Destination URI
 
$fd
 From URI domain
 
$fn
 From display name
 
$ft
 From Tag
 
$fu
 From URI
 
$fU
 From URI username
 
$mb
 SIP message buffer
 
$mf
 Flags
 
$mF
 Flags in hexadecimal
 
$mi
 SIP message ID
 
$ml
 SIP message length
 
$od
 Domain in SIP request's original URI
 
$op
 Port of Sip request's original URI
 
$oP
 Transport protocol of SIP request's original URI
 
$ou
 Request's original URI
 
$oU
 Username in SIP request's original URI
 
$pp
 Process id
 
$rd
 Domain in SIP request's URI
 
$rb
 Body of request/reply
 
$rc
 Returned code
 
$rm
 SIP request's method
 
$rp
 SIP request's port of R-URI
 
$rP
 Transport protocol of SIP request URI
 
$rr
 SIP reply reason
 
$rs
 SIP reply status
 
$rt
 Refer-to URI
 
$ru
 SIP request's URI
 
$rU
 Username in SIP request's URI
 
$Ri
 Received IP address
 
$Rp
 Received Port
 
$si
 IP source address
 
$sp
 Source port
 
$td
 To URI domain
 
$tn
 To display name
 
$tt
 To tag
 
$tu
 To URI
 
$tU
 To URI username
 
$Tf
 String formatted time
 
$Ts
 Unix time stamp
 
$ua
 User agent header
 
$re
 Remote-Party-ID header URI
  AVP(Attribute-Value Pair)概览(AVP Overview)
属性-值对的操作相当于是允许了对用户的首选项(user preferences)进行访问和操作。AVP可以看作是与标识(字符串或整数)相关联的一个值。在OpenSER的处理过程中,AVP与事务捆绑在一起。当事务开始时,AVP被分配,当其结束时,则被释放。AVPs的出现创造了一些服务实现和用户或域名的用户首选项处理的新的可能性。它们可以在配置脚本中被直接使用并从MySQL数据库中加载数据。属性-值对的引用与变量的引用非常相似。         $avp(id[N])Where ID is:l         si : name —— AVP标识名称。“s”和“i”分别表示字符串和整数。l         name —— 别名AVP的名称。可以是字符串,也可以是整数。例子:         $avp (i: 700)         $avp (s: blacklist)对于了解Asterisk的人来说,AVPOPS模块之于OpenSER就相当于AstDB函数之与Asterisk。然而,实现方式非常不同,AVPs更加强大,允许一些更加高级的特性,如数据库的查询和直接将数据插入SIP包等。有许多与AVPs相联系的函数如下:l         avp_db_load:将AVPs从数据库加载至内存l         avp_db_store:将AVPs存进数据库l         avp_db_delete:从数据库中删除AVPsl         avp_db_query:进行数据库查询并将结果存进AVP中l         avp_delete:从内存中删除AVPsl         avp_pushto:将AVP的值插入sip消息l         avp_check:使用一个操作符和一个值来检查AVP的值l         avp_copy:拷贝AVP到另一个l         avp_printf:格式化一个字符串到AVPl         avp_subst:查找并替换一个值到AVPl         avp_op:允许在AVPs上进行算术操作l         is_avp_set:检查这个AVP名字是否被设置l         avp_print:打印内存中的所有AVPs(为了debug)由于文档中的这些函数,你可以对句法进行检查。现在,我们必须要了解如何使用avp_db_load和avp_pushto,它们将用在我们的脚本中。关于AVPs在http://www.voice-system.ro/docs上有一篇非常棒的教程。AVPs不是很简单。但是如果你把它想象成一对简单的属性和值,那么它们其实也不是那么的复杂。然而,数据库中的AVPs的加载是非常令人费解的。默认的表是usr_preference。有时候我们想要的值并不是和一个特定的用户相关,而是同一个域相关。不管怎样,所有从数据库中被加载的AVPs都来自usr_preference表。举例:对于呼叫前转,我们有一个与用户相关的前转呼叫。确实是一个usr_preference(用户优先)。让我们来查看usr_preference表的结构。 Id是一个自增域。l         uuid是一个唯一的用户标识l         username是用户名l         domain代表域l         attribute(AVP名称)l         type(0–AVP str|Val Str, 1–AVP str|Val Int, 2–AVP int|Val Str,3-AVP int|Val int)l         value(AVP值)l         last modified(最后修改的日期)AVPs可以与一个用户相关联也可以与一个域相关联。所以,你能够使用这两个参数中的任何一个来加载AVPs。你可以将AVP与uuid(唯一的用户ID)相关联,与一个用户名相关联(单域设置),或者与用户名和域建立多域设置关系。函数 avp_db_load的第一个参数是源(source),第二个是avp_name。所以,下面的函数将以字符串的形式加载与被请求用户名中的URI相符合的用户的AVP名称为callfwd的AVP值。(avp_db_load("$ruri/username","s:callfwd")之后,我们将把这个AVP插进SIP包中,以将原来的$ruri进行改造。avp_pushto("$ruri","s:callfwd");换句话说,如果这个用户设置了对应的呼叫前转号码,那么这通通话将不会呼给原来的用户,而是呼给存储在AVP s : callfwd中的用户。呼叫前转的特别之处就在于将呼叫前转号码插入了usr_preference表中。AVPOPS模块假造和参数(AVPOPS Module Loading and Parameters)
在模块加载中,我们指定数据库地址,访问参数和AVP表。loadmodule "/usr/lib/openser/modules/avpops.so"modparam("avpops", "avp_url", "mysql://openser:openserrw@localhost/openser")modparam("avpops", "avp_table", "usr_preferences")
 
   使用OpenSER构建电话通信系统——第八章(2)
 
 
 
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!
实现呼叫盲转(Implementing Blind Call Forwarding)
首先,让我们来实现呼叫盲转服务。在INVITE请求的处理过程中,我们将从数据库中的用户优先表中加载名称为callfwd的AVP。如果callfwd被设置给了指定的用户,那么我们在对该请求进行前转前将它“推进”(push)到R-URI中。if(avp_db_load("$ruri/username","s:callfwd")){ #Check the existence of the callfwd attribute on the #usr_preferences table. If found, assign the vaue to the AVP # and push the value to the ruri of the SIP header. avp_pushto("$ruri","s:callfwd"); route(1); exit;};为了能让这个特性得以工作,重要点在于在数据库中插入正确的入口点。AVPs使用的表叫做usr_preferences。
 你可以在SerMyAdmin的帮助下来对用户的喜好进行修改;只需要浏览菜单中“user preferences”即可。在那个选单中你可以查看所有的用户喜好,编辑它们,还可以创建新的。
 如果你工作在多域环境中,请将模块AVPOPS的多域参数功能打开,并用域名来填充数据库。在上面的记录中,我们告诉系统将任何呼给1001的呼叫都呼给URI:sip:1004@yourdomain。Lab——实现呼叫盲转(Lab——Implementing Blind Call Forwarding)
步骤1:让我们使用在第六章里第一次见到的SerMyAdmin接口来插入AVP对。你的浏览器中访问SerWEB的管理接口为::8080/serMyAdmin">http://:8080/serMyAdmin步骤2:使用一个具有“全局管理员”角色的用户登录接口。步骤3:点击用户喜好(User preferences)标签。在这个菜单中,点击“New Preference”按钮,为你想要前转通话的用户来创建AVP;在这个例子中,应该是1000@sermyadmin.org。它的属性比较取名为callfwd,值是你要前转通话的URI;这里应该设置成1004@sermyadmin.org。
 步骤4:编辑openser.cfg以包含上面做的那些说明解释。文件应该以下面的代码结束。在openser.cfg文件中包含下面的代码行。或是简单地从http://www.sermayadmin.org/openser拷贝openser.callfwd1到openser.cfg。在模块加载部分:loadmodule "avpops.so"loadmodule "xlog.so"在模块参数部分:modparam("avpops", "avp_url", "mysql://openser:openserrw@localhost/openser")modparam("avpops", "avp_table", "usr_preferences")在route[3]部分: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"); }; if(avp_db_load("$ru/username","$avp(s:callfwd)")) { avp_pushto("$ru", "$avp(s:callfwd)"); xlog("forwarded to: $avp(s:callfwd)"); route(1); exit; } consume_credentials();步骤5:注册分机1001和1004。使用1001打给1000,这时候这通通话就应该按照usr_preferences表中说明被前转到1004。在忙线或无人接听的时候实现呼叫前转(Implementing Call Forward on Busy or Unanswered) 
这是这一章的第二部分。现在我们将介绍两个新的重要概念。第一个是failure_route,第二个则是append_branch,其被用来派生通话(fork the call)。我们将处理下面的出错情况:l         408 – Timeoutl         486 – Busy Herel         487 – Request Cancelled为了实现在忙线和无人接听的情况下的通话前转,我们将使用出错路由(failure route)的概念。下面的逻辑中,在发送INVITE到标准处理过程前,我们将调用函数t_on_failure(“1”)。这允许我们在failure_route[1]中处理SIP出错消息(返回的消息高于299的,也叫做负应答negative replies)当在这种情况下收到一通通话时,我们将其前转到一个语音邮箱系统。Asterisk能够充当一个相当好的语音邮箱系统。让我们来看看如何整合asterisk来记录语音邮箱消息。我们将在URI使用b(busy)前缀来告诉asterisk服务器播放busy消息,使用u(unanswered)来播放无应答消息。Asterisk将分别使用应用voicemail(b${EXTEN})和voicemail(u${EXTEN})来处理忙线和无人接听的语音邮箱请求。下面是一个完整的脚本,修改部分使用高亮显示。### $Id: openser.cfg 1676 2007-02-21 13:16:34Z bogdan_iancu $## simple quick-start config script# Please refer to the Core CookBook at http://www.openser.org/dokuwiki/doku.php# for a explanation of possible statements, functions and parameters.## ----------- global configuration parameters ------------------------debug=3 # debug level (cmd line: -dddddddddd)fork=yeslog_stderror=no # (cmd line: -E)children=4port=5060# ------------------ module loading ----------------------------------#set module pathmpath="//lib/openser/modules/"# Uncomment this if you want to use SQL database#loadmodule "mysql.so"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"loadmodule "lcr.so"loadmodule "avpops.so"loadmodule "xlog.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")modparam("avpops", "avp_url", "mysql://openser:openserrw@localhost/openser")modparam("avpops", "avp_table", "usr_preferences")# ------------------------- request routing logic -------------------# main routing logicroute{ # # -- 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 (has_totag()) { #sequential request withing a dialog should # take the path determined by record-routing if (loose_route()) { #Check authentication of re-invites if(method=="INVITE" && (!allow_trusted())) { if (!proxy_authorize("","subscriber")) { proxy_challenge("","1"); exit; } else if (!check_from()) { sl_send_reply("403", "Forbidden, use From=ID"); exit; }; }; route(1); } else { sl_send_reply("404","Not here"); }exit; } #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 t_on_failure("1"); 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("401", "Unauthorized");exit; }; save("location"); exit; } else if { sl_send_reply("401", "Unauthorized"); };}route[3] { ## Non-Register request handler if (is_from_local()){ # From an internal domain -> check the credentials and 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"); };if(avp_db_load("$ru/username","$avp(s:callfwd)")) { avp_pushto("$ru", "$avp(s:callfwd)"); route(1); exit; } 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 if (!load_gws()) { sl_send_reply("503", "Unable to load gateways"); exit; } if(!next_gw()){ sl_send_reply("503", "Unable to find a gateway"); exit; } route(5); exit;}route[5] { # # -- 4 -- T_relay for gateways # ## Forward statefully, if failure load other gateways t_on_failure("2"); if (!t_relay()) { sl_reply_error(); }; exit;}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")) { prefix("+1"); 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")) { strip(1); prefix("+1"); 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")) { strip(3); prefix("+"); route(4); exit; } else { sl_send_reply("403", "No perm. for internat.calls"); }; }; if (!lookup("location")) { if (does_uri_exist()) { ## User not registered at this time. ## Use the IP Address of your e-mail server revert_uri(); prefix("u"); rewritehostport("192.168.1.171"); #Use the voicemail IProute(1); } else { sl_send_reply("404", "Not Found"); exit; } 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;}failure_route[1] { ##-- ##-- If cancelled, exit. ##-- if (t_check_status("487")) { exit; };##-- ##-- If busy send to the e-mail server, prefix the "b" ##-- character to indicate busy. ##-- if (t_check_status("486")) { revert_uri(); prefix("b"); xlog("L_ERR","Stepped into the 486 ruri=<$ru>"); rewritehostport("192.168.1.171"); append_branch(); route(1); exit; }; ##-- ##-- If timeout (408) or unavailable temporarily (480), ##-- prefix the uri with the "u"character to indicate ##-- unanswered and send to the e-mail ##-- sever ##-- if (t_check_status("408") || t_check_status("480")) { revert_uri(); prefix("u"); xlog("L_ERR","Stepped into the 480 ruri=<$ru>"); rewritehostport("192.168.1.171"); append_branch(); route(1); exit; };}failure_route[2] { if(!next_gw()) { t_reply("503", "Service not available, no more gateways"); exit; } t_on_failure("1"); t_relay();}
 
   使用OpenSER构建电话通信系统——第八章(3)
 
 
 
注:以下文章如需转载,请注明所属作者,转载地址,谢谢!  检查配置文件(Ispecting the Configureation File)我们的脚本正变得难以debug。现在,让我们介绍XLOG模块。它实现了XLOG函数。这个函数与LOG()函数很相似,但是它允许你在消息中使用诸如请求URI($ru)的伪变量。下面有一个使用XLOG的例子。loadmodule "xlog.so"xlog("L_ERR","Marker 480 ruri=<$ru>");你可以使用下面的命令来检查最近的XLOG消息:tail /var/log/syslog在呼叫盲转中,只有一开始的INVITE消息会被处理,了解这一点是很重要的;我们能够安全地对请求URI和通话进行修改,append_branch不需要被调用。另一方面,对于忙线时和无人接听时的呼叫前转,在开始的INVITE失败后,要在这时派生消息你就必须要执行append_branch()函数。t_on_failure(“1”);t_on_failure()函数告诉OpenSER如果(negative/unsuccessful replies)这些出错状况发生了,就要去处理之。在本上下文中的出错状况是那些以4××和5××开头的错误信息。在调用t_relay()函数之前当你调用t_on_failure时,要告知OpenSER当收到出错信息时将控制权转给failure_route[1]。failure_route[1] { ##-- ##-- If cancelled, exit. ##-- if (t_was_cancelled()) { exit; };failure_route的第一部分处理487 cancelled消息。这个脚本对于此类信息只是简单将处理终止。接下来我们将处理busy消息。##-- ##-- If busy send to the e-mail server, prefix the "b" ##-- character to indicate busy. ##-- if (t_check_status("486")) {revert_uri(); prefix("b"); xlog("L_ERR","Stepped into the 486 ruri=<$ru>"); rewritehostport("192.168.1.171"); append_branch(); route(1); exit; };如果状态变为486,那么执行的动作就是还原URI(486是对于INVITE请求的一个出错消息),URI以“b”打头(告知语音邮箱系统要播放线路正忙的消息)并重写主机地址,将消息发送给语音邮箱系统。调用append_branch()来将目的地址添加到请求中。相同的处理逻辑被应用在对消息408和480的处理上。##-- If timeout (408) or unavailable temporarily (480), ##-- prefix the uri with the "u"character to indicate ##-- unanswered and send to the e-mail ##-- sever ##-- if (t_check_status("408") || t_check_status("480")) { revert_uri(); prefix("u"); xlog("L_ERR","Stepped into the 480 ruri=<$ru>"); rewritehostport("192.168.1.171"); append_branch(); route(1); exit; };Asterisk服务器上的extesions.conf配置文件要写成下面这样。语音邮箱帐号要创建成同OpenSER上的帐号相符。你也可以整合两个数据库以避免维护双数据库入口,具体请参考http://www.voip-info.org/wiki/view/Realtime+Integration+Of+Asterisk+With+OpenSER上面的教程。#extensions.conf file[default]exten=>_9.,1,Dial(ZAP/g1/${EXTEN})exten=>_9.,2,hangup()exten=>_u.,1,Voicemail(u${EXTEN})exten=>_u.,2,hangup()exten=>_b.,1,Voicemail(b${EXTEN})exten=>_b.,2,hangup()实验——测试呼叫前转特性(Lab——Testing the Call Forward Feature)要完成这个实验,对于语音邮箱部分的整合需要asterisk方面的一些经验。这个实验实现起来有些难度。有些IP电话很难发送出busy消息,因为它们有不止一条线路。在获得“486” busy消息之前,要使用所有的线路对于这个实现的成功是很重要的。有一种SNOM的ip电话,具有一个busy按钮用来告知用户电话正忙。为了降低实验难度和复杂性,我们将减少INVITE的超时时间。在产品的环境上,去掉这些语句。modparam("tm", "fr_timer", 5)modparam("tm", "fr_inv_timer", 20)步骤1:测试无人应答的呼叫前转用1000呼叫1002。这通电话应该捎带着“不可得消息”(unavailable message)转向语音邮箱系统。步骤2:测试忙线呼叫前转。将1003摘机。用1000呼叫1003。这通电话应该捎带着“忙线消息”(busy message)转向语音邮箱系统。概要(Summary)这一章中,我们学会了如何使用AVPs来处理诸如呼叫前转之类用户喜好(user preferences)。使用failure_route允许我们实现两种通常情况下的呼叫前转,忙线和无人应答。最后我们还学会了如何将此类消息发送到外部的语音邮箱系统,譬如asterisk服务器。  结束语:这本书就翻译到这里了,有了上面的对openser的基本认识,我想,再通过实践和opensips官网的文章的洗礼,相信大家都能够对opensips掌握了。
 
 
出处:http://blog.csdn.net/speedingboy/archive/2010/08/01/5780670.aspx