免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 40765 | 回复: 19

[电信] [转帖]SIP 协议中文版 [复制链接]

论坛徽章:
0
发表于 2007-02-06 11:45 |显示全部楼层
1、SIP协议介绍
Internet的许多应用都需要建立和管理一个会话,会话在这里的含义是在参与者之间的数据的交换。由于考虑到参与者的实际情况,这些应用的实现往往是很复杂的:参与者可能是在代理间移动,他们可能可以有多个名字,他们中间的通讯可能是基于不同的媒介(比如文本,多媒体,视频,音频等)-有时候是多种媒介一起交互。人们创造了无数种通讯协议应用于实时的多媒体会话数据比如声音,影像,或者文本。本SIP(会话初始协议)和这些协议一样,同样允许使用Internet端点(用户代理)来寻找参与者并且允许建立一个可共享的会话描述。为了能够定位精确的会话参与者,并且也为了其他的目的,SIP允许创建基础的network hosts(叫做代理服务器),并且允许终端用户注册上去,发出会话邀请,或者发出其他请求。SIP是一个轻形的,多用途的工具,可以用来创建,修改和终止会话,它独立运作于通讯协议之下,并且不依赖建立的会话类型。

2、SIP协议功能概况
SIP是一个应用层的控制协议,可以用来建立、修改、和终止多媒体会话(或者会议)例如Internet 电话。SIP也可以邀请参与者参加已经存在的会话,比如多方会议。媒体可以在一个已经存在的会话中方便的增加(或者删除)。SIP显示的支持名字映射和重定向服务,这个用于支持个人移动业务-用户可以使用一个唯一的外部标志而不用关系他们的实际网络地点。SIP在建立和维持终止多媒体会话协议上,支持5个方面:
用户定位: 检查终端用户的位置,用于通讯。
用户有效性:检查用户参与会话的意愿程度。
用户能力:检查媒体和媒体的参数。
建立会话:”ringing”,建立会话参数在呼叫方和被叫方。
会话管理:包括发送和终止会话,修改会话参数,激活服务等等。
        SIP不是一个垂直集成的通讯系统。SIP可能叫做是一个部件更合适,它可以用作其他IETF协议的一个部分,用来构造完整的多媒体架构。比如,这些架构将会包含实时数据传输协议(RTP)(RFC 1889)用来传输实时的数据并且提供QoS反馈,实时流协议(RSTP)(RFC 2326)用于控制流媒体的的传输,媒体网关控制协议(MEGACO)(RFC 3015)用来控制到公共电话交换网(PSTN)的网关,还有会话描述协议(SDP)(RFC 2327)用于描述多媒体会话。因此,SIP应该和其他的协议一起工作,才能提供完整的对终端用户的服务。虽然基本的SIP协议的功能组件并不依赖于这些协议。

SIP本身并不提供服务。但是,SIP提供了一个基础,可以用来实现不同的服务。比如,SIP可以定位用户和传输一个封装好的对象到对方的当前位置。并且如果我们利用这点来通过SDP传输会话的描述,立刻,对方的用户代理可以得到这个会话的参数。如果我们用这个像传输会话描述(SESSION DESCRIPTION SD)一样呼叫方的照片,一个”呼叫ID”服务很容易就建立了。这个简单的例子说明了,SIP作为一个基础,可以在其上提供很多不同的服务。

SIP并不提供会议控制服务(比如议席控制或者投票系统),并且并没有建议会议应该则那样管理。可以通过在SIP上建立其他的会议控制协议来发起一个会议。由于SIP可以管理参与会议的各方的会话,所以会议可以跨异构的网络,SIP 并不能,也不打算提供任何形式的网络资源预留管理。


安全对于提供的服务来说特别重要。要达到理想的安全程度,SIP提供了一套安全服务,包括防止拒绝服务,认证服务(用户到用户,代理到用户),完整性保证,加密和隐私服务。

SIP可以基于IPV4也可以基于IPV6

3、术语
在这个文档中,关键词”必须”,”不允许”,”要求”,”可以”,”不可以”,”应该”,”不应该”,”建议”,”不建议”,”可能”,”可选” 是根据BCP14,RFC 2119[2]的规范描述SIP实现需要的不同层次

4、实施概览
这节通过简单的示例介绍了SIP的基本实现。本节是通过自然的而非正则的示例来介绍的。

        第一个例子说明了SIP的基本功能:定位一个断点,发出通讯请求,通过协商会话参数建立会话,拆卸刚才建立的会话。
        图一表示一个典型的Alice和Bob两个用户间的SIP消息交易交换例子.(每一个消息采用字母”F”和一个用来指向正文的一个数字做标记)在这个例子里,Alice在她的PC上使用一个SIP的应用程序(比如说一个软的电话),呼叫Bob在Internet上的一个SIP电话。这个例子也掩饰了两个SIP代理之间,怎样为Alice和Bob建立会话连接。This typical arrangement is often referred to as the "SIP trapezoid" as shown by the geometric shape of the dotted lines in Figure 1.

Alice 通过Bob的SIP标志 “呼叫” Bob,这个SIP标志是统一分配的资源(Uniform Resource Identifier URI)称作SIP URI。SIP URI在19.1节中定义。它很像一个email抵制,典型的SIP URI包括一个用户名和一个主机名。在这个范例中,SIP URI是sip:bob@biloxi.com,biloxi.com是Bob的SIP服务提供商。Alice有一个SIP URI: sip:alice@atlanta.com。 Alice可以输入Bob的URI,也可以直接在地址本的一个超级链接上点击一下Bob的URI。SIP也提供保密URI,称作SIPS URI。例如:sips: bob@biloxi.com。 一个基于SIPS URI的通话保证这个通话是安全的,并且对呼叫者和被叫的所有的SIP消息是加密传输的(叫做TLS)。在TLS中,请求是通过加密方式传输给被叫方,但是这个加密机制是基于被叫方宿主服务器的实现的。

SIP是基于一个类似HTTP协议的请求应答的通讯模式。每一个通讯都包含对某个功能的请求,并且起码需要一个应答。在这个应答中,Alice的软电话发送一个含有Bbo的SIP URI抵制的INVITE通讯请求。INVITE是一个SIP请求的例子,表示请求方(Alice)希望服务方(Bob)应答。INVTE请求包含一系列的包头域(Header fields)。包头中包含很多属性并且包含了传输消息的附加信息。在INVITE中有如下的字段:呼叫的唯一标志,目的抵制,Alice的地址,Alice和Bob建立会话的类型。INVITE请求(图1中的F1)可能看起来像这样的:

INVITE sip:bob@biloxi.com SIP/2.0
Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds
Max-Forwards: 70
To: Bob <sip:bob@biloxi.com>
From: Alice <sip:alice@atlanta.com>;tag=1928301774
Call-ID: a84b4c76e66710@pc33.atlanta.com
CSeq: 314159 INVITE
Contact: <sip:alice@pc33.atlanta.com>
Content-Type: application/sdp
Content-Length: 142
(Alice’s SDP not shown)


atlanta.com . . . biloxi.com
.         proxy                                 proxy                 .
.                                                                                  .
Alice’s . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . Bob’s
softphone                                                                                         SIP Phone
|                                         |                                         |                                         |
| INVITE F1                         |                                         |                                        |
|--------------->                        | INVITE F2                         |                                         |
| 100 Trying F3                 |--------------->                        | INVITE F4                         |
|<---------------                        | 100 Trying F5                 |--------------->                        |
|                                        |<--------------                        | 180 Ringing F6                 |
|                                        | 180 Ringing F7                 |<---------------                        |
| 180 Ringing F8                 |<---------------                        | 200 OK F9                         |
|<---------------                        | 200 OK F10                 |<---------------                        |
| 200 OK F11                 |<---------------                        |                                         |
|<---------------                        |                                         |                                         |
|                                                         ACK F12                                                 |
|                                        ------------------------------------------------->                |
|                                                 Media Session                                                 |
|<================================================>        |
|                                                         BYE F13                                                 |
|                                        <-------------------------------------------------                |
|                                                 200 OK F14                                                         |
|                                        ------------------------------------------------->                |
|                                                                                                                        |
图一:SIP矩形表达的SIP会话建立例子。
在文本消息的第一行,包含了请求的类型(INVITE)。在这行之后的是这个请求的头域。这个例子中包含了最少需要的头域集合。简单介绍一下:

VIA域包含了Alice接收发送请求的服务器地址(pc33.atlanta.com)。同样这个包含了一个分支参数来标志Alice和这个服务器的会话事务。

TO域包含了显示姓名(Bob)和一个SIP或者SIPS URI(sip:bob@biloxi.com)请求将首先传输到这个URI中。显示姓名(Display names)在RFC 2822中描述。
From域也同样包含一个显示姓名(Alice)和一个SIP或者SIPS URI(sip:alice@atlanta.com)这个URI用来标志请求的原始发起者。
这个域也包含了一个TAG参数,这个TAG参数是一个随鸡字串(1928301774),是软电话(softphone)在URI上增加的一个随鸡串。用来做标志用途的。
Call_ID包含一个全局的唯一标志,用来唯一标志这个呼叫,通过随机字串和softphone的自己名字或者IP抵制混和产生的。通过TO TAG, FROM TAG和CALL-ID完整定义了Alice和Bob之间的端到端的SIP关系,并且表示这个是一个对话性质的关系。
CSEQ或者Command Sequence包含了一个整数和一个请求名字。这个Cseq数字是顺序递增的。每当对话中发起一个新的请求都会引起这个数字的顺序递增。
Contact域包含一个SIP或者SIPS URI用来表示访问Alice的直接方式,通常由用户名和一个主机的全名(Fully Qualified Domain Name FQDN)组成。当FQDN作为首选的时候,许多终端用户由于不会由名字登记(而导致不能访问Alice的主机),所以IP地址是可选的。
VIA域告诉大家本请求发送到哪里并且应答到哪里,Contract域告诉大家将来的请求将发送到哪里(奇怪…不是Alice发起的么,将来的请求应该是Bob才对啊)。
Max-Forwards:最大转发数量限制了通讯中转发的数量。它是由一个整数组成,每转发一次,整数减一。
Content-type包含了消息正文的描述(消息正文在本范例中没有列出)
Content-length:包含消息正文的长度(字节数)
完整的SIP包头域的定义在20节。会话的细节,比如媒体的类型,codec,或者采样速率,没有通过SIP来描述。这个可以通过SIP的消息正文来描述,可以通过其他定义的协议在正文中进行描述。有一种是会话描述协议(Session Descripotion Protocol SDP)(RFC2327[1])。这个SDP消息(没有在例子中列出)通过SIP的消息中传送,就像通过附件发送EMAIL一样,或者说通过HTTP传输的网页一样。

由于softphone并不知道bob或者bob的sip服务器biloxi.com在哪里,所以softphone发送INVITE请求到Alice的sip服务器,atlanta.com。这个atlanta.com SIP服务器应该已经在Alice的softphone中配置了,或者可以通过DHCP获得。atlanta.com SIP服务器是一台代理服务器。代理服务器接收SIP请求并且根据请求转发。在这个例子中,代理服务器接收到INVITE请求,并且回送一个100(Trying)应答给Alice的softphone。100(Trying)应答表示INVITE请求已经收到,并且代理服务器正在转发INVITE请求。SIP的应答是通过一个三位数的数字表示的。SIP应答同样包含TO、FROM、Call-ID,CSEQ和在VIA中的分支参数,这个参数使得Alice的softphone可以把请求和应答关联起来。atlanta.com代理服务器收到INVITE请求之后,就去找biloxi.com可能通过DNS服务来找提供这个biloxi.com的SIP服务器。这在[4]中有描述。最后,转发INVITE请求到biloxi.com或者能到达biloxi.com的代理服务器。在转发请求之前,atlanta.com代理服务器会在via头上增加一个一段包含自己抵制的值(INVITE已经包含了Alice的的地址VIA域)。biloxi.com代理服务器收到这个INVITE请求并且返回一个100(Trying)应答给atlanta.com代理服务器标志这它已经收到这个请求并且正在处理这个请求。这个代理服务器通过查询数据库,通常叫做地址服务,这个服务中包含了bob的当前ip地址。(我们在下一节可以看到这个数据库是怎么回事)biloxi.com代理服务增加另一段包含自己地址的VIA头域并且发送它到bob的sip 电话。

Bob的SIP电话接收到INVITE请求并且提醒bob有一个从Alice的呼入,这样bob可以决定是否响应这个呼入。这个意思就是:bob的电话响了。bob的sip电话发送一个180(Ringing)回应,这个回应减通过两个代理服务器原路返回给Alice。每一个代理服务器通过via头域决定该把这个应答发送给哪里,并且在发送之前把自己的地址从头上拿走。虽然DNS和定位服务在路由最初的INVITE请求,180(ringing)响应可以简单返回给发起者而不需要查找发起者在哪里,并且不需要在代理服务器保留状态,同时,每一个转发INVITE的代理也可以得到INVITE的每一个应答,这样的特性也非常有用。

当Alice的softphone收到180(Ringing)应答的时候,它提示Alice,可能是通过一个回铃音,或者屏幕上的一个消息提示。

在这个例子中,Bob决定响应这个呼叫。当他拿起电话,他的SIP电话发送200(OK)回应给发送者,表示这个电话已经接起来了。这个200(OK)包含了一个消息体,这个消息体包含SDP媒体描述,这个媒体描述包含Bob希望和Alice建立何种媒体连接。同样,SDP消息也是两段交换:Alice发送一个给Bob,Bob发送一个回给Alice。这个两段的交换提供基本的兼容性协商,并且基于简单的SDP提出/应答交换模型。如果Bob不想响应这个呼叫或者正在响应别的呼叫,一个错误的响应会代替正常的200(OK)回送出去,这样,就不会有连接建立。SIP完整的返回代码在21节有介绍。Bob发出的200(OK)(图一的F9消息)可能长得像这样的:

SIP/2.0 200 OK
Via: SIP/2.0/UDP server10.biloxi.com
;branch=z9hG4bKnashds8;received=192.0.2.3
Via: SIP/2.0/UDP bigbox3.site3.atlanta.com
;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2
Via: SIP/2.0/UDP pc33.atlanta.com
;branch=z9hG4bK776asdhds ;received=192.0.2.1
To: Bob <sip:bob@biloxi.com>;tag=a6c85cf
From: Alice <sip:alice@atlanta.com>;tag=1928301774
Call-ID: a84b4c76e66710@pc33.atlanta.com
CSeq: 314159 INVITE
Contact: <sip:bob@192.0.2.4>
Content-Type: application/sdp
Content-Length: 131
(Bob’s SDP not shown)

应答的第一行包含了应答代码(200)和原因(ok)。剩下的行包含了包头域。VIA,TO,FROM,CALL-ID,Cseq包头域是从INVITE请求包中直接拷贝过来的。(有三个VIA域值-一个是Alice SIP电话增加的,一个是atlanta.com代理加的,一个是biloxi.com代理加的)。Bob的SIP电话增加了一个TAG参数。这个TAG参数会被参与对话的各方所使用,并且在以后的对话中被使用。Contract域包含了一个能直接联系到Bob的URI。Content-type和Content_Length域包含了消息体(没有在例子中体现),这个消息体里边是Bob的SDP媒体信息。

除了DNS和位置服务之外,代理服务器可以自主决定路由,也就是说自己决定应该向哪里转发请求。比如,如果Bob的SIP电话返回一个486(电话正忙)信号,biloxi.com这个代理服务器可以转发这个INVITE请求到Bob的语音邮箱服务器。一个代理服务器可以同时向N个地方发送INVITE请求。这种并发寻找就是传说中的分流(forking)。


在这个例子中,200(OK)应答通过两个代理并且发送到Alice的softphone上,Alice的softphone收到这个应答,停止振铃,并且标志电话Bob已经接听。最后,Alice的电话发送一个确认消息,ACK,到Bob的SIP电话来确认接收到了这个最后的200(o’k)应答。在这个例子中,ACK信号是直接由Alice的softphone发送到Bob的SIP phone上,跨过了两个代理服务器。这是因为两个端点(Alice和Bob)通过INVITE/200(OK)的请求应答包中的Contact包头域都知道互相之间的地址了,这个地址是最开始发起INVITE请求的时候所不知道的。所以,不需要两个代理服务器再查找对方的地址了,所以代理服务器不参与接下来的通话流了。这就完成了一个完整的使用INVITE/200/ACK 三方握手来建立SIP会话的过程。会话建立过程中的细节描述再13节由描述。

现在,Alice和Bob的媒体会话开始了,他们通过发送刚才建立会话所交换的SDP包中约定的互相明白的媒体包来进行会话。一般情况下,端到端的媒体包和SIP信号控制包通过不同的通讯路径来发送。

在会话中,Alice或者Bob都可以改变他们自己的媒体会话属性。这个可以通过发送一个包含新媒体属性描述的re-INVITE请求来完成。这个re-INVITE是捆绑在一个现有的会话的,这样参与会话的对方可以明白这是要改变现有的会话属性而不是新建立一个会话。对方收到这个re-INVITE请求后,会发送一个200(OK)应答表示接受这个改变。请求方通过一个ACK来表示接受了对方的这个200(OK)应答。如果对方不同意这个媒体属性变化,他会发送一个错误的应答比如488(暂时不能进行),这个也会收到发起者的一个ACK响应。不管怎样,就是是re-INVITE的失败也不会影响到现有的会话-原有的会话还可以用上次的媒体会话属性继续。可以在14节找到会话属性更改的细节说明。

在通话结束的时候,Bob首先断开(挂机hangs up),并且发送一个BYE的消息。这个BYE的消息将直接送到Alice的softphone,同样是跳过代理的。Alice通过发送200(OK)应答来确认收到了这个BYE消息,这个消息终止了会话并且应答了BYE的请求。ACK在这里不需要发送-一个ACK信号只在响应一个INVITE的响应的时候被发送。我们稍晚一点会讨论这个INVITE的特别处理,但是基于SIP的可靠性的机制,一个通话的时间可以认为包含电话振铃和挂机的时间(but relate to the reliability mechanisms in SIP, the length of time it can take for a ringing phone to be answered, and forking.)基于这样的原因,SIP请求的处理通常根据是否INVITE请求进行分类,INVITE类和非INVITE类请求分开处理。结束会话的细节可以在15节查到。

24.2节描述了图1中使用的全部消息详细解释。在某些情况下,所有会话中的包都继续通过代理转发会很有用。比如,如果biloxi.com代理服务器希望在INVITE之后继续保持SIP消息流,他会在INVITE中增加一个头域(Record-Route)包含一个URI指向这个代理服务器的hostname或者IP地址。这个消息会被Bob的SIP电话和Alice的softphone所接到(因为Record-Route头域将在200(OK)应答中被送回),并且在会话中一直保存。那么biloxi.com代理服务器就可以继续接收和转发ACK,BYE,给BYE的200(OK)应答。每一个代理都可以单独决定是否接收INVITE以后的后续消息,并且这些后续消息都可以被发送到那些决定接收后续消息的代理服务器。这种情况通常发生在提供mid-call业务的代理服务器上。

登记服务是另一个常用的SIP操作。登记服务是biloxi.com代理服务器知道Bob当前地址的一个方法。在初始化的时候,或者每隔一段时间,Bob的SIP 电话发送REGISTER消息给biloxi.com的一个注册服务器。REGISTER消息包含了Bob当前登陆服务器的SIP或者SIPS的URI(sip:bob@biloxi.com)(转换成为Contact域中的SIP或者SIPS URI)。登记服务器登记这个映射,这个叫做绑定(binding),写到一个数据库里边,叫做定位服务(location service),这个数据库可以被biloxi.com的代理服务器使用。通常登记服务器和代理服务器是做在一起的。一个很重要的概念就是SIP服务器的差别在逻辑上,并非在物理上的差别。

Bob并没有限定非得在一个单个设备上发起注册。比如,他家里的SIP电话和公司的SIP电话都可以注册。这些消息在定位服务(location service)中保存,并且允许代理服务器通过不同的手段查找Bob。同样的,不同的用户也可以在同一个设备上同时注册。

定位服务(location service)是一个逻辑概念。他是让代理服务通过输入一个URI来查询到底应该向哪里转发请求。可以简单通过用户注册来建立这个定位服务所需要的资料,也可以通过其他方法。可以通过其他任意的地址映射方式来实现定位服务。

最后在SIP中需要注意的是,注册服务只是用来提供路由收到的SIP请求的,它并不做请求的身份认证的判定。在SIP中授权和认证可以通过建立在基于请求/应答的模式上的上下文相关的请求来实现,也可以使用更底层的方式来实现(具体在26节有描述)。

完整的注册SIP消息描述例子在24.1节。

其他SIP的操作,比如检查SIP服务器的负载,或者使用客户端使用可选项(OPTIONS),或者用CANCEL取消一个未决的请求,在后续的章节中会介绍。

5、协议的结构
SIP是一个分层的协议,意思是说SIP协议由一组相当无关的处理层次组成,这些层次之间只有松散的关系。协议分成不同层次来描述是为了能够更清晰的表达,在同一个小节里有功能的公共要素的交叉描述。本协议并没有规定一个具体的实现。当我们说一个要素”包含”某一个层,我们的意思是这个要素复核这个层定义的规则。

不是SIP每一个要素都一定包含每一个层。此外,SIP定义的要素是逻辑上的要素,不是物理要素。一个物理的实现可以实现不同的逻辑要素,或许甚至是基于串行事务处理原理。SIP最底层的是它的语法和编码层。编码方式是采用扩展的Backus-Naur Form grammar(BNF范式)。完整的BNF描述在25节;第7节有简要的SIP消息结构描述。

第二层是传输层。它定义了一个客户端如何发送请求和接收应答,以及一个服务器如何接收请求和发送应答。所有的SIP要素都包含一个通讯层。第18节有通讯层的描述。

第三层是事务层。事务是SIP的基本组成部分。一个事务是客户发送的一个请求事务(通过通讯层)发送到一个服务器事务,连同服务器事务的所有的该请求的应答发送回客户端事务。事务层处理应用服务层的重发,匹配请求的应答,以及应用服务层的超时。任何一个用户代理客户端(user agent client UAC)完成的事情都是由一组事务构成的。有关事务的讨论在第17节有描述。用户代理包含一个事务层,来实现有状态的代理服务器。无状态的代理服务器并不包含事务层。事务层包含一个客户元素(可以认为是一个客户事务)和一个服务器元素(可以认为是一个服务器事务),他们都可以用一个有限状态机来处理特定的请求。

在事务层之上是事务用户(TU)。每一个SIP实体,除了无状态代理,都是一个事务用户。当一个TU发出一个请求,它首先创建一个客户事务实例(client transaction instance)并且和请求一起发送,这包括了目标IP地址、端口号、以及发送请求的设备。TU可以创建客户事务,也可以取消客户事务。当客户取消一个事务,它请求服务器终止正在处理的事务,并且回滚状态到该事务开始前的状态,并且产生指定的该事务的错误报告。这是由CANCEL请求完成的,这个请求有自己的事务,并且包含一个被取消的事务(第9节)。

SIP要素,包含,用户代理客户端和服务器,无状态和有状态代理服务器和注册服务器,包含一个可以互相区别的核心(Cores)。Cores,除了无状态代理服务器,都是事务用户。UAC(用户代理客户端)和UAS(用户代理服务端)的cores的行为依赖于实现,对所有的实现来说,有几个公共的原则(第8节)。对UAC来说,这些规则约束请求的建立;对UAS来说,这些规则约束请求的处理和应答。由于注册服务在SIP中是一个重要的角色,所以UAS处理REGISTER请求有一个特别的名字:登记员(registrar,登记服务器)。第10节描述了UAC和UAS的对REGISTER实现的core(核心)行为。第11节描述了OPTIONS的UAC和UAS的core实现,这个OPTIONS用来检测UA的处理能力的(UA-user agent)。

