使用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地址,譬如:
在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:
[email protected]。一种可靠安全的SIP URI也可以被使用,譬如sips:
[email protected]。使用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:
[email protected])。这个头域有一个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 PBX
l NAT穿透
l SIP.EDU
OpenSER 框架(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|restart
OpenSER的执行有几个启动选项。展现于下面的这些选项允许你改变守护进程(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 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 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=yes
fork参数用来定义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 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;
};
当一个请求进入主路由块时,要进行一些检查。
首先检查的是前转(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(比如,
[email protected]可以是原有URI
[email protected]的别名)。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 works
reliably
# 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: 1001
password: 1001
在完成配置后,你需要注册IP话机。不是所有的设备都会做自动注册的操作。
1. 使用下面的命令检查电话是否注册上:
openserctl u1 show
2. 用第一个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:5060
REGISTER 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.
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:5060
REGISTER 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.
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:29040
SIP/2.0 401 Unauthorized.
Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-13517a5a8218ff45-1--d87543-;rport=29040.
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:5060
REGISTER 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.
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:29040
SIP/2.0 200 OK.
Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-da776d09bd6fcb65-1--d87543-;rport=29040.
Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..
CSeq: 2 REGISTER.
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 authentication
if (!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:5060
Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.
Supported: replaces.
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:5060
SIP/2.0 407 Proxy Authentication Required.
Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.
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:5060
Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.
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:5060
Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKcdb4add5db72d493.
Supported: replaces.
Proxy-Authorization: Digest username="1001", realm="192.168.1.155", algorithm=MD5, uri="sip:
[email protected]", nonce="4626420b4b162ef84a1a1d3966704d380194bb78", response="06736c6d7631858bb1cbb0c86fb939d9".
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 Snippet
In 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 create
MySQL password for root:
Enter password:
Enter password:
creating database openser ...
Core OpenSER tables succesfully created.
Install presence related tables ?(y/n):y
creating presence tables into openser ...
Presence tables succesfully created.
Install extra tables - imc,cpl,siptrace,domainpolicy ?(y/n):y
creating extra tables into openser ...
Extra tables succesfully created.
Install SERWEB related tables ?(y/n):n
Domain (realm) for the default user 'admin': voffice.com.br
需要密码才能够访问该数据库。这时候,密码是空的。该脚本会询问密码两次;两次都要按回车键。此脚本也会询问域(realm);要告诉管理者用户的域。
步骤3:配置OpenSER使用MySQL。
按照下面文件的高亮部分进行修改:
# ------------------ 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 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 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.
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 works
reliably
# 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 启动,终止,重启OpenSER
l 展示,授权,撤销ACLs
l 添加,删除,列出别名
l 添加,删除,配置AVP
l 管理LCR(low cost routes)
l 管理RPID
l 添加,删除,列出订阅者
l 添加,删除,展示usrloc表“in-ram”
l 监控OpenSER
我们将在下面的章节中学习一些它的选项.下面是openserctl help命令的输出信息:
/etc/openser# openserctl help
database engine 'MYSQL' loaded
Control engine 'FIFO' loaded
/usr/sbin/openserctl 1.2 - $Revision: 1.3 $
Existing commands:
-- command 'start|stop|restart'
restart ............................ restart OpenSER
start .............................. start OpenSER
stop ............................... stop OpenSER
-- command 'acl' - manage access control lists (acl)
acl show [<username>] .............. show user membership
acl grant <username> <group> ....... grant user membership (*)
acl revoke <username> [<group>] .... grant user membership(s) (*)
-- command 'alias_db' - manage database aliases
alias_db show <alias> .............. show alias details
alias_db list <sip-id> ............. list aliases for uri
alias_db add <alias> <sip-id> ...... add an alias (*)
alias_db rm <alias> ................ remove an alias (*)
alias_db help ...................... help message
- <alias> must be an AoR ( username@domain)"
- <sip-id> must be an AoR ( username@domain)"
-- command 'avp' - manage AVPs
avp list [-T table] [-u <sip-id|uuid>]
[-a attribute] [-v value] [-t type] ... list AVPs
avp add [-T table] <sip-id|uuid>
<attribute> <type> <value> ............ add AVP (*)
avp rm [-T table] [-u <sip-id|uuid>]
[-a attribute] [-v value] [-t type] ... remove AVP (*)
avp help .................................. help message
- -T - table name
- -u - SIP id or unique id
- -a - AVP name
- -v - AVP value
- -t - AVP name and type (0 (str:str), 1 (str:int),
2 (int:str), 3 (int:int))
- <sip-id> must be an AoR ( username@domain)
- <uuid> must be a string but not AoR
-- command 'db' - database operations
db exec <query> ..................... execute SQL query
db show <table> ..................... display table content
-- command 'lcr' - manage least cost routes (lcr)
* lcr *
* IP addresses must be entered in dotted quad format e.g. 1.2.3.4 *
* <uri_scheme> and <transport> must be entered in integer or text,*
* e.g. transport '2' is identical to transport 'tcp'. *
* scheme: 1=sip, 2=sips; transport: 1=udp, 2=tcp, 3=tls *
* Ex
http://cache.baidu.com/c?m=9f65cb4a8c8507ed4fece763105392230e54f76238d586482ec3933fc239045c163bbffd707e5619d0c7616402af4841e8f22b723d0125a09cbc8d49dab0962d2c8f3034075ddc074d8d0dea960673ce7fcb4de8df0ee0cee733e3fa898782&p=8b2a910a8faf50ea02bd9b7f0c5e&user=baidu&fm=sc&query=mi_fifo&qid=ce2293ce01034c52&p1=4