在对话中,有其他的相关会被发送。一个对话是一个持续一定时间的两个用户之间的端到端的SIP关系。对话过程要求两个用户代理之间的信息是有序的而且请求被正确路由传输的。在这个规范中,只有INVITE请求可以用来建立会话。当一个UAC在一个对话中发出请求的时候,它不仅遵循第8节描述的一般UAC规则而且也遵循对话中的请求规则。第12节讲述了对话并且讨论了对话的创建和维持,以及在对话中创建一个请求。

SIP中最重要的方法就是INVITE方法,它用来在不同的参与者中创建会话使用。一个会话由一组参与者,他们之间用于交流的媒体流组成。第13节讲述了这些会话的创建初始化过程,以及创建一个或一组对话。第14节讲述了在对话中使用INVITE请求来改变会话的属性。最后,第15节,讲述了如何终止会话。

第8、10、11、12、13、14、15节讲述了完整的UA核心(第9节描述了取消,在UA核心和代理核心中使用)。第16节讲数了代理服务器,代理服务器用于在两个UA之间做消息路由使用。

[ 本帖最后由 第一支烟 于 2007-2-6 11:55 编辑 ]

论坛徽章:
0
发表于 2007-02-06 11:56 |显示全部楼层
6、协议的定义
以下讲述的名次对SIP有着额外的意义:
Address-of-Record: 记录地址。一个address-of-record(AOR)是一个SIP或者SIPS URI它指向了一个具有定位服务的主机,这个主机可以把URI映射成为用户真正物理位置的URI。通常情况下,定位服务器是通过登记服务来建立的。一个AOR经常被认为是一个用户的”公共地址”
Back-to-Back UserAgent:背对背的用户代理(B2BUA)是一个逻辑实体,它就像用户代理服务器(UAS)一样接收和处理请求。为了决定该如何应答一个请求,B2BUA就像UAC一样工作,并且发出请求。但是它不像代理服务器(proxy),它维持对话状态,并且参与已经建立的对话中的每一个请求。由于它是直接的UAC和UAS的串连,所以,不需要对他有额外的定义。

Call:呼叫,一个呼叫是一个非正式的术语,它是指在端点之间一个一些通讯行为,通常用于建立多媒体对话。
Call Leg: 对话的别名;在本规范中没有使用。

Call Stateful: 如果一个代理服务器(proxy)保存一个对话的状态(从最开始的INVITE到对话终结的BYE),那么这个代理服务器就是请求有状态的。一个请求有状态(call stateful)的代理服务器也一定是事务有状态的,但是事务有状态的不一定是请求有状态的。

Client:客户端。一个客户端是一个任意的网络元素,它发出SIP请求和接收SIP应答。客户端可能会也可能不会和人交互。用户代理客户端(UAC)和代理服务器都是客户端。

Conference: 一个包含多个参与方的多媒体会话(见后)。

Core:核心。核心定义了SIP实体的特定类别。比如定义了一个有状态和无状态的代理服务器,一个用户代理或者注册服务器(registrar)。所有的核心,除了无状态代理服务器,都是事务用户。

Dialog:对话,一个对话是持续一段时间的两个UA之间的端到端的SIP关系。一个对话由SIP消息建立,就像用2xx响应INVITE请求。我们用Call identifier,local tag(本地tag),remote tag(对方tag)来标志一个对话,一个对话在RFC 2543中被正式叫做CALL LEG.

Downstream: 它是事务中的消息传递方向。它特指从UAC到UAS的请求流的方向,

Final Response:终结响应。一个响应终端SIP事务的应答,和事务中间的临时响应相反。所有的2xx,3xx,4xx,5xx,6xx响应都是终结响应。

Header:头。头域是在SIP消息头部用来描述这个SIP消息信息的部分。它由一堆头域字段组成。

Header Field:头域字段。头域字段是在SIP消息头域的字段。一个头域字段可以由多个头域字段行组成。一个头域字段由一个头域名和(零个或多个)头域值组成。多个头域值用’,’分割。某些头域字段只能有单个值,比如结果域(result)就只能有一个值。

Header Field Value:头域值。一个头域值是一个单个的值,一个头域字段可以有0个或者多个头域值。

Home Domain:宿主机。一个提供SIP服务的主机。一般指的是在登记服务中指明的记录地址中的URI的主机。

Informational Response:提示应答。和临时应答一样。
Initiator, Calling Party, Caller: 用INVITE初始一个会话(和对话)的那方。一个caller从发出INVITE请求建立对话开始,到对话终止都一直是这个角色。

Invitation: 一个INVITE请求。

Invitee,Invited User,Called Party, Callee:被叫方。收到INVITE请求并且建立会话的那方。一个被叫方从收到INVITE请求起,到终止INVITE建立的对话结束,都称作被叫方。

Location Service: 定位服务。定位服务是用来给SIP转发或者代理服务器确定被叫方可能的位置使用的。它包含一张绑定了address-of-record的表,被叫方可能有0到多个记录。绑定的记录可以通过多种渠道添加和删除;本规范定义了REGISTER方法来更新绑定表。

Loop:环路。当请求抵达一个代理服务器,代理服务器转发这个请求,当这个请求再次来到同一个代理服务器,就称之为环路。当第二次抵达的时候,Request-URI中包含了上次抵达的资料,并且由于并没有什么东西可以改变转发的策略,这样就导致这个请求还会再次被转发回来。环路请求是错误的,所以,处理程序需要检测和防止协议中出现的环路请求。

Loose Routing:丢失路由。代理服务器在下述情况下会丢失路由。
A proxy is said to be loose routing if it follows the procedures defined in this specification for processing of the Route header field. These procedures separate the destination of the request (present in the Request-URI) from  the set of proxies that need to be visited along the way (present in the Route header field). A proxy compliant to these mechanisms is also known as a loose router.

Message:消息。SIP元素之间传送的协议数据就是消息。SIP消息既可以是请求也可以是应答。

Method:方法。方法是在服务器请求处理的主要功能。方法是请求消息自身携带的。典型的方法就是INVITE和BYE。

Outbound Proxy:对外代理服务器。一个代理服务器接收到客户的请求,即使它不是由Request_URI所决定的服务器。通常一个UA会手工配置一个对外的代理服务器,或者可以通过一个自动配置的协议自动配置一个。

Parallel Search: 并行搜索。并行搜索情况下,代理服务器会向多个用户可能存在的地方发起请求,并且等待应答。同串行搜索不同的地方是,并行搜索不会等待上一个请求应答回来之后再发起下一个搜索,而是一个接一个的发起搜索请求。

Provisional Response: 临时应答。服务器用来标志自己正在处理的应答,但是本应答并不结束一个SIP事务。1xx应答就是临时的,其他应答标志着事务的结束。

Proxy,Proxy Server:代理、代理服务器。一个中间的实体。它本身即作为客户端也作为服务端,为其他客户端提供请求的转发服务。一个代理服务器首先提供的是路由服务,也就是说保证请求被发到更加”靠近”目标用户的地方。代理服务器对某些强制政策有用(比如,确认一个用户是否允许建立一个呼叫等)。一个代理服务器翻译,并且,如果有需要的话,再转发前会重写请求消息。

Recursion:回路、递归。一个客户端,在响应请求的时候产生新的到Contract包头域的URI请求的时候,会在3xx响应中陷入递归。A client recurses on a 3xx response when it generates a new request to one or more of the URIs in the Contact header field in the response.

Redirect Server:重定向服务器。一个重定向服务器是一个产生3xx应答的UAS服务器,指示客户端连接别的URI。

Registrar: 登记员。一个登记员(登记服务器)是一个接收REGISTER请求得服务器。他把请求得信息放到定位服务器中,这样可以让定位服务器很方便得查找位置信息。

Regular Transaction:常规事务。凡不包含INVITE,ACK,或者CANCEL方法得事务就是常规事务。

Request: 请求。 一个由客户端发到服务端得SIP信息,用于执行特定得功能。

Response:应答。一个由服务端发到客户端得SIP信息。用来标志从客户端发往服务端得请求处理得情况得。

Ringback: 回铃音。回铃音是一个信号音。是给呼叫方得一个信号表示被叫方正在振铃(Ringing)。

Route Set: 路由集。路由集合是一个顺序得SIP或者SIPS URI。这些URI描述了传递一个请求所必须经历得代理列表。一个路由集可以是自适应得,因为包头中包含了Record-Route(记录路由),也可以是依赖配置得到得。

Server:服务器。一个server是一个网络元素接收请求并且处理请求并且发送回应给请求方。典型得服务器就是代理服务器(proxies),用户代理服务器(user agent servers),重定向服务器,登记服务器。

Sequential Search:顺序查找。在顺序查找中,代理服务器顺序尝试联系地址,在处理下一个之前必须等待上一个请求已经有一个结束应答。一个2xx或者6xx系列得最终应答总是结束一个顺序查找。

Session:会话。根据SDP得描述:”一个多媒体会话是一个由多媒体发送方和接受方组成得集合,并且包括在发送方和接受方之间得数据流。一个多媒体会议是一个典型得多媒体会话。”(RFC 2327[1])(一个session在SDP订一下可以是一个或者多个RTP sessino)。在定义中,一个被叫方可以被多次邀请,被不同得呼叫方邀请,到同一个会话。在SDP中,一个会话可以被SDP用户名,session id,网络类型,地址类型,地址元素得一个集合串所规定。

SIP 事务:一个SIP事务是在客户端和服务端得事件,包括了从第一个由客户端发送到服务端得请求,到最后一个(非1xx)服务端向客户端发出得终结应答。如果请求是一个INVITE请求,并且终结应答是一个非2xx得应答,那么事务还包括一个ACK给服务器做应答。给INVITE请求的2xx应答的ACK回应,是一个独立的事务。

Spiral:回溯。一个回溯是指一个SIP请求,路由给一个proxy,并且转发,但是又被路由回这个proxy,但是不同于回路(递归)的是,这次路由回来的请求包的包头中,包含了不同于原请求的请求包部分,使得本次proxy决定的路由转发与上次不同。通常,这是说,请求的Request-URI不同于上次的Request_URI。一个回溯不是一个错误,不同于回路(环路loop)。通常导致这样的现象是呼叫转发(call forwarding)。一个用户呼叫joe@example.com。example.com代理服务器转发请求到Joe的PC,并且Joe的pc呼叫转移到bob@example.com。这个请求被转发回example.com代理服务器。可是这个并不是一个环路(loop)。因为请求的目的地址变成了另一个用户,这就是回溯,是一个合法的情况。

Stateful Proxy:有状态的代理服务器。在逻辑上,有状态的代理服务器就是处理一个请求的过程中,维持的一个本规范所定义的客户端和服务端的事务状态机。也是一个事务又状态代理服务器(transaction stateful proxy)。具体的stateful proxy在第16节定义。一个(事务)有状态代理服务器和一个call stateful proxy不是一回事。

Stateless Proxy:无状态的代理服务器。在逻辑上,无状态代理服务器在处理请求中,并不维持客户和服务端的事务状态机。一个无状态的代理服务器直接转发每一个接收到的请求和每一个接收到的响应。

Strict Routing:严格路由。路由处理规则如果复核RFC2543协议(and many prior work in progress versions of this RFC) 就是一个严格路由。在这个规则下,如果在包头中包含Route域,那么代理服务器就会删除Request_URI域内容。本文档并不要求一定要有严格路由,本文档只要求松散路由就可以了。支持严格路由的代理服务器也叫严格路由器。

Target Refresh Request: 目标刷新请求。一个Target Refresh Request是一个在对话中发出的请求,用来更改对话目标的请求。

Transaction User(TU):事务用户。在transaction 层之上的协议层。TU包括了UAC 核心,UAS core,和proxy core。


Upstream:上行流。一个在事务中的消息流向方向。它是指由用户代理服务器(UAS)发出应答到用户代理客户端(UAC)的消息流向方向。

URL-encoded:一串根据RFC2396-2.4节编码的字符。

User Agent Client(UAC):用户代理客户端。用户代理客户端是一个逻辑的概念,他创建一个新请求,并且用客户事务状态机发送这个请求。UAC角色只在事务中存在。换句话说,UAC就是一小段代码初始化一个请求,并且在事务中遵循UAC的规则。如果它接下来收到一个请求,那么在那个事务中,它就是作为UAS来处理请求。

UAC Core:UAC核心。在transaction和transport层之上得UAC实现的功能集合。

User Agent Server(UAS): 用户代理服务器.UAS是一个逻辑的实体,对SIP请求做响应的。应答接受、拒绝、或者转发对应的请求。UAS角色在事务中存在。换句话说,是响应请求的一小段软件,在事务中作为UAS存在。如果他发出请求,那么他就在事务中作为UAC存在。

UAS Core:UAS核心。在transaction和transport层智商的UAS实现的功能集合。

User Agent(UA)。一个逻辑实体的概念,包含UAC和UAS。
UAC和UAS,就像代理服务器和转发服务器,是在事务by事务的原理(串行事务处理)上定义的。例如,当发出一个初始化INVITE请求的时候,UA作为UAC初始化一个呼叫动作,当从被叫方接收到一个BYE请求的时候,UA作为UAS响应。类似的,同样的代码可以对一个请求做为proxy服务器处理,对另一个请求作为重定向服务器。

proxy,location,registrar服务器都是逻辑实体,在它们的实现中,可能是作为单个应用实现的。

7、SIP消息:
SIP协议是一个基于文本的协议,使用UTF-8字符集(RFC2279[7])。
一个SIP消息既可以是一个从客户端到服务器端的请求,也可以是一个从服务器端到客户端的一个应答。
即使在字符集上和语法细节上有所不同,请求(7.1)还是应答(7.2)消息都基于RFC2822格式的。(SIP允许包头域不是标准的RFC2822包头域)。这两种消息类型都由一个起始行,一个或者多个包头域,一个可选的消息中文组成。

一般消息=                 起始行
*消息包头
CRLF
[消息正文]
起始行=                        请求行/状态行

起始行、每一个包头行,空行、都必须由回车换行组成(CRLF)。即使消息中文没有,也必须有一个空行跟随。
除了在字符集上的区别以外,很多SIP的消息和包头域的格式都同HTTP/1.1一样。我们在这里就不重复它的语法和语义了,我们用[HX.Y]来标志HTTP/1.1规范(RFC2616[8])的X.Y节的描述。
SIP并非一个HTTP的超集或者扩展。
7.1 请求
SIP请求是根据起始行中的Request-Line来区分的。一个Request_line包含方法名字,Request-URI,用单个空格(SP)间隔开的协议版本。
Request-Line由CRLF结束。除了用作行结束标志以外,不允许CR或者LF出现在其他地方。在其他域中,不允许出现线形的空白(liner whitespace LWS)

Request-Line        =        Method SP Request-URI SP SIP-VERSION CRLF
Method: 这个规范规定了6中方法:REGISTER用于登记联系信息,INVITE,ACK,CANCEL用于建立会话,BYE用于结束会话,OPTIONS用于查询服务器负载。SIP扩展、标准RFC追加可能包含扩展的方法。
Request-URI: Request-URI是一个SIP或者SIPS URI,他们在19.1节由描述。也可以是一个通用的URI(RFC 2396[5])。它标志了这个请求所用到的用户或者服务的地址。Request-URI禁止包含空白字符或者控制字符,并且禁止用”<>”括上。
SIP 元素可以支持除了SIP或者SIPS之外所规定的Request-URIs。比如”tel” URI模式(RFC 2806[9])。SIP元素可以用他们自己的机制来转换non-SIP URIs到SIP URI,SIPS URI或者其他什么格式的URI。
SIP-Version:请求和应答消息都包含当前使用的SIP版本,这个遵循[H3.1](类似HTTP用SIP替代,用SIP/2.0替代HTTP/1.1)中关于版本的规定,版本依赖,升级版本号。一个应用,发出的SIP消息一定包含了SIP-Version “SIP/2.0”。这个SIP版本串是大小写不铭感的,但是在实现中必须发送大写。
不像HTTP/1.1,SIP把版本号当作一个文本串处理。在实现上,这个应该不会有区别。
7.2应答
SIP应答和SIP请求的区别在于在START-LINE中是否包含一个STATUS-LINE。一个status-line在由数字表达的status-code之前,是一个协议的版本串,每一个元素之间用一个单个SP(空格)分开。
除了最后用作结束标志以外,CR/LF不允许出现在其他地方。
status-line        = SIP-VERSION SP STATUS-CODE SP Reasong-Phrase CRLF
Status-Code 是一个3位的数字result code,用来标志处理请求的一个结果。Reason-Phrase是一个简短的Status-Code的说明。Status-Code是为了能自动处理使用的,但是Reason-Phrase是用来给用户看得。一个客户端并不要求一定要显示或者解释这个Reason-Phrase。本文档建议输出这个reason-phrase,实现中可以选择其他文本,比如用请求包头中指定的合适语言来显示。
status-code的第一个数字表示了应答的类型。接下来两个数字并不作分类使用。基于这个原因,任何status code在100到199的可以统称位”1xx应答”,类似的,在200到299的可以统称位”2xx应答”,依此类推。SIP/2.0允许6类应答:
1xx:临时应答-请求已经接收,正在处理这个请求。
2xx:成功处理-请求已经成功接收,并且正确处理了这个请求。
3xx:重定向-还需要附加的操作才能完成这个请求,本请求转发到其他的服务器上处理。
4xx:客户端错误--请求包含错误的格式或者不能在这个服务器上完成。
5xx:服务器错误-服务器不能正确的处理这个显然合法的请求。
6xx:全局错误-请求不能被任何服务器处理。
21节定义了详细的代码说明。
7.3 头域
SIP头域和HTTP头域在语法和语义上都比较类似。特别的,SIP头域遵循[H4.2]关于消息头的语法的定义,并且遵循多行的扩展头域的规则。(后者 is specified in HTTP with implicit whitespace and folding)。这个规范遵循RFC2234[10],并且把清晰的空白和封装作为内在的语法规则。
[H4.2]也定义了具有相同域名的多个头域,他们的值是用逗号分开的列表,可以合并到一个头域中。这个也适用于SIP,但是细节上略有不同,因为语法不同。实际上,任何SIP的包头语法都是基于如下范式的:
header = “ header-name” HCOLON header-value *(COMMA header-value)
这个允许合并在具有同一个域名的多个头域,到一个用逗号分割的单个头域中。Contact头域除了当域值是”*”之外,都允许用逗号分割的列表。
7.3.1 头域格式。


头域遵循在RFC2822的2.2节定义的通用头域格式。每一个头域都由一个域名加上冒号(”:”)和域值组成。
                field-name:field-value
消息头的正则语法在25节中有介绍介绍。
在消息头中,允许在冒号的左右有任意个数的空白;但是,在实现中,建议避免域名和冒号中间有空格,并且建议在冒号和值之间使用单个空格(SP)。
                Subject:         lunch
                Subject    :     lunch
                Subject         :lunch
                Subject: lunch
所以,上面的都是合法的,也是相等的,但是最后一种是我们所推荐的。
头域可以扩展成为多行的,只要在每一个附加行前边用至少一个SP或者水平TAB(HT)打头就可以了。这种多行的头域在行结尾并且在下一行之前的空白SP(或者HT)将被认为是一个单个的SP字符。所以,下边的例子是相等的:
                Subject: I know you’re there, pick up the phone and talk to me!
                Subject: I know you’re there,
                        pick up the phone,
                        and talk to me!
头域中的不同域名的相关顺序并没有什么意义。虽然如此,我们还是强烈建议与路由相关的域(VIA,ROUTE,Record-Route,Proxy-Require,Max-Forwards,Proxy-Authorization等等)放在消息头的最前边,这样可以提高处理的速度。相同域名的域之间的顺序非常重要。只有当单个头域的域值是可以用逗号分割的列表的时候,才可以表达成为同一个域名的多个头域(这就是说,如果遵循7.3定义的语法)。也就是意味着必须可以将同一个域名的多个头域在不改变消息语义的前提下,改换表达成为一对”域名: 域值”;这个转换是通过顺序增加每一个域的域值,域值之间用逗号分割。这个规则有几个例外,就是WWW-Authenticate,Authorization,Proxy-Authenticate,和Proxy-Authorization头域。多个头域行可以在消息头中出现,但是由于他们的语法并不遵循7.3中定义的通用格式,所以,他们并不能合并成为单个头域行。
在实现上,必须能够既能够处理多个头域行的情况,也必须能够处理用逗号分割的合并的单个头域行的情况。
下边的几组头域是相等的:
        Route: <sip:alice@atlanta.com>
        Subject: Lunch
        Route: <sip:bob@biloxi.com>
        Route: <sip:carol@chicago.com>
       
Route: <sip:alice@atlanta.com>, <sip:bob@biloxi.com>
        Route: <sip:carol@chicago.com>
        Subject: Lunch

        Subject: Lunch
        Route: <sip:alice@atlanta.com>, <sip:bob@biloxi.com>
                        <sip:carol@chicago.com>

下边各组是合法的,但是并不相等。
Route: <sip:alice@atlanta.com>
Route: <sip:bob@biloxi.com>
Route: <sip:carol@chicago.com>

Route: <sip:bob@biloxi.com>
Route: <sip:alice@atlanta.com>
Route: <sip:carol@chicago.com>

Route: <sip:alice@atlanta.com>,<sip:carol@chicago.com>,<sip:bob@biloxi.com>

每一个头域值的格式是依赖于它的头域名的。他可以是任意顺序的TEXT-UTF8字符,也可以是一个空格,标记,分隔符,引号括起来的字串的组合。很多头域都回附带一个通用的域值格式。这个域值格式是由分号分开的参数名和参数值的组合:
        field-name: field-value *(;parameter-name=parameter-value)
虽然在域值里边可以有任意数量的parameter-name/parameter-value对,但是不能允许有相同的parameter-name存在(唯一性)。除了特别指出的头域之外,头域中的域名、域值、parameter name parameter-value都是大小写不敏感的。标记词始终是大小写不铭感的。除非有特别的指定,引号串的字符串是大小写敏感的。例如:
        Contact: <sip:alice@atlanta.com>;expires=3600

        CONTACT: <sip:alice@atlanta.com>; ExPiReS=3600
相同。
        Content-Disposition: session;handling=optional

        content-disposition: Session;HANDLING=OPTIONAL
相同。
下边的两个头域不相同:
        Warning: 370 devnull “Choose a bigger pipe”
        Warning: 370 devnull “CHOOSE A BIGGER PIPE”

7.3.2 头域分类。
有一些头域是仅仅在请求(或者应答)中有效的。这些头域叫做请求头域或者应答头域。如果消息中的头域与这个消息的类型不匹配(比如在应答消息中出现的请求头域),这个头域必须被忽略。20节定义了每一个头域的分类。
7.3.3 缩写格式
SIP提供了一个用缩写格式来表达通用头域名字的机制。这个有助于避免消息过大而导致通讯层无法传输(比如在UDP传输的时候超过了最大传输单元(MTU))。这个缩写格式在20节定义。
缩写格式的消息头域名字可以在不改变消息语义的情况下替代较大的消息头域名字。在单个消息中,头域名字既可以用长的格式,也可以用缩写格式。在实现中,必须同时支持对长名字和缩写名字的处理。
7.4包体
请求信息,包括这个规范以后的扩展的新请求,都可以包含一个消息正文体。对消息正文体的解释依赖域请求的方法(请求类型)。对于应答消息来说,请求方法和应答状态(response status code)决定了消息正文体的格式。所有的应答消息都可以有一个消息正文体(body)。
7.4.1 消息正文类型(MessageBodyType)
消息中的internet媒体类别必须在Content-Type头域中指明。如果消息正文(body)通过某种形式的编码(encoding),比如压缩等等,都必须在Content-Encoding 头域中指明,否则Content-Encoding域必须忽略。如果可行,消息体的字符集作为Content-type头域的值的一部分表达。

在RFC2046[11]中定义的多部分”multipart” MIME类型可以在消息体中应用。在由多部分组成的消息体发送的时候,如果接受方的实现中,包头域的Accept域中,不包含多部分的标记,那么发送方必须发送一个非多部分的session description。
SIP消息可以包含二进制的包体或者部分包体。如果发送方没有其他显示的字符集参数指出,媒体的文本”text”子类型会是缺省的字符集”UTF-8”。
7.4.2 消息体长度
在Content-Length头域中存放了包体的字节长度。第20.14节讲述了本域的详细解释。
HTTP/1.1的“chunked”传输编码方式并不适用于SIP。(备注:chuncked编码传输方式是通过把消息正文体分为一系列的块来传输的,每一块有它自己的大小标记)

7.5 分帧的SIP消息(Framing SIP Messages)
不同于HTTP的是,SIP实现可以使用UDP或者其他非可靠传输协议。每一帧包括一个请求或者应答。第18节讲述了非可靠传输的应用。
在处理以面向流的通讯为基础的SIP消息的时候,必须忽略在开始行之前的CRLF[H4.1]。
Content-Length头域用来确定每一个SIP消息在通讯流中的结束位置的。在基于面向流通讯基础上的SIP消息一定要使用这个头域。

8 一般用户代理行为
一个用户代理代表了一个终端系统。它包含一个用户代理客户端(UAC),用来产生请求的,它包含一个用户代理服务端(UAS),用来响应请求的。UAC可以由一些外部的东西来发出请求和处理应答(比如用户按了一个按钮,或者按下了一个电话键产生了一个音频信号等等)。UAS是一个能够接收请求,并且产生应答的东西,它可以根据用户输入,外部输入,程序执行结果或者其他什么机制来产生应答。

当一个UAC发送一个请求,这些请求可能通过一些PROXY(代理服务器)传递到UAS上。当UAS产生一个应答,那么这个应答就会同样的被传送到UAC。UAC和UAS的处理由两个特点。第一,基于请求或者应答是否在一个对话里,第二,基于请求的方法(method)。会话的彻底描述在第12节;哪里描述了点对点的用户代理之间的关系,并且通过一些SIP方法建立了会话,比如INVITE方法等。

在本节,我们将讨论在处理对话外的请求时,UAC和UAS的方法无关的规则。这些当然也包括用于建立会话的请求。在26节讲述了对在对话外的请求和应答的安全处理。特别时,UAS和UAC之间的互相认证的机制。通过用S/MIME加密的消息体可以提供有限的隐私保证。
8.1 UAC特性
本节讲述UAC在会话外的特性。
8.1.1 产生一个请求
一个合法的SIP请求必须至少包含如下头域:TO,FROM,Cseq,Call-ID,Max-Forwards, Via;这些字段在所有SIP请求中必须包含。这6个字段是SIP消息的基本组成部分,他们提供了用于路由用的核心信息,包含了消息的地址,响应的路由,消息传递次数,详细的顺序,事务的唯一标志。
这些头域字段是必须包含在请求行之后的,请求行包含了请求的方法,Request-URI,SIP的版本号码。
有两个在对话外的发送请求的示例(通过INVITE请求建立连接,第13节),(通过OPTIONS请求查询负载,第11节)。
8.1.1.1 Request-URI
最开始的Request-URI头域应该是TO头域的的值。但是在REGISTER方法中,有一个值得注意的不同;REGISTER方法的Request-URI头域在第10节中指出。出于隐私的原因而把这些字段的值设置成为同一个值并不太合适(尤其是如果初始的UA期望Request-URI会在传输中改变的话)。
在一些特定的情况下,预先设置的路由表(route-set)会影响消息中的Request-URI。一个预置路由表是由一串server的URI组成,这些服务器是UAC往外发送会话外请求所需要经过的。通常,他们是由用户或者服务提供商手工在UA上设置的,或者通过一些非SIP的方法自动设置。当一个提供商希望配置一个出口proxy给一个UA,我们强烈建议通过一个预置一个单个URI路由表的方式来实现,这个单个路由就是出口proxy。
当要使用预置路由表(route set),必须提供Request-URI和Route头域(在12.2.1.1节中有详细描述)(甚至在没有对话存在的时候也必须提供),并且把Request-URI当作远端目标URI。
8.1.1.2 TO
To头域是第一个并且也是最先指定请求的”逻辑”接收地,或者是这个请求的用户或者资源的address-of-record。这个域内的地址可以是也可以不是请求的最终接收者。TO头域可以用SIP或者SIPS URI,也可以用其他方式的URI(比如电话URL (RFC2806[9]))。所有的SIP实现必须支持SIP URI的实现。任何支持TLS的实现必须支持SIPS URI的实现。
To头域允许有一个显示用的姓名。
UAC可以通过无数的方法来知道在一个给定请求的时候该如何填写TO头域。通常用户会建议采用人工界面中输入的To头域,可能手工输入这个URI或者从地址本中选择(就好像outlook邮件中的to一样)。用户通常不会输入完整的URI,可能只是一个简单的字串(比如”bob”)。这就要求UA能够判断用户输入的这个到底是那个URI。一般使用用户输入的字串加上”@”标志和主机的名字组合成为SIP URI(比如sip:bob@example.com)。如果希望通讯在保密机制下进行,那么就用用户输入的字串组成SIPS URI的部分,用户输入的将加上”@”和主机的名字作为整个SIPS URI。这个主机的名字通常是请求方的主机名字,这个主机允许处理外发请求。这个很像”缩位拨号”的机制,这个机制要求请求者自身的主机能够解释这个缩位拨号一样。
如果UA不希望指定主机,那么就需要将用户输入的电话号码解释成为一个电话的URL。相当于,每一个请求经过的主机都会有机会来处理这个请求。比如,一个用户在机场可能登陆机场的代理服务器,并且通过机场的代理服务器发出一个请求。如果他输入”411”(美国本地电话本查询服务号码),这个就需要机场的外发的代理服务器进行解释和处理,而不是解释成有主机的用户。在这里,tel:411是一个正确的解释。
在会话外的请求中,不能包含To tag字段,在to头域中的tag是用来在对话中做标志的。既然对话还没有建立,那么tag就不能存在。
20.39节有进一步的描述。
下边这个例子是一个To头域的例子:
To: Carol <sip:carol@chicago.com>
8.1.1.3 From
From头域包含了请求发起者的逻辑标志,可能是用户的address-of-record。就像To头域一样,From头域也包含一个URI并且可以包含一个显示的姓名。SIP可以用这个头域来实现对请求的检查和选择一个规则进行对请求的处理(比如,自动的呼叫拒绝,凡是x人发过来的东西,一律无视)。同样的,因为From头域包含的是逻辑名字,所以From URI也可以不包含IP地址或者UA对应的主机名字FQDN。
From头域可以包含一个显示姓名。在客户身份隐藏的情况下,一个UAC应该使用显示名字”Anonymous”,连通一个语法正确,但是没有意义的URI(比如:sip:thisis@anonymous.invalid)。通常,用户或者用户的本地主机的管理人员会事先规定请求头域中的From头域的值。如果给定的UA是多个用户共同使用的,那么必须有一个URI对应身份的profile,这样才能够切用户的profile。收到请求的服务方可以根据这个用于分辩身份的URI来区分同一个UA上的不同的用户,并且根据他们的From头域来判定他们的身份。(22节有更多的验证说明)。
From域必须包含一个由UAC产生的新的”tag”参数。19.3节有tag的详细描述。20.20节有更深入的资料。
例子:
From: “Bob” <sips:bob@biloxi.com> ; tag=a48s
From: sip:+12125551212@phone2net.com;tag=887s
From: Anonymous <sip:c8oqz84zk7z@privacy.org>;tag=hyh8
8.1.1.4 Call-ID
Call-ID是一个在一系列消息中,区分一组消息的唯一标志。在对话中的任一UA的所有请求和所有应答的Call-ID必须一致。在UA的每次注册中,都应该是一样的。在会话外的时候,UAC发起一个新的请求,这个Call-ID头域必须由UAC产生一个全局(在时间和空间上都是)唯一的Call-ID, 除非是请求头的方法(method)指明了别的产生方式。所有的SIP UA都必须保证自己产生的Call-ID不会和其他UA产生的Call-ID重复。注意,如果是请求的重新尝试,则重新尝试的请求不被当作一个新的请求,所以不需要新的Call-ID(重新尝试的请求例如:认证冲突等等)。(见8.1.3.5)
我们强烈建议用密码乱序随机串(RFC 1750[12])来产生Call-ID。实现中,可以用类似”localid@host”这样的格式产生。Call-ID是大小写敏感的,并且通过简单字节/字节的来进行比较。
采用密码乱序随机串可以降低会话被窃听的机会,并且降低Call-ID重复的冲突。不规定或者要求使用用户界面来选择输入Call-ID头域的值。参见20.8节。
例子:
Call-ID: f81d4fae-7dec-11d0-a765-00a0c91e6bf6@foo.bar.com
8.1.1.5 Cseq
Cseq 头域是用来区分和做位事务的顺序使用的。他由一个方法(method)和一系列的顺序号码组成。方法(method)必须和请求的方法一致。对于对话外的非REGISTER请求来说,顺序号码可以是任意的。这个顺序号码必须可以由32位的无符号整数表达,必须小于2^31。只要遵循了上述指导方针,客户端可以用任意的方法来产生这个Cseq头域。12.2.1.1节讲述了在对话中如何创建Cseq
例子:
Cseq: 4711 INVITE
8.1.1.6 Max-Forwards
Max-Forwards头域用来限制请求到他的目的地中间的跳转。它包含一个每隔一个跳转就自动减一的数字。如果Max-Forwards在到达目的之前就减到0,他会报告一个483(太多的路由)错误回应。
一个UAC必须为每一个请求填写一个Max-Forwards头域,这个字段的缺省值应该是70。这个数字是保证了请求在没有环路的SIP网络中都能够送达,也保证了在有环路的时候,尽量少消耗proxy的资源。如果这个数字要变小,则必须保证UA知道整个网络的拓扑结构。
8.1.1.7 Via
Via头域是标志了用于事务传输的传输设备,并且也标志了应答送回的地址。只有当需要通过选择传输设备到达下一个节点(hop)的时候,才需要在头域中包含Via域。当UAC创建一个请求,它必须在头域中添加一个Via域。protocol 名字和protocol版本必须分别是SIP和2.0。Via头域必须包含一个分支(branch)参数。这个参数用于区分请求创建的事务。这个参数客户端和服务器都会使用。除了CANCEL和给非2xx应答的ACK以外,branch参数对UA发出的所有的请求来说,在时间和空间上必须唯一。在下边的讲解中,CANCEL请求的branch参数必须和他所取消的请求的branch参数一致。在17.1.1.3节中讲述了给非2xxx(non-2xx)应答的ACK必须和其对应的的INVITE请求有相同的branch ID。
利用branch ID参数的唯一性来作为事务的ID(transaction ID),并非RFC 2543的一部分。根据本标准产生的branch ID必须用”z9h64bK”开头。这7个字母是一个乱数cookie(定义成为7位的是为了保证旧版本的RFC2543实现不会产生这样的值),这样服务器收到请求之后,可以很方便的知道这个branch ID是否由本规范所产生的(就是说,全局唯一的)。在这样的要求下,精确的branch ID的格式必须事先有实现的定义。
Via头域中,maddr,ttl,和sent-by字段会在transport层处理请求的时候设置(18节)。Via在proxy的处理在16.6节8段和16.7节3段描述。
8.1.1.8 Contact
Contact头域提供了访问后续请求的特定UA实例的联系方法:SIP或者SIPS URI。在任何会建立一个对话的请求中,Contact头域必须提供和包含一个SIP或者SIPS URI。在这个规范中定义的方法中,只有INVITE请求会建立一个会话。对这些请求来说,Contact的作用域是全局性的。这就是说,Contact头域中包含的URI是UA能够接收请求的,这个URI必须是有效的,甚至在对话外的请求中的Contact头域中的URI也必须是有效的。
如果Request-URI或者头上的Route头域包含了SIPS URI,Contact头域也必须是一个SIPS URI。在20.10节有更进一步的说明。
8.1.1.9 Supported 和 Require
如果UAC支持服务端响应请求的SIP扩展,UAC应该在请求的时候包含一个Supported头域说明options tags(19.2节)描述那些SIP扩展。option tags中出现的扩展说明必须是遵循RFCs的标准扩展说明。这样可以防止服务端支持非标准的客户端扩展实现。在Support头域中的对于SIP扩展定义中,严格遵守不支持试验性质的或者说明性质的RFCs扩展,这个也是由于这些扩展是描述提供商定义的扩展说明。如果UAC要求UAS能够支持扩展,以便UAS能够处理UAC的特定请求,那么它必须在请求头中增加一个Require头域来说明处理本特定请求需要什么样的一个扩展option tags。如果UAC需要请求经过的所有proxy都支持它发出的某个请求的扩展部分,它必须增加一个Proxy-Require头域来说明需要Proxy支持何种option tag扩展。
如同在Supported头域指出的,Require和Proxy-Require头域中的option字段必须限定于RFCs的标准扩展。
8.1.1.10 附加信息部分
在一个新请求创建以后,以上的头域都被正确初始化了以后,就可以位这个请求增加它所需要的附加头域了。SIP请求允许包含一个MIME-encoded消息正文。无论请求包含哪种消息正文,都必须引入头域来指出这个正文的类型,以及这个正文的一些其他说明。关于这些头域的详细说明,请参见20.11节到20.15节。
8.1.2 发送一个请求
于是,我们就开始查找请求发送的目标。除非有其他的特定说明,目标必须是通过DNS来查找的(参见[4]说明)。如果路由表(route set)中的第一个元素表明这是一个严格路由(strict router,在12.2.1.1节中讲述),那么这些过程必须在请求的Request-URI中说明。否则,这些过程在请求中被应用于第一个Route头域中(如果存在),或者在请求的Request-URI中(如果Route头域不存在)。这样一些过程产生了一系列的地址,端口,和用于传输的传输器。无论那个URI用在这个[4]中描述的过程的输入,如果Request-URI指明了SIPS,那么UAC必须按照[4]中描述的说明来认为输入的URI是SIPS的URI。
本地策略可以指定一套额外的目的地用于发送。如果Request-URI包含一个SIPS URI,任何额外的目的地都必须用TLS来表达。除此之外,如果请求没有包含Route头域,那么就没有对额外的目的地有什么其他的限制了。这个就提供了一个简单的外发(outbound)proxy的事前路由的选择。但是,用这样的方法配置一个外发proxy是不推荐的;应该由单个UPI规定的预先设定的路由集来指定外发proxy。如果请求包含了Route头域,请求应该发送到Route头域最上边的一个位置,但是请求也可能被发给由本文档约定的Route或者Request-URI所指定的服务器(同RFC2543定义的相反)。特别的,一个配置了外发proxy的UAC应该首先尝试把请求发送给由第一个Route头域值指定的位置,而不是采用把所有消息都发给外发proxy的策略。这就保证了外发的proxy通过不增加Record-Route头域而不参与后续请求的路径。这个也允许让不能分析第一个Route URI的终端,把请求交给外发proxy来发送。UAC应该遵循[4]中定义的过程来实现有状态的元素,尝试每一个地址直到连接到一个服务器。每一个尝试都是一个事务,因此,每一个都有一个不同的Via头域值和一个新的branch参数值。
此外,在Via头域中的transport的值被设置成为要到目标服务器所必须的transport。
8.1.3 处理应答
应答首先是被transport层处理,并且被transport层发送给上一层transaction层处理。transaction层处理完成之后将应答发送给更上一层TU处理。在TU层进行的对应答的主要处理是方法相关的。但是也有集中通用的处理原则:
8.1.3.1: transaction 层的错误
在某些情况下,从transaction层返回的应答不一定是一个SIP消息,而是一个transaction层的错误。当从transaction层收到一个timeout错误的时候,必须将这个timeout错误当作是收到了一个状态码是408(请求timeout)的应答。如果transport层报告了一个严重错误(通常取决于UDP传输中的严重的ICMP错误,或者是TCP连接中的错误),必须把这个错误当作是状态码503(服务未提供)的错误。
8.1.3.2 未知的应答
UAC必须把自己不认识的所有最终应答当作是x00的那类应答,当然UAC也必须能够处理所有的类别应答的x00的应答。比如,如果UAC收到了不认识的应答代码431,他可以很安全的假设在他发出的请求中有什么地方弄错了,并且可以很简单的把这个应答错误当作是接收到了一个应答代码是400(非法请求)的错误应答。并且,UAC必须能够处理所有的不认识的非终结应答响应当作是183(session progress)。一个UAC必须能够处理100和183应答。
8.1.3.3 Vias
如果在应答中,有不只一个Via头域值存在,那么UAC应该丢弃这个消息。包含超过一个Via头域值的消息是因为被错误的路由或者消息被破坏。
8.1.3.4 处理3xx应答
由于接收到一个重定向的应答(比如,状态码是301的应答),客户端应该用在Contact头域中的URI(s)来组织一个或者多个基于重定向以后的新请求,这个处理过程同proxy处理一个3xx类别的应答很类似,相关资料在16.5节和16.6节中有描述。客户发起请求的时候只有一个目标URI,就是原始请求中的Request-URI。如果客户端想在这个请求基础上重构一个基于3xx类别应答的新请求,那么就需要把这个需要尝试的URIs放到目标集合中去。基于本规范的规定,一个客户端可以选择放置那个Contact URIs到目标集合中(target set)。同proxy会递归一样,客户端处理3xx应答的时候必须不能重复添加任何URI到target set。如果原始请求的Request-URI头域中包含了一个SIPS URI,那么客户端可以选择改乘一个非SIPS URI,但是需要知会客户转发到一个非安全的URI去。任何新的请求都可能导致接收到这些请求的3xx应答,并且在Contact中包含原始的URI。可以通过对两个位置的配置来形成互相重定向。只要保证在target set中任何URI都只出现1次就能避免无穷的重定向循环。当target set增长了,客户端可以对这个URIs,用任意顺序来产生新的请求。常见机制是通过在Contact头域的值中设置”q”参数来指定这个顺序。对这些URIs的请求可以是并行产生的也可以是串行产生的。有一个实现是按照q参数值递减的方法顺序处理URIs,并且对相同q参数值的URIs进行并行处理。还有一种就是直接按照顺序的方法处理URIs,对于q参数值不同的按照递减的顺序处理,对于q参数值相同的按照随机顺序处理。
如果发送给连接表上的地址失败了,(在下一段我们有定义),那么就选择列表中的下一个地址进行发送,直到列表全部遍历一遍。如果列表遍历完了还没有,那么请求就失败了。
通过响应码(大于399)我们可以知道请求的失败;对于网络错误来说,客户transaction会给transaciton user报告transport层的通讯错误错误。注意有一部分响应码(8.1.3.5)表示请求可以被重试;这个时候,请求可以重发而不是简单的当作一个错误。如果某个contact地址发送失败,那么client应该尝试下一个contact地址。这个会导致创建一个新的客户事务来处理这个新的请求。
为了在处理3xx应答中创建一个基于一个contact地址的请求,UAC必须首先从target set中拷贝除了”method-param”和”header”URI参数之外的整个URI到Request-URI(见参数的定义见19.1.1)。通过使用”header”参数来创建新请求的头域值,按照19.1.5节的指引,根据重定向的请求来重写头域的值。
注意在某些情况下,在contact地址中进行通讯所需要的头域,可能代替添加到现有的在原始转发的请求的请求头域中。作为通用的规则,如果头域可以接受用逗号分割的值列表,那么新的头域值可以添加到原始转发的请求的任何值后边。如果请求头域不接收多值列表,那么原始转发请求中的头域值可以由contact地址中进行通讯所需要的头域的值所替换。比如,如果contract地址返回了如下的值:
sip:user@host?Subject=foo&Call-Info=Http://www.foo.com
那么在原始转发请求中的任何Subject头域都被重写,但是HTTP URL仅仅添加到现存的Call-info 头域值中。
我们推荐UAC重用与原始转发请求相同的To,From,和Call-ID域值,但是也允许UAC为新的请求改变Call-ID头域。
最后,当一个新的请求构造好以后,这个请求将通过一个新的客户transaction发送,应此在最开始的Via头域中必须有一个新的branch ID (8.1.1.7节说明)
在其他的方面,转发的请求应该重用原始请求的头域和消息体。
在某些情况下,Contact头域的值可能在UAC中暂时或者永久保存,这个依赖于接收到的请求码和过期的时间;参见21.3.2和21.3.3节。
8.1.3.5 处理4xx应答
某个4xx应答码要求特定的UA处理,和请求的方法无关。
当接收到401(未授权)或者407(Proxy认证需要)应答的时候,UAC应该遵循在22.2和22.3中规定的认证步骤,重新发送带认证信息的请求。
当收到413(请求过大)应答的时候(21.4.11节),这说明请求包含了一个UAS所不能接收长度的消息体。如果可能,UAC应该尝试重新retry这个请求,或者去掉包体或者换一个小一点的长度。
如果收到了一个415(不支持的媒体类型)应答(21.4.13节),那么请求中包含的媒体类别是UAS所不支持的。UAC应该重发这个请求,并且这次发出的请求只包含应答中的Accept头域所指明的媒体类别,并且采用Accept-Encoding头域中指明的encoding方法,还有Accept-Language指明的语言。
如果收到了一个416(不支持的URI Scheme)应答(21.4.14节),Request-URI使用的URI Scheme(方案)是服务端所不支持的。客户端应该重新尝试这个请求,并且换用SIP URI。
如果收到了一个420(非法扩展)应答(21.4.15节),请求的Require或者Proxy-Require头域包含的option-tag中包含了UAS或者proxy不支持的特性。UAC应该尝试去掉应答中的Unsupported头域中列出的扩展以后然后再尝试。
在上述所有的情况中,所有需要重试的请求,都需要经过适当修正成为一个新的请求。这个新请求采用一个新的transaction并且应该有和上次请求相同的Call-ID,TO,From头域,Cseq应该有一个新的顺序号码(比原有顺序号码更大)。
其他的4xx应答,包括尚未制定的,是否允许请求重试,依赖于具体的方法和应用。
8.2 UAS特性
UAS在处理对话外的请求的时候,有一组规则需要遵守,这组规则与方法无关。12节指明了一个方法来判定一个请求是否在一个对话里。
注意,请求的处理是原子级别的。如果请求被处理,那么这个请求的相关状态一定是一起更新的。如果它被拒绝了,那么这个请求的所有相关状态一定是没有改变的。
UASs应当遵循本节所规定的顺序来处理请求。(就是说,首先是身份认证,然后是方法判定,然后是头域,然后按照本文规定处理剩余部分)
8.2.1 方法判定
当请求被认证(或者身份认证被忽略),UAS必须首先判定这个请求的方法。如果UAS发现自己不能处理这个请求的方法的时候,它必须给出一个405(方法不支持)的应答。产生应答的步骤在8.2.6节规定,并且UAS必须在给出的405(方法不支持)应答中增加一个Allow头域。这个Allow头域必须列明哪些方法UAS支持。Allow头域的说明在20.5节。
如果请求中的方法是服务器所支持的,那么处理将继续。
8.2.2 包头判断
如果UAS不认识请求中的包头域(就是说,包头域不在本规范中定义或者不在任何扩展中定义),那么服务器必须忽略掉这个包头域并且继续处理本请求。UAS必须忽略任何处理本请求所不需要的长得畸形的包头域。
8.2.2.1 TO 和Request-URI
To头域包含了由From域描述的发送者发出的请求的原始接受者。原始接受者可能是也可能不是正在处理这个请求的UAS,取决于呼叫转移或者其他的proxy操作。当TO域值和自身不相符的情况下,UAS可以自行决定是否接收这个请求。但是,我们依旧是建议UAS处理这个请求,甚至TO这个头域是以他们不认识的URI方案表达的(比如一个tel:URI),或者To头域并非指向这个自身处理的UAS。当然,另外一方面来说,如果UAS决定拒绝这个请求,它应该产生一个403(禁止访问)的状态码,并且交给服务器的transaction层来发送。
但是,Request-URI确定UAS来处理这个请求。如果Request-URI使用了一个UAS所不支持的方案(比如tel:URI),那么UAS应当拒绝这个请求,并且给出拒绝代码416(不支持的URI方案)。如果Request-URI并没有指明本UAS来处理这个请求,那么UAS应当给出一个404(未找到)的应答。比如,一个UA使用REGISTER方法来绑定它的address-of-record到一个特定的联系地址,将会收到Request-URI等于那个特定联系地址的请求。
其他潜在的Request-URI资源包括建立和刷新对话的UA发出的请求和应答的Contact头域。
8.2.2.2 合并的请求
如果请求的To头域中没有tag标志,UAS的处理核心必须检查基于正在进行的transactions上的请求。如果接收到的请求和正在处理的transaction的请求中的头域From tag,Call-ID,CSeq精确匹配了,但是请求并不匹配那个事务(基于事务匹配机制17.2.3节),UAS核心应该产生一个482(检测到循环)应答并且给服务器的transaction层发送。
这是由于相同的请求通过不同的路径到达UAS,很多情况下是由于分支的原因。UAS处理了第一个请求并且给其他所有这个请求以482(检测到循环)应答。
8.2.2.3 Require
如果请求的各项要素通过了UAS的判定,那么如果存在Require头域,接下来就是检查Require头域。Require头域是UAC用来通知UAS应该用什么样的SIP扩展来处理本请求的。Require的格式在20.32节中有介绍。如果UAS不支持请求的Require头域中的option-tag列表,那就必须产生一个420应答(错误的扩展)。并且UAS必须添上Unsupported头域,里边填上刚才接收到的请求的Require头域中,哪些options是自己所不支持的。
需要注意的是,Require和Proxy-Require禁止出现在SIP CANCEL请求中,或者回应给非2xx应答的ACK请求中。就算出现了在处理的时候也必须被忽略。并且回应给2xx应答的ACK请求必须只能包含在初始请求(在这个ACK请求之前的请求)中包含的Require和Proxy-Require所规定options,这样才能保证服务端能够正确处理。
例子:
UAC->UAS: INVITE sip:watson@bell-telephone.com SIP/2.0
Require: 100rel
UAS->UAC: SIP/2.0 420 Bad Extension
Unsupported: 100rel
这个特性(Unsupported)是为了保证客户-服务端都能够无阻碍的交互,除非是options对方不支持(就像上边的例子说明的一样)。对于相互匹配的客户-服务端(相互匹配的意思就是客户端Require的正好是服务端支持的),那么这些请求、应答将会处理的非常迅速,减少了一个请求的往返协商的浪费。另外,这个也避免了客户端不知道服务端到底不支持那些特性扩展。
某些特性扩展只对终端(endsystem)有效例如呼叫处理域等等。

8.2.3 内容处理
当UAS支持客户端请求中要求的扩展支持后,UAS要检查消息头域描述的消息体部分。如果UAS并不支持消息体部分的类型(Content-Type指明),语言(Content-Language指明),编码(Content-Encoding指明),并且这个消息体部分并非可选的消息体(Content-Disposition头域指明),UAS必须回应一个415错误应答(不支持的媒体类型)应答。并且如果不支持请求中包含的消息体的正文类型,那么在应答中必须包含UAS所支持的消息体的类型列表(在Accept头域中指明)。如果不支持请求包含的消息体的encoding方式,那么应答中必须包含Accept-Encoding头域列明服务端支持的encoding方式。如果请求中的语言部分不支持,那么就必须在应答中包含Accept-Language头域列明支持的语言。在这些检查之外,消息体正文的处理依赖于方法和类型(method and type)。关于处理内容相关的头域的进一步的资料在7.4、20.11到20.15节。
8.2.4 应用扩展
如果UAS希望应用一部分SIP扩展,那么不可以在产生应答的时候做SIP扩展,除非这个扩展是在请求中的Supported头域中指明了的。如果这个扩展并没有在本请求的Supported头域中指明,那么服务端必须基于基准SIP给出应答,或者给出已知客户端支持的扩展应答。在极少数情况下,如果服务端不用扩展就无法处理请求,那么服务端应该发送421(需要扩展支持)应答。这个应答说明如果没有适当的扩展就无法给出正确的应答。在应答中需要的扩展必须在应答中的Require头域中指出。我们并不推荐这个方法,因为它会降低协同工作的能力。
除了421应答之外的其他应答中,如果需要应用扩展,那么这些扩展需要在421的应答中的Require头域中列明。当然,服务端不允许使用没有在请求中的Supported头域中列明的扩展。因此,应答中的Require头域只会包含标准的RFCs的扩展option tags。
8.2.5 处理请求
当所有的检查都通过了以后,UAS根据方法决定如何处理请求。第10节讲述了REGISTER请求的处理,11节讲述了OPTIONS请求的处理,13节讲述了INVITE的处理,15节讲述了BYE的处理。
8.2.6 产生应答
UAS产生应答,需要遵守接下来的几个小节中讲述的步骤产生一个应答。
在本节没有描述的应答代码的其他的行为,也可能会跟据应答代码的不同而要求。当创建应答的步骤完成之后,UAS将应答交给收到这个请求的服务端的transaction去处理。
8.2.6.1 发送一个临时应答
很多情况下,在与方法无关的应答规范中,在非INVITE请求的情况下,我们都要求UAS不应该发送临时应答给请求者。在这种情况下,UAS应该尽快发送一个终结应答给非INVITE请求。
当需要产生一个100(Trying)应答的时候,所有对应请求中包头的Timestamp域必须也拷贝到这个应答包头(就是说如果请求中有Timestamp,应答中也必须有timestamp)。而且如果应答有延时,那么UAS应该在这个Timestamp上增加延时的值。这个数值必须包含了接收请求和发送应答的时间,用秒来计数。
8.2.6.2 包头和Tags
应答中的From头域必须和请求中的From头域相等。应答中的Call-ID头域必须和请求中的Call-ID头域相等。应答中的Cseq头域必须和请求中的Cseq头域相等。应答中的Via头域必须和请求中的Via头域相等,而且顺序也必须相等。如果请求中包含了To tag,那么应答中的To头域必须和请求中的To头域相等。如果请求中的To头域并不包含Tag,那么应答中的To头域的URI必须和请求中的TO头域的URI相等;此外,UAS还必须增加一个Tag到To头域上(100(trying)应答是一个例外,在100中可能已经存在了一个tag)。这就提供了一个UAS正在应答的标志,也许就是对话ID的一部分。对同一个请求来说,它的应答必须有相同的tag标志,包括终结应答和临时应答(同样100(trying)除外)。生成tag的步骤在19.3节。
8.2.7 无状态UAS行为
无状态UAS就是说UAS本身不保持事务的状态。但是它在发送对应请求的应答之后并不保存请求的状态。如果无状态UAS接收到了一个重新发送的请求,它会重新产生一个应答并且重新发送应答,就像它接收到的是一个第一个请求一样(就像是新请求而不是重发的请求一样)。只有当相同请求的处理会导致相同的应答的时候,这个UAS才会是无状态的。例如:这条规则排除了无状态的登记服务。无状态的UAS并不包含一个transaction层;他们直接从通讯层接收请求和发送应答。
无状态的UAS通常用于处理不需要身份认证的请求,而且这些请求的应答是可以预期的。如果当不需要身份认证的请求被作为有状态的UAS来处理,那么大量的不需要身份认证的请求会造成服务器大量的事务状态,可能会导致UAS的处理非常慢,甚至中断处理,这样就导致了DoS(denial of service)状态。在26.1.5节中有进一步的描述。
无状态的UAS有下列重要的特性:
o 无状态的UAS不许发送临时应答(1xx)
o 无状态的UAS必须忽略ACK请求
o 无状态的UAS必须忽略CANCEL请求
o To头域必须依据无状态的规则来产生-就是说对于相同的请求,产生相同的tag标记。19.3节有讲tag标记。
对于其他情况而言,无状态的UAS遵循有状态的UAS的规则。对于一个新请求来说,同一个UAS可以是有状态的,也可以是无状态的。
8.3 重定向服务器
在某些架构下,可以透过重定向机制,来降低proxy服务器上的路由请求的压力,从而提高消息转发的效率。重定向允许服务器在一个请求的应答中,推送路由信息到客户端,这样就可以做到把自己从后续的消息流中脱离出来,但是同时又能提供准确的请求定位服务。当请求的发起者接收到转发的应答之后,他会重新产生一个基于接收到的URI(s)的请求。从network的中央转发到最边缘,转发机制可以适用于跨度很大的网络。
转发服务器在逻辑上是通过一个服务器的transaction层建立的,他作为transaction user可以访问某些类型的位置服务(参见第10节有关注册服务器和位置服务的说明)。这个位置服务可以很方便的用数据库里边的一个URI对应多个地址URI(可能在这个地址找到对应的客户)来实现。
一个重定向服务器并不发出任何指向它自己的请求。接收到任何一个非CANCEL的请求之后,服务器要么拒绝这个请求,要么从位置服务器上找到这个请求应该去的其他位置然后返回一个3xx的终结应答。对于合法的CANCEL请求,它应该返回一个2xx的应答。这个应答将会结束掉SIP事务。重定向服务器在整个SIP事务中位置事务的状态。在重定向服务器之间检测循环死锁应该是客户端的责任。
当重定向服务器给请求方返回一个3xx应答的时候,它会在Contact头域填写一组(一个或者多个)其他位置信息。如果需要指明这个Contact数据的生存周期,可以在Contact头域添加一个”expires”参数。Contact头域包含指向新位置的URI或者可以试试看的用户名,或者就是简单的附加通讯参数等等。301(永久去除)或者302(临时去除)应答同样可以指定和原始请求一样的目的位置和用户名,但是会附加像不同的服务器或者多点传送地址、或者SIP由UDP到TCP传输等等这样的通讯参数。
重定向服务器必须不能重定向一个请求到它自己的Request-URI列表中的地址;但是倘若那个URI并没有指向自己,重定向服务器可以路由这个请求到目的URI,或者可以返回一个404拒绝。

如果一个客户端正在使用外出的proxy,并且这个proxy实际上是重定向请求,那么就可能出现无限重定向循环。

注意,Contact头域的值可能指向一个与原始呼叫者无关的资源。比如,一个SIP对PSTN(本地电话网)网关的呼叫可能被转递给一个特别的说明,比如”你所呼叫的电话号码已经改为….”。Contact应答头域可以包含任何合适的能处理这个呼叫对方的URI,并不限于SIP URI。比如,它可以是电话,传真,或者IRC(如果有支持和定义的话)或者一个mailto:(RFC 2368[32]) URL。在26.4.4节讲述了对SIPS URI到非SIPS URI的实现和限制。

在Contact头域中的”expires”参数指明了这个URI的生存周期。这个参数的值是以秒作为单位的。如果这个参数没有提供,那么这个”expires”的缺省是有Expires头域所指定的。这个参数中如果设置了非法的值,那么都应改更改成为缺省的3600。这提供了对RFC2543的向后兼容,RFC2543允许在这个头域中填写绝对时间。在本协议中,如果这个expires填写了绝对时间,那么就会当作是非法的值,就会被当作是3600。

重定向服务器必须忽略自己所不能理解的特性(包括不认识的头域,不认识的Require中的option tag,甚至是不认识的方法名),并且带着问题处理这个请求的重定向。

9 取消一个请求(Cancel)
前边几节讲述了对所有方法的处理请求和处理应答的UA的通用处理过程。在本节中,我们讨论一个通用的方法,CANCEL。CANCEL请求,就像名字所说的,是用来取消客户端发起的上一个请求的。特别是,它请求UAS去终止上一个请求并且对上一个请求产生一个错误的应答。CANCEL对UAS已经给出终结应答的请求无效。所以,CANCEL请求的最大用处是取消需要服务器长时间处理的请求。也就是说,CANCEL最常用来处理取消INVITE请求,因为INVITE通常需要花费很长时间来产生一个终结应答。在这种使用中,UAS接收到对一个INVITE请求的CANCEL请求,当这个INVITE还没有得到终结应答的时候,UAS会”停止振铃”,并且给INVITE请求一个错误的应答(487)。
CANCEL可以由proxy或者UAC发起。15节讲述了在何种情况下,UAC会CANCEL一个INVITE请求,在16.10节讲述了proxy对CANCEL的使用。
一个有状态的proxy需要对CANCEL进行响应,而不是简单的转发从下行流中接收到的一个应答。基于这个原因,CANCEL是一个”点对点”(hop-by-hop)的请求,也就是说,CANCEL需要每一个有状态的proxy节点进行处理和应答。
9.1 客户行为(Client Behavior)
CANCEL请求不应该取消除了INVITE之外的请求。因为除了INVITE之外的请求的响应都是立即响应的,所以,发送CANCEL来取消一个非INVITE请求总是形成一种赛跑的局面(就是说,cancel先到还是被取消的请求先到)。
下列步骤用于创建一个CANCEL请求。在CANCEL请求中的Request-URI , Call-ID , To , Cseq的数字部分,From这些头域都必须和被取消的请求头域一样,包含这些头域的tags. 客户端创建的CANCEL必须只有一个Via头域值,这个头域值和被取消的请求的最上一个Via头域值相同。这些头域的值和请求的值相同可以让CANCEL请求和被取消的请求相匹配(9.2节描述了如何匹配)。在Cseq请求头域的method部分必须是一个CANCEL方法。这个让这个CANCEL请求被当作自己的事务而被正确的鉴别和处理(参见17节)。
如果被取消的请求包含一个Route头域,CANCEL请求也必须包含这个Route头域的值。这个是让无状态的proxy能够正确路由CANCEL请求。
CANCEL头域必须不能包含任何Require或者Proxy-Require头域。
一旦CANCEL请求被创建了,客户端应当检查是否收到了这个CANCEL请求取消的原始请求的任何应答(临时的或者终结的应答)。如果没有任何临时应答收到,这个CANCEL请求一定不能发送,直到客户端等到了第一个临时应答。如果原始请求已经收到一个终结应答,这个CANCEL也不应当发送,因为CANCEL请求对已经产生了终结应答的请求没有任何作用。当客户端决定发送一个CANCEL,它会为这个CANCEL创建一个客户transaction,并且通过目的地址、端口、传输层来发送CANCEL请求。这个CANCEL中的目标地址、端口和传输层必须和原始请求一样。
如果允许在接收应答之前发送CANCEL请求,那么服务端必须支持在接收原始请求之前接收到CANCEL请求。
注意,原始请求的事务和CANCEL请求的事务都是互相独立的。也就是说,UAC判定一个请求的取消不能依赖原始请求的一个487(请求终止)应答,遵循RFC2543协议,UAS不会产生这样一个应答。如果原始请求经过了64*T1(T1在17.1.1.1节定义)秒还没有应答,客户端应当认为原始请求已经取消,并且应当销毁对应原始请求的客户端事务。
9.2 服务端行为(Server Behavior)
CANCEL请求要求服务端的TU取消相关的事务(transaction)。TU根据接收到的CANCEL请求,并且假定请求的方法不是CANCEL或者ACK,并且用17.2.3节描述的事务匹配方法来匹配事务,这样TU就可以决定那个事务需要被取消了,被匹配的事务就是需要被取消的事务。
服务端对CANCEL请求的处理依赖于服务器的类型。一个无状态的proxy会转发这个请求,一个有状态的proxy可能会响应这个请求,并且自己再产生一些CANCEL请求,UAS会响应这个CANCEL请求。16.10节讲述了proxy怎样处理CANCEL请求。
当UAS收到CANCEL请求,首先按照8.2节的UAS通用处理方法进行处理。不过,既然CANCEL请求是基于”点对点”(hop-by-hop)的,也是不能再提交的,他们不能由服务器为了获得Authorization头域中正确的认证而反复尝试。注意,因此CANCEL请求也不能包含Require头域。
如果根据上边的步骤,UAS不能找到与CANCEL请求相匹配的事务,它应该给CANCEL一个481应答(调用的Leg/Transaction不存在 会话/事务不存在)。如果对应原始请求的事务存在,那么UAS在接收到CANCEL请求的处理就依赖于是否已经给这个原始请求发出了终结应答。如果已经发出了,不会对CANCEL请求对应的原始请求做任何处理,不会更改任何会话状态,不会对原始请求的应答做任何处理。如果UAS没有发出对原始请求的终结应答,它会依赖于CANCEL所取消的原始请求方法。如果原始请求方法是INVITE,UAS应当立刻响应INVITE一个487(请求终止)。本协议中,对CANCEL取消的其他本协议中定义的方法没有约定。
不管原先请求的方法是什么,只要CANCEL匹配一个事务,UAS就响应CANCEL请求一个200(OK)应答。这个应答根据8.2.6节约定构造。注意,给CANCEL应答的To tag和给原始请求的应答的To tag应该是一样的。对CANCEL的应答会通过服务端transaction来传送。
10 注册(Registrations)
10.1 概览
SIP提供了一个搜索机制,如果一个用户希望建立和其他用户的会话,SIP必须查找能够找到对方用户正在使用的当前主机(hosts)。这个搜索机制经常被SIP网络基本元素使用,比如proxy服务器,重定向服务器等等。他们在接收、以及响应一个请求的时候,会基于这个用户的位置信息来判定这个消息应该发送到哪里。要实现这个,SIP网络部件考虑了一个抽象的服务:位置服务;位置服务是通过对特定地区提供地址绑定来实现的。这些地址绑定转换输入的SIP或者SIPS URI,比如sip:bob@biloxi.com,转换到一个或者一组更加”接近”目标用户的URI,比如sip:bob@engineering.biloxi.com。基本上,一个proxy会从把输入的URI转换到用户实际位置的位置服务中得到最终用户的位置。
注册服务为特定地区的位置服务创建绑定关系,这个绑定关系是用来建立包含一个或者多个联系地址的address-of-record URI。因而,当那个地区的proxy接收到一个请求,这个请求的Request-URI和address-of-record的记录匹配,那么这个proxy会转发请求到这个address-of-record中登记的联系地址中去。通常,只有当对那个address-of-record的请求会被路由到这个区域的时候,登记这个address-of-record 到这个这个区域的位置服务才是有意义的。在大多数情况下,这个就要求登记服务所覆盖的区域和URI中的address-of-record所覆盖的区域相同。有很多种方法来建立位置服务。一个方法是administratively(管理)。在上述的例子种,Bob我们通过查询公司数据库知道他是一个工程部职员。不过,SIP提供了一个让UA能够创建精确绑定的机制。这个机制就是登记服务。
登记服务需要向一个特殊的UAS服务器(注册服务器registrar)发出REGISTER请求。注册服务器(registrar)为一个区域的位置服务作为前端接入,根据REGISTER请求的内容读写位置对照表。这个位置服务通常为处理这个区域的proxy服务器提供位置服务。
总的登记服务处理流程在图2中说明。需要注意的是,登记服务器(registrar)和proxy服务器都是逻辑上的角色,可以在网络中用一个设备来部属;在例子图中是为了能够清楚的表示所以分开描述。还需要注意的是如果他们(登记服务器和proxy)本身是分开的,那么UA可以通过proxy服务器发送注册请求。
SIP本身对实现位置服务器(location service)没有特别的要求。唯一的要求是某些区域的注册服务器(registrar)必须能够对位置服务的数据进行读写,并且这个区域的proxy或者重定向服务器必须能够兼容读取相同的数据。注册服务器(registrar)可能和某一个区域的proxy服务器部署在一起。
10.2 构造一个REGISTER请求
REGISTER请求用来增加、删除、查询绑定资料。一个REGISTER请求可以增加一个address-of-record和一个或者多个联系地址之间的绑定。在合适的第三方认证的情况下,可以做address-of-record的登记。客户端同样可以删除前边绑定的内容也可以查询address-of-record的当前绑定地址。除了特别说明以外,REGISTER请求的构造以及客户端如何发送REGISTER请求和通常的8.1节描述的和17.1节描述的UAC发出请求是一致的。
一个REGISTER请求并不建立一个对话。当基于事先给定路由集(8.1节)的情况下,一个UAC可以在REGISTER请求中包含一个Route头域。在REGISTER请求和应答包中,Record-Route头域并没有任何意义,如果这个头域存在,必须被忽略。另外,UAC一定不能基于REGISTER请求的应答包中的任何Record-Route头域来创建新的路由集合。
下面这些头域,除了Contact,必须在REGISTER头域中包含。Contact头域可选。
Request-URI:        这个头域指明了登记服务所指明的位置服务所在的区域(比如sip:chicago.com)。”userinfo”和”@”元素在SIP URI中不能出现。
To:                        这个头域包含了被查询、增加、修改的address-of-record。to头域和Request-URI头域通常是不同的,因为这个由用户名组成。这个address-of-record必须是一个SIP URI或者SIPS URI.
From:                        这个头域包含了提交这个注册信息的用户的address-of-record资料。这个值和To头域的值相同,除非这个请求是第三方发起的注册请求。
Call-ID:                        UAC发出的给某个注册服务器(registrar)的所有注册请求都应该有相同的Call-ID头域值。如果相同的客户端用了不同的Call-ID值,注册服务器(registrar)就不能检测是否一个REGISTER请求由于延时的关系导致了故障。
Cseq:                        Cseq值保证了REGISTER请求的正确顺序。一个UA为每一个具备相同的Call-ID的REGISTER请求顺序递增这个Cseq字段。
Contact:                        REGISTER请求可以有一个Contact头域。这个头域可以有0个或者多个包含绑定地址信息的值。
UA在没有收到上一个注册请求的应答或者上一个REGISTER请求超时之前,禁止发送新的注册请求(就是说,包含一个新的Contact头域值,而不是重发)。


bob
+----+
| UA         |
|            |
+----+
|
|3)INVITE
|         carol@chicago.com
chicago.com                         +--------+                         V
+---------+ 2)Store        |Location|4)Query  +-----+
|Registrar|=======>        | Service|<======= |Proxy|sip.chicago.com
+---------+                         +--------+=======> +-----+
A                                                                 5)Resp                 |
|                                                                                         |
|                                                                                        |
1)REGISTER|                                                                                 |
|                                                                                         |
+----+                                                                                         |
| UA |            <-------------------------------+
cube2214a| |                                                                         6)INVITE
+----+                                                                 carol@cube2214a.chicago.com
carol
                                图2:REGISTER例子
下边的Contact头域参数在REGISTER请求中有特别的意义:
action: 在RFC2543中的“action”参数已经废弃了,UAC不能使用”action”参数。
expires: “expires”参数表明UA的绑定的有效时间。以秒为单位的整数。如果本参数没有制定,那么这个参数的值就是Expires头域的值。实现中,可以把超过2**32-1的值(4294967295秒或者136年)认为是2**32-1。非法的值应当视同3600。

10.2.1 增加绑定
REGISTER请求是向注册服务器(registrar)发送一个包含对某一个address-of-record的地址的SIP请求应当发送的实际联系地址。address-of-record包含在REGISTER请求的To头域中。
请求中的Contact头域通常包含了SIP或者SIPS的URI,这些URI表明了特定的SIP端点(比如sip:carol@cube2214a.chicago.com),他们也可以使用其他的URI表示方法。一个SIP UA可以选择注册一个电话号码(比如使用tel URL, RFC 2806[9])或者一个email地址(比如用mailto URL, RFC2368[32])来作为address-of-record的联系地址Contact域。
例如,Carol,有一个address-of-record”sip:carol@chicago.com”,将会在区域chicago.com的注册服务器上注册。她的注册服务信息将会被chicago.com区域的proxy服务器使用,用来路由和转发到Carol的address-of-record请求到她的SIP终端。
当客户端在注册服务器(registrar)上建立好了绑定以后,它可以根据需要发送后续的注册请求,包含新的绑定信息或者修改以前的绑定信息。给REGISTER请求的2xx应答中,在Contact头域中是在这个注册服务器(registrar)上登记的完整的这个address-of-record的绑定列表。
如果REGISTER请求中的To头域中的address-of-record是一个SIPS URI,那么任何在REGISTER请求中的Contact头域都应当是SIPS URI。客户端只有在有其他手段保证非SIPS URI的安全性的情况下,才能在SIPS 的address-of-record的地址上注册非SIPS URI。这个也可以适用域使用非SIP协议的URI,或者用非TLS来加密的SIP设备。
注册并不需要更新所有的绑定。一般情况下,UA只更新它现在的联系地址。
10.2.1.1 设置Contact地址的过期参数
当一个客户端发出一个REGISTER请求,它可能包含一个过期参数用来表示这个注册的地址的有效期。(就像在10.3节讲述的那样,注册服务器(registrar)根据自己的策略选取实际的时间间隔来计算有效期)。
客户端设置有效期的方法有两种:一个是通过设置Expires头域,一个是通过设置”expires”contact头域的参数来设置。 后一种允许针对同一个REGISTER请求中的多个绑定联系地址中的每一个联系地址单独设定有效期,然后所有没有带”expires”参数的Contact头域的值都使用Expires的设置。
如果REGISTER中没有两种有效期都没有设置,这就表明这个有效期由服务器来决定。
10.2.1.2 Contact Adress的参数选择
如果在一个REGISTER请求中包含多个Contact,着说明UA想要把这些Contact头域的内容都和To头域中制定的address-of-record地址绑定起来。这个列表可以用”q”参数来区分Contact头域的优先级。”q”参数用来标志特定Contact头域值和其他绑定的address-of-record的联系地址之间的优先级。16.6节讲述了一个proxy服务器如何使用优先级。
10.2.2 删除绑定
注册信息是一个纯粹软件的状态,并且如果不刷新会过期。如果需要,也可以被删除。一个客户端可以设置注册服务器(registar)的有效期(10.2.1)。一个UA可以通过发出有效期为”0”的REGISTER请求,使某一个联系地址立刻失效。UAS都需要实现这个机制使得在联系地址过期前能够被删除。
REGISTER规范中的Contact头域如果设置成为”*”则表示需要操作所有的注册项。但是也只能在具有一个Expires头域并且这个头域为’0’的情况下能够使用。(这就是说,只能够在要删除所有的注册项的时候使用”*”)。
用”*”来删除所有的注册项有一个好处,就是使得UA不需要知道每一个注册项的精确值。
10.2.3 访问绑定
无论请求是否包含了Contact头域,给任何REGISTER请求的成功应答都包含了一个完整的绑定列表。如果REGISTER请求头域中不包含Contact头域,那么注册服务器的绑定列表将不会改变。
10.2.4 刷新绑定
每一个UA都对先前它建立的绑定信息由刷新的义务。禁止对其他UA建立的绑定信息进行刷新。在注册服务器(registrar)给出的200(OK)应答中,包含了的Contact头域中列明了所有的当前绑定信息。UA需要挨个比较这些联系地址,看看是否这个地址是可以建立联系的,这个比较是通过19.1.4节中的比较规则来进行的。如果是,它通过更新expires参数来更新过期时间(或者Expires头域)。于是在这些绑定信息过期前,UA为每个绑定发出REGISTER请求来刷新绑定。也可以通过一个REGISTER请求来刷新数个绑定请求。
UA在一个刷新周期中,应该使用相同的Call-ID来进行注册调用。刷新的注册信息应该是和原来登记的信息一致。
10.2.5 设置内部时钟
如果REGISTER请求的应答中包含一个Date头域,客户端可以用这个头域来校正当前内部的时钟。
10.2.6 寻找注册服务器
UA有3种方法来决定向哪里发出注册请求:通过配置,使用address-of-record,广播方式。一个UA可以用非本文档规定的方式,配置一个注册服务器的地址。如果UA没有配置任何注册服务器的地址,UA应该用请求的Request-URI部分种的address-of-record的服务器部分(host part),用普通的SIP服务器定位机制[4]。比如,用户”sip:carol@chicago.com”地址的注册服务应该是”sip:chicago.com”。
最后,UA可以通过监听广播的形式来获得注册服务器地址。监听广播的注册服务器是通过监听著名的”全部SIP 服务器”广播地址”sip.mcast.net”(224.0.1.74)。没有Ipv6的广播地址。SIP 的UA可以监听这个地址,并且用这个来知道其他本地用户的地址(见附件[33]);不过他们并不对请求做响应。通过监听广播的登记服务可能在某些环境下不能用,比如,基于同一个本地网络的多个商业应用的情况。
10.2.7 传送一个请求
当REGISTER请求被构造好以后,并且也有了登记服务起地址,UAC遵循8.1.2节的说明来提交transaction层来发送REGISTER请求。如果transaction层返回一个由于REGISTER请求没有应答的超时错误,UAC不应该立刻重新尝试对同一个注册服务器的注册请求。
立刻重新尝试很有可能导致再次超时。等待一个合理的时间在尝试可以降低网络的负载,在这里并没有一个约定的等待时间间隔。
10.2.8 错误响应
如果UA接收到一个423(间隔太简略)应答,它可能需要更改REGISTER请求中的所有有效期,使得这些有效期必须大于等于423应答头中的Min-Expires头域中的有效期,并且重新尝试发送这个REGISTER请求。
10.3 处理REGISTER请求
一个注册服务器(registrar)就是一个UAS,这个UAS用来响应REGISTER的请求,并且维持一个绑定表,这个绑定表用来提供给它所管理的区域中的proxy服务器和重定向服务器的。一个注册服务器根据8.2和17.2中的规定来处理请求,但是它仅仅处理REGISTER请求。一个注册服务器禁止产生6xx应答。一个注册服务器可以适当的转发REGISTER请求。通常用于一个注册服务器(registar)监听一个多点广播,并且通过302应答(临时转移)转发这个多点广播的REGISTER请求到它正确的处理位置。
注册服务器必须忽略在REGISTER请求中的Record-Route头域,并且也不能在REGISTER请求的应答中包含任何Record-Route头域。注册服务器可能收到一个有proxy转发过来的REGISTER请求,这个请求中由于proxy处理这个请求当作未知请求所以添加了Record-Route头域。
一个注册服务器必须知道(例如,通过配置)它所管理的区域。注册服务器一定需要按照接收到的REGISTER请求顺序进行处理。
REGISTER请求必须当作原子请求来处理,意味着给定的REGISTER请求要么就完整处理,要么就完全拒绝。每一个REGISTER信息的处理都和其他的注册和绑定信息的处理无关。
当接收到一个REGISTER请求,注册服务器(registrar)按照如下步骤处理:
1、        注册服务器(registrar)检查Request-URI来决定是否它属于本注册服务器所管理的区域的Request-URI。如果不是,并且如果这个服务器同时也作为一个proxy服务器,那么这个服务器应当转发这个请求到指定的区域。这个转发是根据16节所规定的proxy通用信息处理规则来进行的。
2、        为了保证注册服务器能够支持所需要的扩展,注册服务器必须遵循8.2.2节描述的情况来处理Require头域。
3、        一个注册服务器应当对UAC进行身份认证。SIP UA的身份认证机制在22节描述。注册这个动作需要遵循SIP的通用的身份认证框架。如果没有任何认证机制,注册服务器可以使用From地址来作为原始请求的信任依据。
4、        注册服务器应当检查认证的用户是否通过认证来更改这个address-of-record的登记权限。比如,一个登记服务起(registrar)可以通过访问一个认证数据库,这个认证数据库映射了用户名和一个address-of-record列表,这些列表是用户可以更改绑定信息的列表。如果认证用户并没有权利修改绑定信息,注册服务器应当返回一个403(禁止访问)并且跳过后续步骤。在支持第三方认证和注册的架构下,一个实体可以被授权来更新多个address-of-record的注册信息。
5、        注册服务器(registrar)从REGISTER请求的To头域中解出address-of-record。如果这个address-of-record并非在这个Request-URI指明的区域中合法,那么注册服务器必须发出一个404(没有找到)的应答,并且跳过后续步骤。接着URI必须转换成为标准的格式。所有的URI参数都必须删去(包括用户参数user-param),并且任何非法(escaped)字符必须转换成为合法字符(unescaped)格式。最后形成一个可以用于绑定的列表。
6、        注册服务器(registrar)检查是否请求包含了一个Contact头域。如果没有包含,它跳过到最后一步。如果Contact头域包含了,注册服务器检查是否有一个Contact头域值是”*”,并且包含了一个Expires头域。如果请求有其他的Contact头域或者任何有效期的值是非0的,这个请求就是非法请求,并且服务器必须送回一个400(非法请求)的应答,跳过后续步骤。如果没有,那么注册服务器检查是否Call-ID复核每一个绑定的值。如果不符合,它必须删除绑定。如果复核,它必须仅仅删除保存的绑定表中CSeq值小于请求中的Cseq值的记录。否则,更新必须终止,请求失败。
7、        现在注册服务器(registrar)可以依次处理Contact头域中的联系地址了。对于每一个地址,它根据下边的规则进行有效期检查。
- 如果含有”expires”参数,这个参数值就是最终的有效期。
- 如果没有这个参数,并且请求有一个Expires头域,那么这个值就是有效期。
- 如果没有Expire头域也没有参数,那么本地的缺省配置应当作为有效期。
注册服务器可以选择一个小于请求中的有效期值作为最后的有效期。当且仅当请求的有效期大于0 并且小于1个小时并且小于一个注册服务器配置的最小有效期的时候,注册服务器可以响应一个423(有效期过小)的错误来拒绝。这个应答必须包括了一个Min-Expires头域来表明本注册服务器所接收的最小有效期,然后跳过所有后续步骤。
允许注册服务器设置注册服务器自己的有效期防止了过分频繁的刷新注册信息,并且也降低了维持和管理注册信息的工作量。在服务的创建的时候,注册信息中的有效期会经常用到。一个例子是follow-me(跟随我)服务,在这个服务中,一个用户可能在一个终端上只停留一小会儿。因此,注册服务器应当接收简略的注册;一个请求只有当它的有效期过短的时候(短到可能降低注册服务器性能的时候)才应当被拒绝。
对于每一个地址,注册服务器在当前绑定列表中用URI比较规则进行搜索。如果绑定不存在,它会暂时性的添加进去。如果绑定存在,注册服务器检查Call-ID值。如果Call-ID值在这个已经存在的绑定中和本次请求的Call-ID值不一样,那么如果绑定的有效期为0就删除这个绑定,否则就刷新这个绑定。如果Call-ID值一样,那么注册服务器比较Cseq值,如果请求中的这个值比现存的绑定值中的Cseq高,那么必须更新或者删除这个绑定(如果有效期为0 就删除,否则就刷新)。如果这个Cseq值比现存的Cseq值小,那么必须终止更新并且请求失败。
这个规则确保了从同一个UA过来的请求顺序处理,对于非顺序的请求跳过处理。
每一个绑定记录都包含了当时请求的Call-ID和Cseq的值。
只有当且仅当所有的绑定更新和绑定添加都完成的时候,绑定才可以做COMMIT处理(就是说,这次修改对proxy和重定向服务器可见)。如果任何更新或者添加失败了(比如,由于后台的数据库commit失败),必须给出一个500(服务器错误)的应答,并且中间进行的临时更新都必须删除。
8、        注册服务器(registrar)返回一个200(OK)应答。这个应答必须包含Contact头域,并且这个头域的值中列举了所有当前绑定的注册信息。每一个Contact值都必须包含一个”expires”参数,用来标志还有多久这个绑定信息就过期了。应答也必须包含一个Date头域。

11 查询能力
SIP方法OPTIONS允许一个UA来查询另外一个UA或者proxy服务器的能力。这个提供个客户端一个手段来查询服务端支持的方法,内容类型,扩展,codecs等等。这些都不用”ringing”对方。比如,在客户端试图在INVITE请求头中增加一个请求字段选项的时候,它并不知道对方UAS能否支持这个选项,它就可以用OPTIONS来查询一下UAS,通过检查OPTIONS返回的Supported头域,就可以知道是否支持这个选项。所有的UA都必须支持OPTIONS方法。
OPTIONS请求的目标是用Request-URI指明的,这个既可以是一个UA也可以是一个SIP服务器。如果OPTIONS指向一个proxy服务器,Request-URI设置成为一个没有用户部分(user part)的,类似REGISTER请求中的Request-URI一样。或者,一台服务器收到一个OPTIONS请求并且Max-Forwards头域值是0的时候,它就需要响应这个请求而不需要关心Request-URI的内容。
这个机制就像HTTP/1.1一样。这个机制可以用来实现类似”traceroute”功能来通过发出一系列的有着增量Max-Forwards头域的OPTIONS请求来检查每一个途径节点的能力。
就像对一般UA机制来说,如果OPTIONS没有应答,transaction层能够返回一个超时错误。这个可能标志着对方无法到达因此无响应。OPTIONS请求可以作为建立会话的一部分,用来查询对方的能力使用,这样在后续对话中可以使用双方兼容的方式。
11.1 构造OPTIONS请求
一个OPTIONS请求可以根据8.1.1节中的标准构造方法来进行构造。
Contact头域在OPTIONS请求中可以存在,也可以不存在。
Accept头域应当包含在请求中,用来标志UAC希望接收应答中的消息体的类型。通常情况下,这个设置成为UA的多媒体兼容能力,比如SDP(应用/SDP)格式。
对于一个OPTIONS请求的应答是假定是在原请求中的Request-URI范围内的。但是,仅当一个OPTIONS请求作为建立对话的一部分而发送的时候,后续的请求应当由收到并且响应这个OPTIONS请求的服务器进行处理。(就是说如果在建立会话的时候使用OPTIONS请求,那么OPTIONS之后的这些请求都应该由这个OPTIONS查询的服务器处理,这样才能保证使用的特性和OPTIONS查询出来的能力是一样的)

OPTIONS请求的例子:
OPTIONS sip: carol@chicago.com
Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9Hg4bKhjhs8ass877
Max-Forwards: 70
To: <sip:carol@chicago.com>
From: Alice <sip:alice@atlanta.com>;tag=1928301774
Call-ID: a84b4c76e66710
Cseq : 63104 OPTIONS
Contact: <sip:alice@pc33.atlanta.com>
Accept: application/sdp
Content-Length: 0
11.2 处理OPTIONS请求
给OPTIONS的应答的构造遵循标准的构造规则(8.2.6节描述)。应答码的选择必须和处理INVITE请求一样的应答码(就像处理INVITE请求一样的返回)。这就是说,200(OK)应当在UAS能够接收请求的时候返回,486(忙)应当在UAS如果忙的时候返回。这样OPTIONS可以用来检测UAS的基本状态,这就是说,我们可以用OPTIONS来知道UAS能否接收INVITE请求。
在一个对话中的OPTIONS请求会产生一个200(OK)的应答,这是和在对话外创建的并且对对话没有任何影响的请求相同。
这个OPTIONS的使用有一定的限制,因为对于proxy处理OPTIONS和INVITE请求的不同。一个分支的INVITE可以有多个200(OK)的应答返回,但是一个分支的OPTIONS只能有单个200(OK)应答返回。因为这是由于proxy处理OPTIONS请求是当作非INVITE的处理。参见16.7节有详细的说明。
如果OPTIONS请求的应答是由proxy服务器给出的,proxy返回一个200(OK)的应答,列出这个服务器的各种选项和能力。
应答没有消息体
Allow,Accept,Accept-Encoding,Accept-Language,和Supported头域应当在200(OK)应答中出现。如果这个是由proxy产生的应答,那么Allow头域应当忽略,因为proxy是方法无关的(也就是说不知道该如何处理方法的)。Contact头域可以在200(OK)的应答中出现,并且与3xx应答有相同的语义。这就是说,他们可以列出指向客户的一串名字和方法的集合(用以转发请求)。一个Warning头域是可以存在的。消息体也可以存在,消息体的类型是由OPTIONS请求的Accept头域指明的(application/sdp是缺省的,如果Accpet头域不存在的话)。如果Accept头域中包含了一个类型能描述媒体接收能力,UAS应当在应答中包含一个消息体用于这个用途。详细的application/sdp包体说明在[13]中描述。
UAS生成的OPTIONS应答例子。(对应11.1节中的请求例子)
SIP/2.0 200 OK
Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKhjhs8ass877
; received = 192.0.2.4
To: <sip:carol@chicago.com>;tag=93810874
From: Alice <sip:alice@atlanta.com>;tag=1928301774
Call-ID: a84b4c76e66710
Cseq: 63104 OPTIONS
Contact: <sip:carol@chicago.com>
Contact: mailto:carol@chicago.com
Allow: INVITE,ACK,CANCEL,OPTIONS,BYE
Accept: application/sdp
Accept-Encoding: gzip
Accept-Language: en
Supported: foo
Content-Type: application/sdp
Content-Length:274
(SDP not shown)
12 对话(Dialog)
一个UA的核心概念就是对话。对话是表现为两个用户代理(UA)之间的持续一段时间的点对点的SIP关系。对话(Dialog)使得用户代理之间的消息顺序传递和两个用户代理之间的请求正确路由更加容易。对话(Dialog)可以认为是对SIP消息解释的上下文关系。第8节讲述了方法无关的UA处理和响应对话(Dialog)外的请求。本节将讨论如何通过请求和应答来创建一个对话(Dialog),并且在对话(Dialog)中如何发起和响应后续的请求。
一个对话在参与对话的UA中都有一个dialog ID作为标记,这个ID由Call-ID,和一个本地tag和远程tag组成。各个UA的dialog ID在对话中是不一样的。特别是,在一边UA的本地tag,在另外一方就是远程tag。这些tag都是互相不透明的,并且使得整个dialog ID是唯一的。
dialog ID同样是和所有的To头域中包含了tag参数的请求及应答相关。
填写一个消息中的dialog ID的规则依赖于SIP元素是UAC还是UAS。对于UAC来说,dialog ID中的Call-ID的值会填写到消息中的Call-ID域中,远程tag放在消息中的To的tag参数中,本地tag放在From的tag参数中。(这些规则对请求和应答都适用)。对于UAS来说,dialog ID的Call-ID值放在消息的Call-ID头域中,远程tag放在From头域的tag中,本地tag放在To头域的tag参数中。
一个对话包含一些特定的状态用于以后的对话中的消息传送。这个状态由dialog ID,本地序列号(用来排序UA到对方的请求的序列),远程序列号(用来排序请求从远端到本UA),本地URI,远端URI,remote target,一个布尔类型的标记”secure”,路由集合(一组有序的URI)组成。
路由集合是由发送请求到对方需要途径的一组服务器列表组成。一个对话可以处于”early”状态,这是由于当这个对话收到了临时应答而创建,并且当收到了2xx终结应答的时候转换到”confirmed”状态。对于其他应答,或者没有应答,”early”对话将会终结。

12.1 创建一个对话
对话是由对一组特定请求的没有失败的应答来创建的。在本规范中,只有包含To tag的2xx和101-199应答,并且请求是INVITE的,会建立一个对话。当收到一个非终结应答的时候,对话会建立成”early”状态,并且成为early dailog。创建对话的时候可以使用Extension来定义扩展。13节描述了INVITE请求的更多细节。在这里,我们描述与方法无关的对创建对话状态的处理。
UA必须按照下边描述的方法对dialog ID进行赋值。
12.1.1 UAS行为
当UAS响应一个请求给出一个应答,并且这个应答会建立一个对话的时候(比如对INVITE的2xx应答),UAS必须拷贝所有的请求中的Record-Route头域到应答中去(包括URI,URI参数,和其他任何Record-Route头域的参数,无论UAS是不是认识的参数都需要原样拷贝),并且必须维持这些参数的顺序。UAS必须增加一个Contact头域给应答。这个Contact头域包含一个UAS在后续对话请求中接收请求的地址(这个包含了给INVITE请求的2xx应答的ACK请求处理的地址)。通常情况下,UAS会用IP地址或者FQDN形式来发布自己的这个Contact地址。这个在Contact头域中的URI必须是一个SIP或者SIPS URI。如果创建对话的请求在Request-URI中包含的是SIPS URI,或者在Record-Route头域的最上的一个值是SIPS URI,或者如果请求中没有Record-Route头域但是请求中的Contact头域是SIPS URI,那么给出的应答中的Contact头域必须是一个SIPS URI。 这个URI应该是全局有效的(就是说,这个URI可以用于对话外的消息)。同样的,在请求INVITE中的Contact头域的URI也不应当仅限于这个对话中使用。因此它可以用于对话外的消息中。
UAS接着创建这个对话的状态。对话状态必须维持直到对话结束。
如果请求是通过TLS过来的,并且Request-URI包含一个SIPS URI,”secure”标志将被赋值成为TRUE。
路由集合必须设置成为请求中的Record-Route的URI列表,保留所有的URI参数和顺序。如果请求中没有Record-Route头域,那么路由集合必须设置成为空。这个路由集合,即便是空的,为了以后的对话中的请求,也要覆盖任何预先存在(pre-existing)的路由集合。remote taget必须设置成为请求的Contact头域中的URI。
远程序列号必须设置成为请求中的Cseq头域的序列号。本地序列号必须设置成为空。dialog ID中的呼叫标志应该设置成为请求的Call-ID头域的值。dialog ID的本地tag必须设置成为对请求的应答包中的To头域的tag,并且dialog ID的远程tag必须设置成为请求中的From 头域中的tag。UAS必须能够处理接收到的请求中的From头域没有tag标志,在这种情况下,这个tag就是空值。这是为了兼容RFC2543协议,它并没有定义From tag。
远程URI(remote URI)必须设置成为From头域中的URI,并且本地URI必须设置成为TO头域中的URI。
12.1.2 UAC行为
当一个UAC发出一个请求,这个请求能够建立一个对话(比如这个请求是INVITE),它必须在Contact头域中提供一个基于全局的SIP或者SIPS URI(例如,可以在对话外使用的SIP URI)。如果请求包含一个Request-URI或者最上的Route头域是SIPS URI,Contact头域也必须包含的是SIPS URI。
当一个UAC接收到应答,并且这个应答建立对话的时候,它也同样构造这个对话的状态。这个状态必须维持到对话的结束。
如果这个请求是基于TLS发送的,并且Request-URI包含一个SIPS URI,那么”secure”标志被设置成为TRUE。
路由集合必须设置成为应答中的Record-Route头域的URI列表,保留所有的URI参数和顺序。如果在应答中没有Record-Route头域,那么这个路由集合必须设置成为空集合。这个路由集合即便是空的,为了以后的对话中的请求,也要覆盖任何预先存在(pre-existing)的路由集合。remote taget必须设置成为应答中的Contact头域的URI。
本地序列号必须设置成为请求中的Cseq头域的序列号。远程序列号必须设置成为空(他会由远端的UA在对话中发送请求而建立)。dialog ID中的呼叫标志必须设置成为请求的Call-ID头域的值。dialog ID的本地tag必须设置成为请求中的From头域的tag,dialog ID的远程tag必须设置成为应答中的To头域的tag。UAC必须能够处理接收到的应答的To头域中没有tag的情况,在这个情况下,tag值取值成为空。这是为了能够向下兼容RFC2543,它没有规定To的tag。
remote URI必须设置成为To头域的URI,local URI必须设置成为From头域的URI。

12.2 对话中的请求
当两个UA之间的对话建立以后,他们都可以在对话中初始化一个新的事务(transaction)。如果UA发送请求,将遵循UAC的事务规则。UA接收请求将遵循UAS的规则。在建立对话的事务过程中,UA扮演的角色可能是不一样的。
在对话中的请求可以包含Record-Route和Contact头域。不过,虽然他们会修改remote target的URI,但是这些请求也不会导致对话的路由集被改变。明确说,如果请求不是刷新target的请求,那么这个请求不会更改对话的remote target URI,如果请求是刷新target的请求,那么这个请求才会更改对话的remote target URI。对于用INVITE建立的对话来说,唯一的能够刷新target的请求就是re-INVITE(见14节说明)。可能会有其他扩展定义通过其他方法来刷新target的请求。
注意ACK不是一个刷新target的请求。
刷新target请求只会更改对话的remote target URI,并且更改由Record-Route指定的路由集合。如果更新路由集合会带来严重的和RFC2543向后兼容问题。

12.2.1 UAC行为
12.1.1.1 产生请求
在对话中的请求是通过用许多对话的状态部分来构造的。在TO头域中的URI部分必须设置成为对话状态中的remote URI。To头域的tag参数必须设置成为dialog ID中的remote tag部分。请求的From URI必须设置成为对话状态中的local URI。From头域的tag参数必须设置成为dialog ID的local tag部分。如果remote或者local tag是空值,那么tag参数必须分别从From或者To头域中去除。
在请求序列中的原始请求的To和From头域的URI的使用方法是为了向下兼容RFC2543协议的,在RFC2543协议中,使用URI作为对话的标志。在这个规范中,只有tags用于区分对话。有可能在本协议的后续版本中,在对话中的请求必须强制反应原始请求的To和From头域的URI将会去除。
请求的Call-ID必须设置成为对话的Call-ID。在对话中的请求必须严格遵循单个递增的Cseq序列号(每次增加1)(当然要除了ACK和CANCEL,这两个请求中的Cseq必须和原始的请求或者确认请求一样)。因此,如果本地序列号(local sequence number)不为空,那么本地序列号码必须依次增加1,并且这个数值要存放到Cseq头域中。如果本地序列号码是空的,那么在8.1.1.5节约定的初始值必须填写进去。在Cseq头域中的method字段必须和请求的方法(method)一致。
通过使用32位的长整数,使得即使每秒种产生1笔请求,也会要136年才会用完这个整数出现重复。这个序列号的初始值的选取是为了让对话中后续的请求序列号不会重复。非0的初始值可以考虑采用时间来作为初始的序列号。一个客户端可以用31位有符号整数或者32位无符号整数来存放时间作为初始化的序列号。
UAC使用remote target和路由集合来构造请求中的Request-URI和Route头域。如果路由集合是空的,那么UAC必须把remote target URI放到Request-URI中,并且UAC不能添加Route头域到请求中。
如果路由集合不为空,并且路由集合的第一个URI包含lr参数(见19.1.1),那么UAC必须填写remote target URI到Request-URI,并且必须包含Route头域,这个Route头域按照顺序填写路由集合和路由集合的参数。
如果路由集合不为空,并且路由集合的第一个URI没有包含lr参数,那么UAC必须把第一个URI放在Request-URI中,并且拆去所有不被Request-URI允许的参数。UAC必须增加一个Route头域顺序包含所有剩下的路由集合元素,及其参数。UAC接着必须把remote target URI放在Route头域的最后一项。
例如,如果remote targe是: sip:user@remoteua 并且路由集合包括:
<sip:proxy1>,<sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>
那么请求应该有下列的Request-URI和Route头域
METHOD sip:proxy1
Route: <sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>,<sip:user@remoteua>
如果路由集合的第一个URI不包含lr参数,那么对应的说明proxy并不能支持本文档所约定的路由机制,而是支持RFC2543文档所约定的路由机制,那么在发送信息的时候需要通过替换Request-URI为接收到的第一个Route头域的值。将Request-URI的值放在Route头域的目的是为了保护Request-URI,使得它经过严格路由的时候不丢失(当请求遇到一个松散路由的时候会返回到Request-URI中?????。)
在对话内的任何一个刷新target的请求中,都应当包含一个Contact头域,并且这个URI除非有必要,否则都应当是和对话内上次请求的URI值一样。如果”secure”标志设置成为TRUE,那么URI也应当是SIPS URI。
如果在12.2.2节讨论的那样,在刷新target请求中的Contact头域会更新remote target URI。这个允许UA提供一个新的联系地址(Contact address),表明它在对话中改变了自己的地址。不过,如果请求不是刷新target的请求,那么不会影响对话中的remote target URI。请求中的剩下的部分请按照8.1.1节描述的填写。一旦请求被创建了,请求将按照对话外请求发送标准步骤(8.1.2节)来解析服务器的地址并且发送请求。
8.1.2节中的步骤一般把请求发送到Route头域的最上一个地址,或者如果没有Route头域,那么就发送到Request-URI地址。由于受到特定的限制,这些步骤也允许把请求发送到另外一个地址(比如在route set中没有的缺省的外发proxy)
12.2.1.2 处理应答
UAC将会从transaction层收到请求的应答。如果客户端的事务层返回一个超时,这会等同于一个408(请求超时)的应答。UAC处理3xx应答的时候,在这个应答是在对话内的请求的应答的处理方法和在对话外的处理方法是一样的。这个方法在8.1.3.4节中描述。需要注意的是,虽然UAC会尝试新的地址(处理3xx应答的时候),但是它依旧使用对话内的路由集合来构造请求的Route头域。
当UAC收到一个刷新target请求的2xx应答的时候,如果对话的remote target URI存在,那么它必须用这个应答的Contact头域的值来替换对话的remote target URI。
如果对话那的请求的应答是481应答(呼叫/事务不存在Call/Transaction Does Not Exits)或者一个408(请求超时),那么UAC应当终止这个对话。并且UAC应当在请求完全没有应答的时候(客户端transaciton将会通知TU这个超时)客户端transaction终止这个对话。
对于INVITE初始化的对话,终止对话需要发送一个BYE。
12.2.2 UAS行为
在对话中发送的请求,就像其他请求一样,是原子请求。如果UAS收到某个请求,所有的相关状态要么一起改变,要么就一起不变。在某些请求中,请求会影响好几个状态(比如INVITE请求)。
UAS从transaction层收到请求。如果请求的To头域有tag字段,UAS的处理核心需要校验对话的ID,拿请求中的tag和现存的对话相比较。如果匹配成功,那么就是一个在对话中的请求。在这种情况下,UAS首先使用8.2节中的对话外请求处理的步骤。如果请求To头域包括了一个tag字段,但是对话的ID并不匹配现存的对话,UAS可能是因为崩溃而重新启动,或者收到了一个另外(可能是错误的)UAS(UAS可以构造To的tags,这样UAS在灾备恢复下,可以把这个tag看成它自己的)。还有一种简单的可能是请求发送错误了。在这个基础上,UAS可以选择接受或者拒绝请求。在允许的情况下,尽量处理这些请求会提供灾难恢复的机制。UAS如果希望支持这样的特性就必须遵循一些原则,比如用始终使用单调递增的Cseq序列号,甚至是在重新启动之后也这样,在重启动后重建路由集合,处理越界的RTP时间戳和序列号等等。
如果UAS由于不希望重构对话而拒绝这个请求,它必须应答对方一个481(呼叫/事务不存在。Call / Transaction不存在)应答。
对于在对话中接收到的,那些不会用任何形式更改对话状态的请求,比如OPTIONS请求,他们等同于在对话外的处理请求。
如果远端的序列号(remote sequence number)是空的,它必须设置成为请求中的Cseq头域的序列号(sequence number)。如果remote sequence number不是空的,但是请求中的sequence number小于这个remote sequence number,请求就是非顺序的,并且必须通过应答500(服务器内部错误)打回去。如果remote sequence number不是空的,并且请求中的序列号大于这个remote sequence number,请求就是按照顺序的。这个请求中的Cseq的序列号可以比remote sequence number大不止1。在这种情况下,并非是错误的,并且UAS应当准备接收和处理比上次处理的请求Cseq值大于1 的请求。UAS必须设置remote sequence number成为请求中的Cseq头域中的序列号。
如果一个proxy废弃掉一个UAC产生的请求,并且UAC重新递交这个请求的时候。这个请求是会具有一个全新的Cseq序列号。UAS是不会收到第一个请求的,这样,Cseq序列号就会出现间隔,这样的间隔并非是一种错误的情况。
当UAS接收到一个target刷新请求的时候,如果请求中存在Contact头域,它必须用Contact头域中的URI来替换对话的remote target URI。
12.3 终止对话
在建立对话中的终结对话,跟请求方法无关,如果对话外的请求产生了一个非2xx终结应答,任何前边请求创建的”早期对话”(early dialogs)将会终止。在已经建立的对话中,终结对话就是请求方法相关的。在这个定义中,BYE方法将会终结一个对话。15节有细致的讨论。
13 初始化一个会话
13.1 概览
当UAC希望初始化一个会话(比如,audio,video或者游戏),它首先构造一个INVITE请求。这个INVITE请求一个服务器来建立一个会话。这个请求可能会由proxy层层转发,最后到达一个或者多个可能能够处理这个邀请的UAS。这些UAS需要看看是否用户接收这个邀请。然后UAS可以接收这个请求(也就是会话建立了),通过发送2xx应答。如果邀请被拒绝,根据拒绝的原因,3xx,4xx,5xx或者6xx应答将会发送。在发送终结应答之前,UAS可以发送一些临时应答(1xx)应答给UAC,以便UAC能够掌握建立会话的进度。
当收到了一个或者多个临时应答,UAC可能收到一个或者多个2xx应答或者一个非2xx终结应答。由于在INVITE终结应答之前,可能有不少时间,在INVITE事务的可靠性机制和其他的请求不同(比如OPTIONS)。当UAC收到了终结应答,UAC需要给每一个INVITE的终结应答,发送一个ACK请求。发送ACK请求的步骤依赖于应答的类别。对于在300到699的终结应答,ACK是在transaction层处理的,并且遵循一系列规则(17节)。对于2xx应答,ACK是由UAC处理核心产生的。
INVITE的一个2xx应答会建立一个会话,同时也建立了一个基于发送INVITE请求的UA和产生2xx应答的UA之间的对话。因此,当从多个远程UA收到了多个2xx应答(可能由于INVITE的分支),每一个2xx建立一个不同的对话(dialog)。所有这些对话都是同一个呼叫的组成部分。
本节介绍了INVITE请求建立会话的详细过程。支持INVITE的UA也一定同时支持ACK,CANCEL和BYE。
13.2 UAC处理
13.2.1 创建一个初始化的INVITE
由于初始化的INVITE请求是一个对话外的请求,它遵循8.1.1节的步骤创建。除此之外还有专门针对INVITE的附加处理步骤。
在INVITE中应当包括一个Allow头域(20.5节)。它用来标志在这个INVITE建立的这个对话(dialog)中什么样的方法可以接受。比如,一个UA可以在对话中接收和处理INFO请求[34],那么在INVITE请求的Allow头域中应当列出这个INFO方法。在INVITE请求中,Supported头域应当包含。这个头域包含了所有这个UAC支持的扩展部分。
在INVITE中可以包含一个Accept头域(20.1节)。这个标志了UA在后续建立的对话中,能兼容的接收和发送的Content-Type。Accept头域支持不同会话描述格式(session descrioption format)的时候特别有用。
UAC可以通过包含一个Expire头域(20.19节)来限制邀请的有效期限。如果Expire头域的时间到了还没有接收到INVITE的终结应答,UAC处理核心应当像9节描述的那样产生一个对INVITE请求的CANCEL请求,
UAC还可以根据需要增加Subject(20.36节),Organization(20.25节)和User-Agent(20.41节)头域。这些头域都包含了INVITE的相关资料。UAC可以给INVITE增加一个消息体。8.1.1.10节讲述了如何构造Content-Type头域来描述消息体。
对于消息体,有一些特别的规定――他们是基于某种磋商机制的,他们对应的Content-Disposition 是”session”(会话的)。SIP使用一个请求/应答模型,UA发出一个会话描述,称作是请求,里边包含了会话的描述。这个请求标志了特定的联系内涵(比如audio,vidio,game),这些内涵的参数(比如解码器等等),并且从应答方接收媒体信息的地址。对方UA会回应另外一个会话的描述,称之为应答,标志了能接受的联系内涵,这些内涵的参数。这个请求/应答的交换实在对话的上下文进行中的,所以如果一个SIP INVITE请求导致了多个对话,每一个对话都包含自己独立的请求/应答的交换。请求/应答模型定义了对于请求和应答的限制。(比如在上一个请求尚未处理完成情况下不能发起下一个请求)。这也导致了请求/应答在SIP消息中出现的位置限制。在这个规范中,请求和应答只能出现在INVITE、ACK请求和其应答中。请求和应答的使用中更进一步被限制。在初始化一个INVITE事务中,规则如下:
o 初始化请求必须在INVITE中,如果不在INVITE请求中,就必须在UAS回送给UAC的第一个非失败的可靠消息中。在这个规范中,这个应答就是2xx应答。
o 如果初始的请求是一个INVITE,那么应答必须是由UAS发送回给对应发出INVITE请求的UAC的可靠的非失败的消息。在本规范中,只有2xx应答对应这个INVITE请求。同样相同的应答可能在之前发送的零食应答中存在。UAC必须把它接收到的第一个会话描述当作是应答,并且必须忽略任何在初始INVITE请求中后续的会话描述应答描述。
o 如果初始请求是在第一个可靠的非失败的UAS回送给UAC的消息中,那么应答必须在这个消息的确认消息中(在本规范中,就是给2xx应答的ACK确认消息)
o 在发送或者接收到第一个请求的应答之后,UAC可以同样依据这样的问答方法产生后续的请求。但是只能在收到每一个请求的应答之后才能发起下一个请求。不能在上一个请求尚未收到应答的时候发起下一个请求。
o 当UAS发送或者接收到初始化的请求的时候,禁止在它给初始的INVITE请求的应答中产生后续的请求(协商会话描述请求)。这就意味着基于本规范的UAS在完成初始化的事务之前,不会产生任何会话描述请求。

具体来说,根据本规范,上边的规则分别定义了两种UA之间交换信息的方法。请求实在INVITE中,应答是在2xx(可能在1xx中也存在,具备相同的值)中,或者请求在2xx中,应答在ACK中。(这个意思是说,两个UA之间建立连接的时候,首先需要协商一下两个UA能够支持的消息体正文,那么这个协商关系也是通过问答形式的,也就是通过请求/应答的,这个媒体磋商的请求既可以在UAC发起的INVITE请求中,也可以在UAS回应的2xx应答中。同样的,媒体磋商的应答既可以在UAS的2xx应答或者1xx应答中,也可以在ACK确认请求中)。所有的支持INVITE请求的UA都必须支持两种交换方式。会话描述协议(session description protocol sdp)(RFC 2327[1])在所有的UA中都必须得到支持,并且它的用法和请求/应答的构造必须遵循[13]中定义的步骤。
在上边讲述的请求/应答模型中,只能适用于在包头域Content-Disposition中的值是”session”的包体情况。因此,有可能会INVITE和ACK请求中都包含一个包体信息(比如,INVITE包含一个相片(Content-Disposition:render)并且ACK包含一个会话描述(Content-Disposition:session))。
如果Content-Disposition头域不存在,Content-Type 是application/sdp的包体实现就等同于Content-Disposition”session”,其他Content-Type的情况就是实现”render”。
当INVITE请求创建以后,UAC遵循对话外请求发送的步骤进行发送(8节)。这也就是创建一个客户事务并且由这个客户事务发送请求并且处理应答。
13.2.2 处理INVITE应答
当INVITE请求被传送给INVITE的客户事务层进行处理,UAS等待INVITE的应答。如果INVITE客户事务层返回一个超时而不是收到一个应答,那么这个TU就应当像收到一个408(请求超时)应答(8.1.3节)那样进行处理。
13.2.2.1 1xx应答
有可能在收到一个或者多个终结应答之前,UAC会收到0个或者1个或者多个临时应答。INVITE的临时应答会建立”early dialogs”(早期对话)。如果一个临时应答在To头域中有一个tag子顿,并且应答的dialog ID并不是已经存在的对话的ID,那么就应当遵循12.1.2节定义的步骤创建一个对话(早期对话)。
early dialog只会在下边这个情况中需要:如果一个UAC需要在完成初始的INVITE事务之前,给对方发送一个对话内的请求的时候,就需要early dialog。在临时应答中的头域可以在当对话是early state的时候都有效(也就是说,比如一个临时应答的Allow 头域包含的方法,在对话状态是early state的时候都是有效的。[由于Allow是允许的方法集合,所以,当对话状态是早期对话的时候,这个Allow的集合是不会改变的,但是当创建正式的dialog之后,Allow的集合可能会改变哦]。)
13.2.2.2 3xx应答
一个3xx应答可能包含一个或者多个Contact头域值,这个头域值提供了被叫方可能存在的地点。UAC可以根据3xx应答的状态码(21.3节)来决定是否尝试这些新的地址。
13.2.2.3 4xx,5xx,6xx应答
在INVITE请求中,可能会收到单个非2xx终结应答。4xx,5xx,6xx应答如果包含了Contact头域,那么这个头域值指示了错误的详细信息的解释地点。后续的终结应答(只有可能在发生错误的情况下),必须被忽略掉。
所有的早期对话都会由于接收到非2xx终结应答而结束。
一旦接收到了非2xx终结应答,UAC处理核心就认为INVITE事务结束了。INVITE客户事务处理生成对这个应答的ACK(参见17节)。
13.2.2.4 2xx 应答
单个INVITE请求可能会导致多个2xx应答返回给UAC,这是因为proxy可以分支。每一个应答都是由To中的tag参数来进行区分的,并且每一个应答都代表了一个独立的对话,具备单独的对话ID。
如果在2xx应答中的对话ID和一个现存的对话匹配,那么这个对话必须切换到”confirmed”状态,并且对话的路由集合必须基于2xx的应答进行重新计算(参见12.2.1.2)。如果不匹配,那么必须创建一个新的对话,这个对话具备”confirmed”状态,参见12.1.2的步骤进行创建。
注意在对话状态中,只有路由集合不需要重新计算。其他部分比如对话内的最大的序列号(远程的和本地的)等都不需要重新计算。路由集合只是由于需要向后兼容而需要重新计算。RFC 2543并没有要求在1xx应答中反射Record-Route头域回来,只在2xx请求中要求了。我们不能更新对话状态的全部部分,因为在早期对话(early dialog)中可能会存在对话中的请求,比如更改序列号等等。UAC核心必须为每一个2xx应答,产生一个ACK请求。除了在Cseq和身份认证相关的头域之外,ACK请求的头域的创建和在对话中的请求创建的方法一样(12节)。Cseq头域的序列号部分必须和需要确认的INVITE请求一样,但是Cseq的方法部分必须是ACK。ACK必须包含和INVITE请求相同的信任状。如果2xx包含一个媒体磋商请求(基于上述的规则),ACK必须在包体中包含一个媒体磋商应答。如果2xx应答的媒体磋商请求不能被接收,UAC核心必须产生一个有合法的应答ACK,并且立刻发送一个BYE请求。

当ACK创建以后,[附件4]中规定的步骤用来检测对方地址,端口和transport。这个请求是直接交给通讯层进行通讯的,而不是交给一个客户事务层进行发送。这是由于UAC核心直接处理ACK的重发,而不是事务层进行重发的处理。每次收到一个重发的2xx终结应答的时候都必须发送一个ACK到通讯层。

UAC核心认为INVITE事务在接收到第一个2xx应答后的64×T1秒后完成。在这个时间点后,所有没有转换成为建立连接状态的早期对话都会被终止。一旦UAC确认INVITE事务完成了,那么缺省认为不会收到新的2xx应答了。如果,在相应了对INVITE请求的全部应答之后,UAC并不希望创建这个对话,那么UAC必须通过15节描述的那样发送BYE请求来结束对话。
13.3 UAS处理
13.3.1 处理INVITE
UAS核心从事务层收到INVITE请求。首先根据8.2节定义的步骤进行处理请求,8.2节中定义的是跟对话内外无关的请求的处理。如果处理顺利完成(没有产生应答),UAS核心根据如下步骤进行额外处理:
1、        如果INVITE请求包含一个Expires头域,UAS核心就设置一个时钟计数=这个头域值。如果时钟到了,这个邀请就过期了。如果在UAS尚未产生终结应答的时候就超时了,那么487(请求终止)应答应当产生给UAC。
2、        如果请求是一个对话中的请求,12.2.2节定义的方法无关的处理步骤将首先进行处理。这个处理可能会影响到会话;14节讲述了细节。
3、        如果请求的To头域包含了一个tag,但是对话的ID与现存的任何一个对话都不匹配,那么UAS可能是由于崩溃而重新启动的,或者是由于接收到了本应当发送给另外一个UAS的请求(或者就简单是由于请求填写错误)。12.2.2节提供了这种情况的处理指引。从这开始的处理将假定这个INVITE是在对话外的,并且INVITE请求的目的是建立一个新的会话。INVITE请求可能包含一个会话描述,在这种情况下是希望和UAS进行会话媒体的磋商。即使INVITE请求是对话外发出的,这个INVITE参与的用户也有可能正是那个会话中的参与方。这个是由于在多方会议中,某个正在会议中的用户,被其他参与方邀请参加。如果需要鉴别这样的情况,UAS可以使用会话描述来检查是否重复邀请。比如,SDP包含了会话的ID和版本号。如果这个用户本身就是会话中的一方,并且session参数包含的会话描述没有改变,UAS可能就悄悄接受这个邀请(就是说,在不提示用户的情况下发送2xx应答)。
如果INVITE并没有包含某个会话描述(session description),UAS就是被邀请创建一个会话,并且UAC已经希望UAS来提供这个会话offer。UAS必须在它的给UAC的第一个非失败的可靠消息中提供这个offer。在本规范中,给INVITE请求的2xx应答中就应当提供这个offer。
UAS可以提示进度,接受,转发,或者拒绝这个邀请。在这些情况下,它通过按照8.2.6节描述的步骤建立应答。
13.3.1.1 提示进度
如果UAS不能马上接受或者拒绝邀请,那么它可以提示某种形式的进度给UAC(比如提示一个回铃声等等)。这是通过一个101到199的临时应答实现的。这些临时应答建立了早期对话(early dialog)(通过8.2.6和12.1.1)。如果UAS愿意,UAS可以发送多个临时应答。每一个临时应答都必须包含相同的dialog ID。这些临时应答都并非可靠传送的。
如果UAS打算延长一点时间来响应这个INVITE请求,它需要请求一个”extension”来防止proxy来取消这个事务。proxy有权利来取消超过3分钟未完成的事务。要防止这个取消,UAS必须每分钟发送一个非100临时应答,防止由于1xx临时应答的非可靠传输导致的临时应答丢失。
如果呼叫出于等待状态(比如用户设置成为呼叫等待的)或者这个呼叫正在和PSTN电话系统进行通讯(PSTN系统允许呼叫没有应答),一个INVITE事务是可以被延长处理时间的。
13.3.1.2 INVITE请求转发
如果UAS决定转发这个呼叫,就需要发出3xx的应答。300(多重选择),301(永久转移),302(临时转移)应答中应当包含一个Contact头域,这个头域包含了一个或者多个表明需要重试的URI新地址。这个应答交给INVITE服务端事务层,由服务端事务层负责应答的重发。
13.3.1.3 INVITE请求的拒绝
拒绝INVITE请求的常见情景是被叫方不想或者不能在终端系统上接收这个呼叫。486(用户忙)应当在这样的情况下返回。如果UAS知道没有其他终端系统能够响应这个呼叫,就应当返回一个600(Busy Everywhere)。不过,通常情况下UAS是不太会知道这个情况的,并且这个应答也是罕见的。这些应答是交给INVITE服务端的事务层进行发送的,由这个事务层来保证应答的重发机制的。如果UAS拒绝的是INVITE请求包含的媒体磋商offer,UAS应当返回一个488(Not Acceptable Here)应答。这个应答应当包含一个Warning头域来解释为何offer被拒绝。
13.3.1.4 接受INVITE请求
UAS核心产生一个2xx应答。这个应答建立一个对话,然后遵循8.2.6节和12.1.1节的描述进行处理。
响应INVITE请求的2xx应答包含Allow头域和Supported头域,并且可能包含Accept头域。包含这些头域的目的是为了让UAC不需要再次请求就能够知道UAS的特性以及UAS的扩展支持。
如果INVITE请求包含了一个媒体磋商请求offer,并且UAS还没有发送应答,2xx应答中必须包含针对这个offer的应答。如果INVITE请求没有包含这个offer,而且UAS也尚未发出offer,2xx应答必须包含这个媒体磋商offer。
当应答构建好了以后,它会交给INVITE的服务端事务层进行发送。注意,INVITE的服务端事务将会由于收到这个终结应答并且交给通讯层进行发送而销毁。因此,有必要在没有收到ACK的时候,每隔一定的时间就直接交给通讯层进行发送。2xx交给通讯层进行发送的时间间隔是从T1秒开始,并且每次发送后就加倍,直到到达T2秒的时间间隔(T1和T2的时间间隔定义在17节)。当收到了针对这个应答的ACK请求之后,重发就终止了。这个是与使用什么通讯协议来发送这个应答是无关的。
由于2xx的重发是端到端的,并且在UAS和UAC之间存在采用UDP通讯的节点。所以要保证通过这些节点进行可靠的传送,就必须采用间隔时间重发的机制,哪怕UAS本身的通讯机制是可靠的。
如果服务端的对2xx应答的重发经过了64×T1秒还没有收到ACK请求,那么dialog就认为是confirmed,但是会话却应当终止。这个是用过15节描述的方法发送BYE请求来结束。
14 更改已经存在的会话
一个成功的INVITE请求(13节)既会创建一个基于两个用户之间的对话,也会基于请求/应答模式(offer-answer)创建一个会话。12节讲述了如何通过target refresh 请求来修改一个现存的会话(比如,修改对话的remote target URI)。本节描述如何修改实际的会话(session)。
这个修改可以包括修改地址或者端口、增加媒体流、删除媒体流等等。这是通过发起新的INVITE请求来完成的,并且这个新的INVITE请求是基于建立会话所相同的对话的。在一个现存对话中发出INVITE请求就是re-INVITE.
注意,单个的re-INVITE请求可以同时更改对话和会话的参数。
呼叫方或者被叫方都可以更改现存的会话。
在本协议中,UA检测本地媒体有效性是基于自身的策略的。但是,我们并不建议自动产生re-INVITE或者BYE请求,因为这样可能会导致网络上的阻塞。在任何情况下,如果某些消息将被自动发送,那么他们应当等待一个随机的时间间隔。
注意,上边的这些描述是特指自动产生的BYE和re-INVITE。如果用户由于媒体不兼容而挂机,UA应当正常发出BYE请求,而不视为自动产生的BYE。
14.1 UAC行为
与INVITE相同的会话描述磋商offer-answer模式(13.2.1节)在re-INVITE中也一样采用。假设UAS希望增进一个媒体流,那么UAC将会创建一个新的offer包含这个媒体流,并且发送INVITE请求给他的对方。特别需要注意的是,这个会话的全描述,而不是变化部分需要传送。这个支持无状态的会话处理,并且支持错误恢复机制。当然,UAC可以发送一个re-INVITE请求而不包含会话描述,在这样的情况下,就是在这个re-INVITE的第一个可靠的非失败的应答中将会包含这个会话描述offer(在这个规范中,就是2xx应答)。
如果会话描述格式具有版本号码,那么这个磋商的offer应当标志这个变化了的媒体描述版本。
re-INVITE请求中的To,From,Call-ID,Cseq,Request-URI头域应当和正常的在对话中的请求构造方法一样(12节)。
在re-INVITE请求中,UAC可以选择不增加一个Alert-Infor头域或者具有Content-Disposition=”alert”的消息体。因为UAS通常不会要求提示操作者来响应这个re-INVITE请求。
和INVITE不同的是,INVITE可以分支(分岔成为多份INVITE),re-INVITE是不会分支的,所以,只会由一个单个的终结应答。re-INVITE不会分岔的原因是因为Request-URI标志的是建立对话的UA的目标地址,而不是用户的address-of-record地址。
需要注意的是,在相同的对话中,UAC不能在上一个INVITE请求完成前(无论是那一方发起的INVITE)再次发起一个新的INVITE。
1、        如果有正在处理的INVITE客户事务,TU必须等待这个事务终结或者完成,才能初始化一个新的INVITE。
2、        如果有正在处理的INVITE服务事务,TU必须等待这个事务确认或者终结,才能开始处理一个新的INVITE。
不过,UA可以在INVITE事务正在处理的同时,处理一个普通的事务。也可以在一个普通事务正在处理的同时来初始化一个INVITE事务。如果UA接收到一个针对re-INVITE的非2xx终结应答,则会话参数不能改变,应当就像没有收到过这个re-INVITE请求一样。注意,就像在12.2.1.2节一开始讲的那样,如果非2xx终结应答是一个481(Call/Transaction Does Not Exists),或者一个408(Request Timeout),或者完全没有re-INVITE请求的应答(也就是说从INVITE客户事务端返回一个超时),UAC会终止这个对话。
如果UAC收到一个re-INVITE的491应答,他应当启动一个值为T的时钟,这个T的取值如下:
1、        如果UAC是这个dialog ID的Call-ID的拥有者。(也就是说是UAC产生的Call-ID),那么T取值为一个2.1到4秒的随机数,单位是10毫秒。
2、        如果UAC并非是dialog ID的Call-ID的拥有者,T应当取值是0到2秒的随机数,单位是10毫秒。
当这个时钟到了,如果UAC还希望再次尝试更改会话参数,UAC应当再次尝试re-INVITE请求一次。这个意思是说,如果这个呼叫已经被BYE所挂掉了,那么re-INVITE请求就没有再发的必要。
发送re-INVITE请求的规则,以及针对re-INVITE请求产生的2xx应答而产生的ACK请求的发送规则,等同于初始的INVITE请求(13.2.1节)。
14.2 UAS行为
13.3.1节描述了区分初始的INVITE请求和re-INVITE请求的方法,以及处理对现存的对话中处理re-INVITE请求的步骤。
UAS在发送第一个INVITE的终结应答之前,收到第二个INVITE请求,并且这个请求的Cseq序列号大于第一个INVITE请求,那么就应当给第二个INVITE请求返回一个500(服务器内部错误)应答,并且必须包含一个Retry-After头域,这个头域中应当包含一个0-10秒的随机数。
如果UAS正在处理一个INVITE请求的时候又收到了一个在同一个对话上的INVITE请求必须返回一个491(Request Pending)应答给接收到的INVITE。
如果UA接收到一个对现存的对话的re-INVITE请求,那么就必须检查有关会话描述(session description)的版本标志(version identifiers),或者,如果没有版本标志,那么就需要检查会话描述的正文看看有没有变化。如果会话描述改变了,UAS必须由此调整会话参数,在调整参数的时候可能会要求用户确认。

会话描述的版本可以用来提供给会议的新近加入者,增加或者删除媒体,或者由单点会议更改成为多方会议。
如果新的会话描述是不能被UA接受的,UAS可以用返回一个488(Not Acceptable Here)来拒绝这个re-INVITE请求。这个响应应当包含一个Warning头域(用来提供给请求方,提供这个拒绝的原因)。如果UAS产生一个2xx应答,但是没有收到ACK,它应当产生一个BYE来结束这个对话。
UAS可以不产生180(Ringing)应答给re-INVITE,因为UAC一般不展示这个信息给用户。同样的,UAS可以增加一个Alert-Info头域或者一个由Content-Disposition“alert”的消息体来应答给re-INVITE来让UAC展示这个信息。
如果UAS在2xx应答中提供了一个媒体磋商offer(因为INVITE没有包含一个offer),那么它应当当作新呼叫一样的构造这个offer,就像在[13]中SDP描述一样,遵循发送更改现有的会话的offer的限制。特别需要注意的是,这个意味着它应当包含全部的UA支持的媒体类型和媒体格式。UAS必须确保会话描述在媒体格式,通讯方式,或者其他要求对方支持的参数上,新的会话描述和旧的会话描述一样。这个可以避免对方拒绝这个会话描述。如果,UAC任然是不能接受这个会话描述,UAC应当产生一个它能够接受的会话描述应答,并且发送一个BYE来结束这个会话。

15 结束一个会话
本节描述了结束由SIP建立的会话的步骤。会话的状态和对话的状态是密切相关的。当一个会话由INVITE建立的时候,每一个由不同UAS的1xx或者2xx的应答创建一个对话,并且当完成了会话描述的请求/应答(offer/answer)交互之后,它也就创建了一个会话。这就是说,每一个会话都和单个对话”相关”-会话是对话所创建的。如果初始化的INVITE产生了非2xx的终结应答,它也终结了由本次请求创建的任何会话(如果有的话),并且终结了所有的本次请求创建的对话(如果有的话)。由于事务完整性的保证,一个非2xx的终结应答同样也防止了本次INVITE以后可能创建的会话。BYE请求用于终结指定的会话或者尝试建立的会话。在这里,特定的会话是一个和与之相对的对话的对方UA。当在对话中接收到了一个BYE,任何与该对话相关的会话都应当终止。UA禁止在对话外发送BYE请求。请求方UA可以在已经建立好的对话或者早期对话中发起BYE请求;被叫方只能在建立好的对话中发起BYE请求,不能在早期对话中发起BYE请求。
不过,在一个建立好的对话中,被叫方的UA不能在接收到对应2xx应答的ACK请求前发送BYE请求,或者不能在服务器事务超时前发送BYE请求。如果没有SIP扩展定义了和这个对话相关的其他应用层状态,这个BYE请求同样结束了对话。

在对话和会话中,给INVITE的非2xx的终结应答,使得使用CANCEL比较有吸引力。CANCEL是尝试强制给INVITE请求一个非2xx应答(比如,487应答)。因此,如果UAS希望放弃整个呼叫,它可以发送一个CANCEL。如果INVITE会有2xx终结应答,这个意味着UAS在CANCEL正在处理的时候,接收到一个邀请。UAC可以继续用这个2xx应答建立会话,也可以用BYE终结这个会话。(这个意思是说,一般情况下,如果UAC希望cancel 这个INVITE请求,那么就会发出CANCEL请求,如果接收到了非2xx的终结应答,就意味着CANCEL掉了,但是如果接收到的还是2xx应答,就说明没有CANCEL掉,没有CANCEL掉呢,就可以选择继续建立会话,或者说发送一个BYE来终结会话)

在SIP中,并没有一个很好的”hangin up”(挂机中)定义。它属于一个用户界面的普通常见的细节。通常,当用户挂机,它意味着结束建立会话的尝试,并且终止所有已经建立的会话。对于呼叫方的UA来说,如果没有收到初始INVITE请求的终结应答,这个可能是产生对初始INVITE请求的一个CANCEL请求,并且收到终结应答之后给每一个建立好的对话发出一个BYE。对于被叫方的UA,就是很普通的BYE;粗略来说,当用户(因为响应振铃)摘机,就会产生一个2xx应答,于是挂机会在收到ACK请求之后发送一个BYE。这不是说在收到ACK之前用户不能挂机,这只是表达在用户的电话中的软件,需要保持一小会儿状态,来正确释放状态。如果某个UI(用户界面)允许用户在不摘机的情况下拒绝呼叫,可以用403(Forbidden)来作为INVITE的应答,在这样的情况下,BYE就不能发送。
15.1 使用BYE请求终止一个会话
15.1.1 UAC行为
BYE请求就像其他在对话内的请求一样的构造,参见12节的描述。
当BYE请求创建好了之后,UAC核心处理部分创建一个新的非-INVITE客户端事务,并且用它来处理BYE请求。UAC必须认为当BYE请求一发送到客户端事务,会话就结束了(因此也就停止发送或者接收媒体流)。如果BYE请求的应答是481(Call/Transaction Does Not Exists)或者408(Request Timeout)或者BYE请求压根没有应答(就是说客户端事务返回一个超时),UAC必须认为会话和对话都已经结束。
15.1.2 UAS行为
UAS首先按照通用的UAS接收到请求的处理步骤进行BYE请求的处理(8.2节)。UAS核心处理部分接收到BYE请求以后,首先检查它是否和现存的对话匹配。如果BYE并不匹配现存的任何一个对话,那么UAS应当产生一个481(Call / Transaction Does Not Exists )应答,并且传送给服务器事务。
这个规则意味着如果UAC发送没有带tag标志的BYE请求会被拒绝。这个是一个对RFC2543的改动,RFC2543允许BYE不带tag标志。
UAS核心处理部分接收到BYE请求,并且发现和现存的对话匹配,那么它必须遵循12.2.2的步骤来处理请求。一旦处理完成,UAS应当终止这个会话(因此停止发送和接收媒体流)。唯一一个可以选择不终止的情况是多方会话,在多方会话中参与者允许对话中的其他参与方终止他们自己的会话。不管是否终止会话中的参与方,UAS核心处理都必须给BYE产生2xx的应答,并且必须由服务器的通讯层进行传输。
UAS必须依旧响应在这个对话中接收到的未决的请求。我们建议用487(请求终止)来给这些未决的请求以应答。
16 proxy行为
16.1 概述
SIP代理服务器是路由SIP请求到UAS的,并且路由SIP应答到UAC的。一个请求可能通过多个proxy到达UAS。每一个都会作出路由决定,在发送给下一个节点前对请求做一点修改。应答会通过和请求相同的proxy路径,只是顺序是逆序的。
proxy是一个SIP逻辑上的概念。当接收到一个请求,在做代理服务器之前,首先应该有一个部件来决定是否自身需要响应这个请求。例如,在作为代理服务器处理请求之前,首先判定请求可能是非法的或者请求需要一个信任状。这个元素可以使用任何合适的错误代码来响应这个请求。当直接应答请求的时候,这个元素(proxy)将作为UAS角色,并且必须遵循8.2节描述的UAS行为规范。
proxy对于每一个新的请求来说,既可以作为有状态的也可以作为无状态的模式来处理。当作为无状态的处理模式的时候,proxy就是简单的转发。它转发每一个请求下行到一个由请求所决定的目的地。并且简单转发从上行流取得的应答。一个无状态的proxy在处理完一个消息之后就会丢弃这个消息的相关资料。有状态的proxy会保留这些信息(尤其是事务信息),保留每一个接收的请求和每一个接收请求的应答的相关信息。它保留这些信息用于处理与这个请求相关的后续消息。一个有状态的proxy可以选择”分支”一个请求,路由它到多个地点。任何被路由到多个地点的请求都必须当作有状态的处理。在某些情况下,proxy可以用有状态的通讯协议(比如TCP)来转发请求,而不用自身成为事务有状态的。例如,proxy可以简单转发请求从一个TCP连接到另外一个TCP连接,而不用自身作为有状态的,只要它放置足够的信息在需要转发的消息里,使得能够正确把应答发送到接收到对应请求的连接发送出去。如果在不同传输通讯协议之间进行请求的转发,那么就必须要求proxy的TU采用某种手段来保证可靠的从一个协议有状态的转到另一个协议。
有状态的proxy可以在处理请求中的任何时候转换成为无状态的,只要它不作任何可能导致不能无状态的操作(比如分支,比如产生100应答)。当做这样的转换的时候,所有的状态就只是简单的废弃掉。proxy不应当发起一个CANCEL请求。
在作为无状态的或者有状态的时候,处理请求的步骤是一样复杂的。接下来的步骤是从有状态的proxy角度来些的。最后一节是讲述无状态proxy的区别。
16.2 有状态的proxy
作为有状态的proxy,它必须是一个纯粹的SIP事务处理引擎。它在这里的定义遵循17节讲述的服务端和客户端的事务处理规定。有状态的proxy有一个服务端事务,这个事务与一个或者多个客户端事务相关,这些客户端事务是由高层proxy处理元素产生的(图3),这些高层proxy处理元素就是proxy处理核心。一个输入的请求是通过一个服务端事务来处理的。请求由服务端事务交给proxy处理核心进行处理。proxy处理核心检查请求应当路由到哪里,选择一个或者多个下一个节点。每一个发往下一个节点的外发请求都由客户端事务进行处理。proxy处理核心从客户端事务中得到请求的应答并且把他们的应答交给服务端事务进行发送。
有状态的proxy为每一个接收到的新的请求创建一个服务端事务。任何请求的重复都是由这个服务端事务来处理(参见17节)。proxy处理核心必须遵循UAS的模式,发送一个直接临时应答(比如100 trying)到这个服务端事务上(8.2.6节)。因此,有状态的proxy不应当给非INVITE请求产生100(trying)应答。
这是一个proxy的模型,并非软件实现。在实现上可以扩展并且复用用这个模型定义。
对于所有的新请求来说,包括那些未知方法的请求,proxy处理请求必须:
1、        验证请求(16.3)
2、        预处理路由信息(16.4)
3、        决定请求的目的(targets)(16.5)


+--------------------+
|                                                | +---+
|                                                 | | C |
|                                                | | T |
|                                                | +---+
+---+        |                         Proxy                | +---+ CT = Client Transaction
| S |         |         "Higher" Layer         | | C |
| T |         |                                                 | | T | ST = Server Transaction
+---+         |                                                 | +---+
|                                                 | +---+
|                                                 | | C |
|                                                 | | T |
|                                                 | +---+
+--------------------+
Figure 3: Stateful Proxy Model
4、        转发请求到每一个目的地(16.6)
5、        处理所有的应答(16.7)

16.3 验证请求
在proxy转发请求之前,它必须检查消息的合法性。一个合法的消息必须经过如下的检查:
1、        合法的语法
2、        URI scheme
3、        最大转发次数
4、        (可选)循环检测(loop detection)
5、        proxy-require
6、        proxy-authorization
如果任何一步失败了,proxy都必须作为UAS(8.2)一样,应答一个错误码。
注意proxy并不要求检查合并的请求,并且不能把合并的请求当作一个错误的情况。终端接收到合并的请求会根据8.2.2.2节讲述的内容进行分解。
1、        合法的语法。
请求要能够被服务端事务处理,那么请求就必须是语法无误的。请求中的任何与检查相关的部分或者与请求转发节相关的部分都必须语法严格无误。在检查中,其他部分的严格与否,在检查中都被忽略,并且在转发消息过程中保持不变。例如,proxy不会由于非法的Date头域而拒绝请求。同样的proxy不会在转发请求的时候移去非法的Date头域。这个是为了设计成为可扩展的。以后的扩展可能定义新的方法、新的头域。proxy不能拒绝由于包含了不认识的方法或者不认识的头域而拒绝转发这个请求。
2、        URI scheme检查
如果Request-URI包含一个proxy所不能理解的URI形式,那么proxy应当通过返回一个416来拒绝这个请求(Unsupported URI scheme)。
3、        最大转发次数检查
最大转发次数Max-Forwards头域(20.22)是用来限制转发的次数的。如果请求没有包含Max-Forwards头域,那么这个检查将被忽略。如果请求包含了一个Max-Forwards头域,并且这个头域大于0,那么这个检查就跳过。如果请求包含一个Max-Forwards头域,并且这个头域为0,那么这个proxy不能转发这个请求。如果请求是OPTIONS请求,那么proxy可以作为最终响应者来响应这个请求,并且遵循11节讲述的产生应答。否则proxy应当返回一个483(too many hops)应答。
4、        可选的Loop Detection检查
proxy可以检查看看转发是否可能导致循环。如果请求包含一个Via头域,并且这个头域值,和proxy早先保留的请求的头域值相同,那么这个请求就是以前经过过本proxy。要么这个请求就是循环处理了,要么就是合法的循环处理。要检测请求是否循环处理,proxy可以用branch参数,根据16.6节的第8步来计算,并且和接收到的Via头域做比较。如果参数相等,那么请求就循环了。如果不等,那么请求就是合理的经过,并且继续处理。如果检测到了循环,那么proxy应当返回一个482(LoopDetechted)应答。
5、        Proxy-Require检查
本协议的以后的扩展可能会要求额外的proxy特性。所以终端会在请求中包含一个Proxy-Require头域来表明会使用到那些特性,这样proxy就可以根据Proxy-Require判定自己是否能够支持这些特性。
如果请求包含一个Proxy-Require头域(20.29)并且有一个或者多个本proxy不能理解的option-tags。那么这个proxy必须返回一个420(Bad Extension)错误,并且这个错误应答必须包括一个Unsupported(20.40)头域列明了那些option-tags这个proxy不能支持。
6、        Proxy-Authorization 检查
如果proxy要求在转发请求之前进行身份认证,那么必须根据22.3节中描述的那样进行请求的检查。22.3节也定义了proxy应当怎样处理检查失败的情况。
16.4 路由信息预处理
proxy必须检查请求中的Request-URI部分。如果Request-URI包含了一个本proxy早先放在Record-Route头域中的值(参见16.6,4),proxy必须用Route头域中的最后一个值来替换Request-URI,并且从Route头域中删去这个值。proxy必须接着按照个修改后的请求进行处理。
这个只会在某元素发送请求到proxy(proxy本身可能是一个终端),并且这个请求是基于严格路由的。在接收到请求后重写这个字段是必须的,因为要保持向后兼容性。同时也允许按照本规范实现的元素保护Request-URI通过严格路由的proxy(12.2.1.1节)。
这个要求并没有强制proxy保留状态来检查其早先放在Record-Route头域中的URI。作为替换的方法,proxy只需要保留足够的信息在那些URI里边,这样,在以后出现的时候就能识别了。
如果Request-URI包含了maddr参数,proxy必须检查这个参数来看看是否在proxy配置的可信任的地址列表或者可信任的区域列表中。如果Request-URI包含一个maddr参数,并且这个参数包含了一个proxy可以信任的地址,并且这个请求是通过Request-URI中(指明或者缺省)的端口和协议接收到的,proxy必须抽掉maddr和其他非缺省的端口和通讯参数,并且继续处理。

proxy可能收到一个带有匹配maddr的请求,但是不是在URI中指出的端口和transport接收到的。这个请求需要通过指明的port和transport转发到对应的proxy。

如果Route头域的第一个值就是这个proxy,那么proxy必须从请求中把它移去。

16.5 确定请求的目的
接着,proxy计算请求的目的(或者多个目的地)。目的地集合可以由请求的内容决定或者由绝对位置服务提供。目的地集合中的每一个目的地都由URI来表达。

如果请求中的Request-URI包含了maddr参数,必须把Request-URI放在目标集合中,并且是作为唯一一个目标URI,并且proxy必须按照16.6节中的约定进行处理。

如果Request-URI的区域并非本proxy负责的区域,那么Request-URI必须放在目标集合中,并且作为唯一一个目标URI,并且proxy必须按照16.6节中的约定进行处理。

有很多种情况都会导致proxy收到并非本proxy负责的区域的请求。一个防火墙proxy处理外发的请求(就像HTTPproxy处理外发的请求)就是一个典型的例子。

如果请求的目标集合没有像上边讲述的这样预先设定,那么这就意味着proxy是负责Request-URI所指明的区域的,并且proxy可以用任何机制来决定往哪里发送这个请求。这些机制都可以归结成为访问一个绝对位置服务的形式。这个可能由从SIP注册服务器创建的位置服务器获得信息、读取数据库、查阅服务器、利用其他协议、或者就简单的替代Request-URI来实现。当访问由注册服务器创建的位置服务的时候,在作为索引查询之前,Request-URI必须首先根据10.3节进行规范处理。这些机制的输出结果将作为目的地集合。
如果Request-URI没有提供足够的信息来让proxy能够产生目的地集合,它应当返回一个485(Ambiguous)应答。这个应答应当包含一个Contact头域包含一些应当尝试的新位置。比如,一个到sip:John.Smith@company.com的INVITE可能在某一个proxy是不明确的,因为这个proxy有多个JohnSmith。21.4.23节有细节描述。

任何与这个请求有关的,或者与proxy当前环境有关的信息都可以用来构造目的地集合。例如,由于请求的内容不同或者包头域的不同,可以有不同的目的地集合,又或者不同时间到达的请求也可以有不同的目的地集合,或者不同的时间间隔,上一次失败的请求,甚至是当前proxy的利用率都可以导致目的地集合的不同。

通过这些机制,我们可以有一个可能的目的地列表,他们的URI被增加到目的地集合。每一个目的地只能在目的地列表中出现一次。如果目的URI已经在这个集合中存在了(基于URI类型的相等定义),那么它不能再次增加。

如果原请求的Reuqest-URI指明的区域并非本proxy所负责的区域,那么本proxy不能增加任何额外的目的地到目的地集合。

如果proxy负责Request-URI所指明的区域,那么这个proxy只可以在转发的时候改变请求的Request-URI。如果proxy并非负责这个URI,那么它不会在3xx或者416应答的时候查生递归。

如果原始请求的Request-URI是属于本proxy负责的区域的,那么proxy可以在请求转发的时候增加目的地。他可以在处理过程中,用任何可以获得的信息来决定新的目的地。 例如,proxy可以选择把一个转发应答(3xx)所包含的联系地址合并到目的地集合中。如果proxy使用一个动态的信息源来构造目的地集合(例如访问SIP的注册服务器),它应当在处理请求的过程中监测这个信息源。当有新的目的地出现的时候,就应当加到这个目的地集合里边。就像上边说得,每一个URI只能在集合中出现1次。

只能出现1次的原因是可以降低网络冲突,在合并重定向请求的联系地址的情况下可以防止无限递归的出现。

举例来说,一个简单的位置服务是一个”no-op”(无操作的),返回的目的URI就是输入的请求URI。请求将送到一个特定的下一个节点proxy。在16.6节/6小节定义请求转发中,通过SIP或者SIPS URI表达的,下一个节点的身份, 将在Route的头域最上一层插入。
如果Request-URI是这个proxy所负责的,但是在本proxy中找不到,那么proxy必须返回404(Not Found)应答。

如果目的集合经过上边的处理依旧是空的,那么proxy必须返回一个错误应答,这个错误应答应当是408(暂时不可用)。

16.6 请求转发
当目的地集合不是空的时候,proxy可以开始转发这个请求。有状态的proxy可以按照任意的顺序处理这个目的地集合。它可以顺序处理多个目的地,上一个完成前下一个不能开始。也可以采用并行的处理多个目的地。也可以通过分组的形式,每组之间是串行的,组内是并行的。

通常的处理顺序机制是使用一个Contact头域的qvalue参数来处理(20.10节)。目的地从最高的qvalue开始处理到最低的qvalue。相同qvalue的目的地可以并行处理。

有状态的proxy必须包含针对目的地集合的一个接收到应答和转发出去的原始请求进行匹配的机制。为了完成这样的目的,这个机制是一个由proxy层在转发第一个请求前创建的”response context”(应答上下文)来保障的。

对于每一个目的地,proxy转发请求都遵循下列步骤:
1、        拷贝一个接收到的请求
2、        更新Request-URI
3、        更新Max-Forwards头域
4、        可选增加一个Record-Route头域
5、        可选增加附加的头域
6、        路由信息后处理
7、        决定下一个节点地址、端口、通讯协议。
8、        增加一个Via头域值
9、        如果需要,增加一个Content-Length头域
10、        转发这个新的请求
11、        设置定时器C
下面详细介绍每一步。

1、        拷贝请求
proxy首先把接收到的请求做一个拷贝。拷贝必须包含接收到的请求的全部头域。在接下来的处理步骤中未提及的头域不能删除。拷贝应当保留接收到的请求的头域的顺序。proxy不能用合并的域名来进行域值的重新排序(参见7.3.1)。proxy不能增加、修改、删除消息体。
实际上,在实现中并非只是做一个拷贝;首要的事情是为每一个下一个节点准备一个相同的请求。

2、        Request-URI
在拷贝好的请求中的Request-URI必须用目的地的URI进行替换。如果这个目的URI包含任何在Request-URI中所不能允许的参数,那么这些参数必须被删去。
这个步骤是proxy的本质步骤。proxy通过这个机制来把请求转发到目的地。在某些情况下,接收到的Request-URI会不作更改的添加到目的地集合中。对于这样的目的地来说,上边讲的替换就等于是没有任何操作。

3、        Max-Forwards
如果拷贝的头域包含一个Max-Forwards,proxy必须把这个域值减一。
如果拷贝的头域没有包含一个Max-Forwards头域,proxy必须自己增加一个头域,缺省值是70。现在有一些UA不会在请求中填写Max-Forwards头域。

4、        Record-Route
(假设proxy接收到的这个请求会创建一个对话的情况下),如果希望保留这个请求创建的对话中,后续的请求依旧是要经过本proxy,那么本proxy必须增加一个Record-Route头域值在这个拷贝中,并且增加的这个头域值应当是在其他现存的Record-Route头域之前。通过请求建立的对话可以包含一个预置的Route头域。
如果这个请求已经是一个对话的一部分,proxy如果希望以后这个对话的请求依旧经过本proxy,那么proxy应当增加一个Record-Route头域值。在12节描述的普通的终端操作中,这些Record-Route头域值不会对终端使用的路由集合造成任何影响。

如果请求本身已经在对话中的话,如果proxy不增加一个Record-Route头域在请求的包头,后续的请求也会经过本proxy。但是,如果当终端中断并且重新构造这个对话的时候,本proxy就会从对话所经过的节点中删去。

一个proxy可以在任何请求中增加这个Record-Route头域值。如果请求并没有初始化一个对话,终端将会忽略这个头域值。12节讲述了终端如何使用Record-Route头域来构造Route头域的。

在请求路径上的每一个proxy都是独立的决定是否增加一个Record-Route头域值的-在请求的Record-Route头域上的值并不影响这个proxy决定增加还是不增加Record-Route头域值。

在Record-Route头域中防止的URI必须是SIP或者SIPS URI。这个URI必须包含一个lr参数(参见19.1.1)。这个URI可以和请求将被转发的地方不同。这个URI不应当包含通讯参数,除非该proxy确认在后续请求将会经过的下行节点中,都支持这个通讯参数(比如本地网络等等)。

本proxy提供的这个URI可能会让其他元素(其他proxy)作出路由决定。本proxy,通常,并不知道其他节点的处理能力,所以,它必须严格律己,让自己遵循规范的SIP实现:SIP URI和TCP或者UDP通讯协议。

在Record-Route中的URI必须指向插入它的元素(或者替代元素),这个意思是说通过附件[4]的服务器定位步骤可以顺利找到这个元素,这样后续的请求才能顺利到达同一个SIP元素。如果Request-URI包含一个SIPS URI,或者Route头域的最上的值(经过后续第6步的处理)包含一个SIPS URI,那么插入Record-Route头域的值必须是一个SIPS URI。而且,如果请求不是基于TLS接受的,那么proxy必须增加一个Record-Route头域。在相似的情况下,proxy如果从TLS上接收的请求,但是产生的是一个在Record-Route中或者Route头域最上值中没有SIPS URI的请求(在第6步后处理之后),必须在Record-Route头域中增加一个非SIPS URI。

在安全范畴内的proxy必须在对话中保持这个安全范畴。

当Record-Route头域的URI在应答中又重新到达的时候,如果这个URI值需要重写的时候,这个URI必须是能够唯一确定的URI。(就是说,请求可能会经过这个proxy好几次,造成一个或者多个Record-Route头域值的增加)。16.7节的第8步提供了一个能够让这个URI唯一的一个机制。

这个proxy可以在Record-Route头域中增加一些参数。这些参数在某些请求的应答中会被反射(echo)回来,比如给INVITE请求的200(OK)应答。通过在消息的参数中保持状态比在proxy中保持状态更加有效。

如果proxy想在任何类型的对话中都保持在请求的路径上(比如在跨越防火墙的对话中),它需要给每一个接收到的请求中,都增加Record-Route头域,即使是它所不能理解的方法的请求也要增加,因为这些方法可能是对话相关的,具有对话语义的方法。

在Record-Route头域中增加的URI只是在当这个请求创建对话的时候有效。举一个例子,对于一个对话-有状态的proxy(dialog-stateful proxy),当在对话结束后,如果再收到一个请求,这个请求的Request-URI的值中包含这个URI,那么它就可以选择拒绝这个请求。一个对话状态无关的proxy,当然,没有对话结束的概念,但是他们可以再这个值中填写足够多的信息,这样就可以在以后的请求来到的时候做对话的ID的比较,并且可以选择拒绝不匹配这个信息的请求。终端不能在对话外使用这个对话中的Record-Route头域的URI。参见12节描述的终端使用Record-Route头域的细节。

当proxy需要查看所有对话中的消息的时候,我们就需要Record-routeing。但是,他会降低处理性能和影响扩展性,因此proxy应当只在特定情况下使用record-route。任何初始化一个对话的SIP请求都可以适用Record-Route。在本文档中,只有INVITE请求是可以适用的,以后的扩展文档可能包含其他的方法。

5、        增加附加的头域
在这一步,proxy可能增加其他适当的头域。

6、        处理路由信息
proxy可以有一个本地的策略,这个策略要求请求在传递到目的地之前,必须经历一个proxy集合。这样的proxy必须能够确保所有的类似的proxy都是松路由(loose routers)的。通常,只有当这些proxy都是在相同的区域管理的时候,我们才可能知道这些proxy是否都是松路由的。这个proxy的集合是通过一组URI的集合表示(每一个都包含一个lr参数)。这个集合必须被放置到Route头域中,并且放置在其他头域值之前。如果Route头域不存在,必须增加一个Route头域,包含这组URI的列表。
如果proxy有一个本地策略要求请求经过一个指定的proxy,在压栈Route头域之外的一个方法就是旁路下边的第10步的逻辑转发,而是直接发送这个请求到这个指定的proxy的地址,端口,和协议。如果请求有一个Route头域,这个额外的方法就不能用了,除非它知道下一个节点proxy是一个松路由的节点。否则,使用上边讲的增加Route头域的方法会更有效,更灵活,适应性更好,并且操作更一致。而且,如果Request-URI包含了一个SIPS URI,这个proxy必须用TLS来进行通讯。

如果请求的拷贝中包含了Route头域,这个proxy必须检查这个Route头域的第一个值。如果这个URI并没有包含lr参数,那么proxy必须根据下列步骤修改这个请求:

- proxy必须把Request-URI放在Route头域中的最后一个值。
- proxy必须把第一个Route头域的值放在Request-URI中,并且从Route头域中删去。

把Request-URI添加到Route头域的最后是为了让Request-URI的信息能够通过严格路由的proxy。”Popping”弹掉第一个Route头域值到Request-URI中是为了能够让严格路由元素能够接收到这个请求(并且用它自己的在Request-URI中的URI和在Route顶部下一个节点的URI)。

7、        确定下一个节点的地址,端口和通讯协议。
proxy可以有自己的策略来决定发送请求到特定的IP地址,端口和transport,可以和Route的值或者Request-URI的值无关。当本proxy不能确定对应ip,端口,transport的服务器是一个松路由(loose router)的时候,这样的策略就不能使用了。但是,除了Route头域应当像上边讲的这样使用,我们并不推荐这样的发送请求的机制。

在没有这样一个替代机制的时候,proxy应用附件[4]的步骤来决定应当向哪里发送这个请求。如果proxy重新规格化请求,并且发送到一个像上边6点讲的严格路由的元素,proxy必须应用这些步骤到请求中的Request-URI。否则,如果Route头域存在,proxy必须应用这些步骤到Route头域的第一个值;如果Route不存在,proxy必须应用这些步骤到Request-URI。这些步骤最终得到一个序列集合(地址,port,transport)。与使用那个URI作为附件[4]处理的输入,如果Request-URI指定了一个SIPS URI,那么proxy必须把输入[4]的URI当作是SIPS URI然后遵循[4]的处理步骤进行处理。

就像在附件[4]中讲述的,proxy必须尝试序列集合中的第一组元素,并且依次尝试序列集合中的每一组元素,直到成功为止。

对于每一组的尝试,proxy必须按照这组的通讯要求,对消息进行适当的格式化,并且用一个新的客户端事务(第8到第10点讲述的),进行请求的发送。

由于每一组的发送都是使用心得客户端事务,这就体现了一个新的分支。因而,第8步插入的Via头域中的分支参数必须每组发送的都不一样。

如果客户端事务报告发送请求失败,或者它自身的状态机超时,proxy就应当继续处理序列集合中的下一组元素。当遍历完序列集合之后,请求就不能发送到目的地集合。proxy不需要在应答上下文中放什么应答,然而在别的方面却需要就像从目的地集合收到一个408(Request Timeout)终结响应一样的操作。

8、        增加一个Via头域值
proxy必须在请求的拷贝中增加一个Via头域值,并且在其他Via头域值之前增加。这个值的构造可以参见8.1.1.7。这意味着proxy需要计算自己的分支参数,并且应当是全局唯一的分支,并且包含必要的magic cookie。注意这意味着如果请求循环经过本proxy的时候(也就是数次经过同一个proxy),每次的分支参数都不同。

在proxy构造分支参数的值上,有一个附加的约束,用来进行循环的检测。一个要检测循环的proxy应当创建一个由两部分组成的分支参数。第一部分必须满足8.1.1.7的约束。第二部分是用来做循环检测的,并且是从螺旋中判定是否存在循环(请求数次经过同一个proxy是正常的,这是螺旋,但是如果是循环,那就不正常了。假定proxy是X,CX,XY,YZ,ZX,XA,A目的地是正常的,但是如果CX, XY, YZ, ZX, XY, 这就是循环了)。

循环检测是通过这样的方法检测的:当请求返回给一个proxy,与处理请求相关的字段并未改变,那么这个就是循环了。这个分支参数的后一部分应当反应所有的这些头域(包括所有的Route,Proxy-Require和Proxy-Authorization头域)。这是确保如果请求从别处重新路由回来,而且这些字段改变了,那么这就是一个螺旋而不是循环(参见16.3)。通常建立这个比较值的方法是计算一个hash值,通过基于To tag,From tag,Call-ID头域,收到请求的Request-URI(而不是经过处理过后的Request-URI),Via头域的最上一个,Cseq头域的序列号,任何附加的Proxy-Require或者Proxy-Authorization头域。具体的hash算法是基于实现相关的,但是MD5(RFC1321[35]),用16进制表达,是一个有道理的选择。(基于64位表达的是不太合适的)。

如果proxy希望检测循环,那么”branch”参数必须用包含可能影响处理请求的全部信息构成,包括输入的Request-URI和其他可能会影响proxy处理路由的字段,通过计算得到。这是检测循环所必须的,因为如果请求在路由相关的字段改变以后,重新发回这个服务器,那么新的处理可能会发送到另外的地方,而不是造成一个循环。

在branch参数的计算上,请求的方法不能计算进去。但是作为特例,CANCEL和ACK请求(给非2xx应答的)必须和他们对应的请求有相同的branch值。branch参数用于在服务器处理这些请求的时候体现请求之间的相关性(17.2.3和9.2)

9、        如果需要,增加一个Content-Length头域
如果请求会通过一个基于流的通讯协议发送到下一个节点,并且发送的请求拷贝中没有包含一个Content-Length头域,那么proxy必须增加一个正确的请求包体大小在这个头域(20.14)。

10、        转发请求
一个有状态的proxy必须为这个请求创建一个新的客户端事务(根据17.1节描述的那样),并且指示事务层用第7步指定的地址,端口和协议进行发送。

11、        设定时钟C
为了能够处理INVITE请求没有产生终结应答的情况,TU使用一个定时器(称作定时器C)。当INVITE请求被转发的时候,必须为客户端事务设定一个定时器C。这个定时器C必须大于3分钟。16.7节的2步讲述了这个定时器是如何根据临时应答来更新的,并且16.8节讲述了定时器到时的处理步骤。

16.7 应答的处理
当proxy收到一个应答的时候,它首先尝试定位一个与这个应答匹配的客户端事务(17.1.3)。如果没有匹配,proxy必须作为无状态的proxy来处理这个应答(即使这个应答是信息性质的应答)。如果与应答匹配的客户端事务找到了,那么这个应答将转给这个客户端事务进行处理。

将应答转给对应的客户端事务(或者更通常的说法是发出请求的或者相关的事务),并不是为了更强大的处理能力,它是保证了”晚到”的给INVITE请求的2xx应答能够正确的转发。

当客户端事务把应答交给proxy层,将会执行下列步骤:
1、        寻找适当的应答上下文。
2、        用临时应答来更新定时器C
3、        从最上边移除Via
4、        在应答上下文中增加应答
5、        检查这个应答是否需要立刻发送
6、        如果需要,从应答上下文中选择最好的终结应答。

如果在与这个应答上下文相关的每一个客户端事务都结束的以后,还是没有终结应答转发,那么proxy必须选择从已经收到的应答中,选择转发”best”应答回去。

下列步骤必须在每一个被转发的应答上执行。就像每一个请求有超过一个应答被转发一样:至少有一个终结应答和0个或者多个临时应答。

7、        需要合并认证头域值。
8、        可选的重写Record-Route头域值
9、        转发应答
10、        产生合适的CANCEL请求。

上述每一步在下边有详细的描述:

1、        寻找上下文
proxy通过16.6节定义的方法来在寻找转发原始请求前创建的”应答上下文” 。在这个上下文中进行后续的处理步骤。

2、        为临时应答更新定时器C
对于INVITE事务,如果应答是一个返回码是101到199的临时应答(就是说,除了100的临时应答),proxy必须给这个客户端事务重新设置定时器C。这个定时器可以设置成为其他的值,和原始值不一样,但是这个数字必须大于3分钟。

3、        Via
proxy从应答中移去Via头域中最上的值。

如果在这个应答中没有这个Via头域值,那么应答的含义就是说这个应答不应当被这个proxy转发。本节描述的后续处理步骤也不需要继续处理,而是用8.1.3节定义的UAC处理规则进行处理(传输层处理已经进行)。

这种情况可能会发生,比如,当某一个元素产生一个第10节规定的CANCEL请求。

4、        增加应答到上下文
收到的终结应答都会保存在应答的上下文中,直到收到一个由服务端事务产生的针对这个上下文的终结应答为止。这个应答是从那个服务端事务中收到最佳终结应答的一个候选。即使这个应答不会被选中作为最佳应答,这个应答的信息也需要用来构造成为最佳应答。

如果proxy决定尝试调用3xx应答返回的联系地址,并且把他们添加到目的地集合,它必须在把这个应答添加到应答上下文之前把联系地址从应答中移除。不过,proxy不应当在源请求的Request-URI是一个SIPS URI的情况下,尝试调用非SIPS URI。如果proxy尝试每一个3xx应答给回的联系地址,proxy不应当把这个应答添加到应答上下文中。

从应答中删去联系地址的目的是为了防止下一个节点尝试本proxy已经尝试的地址。

3xx应答可能包含SIP,SIPS和非SIP URI。proxy可以自行决定自己调用那些SIP或者SIPS URI,并且把剩下的放在应答上下文中返回。

如果proxy收到一个对于一个Rquest-URI并非SIP URI的请求的416(不支持的URI scheme)应答,但是原始请求的Request-URI是SIP或者SIPS(就是说,proxy在转发请求的时候自己调换了请求的SIP或者SIPS为一个什么其他的东西),proxy应当增加一个新的URI到目的地集合。这个URI应当是刚才尝试的非SIP URI的SIP URI版本。对于电话URL来说,这个就是把电话URL的电话号码部分放在SIP URI的用户部分,并且设置SIP URI的主机部分成为当前请求发送者的区域。19.1.6节有电话URL到SIP URI的转换细节。

在3XX应答的情况下,如果proxy在416上会产生”递归”(因为尝试SIP或者SIPS URI而导致递归),那么应当在应答上下文中增加这个416应答。

5、        检查转发的应答
当终结应答到达服务端事务的时候,下列应答包必须立刻转发。
- 任何非100(trying)的临时应答
- 任何2xx应答。
如果收到一个6xx应答,那么就不立刻进行转发,如果是有状态的proxy,那么还需要cancel所有的依赖于这个事务的客户端(在10节中描述的那样),并且不能在上下文中创建新的分支。

这个是和RFC 2543的不同之处,2543要求proxy立刻转发6xx应答。对于一个INVITE事务来说,如果立刻转发6xx应答,会使得2xx应答到达别的分支。这个结果就是让UAC在2xx应答之后收到一个6xx应答,这个是不允许发生的。在新的规则下,基于接收到一个6xx应答,proxy应当产生一个CANCEL请求,那么这个会给所有等待的客户端事务一个487应答,这就是6xx应答应当给上行队列的一个结果。

在服务端事务上发送了终结应答之后,下列的应答应当立刻被发送:
- 任何给INVITE请求的2xx应答。

一个有状态的proxy必须不能立刻转发其他的应答。特别是,一个有状态的poxy必须不能转发任何100(Trying)应答。这些应答是作为后续将被转发”最佳”应答的候选,通过上边的”在上下文中增加应答”的步骤增加到应答上下文中。

任何将被立刻发送的应答都必须遵照”7、 需要合并认证头域值。”和”8、可选的重写Record-Route头域值”来处理。

这一步,合并下一步,确保有状态的proxy能够精确转发一个终结应答到一个非-INVITE请求,或者给一个INVITE请求的非2xx应答或者一个或者多个2xx应答。

论坛徽章:
0
发表于 2007-02-06 12:09 |显示全部楼层
好东西帮顶一下:)
该协议对开发人员来说是个磨难啊,呵呵。

论坛徽章:
0
发表于 2007-02-06 13:03 |显示全部楼层
SIP,下一代网络的王者

论坛徽章:
0
发表于 2007-02-06 13:10 |显示全部楼层
都说SIP简单
看了N天了
继续看

论坛徽章:
1
射手座
日期:2013-09-30 15:24:05
发表于 2007-02-06 15:02 |显示全部楼层
好长啊!收下慢慢看了!多谢46

论坛徽章:
0
发表于 2007-02-06 15:36 |显示全部楼层
好长啊,先收藏了慢慢看.谢谢!!!

论坛徽章:
0
发表于 2007-02-07 13:04 |显示全部楼层
把附件给大家吧
有些图没帖上
中英文全有了
SIP.rar 好像也是RFC3261的翻译文件,而且很规范的
^_^

[ 本帖最后由 第一支烟 于 2007-2-7 14:08 编辑 ]

SIP-RFC3261.zip

210.99 KB, 下载次数: 1617

rfc3261[1].txt.pdf

375.32 KB, 下载次数: 2431

SIP.rar

549.06 KB, 下载次数: 2161

论坛徽章:
0
发表于 2007-02-07 22:57 |显示全部楼层
估计很少有人能一口气看完

论坛徽章:
0
发表于 2007-02-08 12:00 |显示全部楼层
谢谢提供。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP