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都必须支持
小虎 2006-05-25 00:07
两种交换方式。会话描述协议(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)
小虎 2006-05-25 00:07
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:[email protected]的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应答。
6、 选择最佳的应答
对于一个有状态的proxy来说,如果根据上边的步骤,没有任何终结应答被立刻发送,并且在客户端事务中的所有的客户端服务都已经终结,那么这个proxy必须发送一个终结应答到一个应答上下文的服务端事务层。
那么这个有状态的proxy就必须从接收到的应答上下文中选择一个”最佳”的终结应答。如果在上下文中没有一个终结应答,那么proxy就必须返回一个408(请求超时)的应答到服务端事务层。
如果应答上下文中有终结应答,那么proxy就必须从这个应答上下文中取得应答来发送。如果应答上下文中有6xx应答,那么就必须选择这个6xx应答。如果没有6xx应答,那么proxy应当选择最小的应答(应答返回代码比较小)。proxy可以选择对应最小应答系列中的任意一个应答(比如2xx系列中的任意一个应答)。proxy应当给那些提供对影响请求的应答更多的机会,比如在4xx系列中,选择401,407,415,420或者484应该稍稍优先一些。
当proxy收到503(Service Unavailable)应答的时候,不应当转发到上行队列中,除非它能够知道这个后续的请求队列都能产生503的应答。换句话说,就是转发503就意味着proxy确实不能处理任何请求,不仅仅是Request-URI里边的这个地址不能处理请
小虎 2006-05-25 00:08
求。如果只有某个应答会产生503,proxy应当产生500应当到上行队列中。
被转发的应答都必须遵照”7、 需要合并认证头域值。”和”8、可选的重写Record-Route头域值”来处理。
例如,如果一个proxy转发一个请求到4个地方,并且收到了503,407,501,和404应答,它可能选择407(Proxy Authentication Required)应答。
1xx和2xx应答可能和建立对话有关。当请求没有包含一个To tag,UAC使用在应答中的To tag来区分请求创建的对话的多个应答。如果请求中没有包含To的tag,那么proxy必须不能为1xx或者2xx应答增加这个tag到To头域。一个proxy不能修改1xx或者2xx应答中的To头域的tag字段。
在请求的1xx应答中,如果应答没有To头域的tag字段的时候,由于proxy不能添加tag字段到这个To头域,它就不能产生它自己的非100临时应答。但是它可以把这个请求分支到其他一个UAS上,这个UAS可以和proxy共享同样的元素。这个UAS可以返回它自己的临时应答,进入请求创建早期对话中。这个UAS并没有必要作为一个proxy的严格处理步骤存在。它可以是一个在proxy内部的一个虚拟的UAS实现。
3到6xx的应答是节点到节点传递的。当产生了一个3-6系列的应答的时候,每一个节点都作为UAS一样,产生它自己的应答,通常基于下行队列的应答产生自己的应答。对于每一个节点来说,在转发3到6系列应答回去的时候,如果这个应答没有包含To tag,那么这个节点也应当不改变这个to tag。
当收到的应当包含了一个To tag,那么这个proxy不能够修改这个To tag。
恩,实际上在proxy转发3到6系列应答的时候,如果替换了To tag也不会让上行队列所经过的节点有影响,保留原始的tag值可以有助于调试。
当proxy需要合并多个应答的信息的时候,从这些应答中选取To tag的方式是任意的,并且产生一个新的To tag可能可以使得调试更加容易。举例来说,当合并401(Unauthorized)和407(Proxy Authentication Required)信息的时候,或者合并一个未加密的Contact值和未通过验证的3xx应答的时候,产生一个新的To tag就会让调试比较容易。
7、 合并认证头域值
如果选择的应答是401(Unauthorized)或者407(Proxy Authentication Required),那么proxy就必须从本应答上下文中的所有其他401(Unauthorized)和407应答中搜集WWWAuthenticate和Proxy-Authenticate 头域值。并且把这些信息增加到这个应答中。最后的401或者407应答中可能会包含多个WWWAuthenticate和Proxy-Authenticate头域值。
由于这个请求的一个或者多个目的地可能是需要请求身份验证的,所以这个搜集步骤就是必须的。客户端需要接收到这些所有目的者的应答并且在下一次尝试的时候,为每一个目的地提供相关的身份证明。在26节有相关的说明。
8、 Record-Route
如果最终发送的应答中包含Record-Route头域值,并且是这个proxy所原创提供的值,那么在发送这个应答前,proxy可能需要重写这个值。这提供了一个机制,让proxy能够给下一个上行节点或者下行节点提供非本机的一个URI地址。这种情况是很常见的,比如,在多地址主机系统就非常有用。
如果proxy是通过TLS收到请求的,并且通过非TLS转发出去,proxy必须重写在Record-Route头域中的URI,重写成SIPS URI。如果proxy通过非TLS接收到请求,转发是通过TLS转发的,那么proxy必须重写Record-Route请求头域的URI为一个SIP URI。
proxy提供的新的URI必须满足同样的Record-Route头域的URI约束(16.6节的第四步)。并且遵循下列的修改:
URI不应当包含通讯参数除非proxy知道下一个上行(同下行队列对应的)节点,对于后续的请求都支持相关的通讯参数。
如果proxy打算修改应答中的Record-Route头域,要做的一件事情就是定位插入的Record-Route头域值。如果请求是螺旋经过的,并且proxy在每次螺旋的时候都插入了Record-Route值,在应答中(必须在反向路径中的正确位置)找到正确的需要修改的值就需要一点技巧。上边的规则强调proxy在增加Record-Route头域值的时候是必须增加唯一的URI,这样才能找到正确的一个能够重写。我们推荐proxy为每一个URI的user portion增加一个唯一个proxy实例标志。
当应答到达的时候,proxy修改第一个和proxy实例标志匹配的Record-Route。这个修改导致产生一个在user portion部分去掉proxy实例的URI。到下一个循环回来处理的时候,同样的算法(用参数从上而下的寻找Record-Route头域值)会更改这个proxy插入的下一个Record-Route头域值。
对于proxy增加Record-Route头域值的请求来说,并非每一个应答都包含一个Record-Route头域。如果应答包含一个Record-Route头域,那么就包含这个proxy增加的值。
9、 转发应答
当”合并认证头域”和”Record-Route”步骤完成以后,proxy可以对这个应答做其他的附加处理。但是这个proxy不能增加、修改、删除消息体。并且除非另有指示,除了Via头域值(在16.7节3步)之外,proxy不能删除任何头域值。特别是,proxy不能删除任何可能增加到与处理和这个应答相关的下一个请求的Via头域值的”接收到”的参数。proxy必须把应答传递到跟这个应答上下文相关的服务端事务。这回导致应答发送到最上的Via头域值的地方。如果服务端事务不在处理这个发送,这个节点必须作为无状态proxy转发这个应答到服务端通讯层。服务端事务可能已经标志这个发送应答失败或者内部状态机已经设置成为超时状态。这些错误都应当记录下来用于诊断错误,但是协议并没有要求proxy做补救措施。
proxy必须维持应答上下文直到所有相关事务都已经终结,甚至在发送完成终结应答后还需要维持。
10、 产生CANCEL请求
如果转发的应答是一个终结应答,proxy必须给依赖于这个应答上下文的所有客户端事务,产生CANCEL请求。在收到6xx应答的时候,proxy同样应当为所有等待在这个应答上下文的客户端事务产生CANCEL请求。等待的客户端事务就是收到了临时应答,但是没有收到终结应答(还是出于处理中的状态),并且没有任何CANCEL请求与之相关的请求。产生CANCEL请求请参见9.1节。
对于要求基于转发终结应答而CANCEL的客户端事务并没有保证终端不会收到给一个INVITE的多个200(OK)应答。基于多余一个分支的200(OK)应答可能会在CANCEL请求处理前到达。进一步说,后续的扩展可能会改掉这个产生CANCEL请求的要求。
16.8 处理定时器C
如果定时器C 被出发了,proxy必须要么用另外一个数值重新设定定时器,要么终结客户端事务。如果客户端事务已经收到了临时应答,那么proxy必须产生一个与之匹配的CANCEL请求。如果客户端事务还没有收到临时应答,那么proxy必须就像收到一个408(Request Timeout)一样的处理。
允许proxy重设定定时器就意味着允许proxy基于当前条件(比如服务器利用率等等)动态的扩展事务的生命周期。
16.9 处理通讯层的错误
如果在转发请求(参见18.4)的时候,通讯层报告了一个错误,那么proxy必须就像收到了一个503(Service Unavailable)应答一样的处理。
如果proxy在转发应答的时候接收到错误,那么他就丢弃应答。proxy不能由于通讯的原因而cancel任何和这个应答上下文相关的客户端事务。
如果proxycancel了这些客户端事务,那么一个恶意的或者出错的客户端可以用一个Via头域导致所有的事务都失败。
16.10 CANCEL处理
一个有状态的proxy可以给他自己产生的其他请求在任何时候都产生CANCEL请求(参见9.1遵从接收到对应请求的一个临时应答)。在接收到一个匹配的CANCEL请求的时候,proxy必须取消任何与应答上下文相关的客户端事务。
当INVITE请求有一个Expires头域并且这个头域值已经超时的情况下,一个有状态的proxy可以对这个处于pending的INVITE客户端事务发出CANCEL请求。可是,通常来说,这是不必要的,因为相关的终端会发出结束事务的信号。
当有状态的proxy在它自己的服务端事务上处理CANCEL请求的时候,并没有新的应答上下文会创建。相反,proxy层寻找与这个CANCEL对应请求的现存的应答上下文。如果找到了对应的应答上下文,那么这个节点应当立刻返回一个200(OK)应答给这个CANCEL请求者。在这个情况下,这个节点就像8.2节定义的UAS一样的工作。进一步说,这个节点应当为每一个依赖于这个上下文的客户端事务产生一个CANCEL请求(就像在16.7节第10步描述的那样)。
如果一个应答上下文没有找到,这个节点就无法CANCEL这个请求。它就必须像无状态proxy一样转发这个CANCEL请求(可能这个节点把被CANCEL的请求在先前也当作无状态的proxy转发了)。
16.11 无状态的proxy
当作为无状态的时候,proxy就是一个简单的消息转发者。很多无状态的处理步骤和有状态的时候很类似。不同的地方在下边描述。
一个无状态的proxy并没有事务的概念,或者用于描述有状态proxy行为的应答上下文。相反的是,无状态的proxy处理消息,无论是请求还是应答,都是直接从通讯层处理的(参见18节)。当然,无状态proxy自己也不重发这些消息。他们只是转发他们收到的任何重发的消息(他们本身并没有能力来分辩那些消息是重发的,那些消息是原始消息)。进一步说,当无状态的处理一个请求的时候,这个节点并不产生它自己的100(Trying)或者其他临时应答。
无状态的proxy必须用16.3节描述的那样来验证一个请求。
无状态的proxy必须遵从16.4到16.5节定义的步骤来处理请求,有如下几点例外:
o 无状态的proxy必须从目的地集合中,选择一个并且只能选择一个目的地。这个选择必须是根据消息的头域并且是和服务器时间无关的。特别是,一个重发的请求必须能够每次都转发到相同的目的地。进一步说,CANCEL和非路由的ACK请求必须和他们相关的INVITE请求有相同的转发目的地。
一个无状态的proxy必须遵循16.6节定义的请求处理步骤,并且有下列的不同:
o无状态proxy的branchID来说,必须要求在时间上和空间上都是唯一的。也就是说,无状态的proxy不能简单的使用一个随机数产生器来计算branchID的第一个部分(16.6节8步)。这是由于请求的重发需要相同的值,并且无状态的proxy不能区分重发的请求和原始请求。因此,branch参数的组成部分要求唯一,这样使得重发的时候能够填写相同的值。对于无状态的proxy来说,branch参数必须作为一个重发无关的消息处理参数存在。
我们没有规定无状态proxy采用何种手段保证branchID的唯一性。不过,下列步骤是推荐的方法。proxy检查在接收到请求的最上Via头域值的branchID。如果它是由magic cookie打头的,那么branchID的第一个部分就是当作接收到的branchID的hash值。否则branchID的第一个部分就当作是Via头域的最上值、To头域的tag、From头域的tag,Call-ID头域,Cseq序列号(除了方法部分),接收到的请求的Request-URI的一个hash值。这些头域值在不同事务中总是不一样的。
o所有其他的消息转换(16.6节)必须保证转发重发的请求的时候能够转发到相同的节点。特别是,如果proxy在Record-Route头域中增加了值,或者在Route头域中增加了值,proxy必须在转发重发的请求的时候增加相同的值。至于Via 的branch参数,这就意味着转发必须是基于时间无关的配置或者请求重发无关的属性。
o一个无状态proxy决定转发的地点是像16.6节10步描述的有状态的proxy一样。但是请求是直接交给通讯层发送的,而不是交给客户端事务。
由于一个无状态的proxy必须转发重发的请求到相同的地方,并且增加标志性的branch参数,它只能用消息中本身的信息和时间无关的配置来计算。如果配置状态不是时间无关的(比如,如果路由表更新了),这个改变相关的请求,在这个改动开始以后,到在事务超时的时间范围内,就不能作为无状态的转发了。这个处理这段时间的请求是实现相关的。通常处理的方法,是把这些请求当作事务有状态的进行转发。
无状态的proxy必须不能对CANCEL做特别的处理。CANCEL的处理就像对其他请求的处理一样进行。特别是,一个无状态的proxy使用相同的Route头域来处理CANCEL请求,就像处理其他请求一样。
对于16.7节中定义的应答处理,对于无状态proxy来说,并不适用。当一个应答到达一个无状态proxy,proxy必须检查最上的Via头域值的sent-by参数。如果这个地址和这个proxy一样(就是和proxy插入的先前的请求中的值一样),那么这个proxy必须从应答中移除这个头域值,并且转发这个应答到下一个Via头域值。这个proxy必须不能增加,修改或者删除消息体。除非有特别的说明,proxy必须不能移除其他的头域值。如果地址不匹配本proxy,消息就必须简单的悄悄扔掉。
16.12 Proxy Route处理的总结
在没有本地策略的情况下,proxy对于包含Route头域的请求处理可以归结于如下的步骤:
1、 proxy会检查Request-URI。如果它指向的是本proxy所负责的区域,那么proxy会用位置服务的结果来替换这个URI。否则,proxy不改变这个URI。
2、 proxy会检查Route头域的最上URI。如果这个URI指向这个proxy,这个proxy从Route头域中移除(这个路由节点已经到达)。
3、 proxy会转发请求到最上的Route头域值所标志的URI,或者Request-URI(如果没有Route头域)。proxy通过附件[4]的步骤来产生地址,端口,通讯协议等等用来转发请求所必须的参数。
如果在请求的路径中,没有严格路由节点,Request-URI会始终标志着请求的目的地。
16.12.1例子
16.12.1.1 基本SIP四边形
本例子是一个基本的SIP四边传送,U1->P1->P2->U2,使用proxy来传送。下边是过程。
U1 发送:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
发给P1,P1是一个外发的proxy。P1并不管辖domain.com,所以它查找DNS并且发送请求到那里。它也增加一个Record-Route头域值:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route:
P2收到这个请求。这是domain.com所以它查找位置服务器并且重写Request-URI。它也增加一个Record-Route头域值。请求中没有Route头域,所以它解析一个新的Request-URI来决定把请求发送到哪里。
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route:
Record-Route:
在u2.domain.com的被叫方接收到这个请求并且返回一个200OK应答:
SIP/2.0 200 OK
Contact: sip: [email protected]
Record-Route:
Record-Route:
u2的被叫方并且设置对话的状态的remote target URI为:
sip: [email protected]并且它的路由集合是:
(
这个转发通过P2到P1到U1。现在U1设置它自己的对话状态的remote target URI为:sip:[email protected]并且它的路由集合是:
(
由于所有的路由集合元素都包含了lr参数,那么U1构造最后的BYE请求:
BYE sip:[email protected] SIP/2.0
Route:
就像其他所有的节点(包括proxy)会做的那样,它会使用DNS来解析最上的Route头域的URI值,这样来决定往哪里发送这个请求。这就发到了P1。P1发现Request-URI中标记的URI不是它负责的域,于是它就不改变这个Request-URI。然后看到它是Route头域的第一个值,于是就从Route头域中移去,并且转发这个请求到P2:
BYE sip:[email protected] SIP/2.0
Route:
P2也发现它自己并非负责这个Request-URI的域(P2负责的是domain.com并非u2.domain.com),于是P2并不改变它。它看到自己在Route的第一个值,于是移去这个,并且向u2.domain.com转发(根据在Request-URI上查找DNS):
BYE sip:[email protected] SIP/2.0
16.12.1.2 穿越一个严格路由proxy
在这个例子中,对话建立通过4个proxy,每一个增加Record-Route头域值。第三个proxy是由严格路由实现的(RFC 2543)。
U1->P1->P2->P3->P4->U2
INVITE请求到达U2包括了:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route:
Record-Route:
Record-Route:
Record-Route:
并且U2返回了一个200 OK。接着,U2根据第一个Route头域值发送下边的BYE请求到P4:
BYE sip:[email protected] SIP/2.0
Route:
Route:
Route:
Route:
P4并不管辖Request-URI指出的域,于是就不更改这个Reqeust-URI。它发现自己在第一个Route头域中,于是把自己从Route头域移除。然后准备发送请求到现在的第一个Route头域值:sip:p3.middle.com,但是它发现这个URI并没有包含lr参数,于是在发送前,它把这个请求更改成为:
BYE sip:p3.middle.com SIP/2.0
Route:
Route:
Route:
P3是一个严格路由,于是它转发到P2:
BYE sip:p2.example.com;lr SIP/2.0
Route:
Route:
P2看到Request-URI是它放在Record-Route头域中的值,于是在进一步处理前,它把这个请求改写为:
BYE sip:[email protected] SIP/2.0
Route:
P2自己并不管辖u1.example.com,于是它根据Route头域的值,转发这个请求到P1。
P1发现自己在Route头域的最上,于是把自己移除,得到:
BYE sip:[email protected] SIP/2.0
由于P1并不管辖u1.example.com并且没有其他的Route头域,P1会基于Request-URI转发这个请求到u1.example.com。
16.12.1.3 重写Record-Route头域值。
在这里例子中,U1和U2是在不同的私有域空间中,并且他们通过proxy P1开始一个对话,这个P1作为不同私有namespace的一个网关存在。
U1->P1->U2
U1发送:
INVITE sip:[email protected] SIP/2.0
Contact:
P1使用自己的定位服务并且发送下边的信息到U2:
INVITE sip:[email protected] SIP/2.0
Contact:
Record-Route:
U2发送200 OK应答回给P1:
SIP/2.0 200 OK
Contact:
Record-Route:
P1重写它的Record-Route头域参数,提供成为U1能够使用的参数,并且发送给P1:
SIP/2.0 200 OK
Contact:
Record-Route:
稍后,U1发送接下来的BYE到P1:
BYE sip:[email protected] SIP/2.0
Route:
P1转发到U2:
BYE sip:[email protected] SIP/2.0
17事务
SIP是一个基于事务处理的协议:部件之间的交互是通过一系列无关的消息交换所完成的。特别是,一个SIP 事务由一个单个请求和这个请求的所有应答组成,这些应答包括了零个或者多个临时应答以及一个或者多个终结应答。在事务中,当请求是一个INVITE(叫做INVITE事务),当终结应答不是一个2xx应答的时候,事务还包括一个ACK。如果应答是一个2xx应答,那么ACK并不认为是事务的一部分。
这个分开的原因是基于传递全部200(OK)应答到UAC的INVITE请求的重要性所决定的。要把所有的200应答全部发给UAC,那么UAS独自负责这些应答的重新传送(参见13.3.1.4),UAC肚子负责挨个ACK确认(参见13.2.2.4)。由于ACK的重传只由UAC发起,所以在自己的事务中进行重传会比较有效。
事务分为客户端和服务端两方。客户端的事务是客户端事务,服务器端的事务就是服务端事务。客户端事务发出请求,并且服务端事务送回应答。客户端和服务端事务都是逻辑上的概念,他们可以被无数部件所包含。特别是,他们在UA中和有状态的proxy服务器中存在。以第四节的例子来说明。在这个例子中,UAC执行客户端事务,它的外发proxy执行服务端事务。外发proxy同时也执行客户端事务,把请求发送到一个那发proxy的服务端事务。这个proxy也同时执行一个客户端事务,把请求发到一个UAS的服务端事务上去。这个在图四中比较明白:
小虎 2006-05-25 00:08
+---------+ +---------+ +---------+ +---------+
| +-+|Request |+-+ +-+|Request |+-+ +-+|Request |+-+ |
| |C||------->||S| |C||-------> ||S| |C||------->||S| |
| |l|| ||e| |l|| ||e| |l|| ||e| |
| |i|| ||r| |i|| ||r| |i|| ||r| |
| |e|| ||v| |e|| ||v| |e|| ||v| |
| |n|| ||e| |n|| ||e| |n|| ||e| |
| |t|| ||r| |t|| ||r| |t|| ||r| |
| | || || | | || || | | || || | |
| |T|| ||T| |T|| ||T| |T|| ||T| |
| |r|| ||r| |r|| ||r| |r|| ||r| |
| |a|| ||a| |a|| ||a| |a|| ||a| |
| |n|| ||n| |n|| ||n| |n|| ||n| |
| |s||Response||s| |s||Response ||s| |s||Response||s| |
| +-+|<-------|+-+ +-+|<------- |+-+ +-+|<-------|+-+ |
+---------+ +---------+ +---------+ +---------+
UAC Outbound Inbound UAS
Proxy Proxy
图4: 事务关系
无状态的proxy并没有客户端或者服务端的事务。事务是一边基于UA或者有状态的proxy,另外一边也基于UA或者有状态的proxy。在SIP事务范畴下,无状态的proxy是用作透明转发很有效。客户端事务的用处是用于从一个元素中接收一个请求,这个客户端是内嵌的(这个元素就是”事务用户”或者TU;它可以是一个UA或者有状态的proxy),并且可靠的把这个请求传送到一个服务端事务。
客户端事务也负责接收应答并且把应答转交TU处理,过滤掉重发的应答或者不允许的应答(比如给ACK的应答)。另外,在INVITE请求的情况下,客户端事务也负责产生给2xx应答的ACK请求。
类似的,服务端事务也负责从通讯层接收请求并且转发这个请求到TU。服务端事务过滤重发的请求。并且服务端事务从TU接收应答并且转发到通讯层来发送。在INVITE事务的情况下,它需要接收给非2xx应答的终结应答的ACK请求。
2xx应答和它的ACK请求通过特定的方式来接收和处理。这个应答只会被UAS重发,并且它的ACK只由UAC产生。由于呼叫者知道整个已经接收呼叫的用户集合,所以需要这种端到端的处理。由于这样的特别处理,2xx应答的重发是基于UA核心的,并非基于通讯层。类似的,给2xx应答的ACK处理也是由UA核心处理的,每个路径上的proxy仅仅转发这些INVITE的2xx应答以及他们的ACK。
17.1 客户端事务
客户端事务是通过维持一个状态机来提供服务的。
TU和客户端事务通过一个简单的接口进行通讯。当TU希望初始化一个新的事务,它创建一个客户端事务并且通过设置ip地址,端口和transport来把一个SIP请求交给它传送。然后客户端事务开始执行它自己的状态机。合乎规格的应答会从客户端事务传送给TU。
总共有两种类型的客户端事务状态机,根据TU传递的请求的方法不同来区分的。一个用于处理INVITE请求。这种状态机对应的是一个INVITE客户事务。另外一个是用来处理其他所有的非INVITE请求的。它对应的是非INVITE客户事务。对于ACK来说,是不存在客户事务的。如果TU希望送一个ACK请求,它直接交给通讯层进行通讯处理。
INVITE事务和其他事务是不同的,因为它的时间周期很长。通常,对于INVITE请求的应答来说,都需要人的参与,这样会导致在应答INVITE请求之前会有很长的延时。在三方握手(人,两方机器)的时候也会有很长的延时。在另一方面,其他请求的响应都是很快就完成的。因为其他非INVITE请求事务是双方的握手,TU能够立刻对非INVITE请求作出应答。
17.1.1 INVITE客户事务
17.1.1.1 INVITE事务概述
INVITE请求包含了一个三方的握手。客户端事务发送一个INVITE,服务端事务回送一个应答,客户端事务发送一个ACK。对于非可靠传输(比如UDP),客户端事务每隔T1重发请求,每次重发后间隔时间加倍。T1是一个估计的循环时间(round-trip time,RTT),缺省设置成为500ms。几乎所有的事务定时器都以T1为单位,并且调整T1的值也就调整了那些定时器的值。请求不会在可靠的通讯协议上重新发送。在接收到1xx应答以后,重发机制完全停止,并且客户端等待更进一步的应答。服务端事务可以发送附加的1xx应答,这个应答并非由服务端事务可靠传输。最后,服务端事务会发送一个终结应答。对于非可靠的传输协议,应答会间隔时间来重发,对于可靠的传输协议,它只发送1次。对于客户端事务所接收的每一个终结应答,客户端事务都发送一个ACK,用于终止应答的重发送。
17.1.1.2 正式的描述
INVITE客户端事务的状态机在图5中展示。初始状态,”calling”,必须保证TU是用INVITE请求来初始化一个新的客户端事务。客户端事务必须把请求发送到通讯层来进行发送(18节)。如果使用的是非可靠传输的通讯层,客户端事务必须启动一个定时器A并且由缺省值T1组成。如果是一个可靠的通讯协议,那么客户端事务不应当启动定时器A(定时器A控制请求的重发送)。对于任何通讯协议来说,客户端事务必须启动一个定时器B并且有着64×T1秒的缺省值(定时器B控制事务的超时)。
当定时器A触发了,客户端事务必须重发这个请求,把请求交给通讯层进行发送,并且重新设置定时器为2*T1。在传输层中重传的定义是指把刚才通过传输层发送的消息,再次交给传输层重新发送一次。
当定时器A在2×T1后触发了,请求必须再次重传(如果客户端事务依旧还是在这个状态的话)。这个处理必须持续下去,这样请求才能每重发一次以后定时器延时1倍。重发机制只有当客户端事务在”calling”状态的时候才能进行。
缺省的T1是500ms。T1是一个RTT的估计时间,是在客户端和服务端的一个事务处理的估计时间。节点可以(不推荐)使用更小的T1值,比如私有网络,并不接到INTERNET的网络可以设置小一点。T1也可以设置成为大一点的值,并且我们建议如果当我们知道RTT值比较大的时候(比如高延时的网络)应当设置T1成为大一点的值。不管T1如何取值,本节要求的重传机制要求的指数延时是必须使用的。
当定时器B触发的时候,如果客户端事务是依旧在”calling”状态,那么客户端事务应当通知TU发生了超时。客户端事务必须不能产生ACK。64×T1是和在不可靠通讯链路上传输7个请求的时间相同。
如果客户端事务在”calling”状态接收到一个临时应答,那么就把状态切换到”proceeding”状态,客户端事务不应当再次重新发送请求了。进一步说,临时应答必须传送给TU。在”proceeding”状态的任何临时应答都必须传送给TU。
当在”calling”或者”proceeding”状态的时候,如果接收到一个应答码是300-699的应答,那么就必须把状态切换到”Completed”。客户端事务必须把收到的应答转给TU,并且客户端事务必须传生ACK请求,即使通讯层是可靠传输的(在17.1.1.3节中有描述怎样根据应答创建一个ACK请求)并且把ACK交给传输层进行传送。ACK必须和原始请求发送到相同的地址,端口和用同样的transport。当客户端事务进入”Completed”状态的时候,应当开始一个定时器D,缺省值是在非可靠通讯上是至少32秒,在可靠通讯上是0秒。定时器D反应了服务端事务在非可靠传输的情况下,在”completed”状态维持的时间。这个是和INVITE请求服务端事务定时器H相同的,定时器H的缺省值是64*T1。不过,客户端事务不知道服务端事务使用的T1值,所以我们用绝对值32秒来代替T1用作定时器D的缺省值。
在”completed”状态下,受到的任何终结应答的重传都应当产生一个ACK应答到通讯层来重新发送,但是新近收到的应答却不能传送给TU。一个应答是否是重传的定义是根据这个应答是否和客户端事务按照17.1.3定义的规则匹配。
图5: INVITE客户端事务
如果在客户端事务状态是”Completed”的时候,定时器D触发,那么客户端事务必须转到终结状态。当客户端状态是”calling”或者”proceeding”状态的时候,接收到一个2xx应答必须导致客户端事务进入”terminated”状态,并且应答必须交给TU处理。处理这个应答的方法依赖于TU是否是一个proxy核心还是是UAC核心。UAC核心会给应答产生ACK,proxy核心会转发一个200(OK)应答到上行队列。这个在proxy和UAC之间,对200(OK)的不同的处理是导致对应答的处理不在事务层进行的原因。
当客户端事务进入”terminate”状态以后,客户端事务必须立刻销毁。这样才能保证正确操作。原因是当给一个INVITE请求的2xx应答的不同处理;对于proxy转发的时候和对UAC处理ACK的时候是不一样的。因此,每一个2xx都需要交给proxy 核心(这样才能被转发),或者交给UAC核心(这样才能被ACK确认)。这期间没有事务层的处理。无论应答是否由通讯层收到,如果通讯层找不到匹配的客户端事务(用17.1.3的方式),那么应答就应当交给核心处理。这是由于与之匹配的客户端事务已经被第一个2xx应答所销毁,后续的2xx应当就匹配不成功了,于是就交给核心来处理。
17.1.1.3 构造ACK请求
本节定义了在客户端事务中构造ACK请求的方法。UAC核心为2xx应答产生ACK请求必须使用13节描述的方法,而不是用下边的方法。
在客户端事务中构造的ACK请求必须包括与原始请求相同的Call-ID, From, Request-URI头域值(就是说和在客户端事务发到通讯层的请求中的这些头域值相同)。在ACK请求中的To头域必须和被确认的应答的To头域值相同,因此通常和原始请求有所不同,不同点在增加了附加的tag参数。ACK必须包含一个单个的Via头域,并且必须和原始请求的最上边一个Via头域值相等。ACK的Cseq头域必须包含和原始请求的Cseq的序列号相同,但是方法参数应当是”ACK”。
如果INVITE请求的应答是有Route头域的,这些Route头域必须也在ACK中。这是确保ACK能够正确路由通过下行队列的无状态的proxy。
虽然请求可以包含一个包体,但是ACK的包体却比较特别,因为请求不能因为不能理解包体而拒绝这个请求。因此,我们不建议在给非2xx应答的ACK请求中放置包体,但是如果放置了,并且假设给INVITE的应答不是415应答,那么包体的类型应当严格和INVITE请求中定义的那样。如果是415应答,那么ACK的包体应当和415应答中的Accept列出的类型一致。
例如:有如下请求
INVITE sip:[email protected] SIP/2.0
Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKkjshdyff
To: Bob
From: Alice
Max-Forwards: 70
Call-ID: 987asjd97y7atg
Cseq: 986759 INVITE
给非2xx终结应答的ACK请求应当是:
ACK sip:[email protected] SIP/2.0
Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKkjshdyff
To: Bob
From: Alice
Max-Forwards: 70
Call-ID: 987asjd97y7atg
Cseq: 986759 ACK
17.1.2 非INVITE客户端事务
17.1.2.1 非INVITE事务概览
非INVITE事务并不使用ACK。他们只是简单的请求-应答的交互。对于非可靠的通讯来说,请求是间隔倍增T1的时间重新传输(直到间隔时间达到T2)。如果收到了一个临时应答,在非可靠通讯上,重传继续知道达到T2。只有当重传的请求收到的时候,服务端事务会重传其发出的最后一个应答,既可以是临时的应答也可以是终结应答。这就是为什么请求在收到一个临时应答之后还需要一直重传的原因;他们能够确保收到一个终结应答。
不像INVITE事务,非INVITE事务不需要对2xx应答做特别处理。UAC对一个非INVITE请求来说,只会产生一个单个的2xx应答。
17.1.2.2 正式的描述
在图6中讲述了非INVITE客户端事务的状态机。这个状态机和INVITE客户端事务的状态机非常像。
当TU用请求来初始化一个新的客户端事务的时候,首先进入的是“trying”状态。当进入这个状态的时候,客户端事务应当初始化一个定时器F,这个定时器F应当有一个初始值64×T1秒。这个请求必须交给通讯层来发送。如果使用的是非可靠传输的通讯协议,客户端事务必须还设置定时器E,初始值是T1。如果定时器E触发了,并且还是在”trying”状态,那么定时器需要设置成为MIN(2*T1,T2),并且重新发送;如果再次触发了,那么就再设置成为MIN(4*T1,T2),每次都是倍增,知道T2。这个过程会一直继续,直到重发的间距是T2为止。缺省的T2是4秒,并且它大概是一个在没有立刻响应的情况下,非INVITE服务端事务处理一个请求的时间。根据缺省的T1和T2,那么间隔就会是:500ms,1s,2s,4s,4s,4s以次类推。
如果定时器F触发了,并且客户端事务依旧是在”trying”状态,那么客户端事务应当通知TU这个超时,并且转入”terminate”状态。如果在”trying”状态的时候收到了一个临时应答,那么这个应答必须转给TU处理,并且客户端事务转到”proceeding”状态。如果在”trying”状态收到了一个终结应答(200-699的应答码),那么应答必须交给TU,并且客户端事务必须转到”Completed”状态。
如果定时器E在”Proceeding”状态触发了,那么请求必须交给通讯层进行传输,并且定时器E必须重新设置成为T2秒。如果定时器F在”Proceeding”状态触发了,那么必须通知TU超时了,并且客户端事务必须转到终结状态。如果在”Proceeding”状态的时候收到了一个终结应答(状态码200-699),这个应答必须发送给TU,并且客户端事务必须转到”Completed”状态。
一旦客户端事务进入”Completed”状态,对于非可靠传输的情况,客户端事务必须设置一个定时器K=T4秒,对于可靠传输的情况,设置定时器K=0秒。这个”Completed”状态维持的目的是为了缓冲可能会收到的其他重发的应答(这是为什么客户端事务在这里为非可靠传输维持一段时间的原因)。T4代表了网络在客户端和服务端事务中传输信息可能的时间。缺省的值T4=5秒。当应答具有相同的事务匹配的时候,根据17.1.3的判定,这个应答就是重发的应答。如果定时器K在这个状态被触发,客户端事务必须转到”Terminate”状态。
当事务进入终结状态,就必须立刻终止了。
17.1.3 客户端事务匹配应答
当客户端事务的通讯层收到一个应答,他必须决定是否由客户端事务来处理这个应答,这样17.1.1和17.1.2才能够正确执行。在Via头域的最上边的branch参数就是用来做这个的。一个应答和一个客户端事务匹配的话,就有两个条件:
1、 如果应答Via最上边的branch参数和创建这个客户端事务的请求的Via最上边的branch参数相同。
2、 如果Cseq头域的方法参数和创建事务的请求的方法相同。这是因为CANCEL方法的事务和源请求的事务不同,但是却有相同的branch参数所决定的。
如果一个请求是广播发送的,他可能从不同的服务器上得到不同的应答。这些应答的最上边的Via都有相同的branch参数,但是在To tag中是不同的。当收到了第一个应答,基于上边的规则,将会判定是这个客户端事务的应答,其他的应答将会视同为重发。这并不是错误的情况;多点传送SIP只是提供了一个根本的”寻找最接近的单点”服务的方法,这样就限定了只需要处理一个单个应答。详情参见18.1.1。
17.1.4 处理通讯错误。
图6:非INVITE客户端事务
当客户端事务发送一个请求到通讯层发送的时候,如果通讯层报告发送失败,那么需要执行下列步骤。
客户端事务应当通知TU这个通讯失败,并且客户端事务应当直接转到”Terminate”状态。TU处理通讯失败的机制在附件[4]中描述。
17.2 服务端事务
服务端事务是用来传输请求到TU并且可靠的传输应答的。它是通过状态机来实现的。服务端事务是当请求到达的时候由核心创建的,事务的处理也是主要围绕着对应请求的(也就是说并非全部都是和对应请求相关)。
和客户端事务对应的,状态机依赖于是否接收的请求是INVITE请求。
17.2.1 INVITE服务端事务
INVITE服务端事务的状态图在图7表达。
当为一个请求创建了服务端事务的时候,服务端事务进入”proceeding”状态。除非服务端事务知道TU在200ms之内会生成临时或者终结应答(在这种情况下,TU可能会产生100Trying应答),他必须生成100(Trying)应答。这个临时应答是用来停止客户端重发请求的,这个可以避免网络风暴。这个100(Trying)应答是根据8.2.6节描述的步骤构造的,除此之外: 如果接收的请求头中的To头域没有tag标志,那么原来描述的可以增加tag标记,更改成为不应该增加tag标志。这个请求必须交给TU处理。
TU可以给服务端事务任意数量个临时应答。只要服务端事务在”proceeding”状态,每个临时应答都应当交给通讯层发送。这些临时应答并非被通讯层可靠的发送(他们并不重新发送临时应答)并且临时应答并不改变服务端事务的状态。如果在”proceeding”状态,收到一个请求的重发请求,那么就需要把从TU最近收到的那个临时应答重新交给通讯层发送一次。请求是否是重发的请求,是基于17.2.3来判定的匹配相同服务端事务的请求。
如果,在”proceeding”状态,TU发送了一个2xx应答给服务端事务,服务端事务必须把这个应答交给通讯层进行发送。这个并非由服务端事务进行重发;对于2xx应答的重发是由TU处理的。服务端事务必须转到”Terminated”状态。
当在”Proceeding”状态的时候,如果TU交给服务端事务一个300到699的应答,那么应答必须交给通讯层进行发送,并且状态机必须进入”Completed”状态。对于非可靠传输的情况,必须设置定时器G=T1秒,对于可靠传输的情况,不设置定时器G(=0的情况就是不设置)
这个是和RFC2543所不同的,2543要求应答都要重发,甚至在可靠传输的情况下。
当进入了”Completed”状态,必须为所有的传输,设置一个定时器H=64×T1秒。定时器H决定何时服务端事务取消重发应答。这个值和定时器B的取值一样,是等同于客户端事务会重试发送请求的时间。如果定时器G触发了,那么应答会交给通讯层再次发送,并且定时器设置成为MIN(2*T1,T2)秒。依此类推,当定时器G再次触发,那么定时器G的值会翻倍,直到T2。这个和非INVITE客户端事务的”trying”请求的重发机制是一样的。进一步说,当在”Completed”状态的时候,如果接收到重发的请求,服务端事务应当把应答交给通讯层再次发送。
当服务端事务在”Completed”状态的时候,如果收到了一个ACK请求,服务端事务必须转到”Confirmed”状态。因为定时器G会在这个状态被忽略,所有的应答重发都会被终止。
如果在”completed”状态的时候,定时器H触发了,就意味着没有收到ACK请求。在这个情况下,服务端事务必须转到”Terminated”状态,并且必须通知TU事务失败。
图7:INVITE服务端事务
设定”Confirmed”状态的目的是为了处理任何附加的ACK消息,这是由重发的终结应答所触发的。当进入这个状态,如果是在不可靠传输协议,那么就要设定一个定时器I=T4秒,如果是可靠传输协议,那么就设定I=0。当定时器I触发了,服务端事务必须转到”Terminated”状态。
当服务端事务状态处于”Terminated”状态,这个事务必须立刻销毁。和客户端事务一样,这是为了保证给INVITE的2xx应答的可靠性。
17.2.2 非INVITE服务端事务
对非INVITE服务端事务的状态机是在图8中表示。
小虎 2006-05-25 00:09
当收到一个不是INVITE或者ACK的请求的时候,状态机会初始化成为”trying”状态。并且这个请求会交给TU处理。当在”trying”状态,任何重发的请求会被忽略。一个请求在通过17.2.3节的步骤,匹配现有的服务端事务,将被认为是重发的请求。
当处于”trying”状态,如果TU交给服务端事务一个临时应答,服务端事务应当进入”Proceeding”状态。这个应答必须交给通讯层进行发送。在”Proceeding”状态下从TU收到的任何应答都必须交给通讯层进行发送。如果一个重发的请求在”proceeding”状态下收到了,那么最近发出的一个临时应答应当再次交给通讯层进行重发。如果在”Proceeding”状态下,TU交给服务端事务一个终结应答(应答码是200-699),那么服务端事务必须进入”Completed”状态,并且应答必须交给通讯层进行发送。
当服务端事务进入了”Completed”状态,对于不可靠传输协议来说,必须设定一个定时器J=64×T1秒,对于可靠传输来说,设定为0秒(就是不设定定时器)。当在”Completed”状态下,当服务端事务收到了一个重发的请求的时候,服务端事务必须交给通讯层终结应答来重新发送。在”Completed”状态下,任何其他TU传递下来给服务端事务的终结应答都必须被抛弃。服务端事务保持这个状态直到定时器J触发,当定时器J触发了以后,服务端事务必须进入”Terminated”状态。
17.2.3 为服务端事务匹配请求。
当服务端从网络上收到一个请求以后,他必须和现有的事务进行判定。这个是根据下边的规则来判定的。
首先要检查请求中的Via头域的最上一个branch参数。如果他以”z9hG4bk”开头,那么这个请求一定是由客户端事务根据本规范产生的。因此,branch参数在该客户端发出的所有的事务中都是唯一的。根据下列规则我们可以判定请求是否和事务匹配:
1、 请求中的最上的Via头域的branch参数和创建本事务的请求的最上的Via头域的branch参数一样,并且:
2、 请求的最上的Via头域的sent-by参数和创建本事务的请求的最上的Via头域的send-by参数一样,并且:
3、 请求的方法和创建本事务的方法一样。这有一个例外,就是ACK,ACK对应的创建本事务的请求方法是INVITE。
这个匹配规则用于INVITE和非INVITE事务。
send-by参数被用于匹配过程,这是因为有可能存在无意/恶意的相同的不同客户端传来的branch参数。
如果最上的Via头域的branch参数不存在,或者没有包含那个”z9hG4bk”,那么就用下列步骤进行判定。这是为了和RFC2543进行兼容的。
如果是INVITE请求,并且这个INVITE请求的Request-URI,To tag,From tag,Call-ID,Cseq,和最上的Via头域都和创建事务的INVITE请求的这些字段匹配,那么这个INVITE请求就是匹配这个事务的INVITE请求。在这个情况下,INVITE就是创建这个事务的INVITE请求的一个重发。ACK请求在匹配创建事务的INVITE请求的Request-URI, From tag, Call-ID ,Cseq序列号(非方法字段), 最上的Via头域,并且To tag和服务端事务发出的应答的To tag相同,这个ACK就是这个事务的ACK。当这些头域比较完成,那么这个匹配也就完成了。在ACK比较中包含To tag的比较是为了在proxy上能够区别给2xx的ACK和给其他应答的ACK,这个proxy可能会转发全部的应答(这个会在某种罕见的情况下发生。特别是,当一个proxy分支一个请求,接着宕机了,应答会转发到别的proxy,这个proxy可能会终止转发多重应答到上行队列)。一个匹配INVITE请求事务的ACK请求,如果这个INVITE请求已经被前一个ACK请求所匹配,那么这个ACK请求就是上一个ACK请求的重发。
图8: 非INVITE 服务端事务
对于所有的其他请求方法,如果请求的Request-URI,To tag,From tag,Call-ID, Cseq(包括Cseq中的方法字段),以及Via头域的最上值,都和创建服务端事务的请求想匹配,那么这个请求就是这个事务的匹配请求。匹配是基于针对每一个头域值的判定进行的。当非INVITE请求和现有事务匹配了,那么它就是创建这个事务的请求的一个重发。
由于匹配规则中包含了Request-URI,服务器不能匹配应答对应到事务。所以当TU传送了一个应答到服务端事务,它必须为这个应答指定传送到那个服务端事务。
17.2.4 处理通讯错误
当服务端事务发送一个应答到通讯层要发送的时候,如果通讯层报告发送失败,那么就需要执行下列的步骤:
首先,附件[4]的步骤需要执行,这就是说需要把应答发送一个备份的地点。如果这个也失败了,基于[4]中对失败的定义,服务端事务应当通知TU发送失败,并且把状态切换到终止状态。
18 通讯(transport)
通讯层负责请求和应答在网络上的实际传输。这包括了在面向连接的通讯方式下的请求和应答所使用的连接管理。
通讯层负责管理像TCP/SCTP之类通讯协议的长连接,或者在这些协议上的TLS连接,并且包括管理打开这些连接的使用者的管理。这包括了客户端或者服务端通讯层打开的连接,这样在客户端服务端通讯函数可以共享这些连接。这些连接采用一组用远端的地址,端口,通讯协议标志的索引来进行管理。当通讯层打开了一个连接,这个连接的索引就设置成为远端的IP,端口,还有打开这个连接的通讯层的实例 。当通讯层接收了一个连接,那么这个连接的索引就被设置成为连接方的源IP地址,port,还有通讯层的实例transport。注意,由于源端口port通常是临时创建的,但是由于通过附件[4]的步骤不能知道它是临时创建的还是配置的,所以通讯层被动接收的连接通常是不被重复使用的。这就是说,如果两个proxy再一个”peering”(点对点)的关系中,使用一个面向连接的通讯协议通常有两个连接要使用,每个都是自己作为主动方连接的。
我们建议在实现中,当发送(或者接收)完成最后一个消息之后,依旧维持这个连接一段时间(这段时间可以是实现自己定义的时间)。这段时间应当是至少等于本节点的事务从创建到结束的最长时间。这是为了让事务能够在他们所创建的同一个连接上完成(比如,在这个连接上完成请求,应答的处理,在INVITE的情况下的给非2xx的ACK应答等等)。这通常意味着至少64×T1秒(参见17.1.1.1中关于T1的定义)。不过,如果当本程序的TU使用的是一个比较大的定时器C(参见16.6节11步)的时候,也可以选取一个比较大的值。
所有的SIP元素都必须实现基于UDP和TCP的通讯。SIP元素还可以实现其他的协议。
要求UA支持TCP是对RFC2543的一个重要改进。这是由于需要处理更大的消息,就像接下来讲到的那样,必须使用到TCP协议。因此,即使是SIP元素不要发送大的消息,但是由于它可能收到大消息并且处理这些消息,所以,要求支持TCP。
18.1 客户Clients
18.1.1 发送请求
通讯层的客户端负责发送请求和接收应答。通讯层的用户把请求交给通讯层的实例进行处理,包括IP地址端口,通讯层实例,还有可能有多点广播的TTL。
如果请求的大小和MTU差是在200个字节以内的,或者它是大于1300字节的,并且路径MTU的大小是未知的,那么请求必须遵循RFC2914[43]控制阻塞的传输协议,比如使用TCP。如果这导致了Via最上边指定的通讯协议的改变,那么Via最上边的值就必须也随之改变。这使得在UDP传输上的消息的分割,并且也提供了大消息的传输阻塞控制。不过,在实现上,必须能够支持达到最大包大小的消息的处理。对于UDP来说,包含了IP和UDP头的大小是65535个字节。
在消息的大小和MTU之间的200个字节的”buffer”,提供了一个机制使得在SIP的应答中,可以超过请求的大小。比如在INVITE请求的应答中,增加了Record-Route头域值。有了这个额外的buffer,应答可以大概比请求大170个字节,而且在Ipv4上不用进行分块传输(假设没有IPSec,大概IP/UDP会使用30个字节)。当MTU是未知的时候,选取1300是基于假设Ethernet的MTU是1500字节的基础上。
如果SIP元素是因为消息大小的限制,所以基于TCP发送一个请求,并且消息如果不是因为大小的限制,会使用UDP来发送,并且如果建立连接产生一个ICMP 协议不支持的错误,或者导致TCP reset,那么这个元素就应当用UDP重试这个请求。这只是为了向后兼容RFC 2543针对不支持TCP的实现。在本规范以后的改动中,这部分内容会有修订。
如果客户端向多个地址发送请求,那么必须增加”maddr”参数到Via头域值上,并且这个参数值指定多个目的地址,对于Ipv4来说,应当增加”ttl”参数=1,IPV6的多点传送在本规范中没有定义,会在后续的标准中描述。
这些规则定义了SIP的多点传送。首要的目的是为了提供”寻找最接近的单点”服务(”single-hop-discovery-like”),这个服务将请求转发到一组类似的服务器,并且只需要处理其中任意一个服务器的应答。这个功能主要用于注册服务。实际上,基于17.1.3的事务处理规则,客户端事务会接收第一个应答,并且因为其他应答包含同样的Via的branch参数,而视这些应答为重发应答。
在请求发送嵌,客户端通讯层必须在Via头域中增加一个”sent-by”栏。这个字段包含了一个IP地址或者主机名,端口。我们推荐使用FQDN方法描述这个主机名。这个字段在某些特定情况下,用于发送应答。如果端口不存在,缺省的值依赖于通讯协议。对于UDP,TCP和SCTP来说是5060,TLS是5061。
对于可靠传输协议,应答通常简单的通过连接发送,并且这个连接是收到对应请求的连接。因此,客户端传输层必须准备在发出请求的同一个连接上接收应答。在出现错误的情况下,服务端可能会尝试新建立一个连接来发送应答。为了能够处理这种情况,通讯层必须准备接收一个从源IP建立的新连接,这个连接的IP是请求发起的源IP,port是在”sent-by”字段中指定的port。这也同样要求准备接收从任意地址和端口来得新连接上接收应答,这个端口是由服务器根据附件[4]的5节所讲述的步骤来选取的。
对于非可靠的传输协议,客户端通讯层必须准备从发送请求的那个原始IP地址上接收应答。(因为应答会送到原始地址去),并且端口号是在”sent-by”字段的端口好。进一步说,和可靠传输一样,早某些情况下,应答会发往不同的地方。客户端必须能够准备从其他地址和端口上接收应答,这个端口是由服务器根据附件[4]的5节所讲述的步骤来选取的。
对于多点传送的情况来说,客户端通讯层必须准备从相同的多点传输组上接收应答,这个组的地址和端口和发出请求的组相同(就是说,它必须是发送请求的那个多点传输组的一个成员)。
如果请求发送的目的IP地址,端口和transport都和现有的一个连接相同,那么建议使用这个连接来发送请求,同时也允许新建立一个连接来发送。
如果请求通过多点发送,那么它发送的一组地址,端口和TTL都是由通讯层的用户提供。如果请求是通过不可靠通讯协议发送,那么发送的IP地址和端口也是由通讯层的用户提供。
18.1.2 接收应答
当应答接收到的时候,客户端通讯层检查最上的Via头域值。如果”sent-by”参数不符合客户端通讯层在请求中插入的值,那么这个应答必须悄悄丢弃。
如果由任何客户端事务存在,客户端通讯层使用17.1.3的步骤来匹配现存的事务和这个接收到的应答。如果匹配到了,应答必须交给事务层进行处理。否则,应答必须交给核心去处理(无论是有状态的proxy,还是无状态的proxy,还是UA的核心)。处理这些”stray”(迷路)的应答是基于核心的策略的(如果是proxy就会转发,如果是UA就会忽略,等等)。
18.2 服务端
18.2.1 接收请求
一个服务器应当能够接收从任何IP地址、端口和协议上过来的请求。他们是通过对这个服务器的SIP或者SIPS URI(附件[4])的DNS查找,得到这个服务器的地址然后连接和发送的请求的。在这里,”handing out”(发布)包含了在REGISTER请求或者转发应答的Contact头域中放一个URI,或者在请求或者应答的”Record-Route”头域中放一个URI。这个URI可以通过放在网页或者名片上被”handing out”(发布)。同样的我们也建议服务器在公网上监听缺省的SIP端口(TCP/UDP是5060,5061是在TCP上的TLS)。如果是在局域网上,或者私有网上,或者一个物理服务器上运行好几个服务实例,那就很自然的可以设置成不同的。对于服务器监听UDP的任何端口和界面,都必须在TCP上也进行同样的监听。这是因为可能消息还需要通过TCP进行传输,比如消息过大的情况。所以,在相反的情况下就不需要了。如果一个服务器在TCP监听了,那么它不一定需要在UDP上也进行相应的监听。当然服务器也可以因为某些原因在特定地址和端口上监听UDP。当服务端事务从任意一个通讯层上接收到一个请求的时候,它必须检查最上的Via头域的”sent-by”参数。如果”sent-by”参数的主机部分包含了一个主机名,或者它包含的IP地址和包的源地址不同,服务器必须增加一个”received”参数到这个Via头域值中。这个参数必须包含收到的包的原地址。由于服务端必须把应答发送给收到请求的那个源IP地址,所以这个可以用来帮助服务端通讯层发送应答。
一个服务端通讯层收到的请求可能是这样的(部分):
INVITE sip:[email protected] SIP/2.0
Via: SIP/2.0/UDP bobspc.biloxi.com:5060
请求是从源IP:192.0.2.4收到的。在请求转交到上层之前,通讯层增加了一个”received”参数,这样请求的部分就是:
INVITE sip:[email protected] SIP/2.0
Via: SIP/2.0/UDP bobspc.biloxi.com:5060;received=192.0.2.4
接着,服务端通讯层尝试和服务端事务做匹配。这个使用的是17.2.3节定义的规则。如果匹配上一个服务端事务,那么请求就交给那个事务去处理。如果没有匹配到事务,请求就交给核心去处理,可能会创建一个新的服务端事务来处理。注意当UAS核心给INVITE请求发送一个2xx应答的时候,服务端事务已经销毁了。这就是说,当ACK收到的时候,不会有匹配的服务端事务,并且基于这个规则,ACK回交给UAS核心来处理。
18.2.2 发送应答
服务端事务使用最上边的Via头域值来决定把应答发送到哪里。它必须遵从如下步骤来发送:
o 如果”sent-protocol”是一个可靠的传输协议比如TCP或者SCTP,或者在其上的TLS,应答必须用现存的到原始请求(创建这个事务的请求)的连接进行发送(如果连接还存在的情况下)。这个要求服务端通讯层保留服务端事务和通讯层连接的相关性。如果连接不存在了,服务端应当创建一个新的连接,如果存在”received”参数,就用对应的在”received”参数中指定的IP地址。如果存
小虎 2006-05-25 00:10
在”sent-by”参数,那么就用”sent-by”指定的port,如果不存在,那么就用缺省的port。如果对应的连接已经失效,那么服务器应当采用附件[4]的步骤来决定使用那个IP地址和端口来建立连接并且发送应答。
o 否则,如果Via头域包含一个”maddr”参数,就必须把应答转发到maddr所指明的地址,并且使用”sent-by”所指定的端口,如果没有sent-by参数,那么就使用5060缺省参数。如果地址是一个多点地址,应答应当使用”ttl”参数所指定的TTL,或者如果没有指定”ttl”参数,则使用TTL=1的参数。
o 否则(对于非可靠传输),如果Via的最上头域包含一个”received”参数,那么应答必须发送到”received”参数所指定的地址,并且使用”sent-by”所指定的端口,如果没有sent-by参数,那么就使用5060缺省参数。如果这步失败了,比如,如果得到一个ICMP”端口不能到达”的错误,那么就应当根据附件[4]的第5节的步骤来决定应当把应答发送到哪里。
o 否则,如果没有receiver-标记,那么应答应当使用附件[4]的第5节指定的步骤,送到”sent-by”参数指定的地址。
18.3 分块
在面向消息的通讯协议中(比如UDP),如果消息有一个Content-Length头域,那么消息体就有可能包含很多字节。并且收到的包中除了这个消息体的Content-Length字节意外,还有通讯层附加的通讯包字节,那么这部分额外的字节应当被丢弃。如果通讯包在没有收到完整的Content-Length字节的消息体就终止了,这就意味着出错了。如果这个消息是一个应答,那么这个消息必须被丢弃。如果消息是一个请求,那么本程序应当给出一个400(Bad Request)应答。如果消息没有包含一个Content-Length头域,消息体的结束点就是消息体的结束点。
在面向流的通讯协议中(比如TCP),Content-Length头域标志这包体的大小。在面向流的通讯协议中,必须使用Content-Length字段。
18.4 错误处理
错误的处理取决于出现错误的消息是请求还是应答。
如果通讯层的用户要求在一个非可靠传输协议上发送一个消息,并且结果是一个ICMP错误,那么错误处理的方法依赖于ICMP错误类型。当通讯层遇到主机、网络、端口或者协议无法到达的错误,或者参数错误的时候,应当通知通讯层的用户发送失败。Source quench和TTL exceeded ICMP错误应当被忽略。
如果通讯层用户要求在一个可靠传输协议上发送一个请求,并且结果是一个连接错误,通讯层应当通知通讯层用户这个发送错误
19 常见消息部件(Common Message Components)
在SIP消息中,有一些很长用的部件。(甚至在SIP消息外这些部件也存在)。这些部件值得我们单独讨论一下。
19.1 SIP和SIPS统一资源标记
SIP或者SIPS 的URI用来标记一个通讯用的资源。就像其他所有的URI一样,SIP和SIPS URI可以放在网页上,email消息里,或者打印出来的名片上等等。在这些URI里边包含了足够的信息来发起和维持到这个资源的一个通讯会话。
一个通讯资源的例子包含下列内容:
o 一个在线服务的用户
o 一个多线电话
o 消息系统中的邮箱
o 网关服务的PSTN电话号码
o 一个组织中的一个部门(比如”销售”,或者”helpdesk”)
SIPS URI定义了对资源的访问是安全的。这就意味着,特别是,在UAC和这个资源的主机之间的通讯是基于TLS的。从资源的主机到用户之间的通讯是加密安全的,这个安全机制是依赖于主机的实现的。任何用SIP URI描述的资源,只要想通过加密的形式进行通讯,都可以通过简单改变一下资源描述府就可以”升级”成为一个SIPS URI。
19.1.1 SIP和SIPS部件
“sip:”和”sips:”描述符是遵循RFC2396[5]的规范定义的。他们使用类似mailto URL的格式定义,允许有SIP请求头域字段和SIP消息体的规范。这使得在网页上或者email中,可以用URI来初始化一个会话,这个会话有特定的主题,媒体类别,紧急类型。这个SIP或者SIPS URI的格式规范在25节定义。一个SIP URI的通常格式是这样的:
sip: user:password@host:port;uri-parameters?headers
这个和SIPS URI的格式是相同的,只是SIPS用”sips”来代替sip。这些符号,和符号的扩展,具有下列意义:
user: 这是在主机的特定资源地址。”主机”(host)在这里通常指的是一个域名。URI中的”userinfo”包含了这个用户域,口令域,并且包含其后的一个@。URI的用户信息部分是可选的,或者说是可以没有的;当目的主机没有用户的概念或者主机本身就是资源的目标,那么这个URI的用户信息部分就是可以没有的。如果在SIP或者SIPS URI中有@,那么用户部分必须不能为空的。如果主机部分可以处理电话号码地址,比如说是一个internet电话网关,那么根据RFC2806[9]定义的电话号码域应当出现在用户信息部分。在19.1.2节有关于在SIP或者SIPS URI中的电话号码描述域的额外说明
password:password字段是和用户相关的。SIP或者SIPS URI语法允许增加password这个字段,这种用法我们是不推荐的,因为把身份认证信息放在明码表示的地方(比如URI)会带来很大的安全风险。比如,通讯层在这个字段用了一个PIN码,那么就会暴露这个PIN码而带来安全隐患。
注意密码字段只是一个用户信息扩展的一部分。实现上并没有标记一个特别的密码部分,可以简单的把”user:password”当作一个简单的用户串来对待。
host:主机提供了SIP资源。host部分包含了一个完整的主机名字或者IPV4/IPV6的地址。我们强烈建议如果可能,就使用完整格式的主机名字。
port: 端口号是请求将被送出的端口。
URI 参数:请求将使用这个URI来构造。
URI参数在hostport部件之后增加,用分号分开。
URI参数有如下格式:
参数名’=’参数值
虽然在同一个URI中允许有任意多个URI的参数,但是同一个参数名只能出现1次。
这个扩展机制包括了transport,maddr,ttl,user,method和lr参数
transport参数决定在[4]中定义的发送SIP消息的通讯机制。SIP可以使用任何网络通讯协议。参数名字是为UDP(RFC 768[14]),TCP(RFC 761 [15])和SCTP(RFC2960[16])定义的。对于一个SIPS URI,transport参数必须指向一个可靠的通讯协议。
maddr参数指明了联系这个用户的服务器的地址,它会覆盖在host域中的地址。当给定了一个maddr参数,URI中的port和transport部件将会在maddr中指出。[4]描述了正确的transport,maddr,hostport规范,用于获得发送请求到目的地所需要的目的地址,端口,通讯协议。
maddr字段用作简单的去掉源路由的方法来使用的。它允许一个URI指定一个必须经过的proxy来到达目的地。我们强烈建议不要把maddr参数用于这个目的(我们反对把maddr用于这个机制)。在实现上应当使用本文中描述的Route机制,如果有需要,则建立一个pre-existing(预先设置的)路由集合(参见8.1.1.1)。它提供了一个完整的URI来描述需要经过的节点。
ttl参数决定了UDP多点报文的生存周期,并且只能用于maddr是一个多点地址并且通讯协议是UDP的情况。例如,为了指定一个到[email protected]的呼叫,使用多点广播到239.255.255.1,并且ttl=15,那么应该使用下边的一个URI:
sip:[email protected];maddr=239.255.255.1;ttl=15
有效的电话描述(telephone-subscriber)的字串一个集合是有效的用户字串的子集。我们用用户URI参数来区别电话号码和用户名(长得像电话号码的用户名)。如果用户串使用了电话号码描述的字串,用户参数值”phone”应当增加。即使没有这个参数,如果本地用户名的命名限制机制允许的情况下,SIP和SIPS URI的接受方也可以把这个@以前的部分解释为电话号码。
从URI中构建SIP请求所需要的method域,可以由method参数指定。
如果指定了lr参数,就标志着这个资源的拥有者是根据本规范来实现的路由机制。这个参数回用于proxy放在Record-Route头域的URI中,也可以出现在pre-existing(预先设置)的路由集合中。
这个参数是用来和RFC2543定义中的严格路由机制向后兼容所使用的,并且rfc2543bis 改变为bis-05。如果一个元素准备发送一个基于没有包含这个参数的URI请求,那么我们可以假定这个请求的接受方是根据严格路由的规范实现的,并且会重新规格化这个消息来保护在Request-URI中的内容。
由于URI参数机制是可以扩展的,SIP元素应当悄悄跳过那些不认识的uri参数。
Headers:头域是从给定URI创造的请求的头域部分。
在SIP请求中的头域可以在URI中用”?”来给出。头域名(hname)和头域值(hvalue)都是用&符号间隔的头域名=头域值的格式。特定的头域名”body”的头域值就是SIP请求的消息体。
表1总结了在URI的不同情况下SIP和SIPS URI的部件用法。扩展的列描述了在SIP消息歪的URI,例如在网页上或者名片上的情况。项目中的’m’是强制必须的意思,’o’是可选的,’-‘是不允许的。处理URI的元素应当忽略掉URI中出现的任何不允许的部件。在表格中的第二列是如果该元素不存在的时候的缺省值。’--'表示本元素不是可选的,或者没有缺省值的意思。
在Contact头域中的URI在头域出现的不同地方有着不同的约束。一个是在消息建立和维持一个对话的时候(INVITE请求以及它对应的200(OK)应答),一个是在注册和转发消息的时候(REGISTER,以及对应的200(ok)应答,以及给任何方法的3xx系列的应答)
19.1.2 Character Escaping Requirements(字符转码要求)
default Req-URI To From reg./redir.Contact dialogContactR-R/Route external
user - - o o o o o o
password - - o o o o o o
host - - m m m m m m
port (1) o - - o o o
user-param ip o o o o o o
method INVITE - - - - - o
maddr-param - - o - - o o o
ttl-param 1 o - - o - o
transp.-param (2) o - - o o o
lr-param - - o - - - o o
other-param - - o o o o o o
headers - - - - - o - o
(1):缺省的通讯端口是依赖于通讯协议的。对于使用UDP,TCP,SCTP的sip来说,是5060,对于使用基于TCP的TLS来说,是5061。
(2)缺省的通讯协议是和sip/sips相关的,对于sip来说,是UDP,对于sips来说,是TCP。
表1:对于SIP头域值,Request-URI及其引用的使用和缺省值。
基于RFC2396[5]的要求和指引,当需要把字符串封装到SIP URI的时候,使用””%” HEX HEX”机制来进行转码。根据RFC2396[5]:
任何指定URI部件保留的字符集都是由这个部件定义的。通常,如果URI的语义由于组成字符被它的US-ACII编码[5]的escape码替代而改变的时候,这个字符就是保留字符。除了USASCII字符(RFC2396[5])之外,比如空格和控制字符,以及URI所使用的分隔符,必须进行转码。URI必须不能包含任何未经转码的空白和控制字符。
对于每一个部件来说,由BNF扩展的合法字符集合规定了那些字符是可以不经转码的。其他字符都必须经过转码。
比如,”@”不是user部件的字符集中的字符,所以,userj@sOn,必须把@符号进行编码,成为”j%40sOn”
在25节中的hname和hvalue的符号展示了在URI的保留字符中,在头域名和头域值中所有需要被转码的字符集合。
user部件的电话描述(telephone-subscriber)部分由特别的转码考虑。在RFC2806[9]关于电话描述部分中未被保留的字符集,由很多字符组成,他们在SIP URI中的不同语法部分的时候,都需要做转码。在电话描述中出现的任何字符,只要不在BNF针对user部分的扩展规则中出现的,都需要做转码。
注意在SIP或者SIPS URI中,host部分不允许做字符的转码(%不在它的扩展部分中)。在以后的Inernationalized Domain Names完成以后,这个限制可能就会改了。当前实现中不允许把在host部分收到的转码字符进行转码处理。因为这个转码处理和IDN要求的处理不一样。
19.1.3 SIP和SIPS URI例子
sip:alice:[email protected];transport=tcp
sip:[email protected]?subject=project%20x&priority=urgent
sip:+1-212-555-1212:[email protected];user=phone
sips:[email protected]
sip:atlanta.com;method=REGISTER?to=alice%40atlanta.com
sip:alice;[email protected]
最后一个URI例子有一个user域”alice;day=Tuesday”。上边定义的转码规则中允许”;”在这个字段中不进行转码。在本协议的设计概念中,这个字段是不透明的。这个字段的值只对负责这个资源的SIP元素有用。
19.1.4 URI比较
在本规范中,部分操作需要比较两个SIP或者SIPS URI是否相等。比如,在这个规范中,注册服务器需要比较在REGISTER 请求中绑定的Contact URI(参见10.3)。SIP和SIPS URI根据如下步骤进行比较:
o SIP和SIPS URI永远不等。
o SIP/SIPS URI的userinfo是大小写敏感的。这包括了含有password或者按照电话描述格式的userinfo的比较。对于URI的其他部分的比较,除了有特别指出之外,都是大小写不敏感的。
o 参数的顺序和头域的顺序对于比较SIP/SIPS URI不起作用。
o 在保留字符集之外的字符(参见RFC2396[5]),等同于他们的””%” HEX HEX”格式。
o IP地址就算是等同于通过DNS查找到的主机名对应的IP地址,IP地址也不能和主机名等同。
o 两个URI如果相同,那么user,password,host,port部分必须相同。
有user部分的URI和没有user部分的URI是不相等的。有password部分的URI和没有password部分的URI也是不同的。
一个不带可选部件的URI和带了这些部件但是值是缺省值的URI是不等的。例如,如果一个URI省略了port部件,并不等于一个定义了5060port部件的URI。同样的规则适用域transport-参数,ttl-参数,user-参数,method部件等等。
定义sip:user@host,和定义sip:user@host:5060(根据RFC2543的变体)不相等。当从URI中取得地址的时候,相同的URI可以取得相同的地址。sip:user@host:5060始终可以得到端口5060。URI: sip:user@host根据[4]所定义的DNS SRV机制,可能可以得出其他的端口来。
o URI uri参数部件按照如下规则进行比较
- 任何在两个URI中出现的uri参数都必须一样
- user,ttl,或者方法uri参数如果只在一方出现,即使和缺省值相等,也判定为两个URI不相等。
- 包含maddr参数的URI和没有包含maddr参数的不相等。
- 其他uri参数,如果在一方出现,则在比较的时候忽略。
o URI头部件的比较是不能忽略的。任何在header部分出现的域都必须在双方URI中进行匹配和比较。比较规则参见20节。
下列URI是相等的:
sip:%[email protected];transport=TCP
sip:[email protected];Transport=tcp
sip:[email protected];newparam=5
sip:[email protected];security=on
sip:biloxi.com;transport=tcp;method=REGISTER?to=sip:bob%40biloxi.com
sip:biloxi.com;method=REGISTER;transport=tcp?to=sip:bob%40biloxi.com
sip:[email protected]?subject=project%20x&priority=urgent
sip:[email protected]?priority=urgent&subject=project%20x
下列URI是不相等的:
SIP:[email protected];Transport=udp (用户名不同)
sip:[email protected];Transport=UDP
sip:[email protected] (端口不同)
sip:[email protected]:5060
sip:[email protected] (通讯协议不同)
sip:[email protected];transport=udp
sip:[email protected] (通讯协议和端口不同)
sip:[email protected]:6000;transport=tcp
sip:[email protected] (header部件不同)
sip:[email protected]?Subject=next%20meeting
sip:[email protected] (就算是phone21.boxesbybob.com
sip:[email protected] 解析到192.0.2.4也不能算相等的。)
注意相等性是不能传递的。
比如 sip:[email protected] 和sip:[email protected];security=on相等
sip:[email protected] 和 sip:[email protected];security=off相等
但是:
sip:[email protected];security=on和sip:[email protected];security=off不等。
19.1.5 从URI中产生请求
对于实现而言,需要能够直接从一个URI来构造请求。URI可以是从名片,网页,或者甚至从某些协议内部得到(比如登记的联系信息等等)。
协议的实现必须包括构造请求的Request-URI中的transport,maddr,ttl,或者user参数。如果URI包含了method参数,那么它的值必须和构造的请求的方法一样。并且method参数不能放在Request-URI中。不认识的URI参数必须放在消息的Request-URI中。
实现中应当把URI中出现的header或者包体部分包含入消息本身,并且当作是请求自己的组成部分。
在实现中,不应当保留那些明显危险的头域字段:From,Call-ID,Cseq,Via和Record-Route。
并且实现中,也不应当保留任何请求的Route头域值,这样可以避免无知的客户端进行恶意攻击。
实现中也不应当保留那些可能会导致错误登记地址或者误导能力的头域字段,这些包括:Accept,Accept-Encoding,Accept-Language,Allow,Contact(在对话中使用),Organization,Supported,和User-Agent。
实现上应当检查每一个请求中所描述的头域的正确性,包括:Content-Disposition, Content-Encoding,Content-Language, Content-Length, Content-Type, Date, Mime-Version, Timestamp。
如果从给定URI构造的请求不是一个合法的SIP请求,那么这个URI就是非法的URI。实现上禁止处理和传送非法的SIP请求。它应当尝试追查为何会有一个非法的URI。
很多情况都可以得到一个非法的请求。这包括但是不限于,头域的语法错误,非法的URI参数合并,或者错误的消息体描述等等。
发送从URI构造的请求可能会导致实现上的能力不够。比如:URI可能指定了尚未实现的通讯协议或者通讯扩展。那个这个具体的实现上来说,应当拒绝发送这些请求,而不是修改这个请求来适应具体实现的处理能力。对于具体实现来说,它不能发送包含它自己不能理解的扩展部分的请求。
比如,从一个包含了未知的或者摆明了不支持的Request头域参数或者method参数的URI中,构造的请求就是不能发送的。
19.1.6 关联SIP URI和tel URL
如果tel URL(RFC 2806[9])转换成为一个SIP或者SIPS URI,那么tel URL的整个电话描述(telephone-subscriber),机器参数,都需要放在SIP或者SIPS URI的userinfo部分。
因此:tel:+358-555-1234567;postd=pp22 会变成:
sip:+358-555-1234567;[email protected];user=phone
或者
sips:+358-555-1234567;[email protected];user=phone
而不是
sip:[email protected];postd=pp22;user=phone
或者
sips:[email protected];postd=pp22;user=phone
通常来说,相等的”tel”URL转换成为SIP或者SIPS URI以后,不一定能得到相同的SIP或者SIPS URI。因为SIP和SIPS URI的userinfo部分是根据大小写敏感的字串。由大小写不敏感的tel URL以及重新排序的tel URL参数并不改变tel URL的相等性,但是在转换成为SIP或者SIPS URI之后,却影响了他们的相等性。
例如:
tel:+358-555-1234567;postd=pp22
tel:+358-555-1234567;POSTD=PP22
是等价的,但是
sip:+358-555-1234567;[email protected];user=phone
sip:+358-555-1234567;[email protected];user=phone
却是不等价的。
类似的:
tel:+358-555-1234567;postd=pp22;isub=1411
tel:+358-555-1234567;isub=1411;postd=pp22
是等价的,但是
sip:+358-555-1234567;postd=pp22;[email protected];user=phone
sip:+358-555-1234567;isub=1411;[email protected];user=phone
却不等价
为了避免这个问题,在构造放在SIP或者SIPS URI中的userinfo部分的电话描述域的时候,应当转换大小写不敏感的电话描述域为小写,并且除了isdn-subaddress和post-dial,把电话描述的参数按照参数名进行排序, 因为他们需要按顺序出现在参数的第一个。(在下边是除了未来扩展参数意外的全部tel URL大小写不敏感的部分)。
根据上边的描述,全部:
tel:+358-555-1234567;postd=pp22
tel:+358-555-1234567;POSTD=PP22
转换成为
sip:+358-555-1234567;[email protected];user=phone
并且全部:
tel:+358-555-1234567;tsp=a.b;phone-context=5
tel:+358-555-1234567;phone-context=5;tsp=a.b
转换成为:
sip:+358-555-1234567;phone-context=5;[email protected];user=phone
19.2 Option Tags
Option tags是一个唯一标志,用来指明SIP中的新options(扩展)的。这些tags在Require(20.32节),Proxy-Require(20.29节),Supported(20.37节)和Unsupported(20.40节)头域中使用。注意这些options是以option-tag=的形式作为这些头域的参数存在的(25节有关定义符号)。
Option tags是根据标准的RFC扩展定义的。这是和过去的试验有所不同,这是协会为了保证多个厂商之间能够持续互相协作(20.32节、20.37节的讨论)。option tags的IANA注册可以保证查找很容易。
19.3 Tags
“tag”参数用于SIP消息中的To和From头域。它作为一个通用的机制的一部分来唯一标志一个对话,这个机制用Call-ID和两个从对话参与者的tag来标志一个对话。当UA在对话外发出一个请求时,它只包含了From tag,提供了对话ID的”一半”。对话根据应答创建完成,这个应答在To头域中提供了对话ID的另一半。SIP请求的分支意味着一个单个请求可以创建多个对话。这个也解释了为何需要对话两方的标志;如果没有被叫方的标志,呼叫方不能分辩和消除由单个请求创建的多个对话。
当UA产生一个tag并且增加进一个请求或者应答的时候,它必须是一个全局唯一的,并且是密码随机数起码是32位的随机数。这个要求是为了让UA能够在同一个INVITE请求中,在给这个INVITE的应答中,在To头域产生一个不同的tag,和原始INVITE请求在From头域中产生的tag不同。这是因为UA可以邀请自己到一个会话,常见的是在PSTN网关的”hairpinning”(发夹)呼叫。类似的,对不同呼叫的两个INVITE也有不同的From tag,并且给这两个呼叫的两个应答也有不同的To tag。
在全局唯一要求之外,产生tag的算法是实现相关的。Tag对于容错系统比较有用,在容错系统下,当主服务器故障的时候,对话会在另外一个服务器上进行恢复。UAS可以产生一个tag,让备用服务器能够认识到这个请求是在故障服务器上的对话,并且能够决定是否恢复对话和对话相关的状态。
20 头域
头域的语法描述在7.3节。本节列出了头域的全部列表,包括了语法注释,含义,和用法。通过本节,我们使用[HX.Y]指当前HTTP/1.1 的RFC2616[8]的规范的X.Y节。每个头域都有示例给出。
关于与方法和proxy处理有关的头域字段在表2和表3中有处理。
“where”列描述了在头域中能够使用的请求和应答的类型。这列的值是:
R:头域只能在请求中出现;
r:头域只能在应答中出现;
2xx,4xx,等等:一个数字的值区间表示头域能够使用的应答代码。
c:头域是从请求拷贝到应答的。
如果”where”栏目是空白,表示头域可以在所有的请求和应答中出现。
“proxy”列描述了proxy在头域上的操作
a:如果头域不存在,proxy可以增加或者连接头域
m:proxy可以修改现存的头域值
d:proxy可以删除头域值
r:proxy必须能读取这个头域,因此这个头域不能加密。
接下来6个栏目与在某一个方法中出现的头域有关:
c:条件;对头域的要求依赖于消息的内容
m:头域是强制要有的。
m*:头域应当被发送,但是客户端/服务端都需要准备接收没有这个头域的消息。
o:头域是可选的。
t:头域应当被发送,但是客户端/服务端都需要准备接收没有这个头域的消息。客户端/服务端都需要准备接收没有这个头域的消息。如果通讯的协议是基于面向流的协议(比如TCP),那么头域值必须被发送。
*:如果消息体不为空,那么头域值就绪要的。(细节请参见20.14,20.15和7.4节)
-:这个头域是不适用的。
“Optional”意味着这个元素可以在请求或者应答中包含这个头域,并且UA可以忽略在请求或者应答中存在的这个头域(这条规则有一个例外,就是Require头域,在20.32节有描述)。”mandatory”(强制)头域是必须在请求中存在的头域,并且也必须是UAS接收到一个请求时能够理解的头域。一个强制头域必须也在应答中出现,并且UAC也能处理这个头域。”Not applicable”(不适用)意味着头域不能在请求中出现。如果一个UAC错误的把这个头域放在请求中,在UAS收到的时候必须被忽略。同样的,如果应答中的”不适用”的头域,也就是说UAS不能在应答中放置的头域,如果出现了,那么UAC也必须在应答中忽略掉这个头域。
一个UA必须忽略他们所不能处理的扩展的头参数。
本规范也定义了常用的头域名的缩写,用于缩小消息的大小。
在Contact,From,To头域中都包含一个URI。如果这个URI包含一个逗号,问号或者分毫,那么这个URI必须使用尖括号括起来(<和>)。所有的URI参数都必须在这些括号内。如果URI并非用尖括号括起来的,那么用分号分开的参数将被视同与header参数而不是URI参数。
20.1 Accept
Accept头域的语法定义遵从[H14.1]。除了如果没有Accept头域,服务器应当认为Accept缺省值是application/sdp以外,语义也是和HTTP/1.1类似的语义。
空的Accept头域意味着不接受任何格式。
小虎 2006-05-25 00:10
Header field where proxy ACK BYE CAN INV OPT REG
Accept R - o - o m* o
Accept 2xx - - - o m* o
Accept 415 - c - c c c
Accept-Encoding R - o - o o o
Accept-Encoding 2xx - - - o m* o
Accept-Encoding 415 - c - c c c
Accept-Language R - o - o o o
Accept-Language 2xx - - - o m* o
Accept-Language 415 - c - c c c
Alert-Info R ar - - - o - -
Alter-Info 180 ar - - - o - -
Allow R - o - o o o
Allow 2xx - o - m* m* o
Allow r - o - o o o
Allow 405 - m - m m m
Authentication-Info 2xx - o - o o o
Authorization R o o o o o o
Call-ID c r m m m m m m
Call-Info ar - - - o o o
Contact R o - - m o o
Contact 1xx - - - o - -
Contact 2xx - - - m o o
Contact 3xx d - o - o o o
Contact 485 - o - o o o
Content-Disposition o o - o o o
Content-Encoding o o - o o o
Content-Language o o - o o o
Content-Length ar t t t t t t
Content-Type * * - * * *
Cseq c r m m m m m m
Date a o o o o o o
Error-Info 300-699 a - o o o o o
Expires - - - o - o
From c r m m m m m m
In-Reply-To R - - - o - -
Max-Forwards R amr m m m m m m
Min-Expires 423 - - - - - m
MIME-Version o o - o o o
Organization ar - - - o o o
表2: 头域概览,A-O
Header field where proxy ACK BYE CAN INV OPT REG
Priority R ar - - - o - -
Proxy-Authenticate 407 ar - m - m m m
Proxy-Authenticate 401 ar - o o o o o
Proxy-Authorization R dr o o - o o o
Proxy-Require R ar - o - o o o
Record-Route R ar o o o o o o
Record-Route 2xx,18x mr - o o o o -
Reply-To - - - o - -
Require ar - c - c c c
Retry-After 404,413,480,486 - o o o o o
Retry-After 500,503600,603 - o o o o o
Route R adr c c c c c c
Server r - o o o o o
Subject R - - - o - -
Supported R - o o m* o o
Supported 2xx - o o m* m* o
Timestamp o o o o o o
To c(1) r m m m m m m
Unsupported 420 - m - m m m
User-Agent o o o o o o
Via R amr m m m m m m
Via rc dr m m m m m m
Warning r - o o o o o
WWW-Authenticate 401 ar - m - m m m
WWW-Authenticate 407 ar - o - o o o
表3:头域概览,P-Z (1)和可能的附加tag一起拷贝。
例子:
Accept: application/sdp;level=1,application/x-private,text/html
20.2 Accept-Encoding
Accept-Encoding头域类似Accept,但是限定了接收应答中的内容的编码[H3.5]。参见[H14.3]。在SIP中的语义和在[H14.3]中的定义是一致的。
一个空的Accept-Encoding头域是允许的。他等同于Accept-Encoding:identity,这就是说,只有identity编码,也就是说没有编码的情况,是允许的。
如果没有Accept-Encoding头域存在,那么服务端应当使用缺省值:identity。
这个和HTTP的定义略有不同,HTTP指出如果本头域不存在,那么任何编码形式都可以使用,只是推荐identity编码而已。
例如:
Accept-Encoding:gzip
20.3 Accept-Language
Accept-Language头域用来在请求中指定首选的的语言的,这个首选的语言是在应答中的消息体中的的原因分析,会话描述,或者状态报告的。如果没有Accept-Language存在,那么服务端应当假设所有的语言客户端都可以接受。
Accept-Language头域遵从[H14.4]节定义的语法。对于SIP来说,也同样支持对语言通过”q”参数来进行排序。
例如:
Accept-Language: da, en-gb; q= 0.8, en;q=0.7
20.4 Alert-Info
当INVITE请求有一个Alert-Info头域的时候,Alert-Info头域就包含的是给UAS的一个额外的信息。当在180(Ringing)应答中出现的时候,Alter-Info头域给出了UAC一个额外的回铃信息。这个头域的一个典型用法就是让proxy增加这个头域用来体哦你嘎一个与众不同的振铃效果。
Alter-Info头域可能会带来潜在的安全隐患。这个隐患以及相应的处理在20.9节有讲述,这个隐患和Call-Info头域的隐患是相同的。
另外,用户应当可以有选择的屏蔽这个特定。
这个可以保护用户不因为使用了未受信任节点发送过来的这个头域而导致的破坏。
例如:
Alter-Info:
20.5 Allow
Allow头域列出了UA支持的方法列表。
如果要提供UA头域,那么所有只要是UA支持的方法,包括ACK和CANCEL都必须列在这个Allow头域中。如果没有Allow头域出现,一定不能以为UA什么方法都不支持。应当解释成为发送这个消息的UA并没有告诉大家它支持什么方法。
在应答中提供Allow头域比在OPTIONS请求/应答中会减小所需要的消息数量。
例如:
Allow: INVITE,ACK,OPTIONS,CANCEL,BYE
20.6 Authentication-Info
Authentication-Info 头域提供了和HTTP类别相同的认证方法。UAS可以在给一个顺利通过认证的请求的2xx应答中包含这个头域,并且是使用基于Authorization头域的分类。
这个头域的语法和语义遵循RFC2617[17]的规范。
例如:
Authentication-Info: nextnonce=”47364c23432d2e131a5fb210812c”
20.7 Authorization
Authorization头域包含了了UA进行认证的信任书。22.2节概述了对Authorization头域的用法,22.4节讲述了和HTTP 认证一起使用的时候的语法和语义。
这个头域,和Proxy-Authorization,并不遵循通常的多头域值的规则。虽然它不是由逗号分割的列表,这个头域名可以出现多次,并且不能应用7.3节的规则合并成为单个头域。
在下边的例子中,在分类参数两边没有引号括起来。
Authorization:Digest username=”Alice”, realm=”atlanta.com”,
nonce = ”84a4cc6f3082121f32b42a2187831a94”,
response=”7587245234b3434cc3412213e5f113a5432”
20.8 Call-ID
Call-ID头域用来唯一区别一个特定的邀请或者一个特定客户端的所有注册项。单个多媒体会议可以分解成为多个不同Call-ID的呼叫,例如,当一个用户数次邀请单个个体加入同一个会议的时候。Call-ID是大小写敏感的并且是字节/字节比较的。
Call-ID头域的简写就是i
例子:
Call-ID: [email protected]
20.9 Call-Info
Call-Info头域提供了对呼叫方或者被叫方的附加信息,如果出现在请求中则是呼叫方的信息,如果出现在应答中则是被叫方的。”purpose”参数中存放了效果图URI。”icon”参数包含了一个呼叫方或者被叫方的图标。”info”参数描述了简要的呼叫方或者被叫方的信息,例如,通过放置一个网页进行介绍等。”card”参数提供了一个名片,比如,基于vCard[36]或者LDIF[37]格式。如果附加新的标记,那么可以通过27节描述的步骤通过在IANA注册来附加。
对Call-Info的使用可能会带来一些安全隐患。如果一个被叫方接到一个恶意呼叫方提供的URI,被叫方可能会由显示一个不合适的内容,或者危险的或者非法的内容,等等。因此,我们建议UA只显示那些它能够检验并且信任发送方身份的Call-Info头域中的内容。这个对于对方UA来说不需要。proxy可以在请求中加入这个头域。
例如:
Call-Info: ;purpose=icon,
http://www.example.com/alice/;purpose=info
20.10 Contact
Contact头域提供了一个URI,这个URI的含义取决于是在请求还是在应答中。
Contact头域包含了一个显示的名字,一个包含参数的URI,还有header参数组成。
本文档定义了一个Contact参数”q”和”expires”。这些参数只有当Contact头域在REGISTER的请求或者应答,或者3xx的应答中才有效。在其他规范中可能会定义一个附加的参数。当头域值包含一个显示的名字,那么带参数的URI应当用”<”和”>”括起来。如果没有”<”,”>”括起来,所有URI后边的参数都将视为header参数,而不是URI参数。显示姓名可以是符号,或者引号引起来的字符串(如果很长的话)。
即使”display-name”是空的,如果”addr-spec”包含一个逗号或者分号,或者?的话,也必须使用”name-addr”的格式。这在display-name和”<”之间可以有也可以没有LWS(线性空白)
这些关于显示名字,URI和URI的参数,header参数的规则同样对To和From头域适用。Contact头域的的角色很像HTTP中的Location头域的角色。但是HTTP头域只允许1个地址,没有其他说明。由于URI中可以包含逗号和分号,所以他们在header或者参数分隔符上是错误的。
Contact头域的缩写是m(“moved”)。
例子:
Contact: “Mr.Watson”
expires=3600,
“Mr. Watson” mailto:[email protected] ;q=0.1
m:
20.11 Content-Disposition
Content-Disposition头域描述了消息体,或者消息的多个部分,或者消息体的一个部分应被UAC或者UAS怎样解释。这个SIP头域扩展了MIME Content-Type(RFC 2183[18])。
SIP定义了Content-Disposition几个新的”disposition-types”。如果取值”session”意味着消息体位呼叫(calls)或者早期(pre-call)媒体,描述了一个会话。取值”render”表示了消息体可是被显示或者展示给用户。注意”render”比”inline”更适合避免MIME消息体作为一个大的消息的一部分做展示(由于SIP消息的MIME消息体经常不被展示给用户)。出于向后兼容的考虑,如果Content-Disposition头域不存在,服务器应当假设Content-Type为application/sdp的部属方式是”session”,为其他方式的时候是”render”。
部属方式“icon”表示消息体部分包含了一个用于表示呼叫者或者被叫者的icon图像,当UA收到这个消息,就可以展示一下,或者在对话过程中一致展示。”alert”意味着消息体部分包含了信息,比如是一段声音,应当由UA展示给用户提示用户这个请求,通常是初始化对话的请求;这个altering消息体可以是一个在180Ringing临时应答发出后的一个铃声。
所有需要展示给客户的具有”disposition-type”的MIME消息体,都应当只在这个消息有适当的安全认证的时候展示。
处理参数,handling-param,描述了UAS在接收到这个内容类型或者部属类型是它所不支持的消息体的时候,应当如何操作。这个参数定了了”optional”和”required”两个值。如果处理参数没有,那么这个处理参数缺省值就是”required”。处理参数在RFC3204[19]中定义和描述的。
如果这个头域不存在,那么MIME类型决定了缺省的内容部属。如果没有MIME类型,那么缺省值就是”render”
例如:
Content-Disposition: session
20.12 Content-Encoding
Content-Encoding头域是对”media-type”(媒体类型)的一个修正。当存在这个头域的时候,它的值就是对包体内容编码的附加说明,并且因此必须根据本字段应用正确的解码机制,这样才能得到正确的Content-Type头域指出的媒体类型的解码。Content-Encoding首要应用于在不丢失媒体类型标记的情况下对消息体进行压缩处理。
如果包体应用了多个编码,那么包体编码必须按顺序在这个字段中进行列出。
所有的Content-Encoding的值都是大小写不敏感的。IANA是这个编码方式的注册机构。参见[H3.5]获得Content-coding的语法定义。
客户端可以在请求中进行包体的内容编码。服务端也可以在应答中进行内容编码。服务端必须只能应用客户端在请求中的Accept-Encoding头域中列出的编码类型。
Content-Encoding简写是e。
例如:
Content-Encoding:gzip
e: tar
20.13 Content-Language
参见[H14.12].例如:
Content-Language: fr
20.14 Content-Length
Content-Length头域标志了消息体的大小,给消息的接受者,以10进制表示的数字。应用程序应当使用这个字段标志的大小来传送消息体,而不关心消息体的媒体类型是什么。如果是基于流的通讯协议(比如TCP),那么本头域必须提供。
消息的大小并不包含CRLF分开的头域和包体。任何大于或者等于0 的Content-Length都是合法的长度。如果消息中不包含包体,那么Content-Length必须设置成为0
对Content-Length的忽略能够简化创建一个类似cgi一样动态生成应答的脚本。(???)
这个头域的简写是l
例如:
Content-Length:349
l:173
20.15 Content-Type
Content-Type头域标志了发给对方的消息体的媒体类型。”media-type”是在[H3.7]中定义的。如果消息体不为空,那么Content-Type头域就必须存在。如果消息体是空的,并且笨头域存在,那么就表示了特定类型的媒体的包体是0长度(比如空的音频文件)。
本头域的简写是c
例如:
Content-Type: application/sdp
c: text/html;charset=ISO-8859-4
20.16 Cseq
请求中的Cseq头域包含了一个单个的数字序列号和请求的方法。这个序列号必须是表示成为一个32位的无符号整数。在Cseq的请求方法部分是大小写敏感的。Cseq头域是为了在会话中对事务进行排序的,提供事务的唯一标志,并且区分请求和请求的重发。如果序列号相等,并且请求的方法相等,那么两个Cseq头域就是相等的。
例如:
Cseq:4711 INVITE
20.17 Date
Date头域包含了日期和时间。和HTTP/1.1不同,SIP只支持最近的RFC1123[20]格式的日期。如同在[H3.3]中,SIP限制了在SIP-date中的时区是”GMT”,但是在RFC1123中支持任意的市区。RFC1123的日期是大小写敏感的。Date头域反应的时间是请求或者应答被发送的那一刻的时间。
Date头域可以用来简化没有后备电池的终端系统,让他们能够获得当前的时间。但是由于是GMT格式的,所以,它要求客户端知道和GMT的时差。
例如:
Date:Sate,13 Nov 2010 23:29:00 GMT
20.18 Error-Info
Error-Info头域提供了对有错误应答码的应答的附加信息。
SIP UAC具有从弹出的窗口PC界面,到只有声音的电话或者网关过来的终端界面。与其强制服务器产生一个错误来选择是发送一个带有详细原因说明的错误代码应答,还是播放一段声音,不如使用Error-Info头域把两个都发送。让UAC来决定用什么来展示给呼叫方。
UAC可以把在Error-Info中的一个SIP或者SIPS URI当作是转发的一个Contact地址,并且据此产生一个新的INVITE,这样可以建立预先录制的声明会话。如果是非SIP URI,那么可以展示给用户。
例如:
SIP/2.0 404 The Number you have dialed is not in service
Error-Info:
20.19 Expires
Expires头域给定了消息(或者内容)过期的相关时间。这个字段的精确定义是方法相关的。对于一个INVITE的超时时间并不影响这个INVITE请求建立的实际的会话。不过,会话描述协议可以描述在一个会话上的的时间限制。
这个头域的值是一个以秒计数的整数,从0到(2**32)-1,从收到请求开始计数。
例如:
Expires:5
20.20 From
From头域表示了请求的来源地。这个可能和对话的来源的不同,被叫方到呼叫方的请求会在From头域使用被叫方的地址。
选项”display-name”是展示给界面的。如果客户标志停留在隐藏状态,那么系统应当使用”Anonymous”作为显示名字。即使是”displayname”是空的,如果”addr-spec” 包含一个逗号,?,或者分毫,那么就必须使用”name-addr”格式。相关的格式在7.3.1节描述。
如果From的URI相等,并且参数也相等,那么这两个头域就是相等的。如果扩展参数在一个头域中存在,但是在另外一个头域中不存在,那么当这两个头域做比较的时候,这个参数将被忽略。这意味着显示名字的存在与否不影响比较的结果。
参见20.10处理显示名字,URI和URI参数,以及头域参数的规则。
From头域的简写是f
例子:
From: “A. G. Bell”
From: sip:[email protected];tag=887s
f: Anonymous
20.21 In-Reply-To
In-Reply-To头域列举了本次呼叫相关的或者返回的Call-ID。这些Call-ID可以备客户端cache起来,这样可以在这个头域中返回。
这允许自动呼叫奋发系统来路由这些返回的呼叫到第一个呼叫的原始请求地点。这也允许被叫方过滤呼叫,这样只有在呼叫中原始请求建立的呼叫才会被接受。这个字段不是对请求验证的一个替代。
例如:
In-Reply-To: [email protected],[email protected]
20.22 Max-Forwards
Max-Forwards头域必须在任何一个SIP请求中使用,来限制中间转发请求到下一个节点的proxy或者gateway的个数。这个在客户端trace一个请求,如果路由失败或者在中间出现循环的时候特别有用。
Max-Forwards是一个0-255的整数,表明了在这个请求消息中允许被转发的剩余次
小虎 2006-05-25 00:11
数。每当服务器转发这个请求一次,这个数字就减一。建议的初始值是70。当不能确定有无循环路由的时候,必须在头域中增加本头域。比如,一个B2BUA应当增加这个头域。
例如:
Max-Forwards:6
20.23 Min-Expires
Min-Expires头域包含了一个服务器所支持的内部状态(soft-state)的最小的刷新时间间隔。这个包括被登记服务器所登记的Contact头域。这个头域包含了一个以秒计数的整数,从0到(2**32)-1。在423(Interval Too Brief)应答中,本头域的用法在10.28,10.3,和21.4.17中有描述。
例如:
Min-Expires:60
20.24 MIME-Version
参见[H19.4.1]
例如:
MIME-Version: 1.0
20.25 Organization
Organization头域包含了发出请求或者应答的SIP节点所属的组织名字。这个字段可以用来让客户端软件过滤呼叫。
例如:
Organization: Boxes by Bob
20.26 Priority
Priority头域标志了客户端评价的请求紧急程度。Priority头域描述了SIP应当处理人工或者UA发过来的请求的优先级。举例来说,这可能是决定呼叫转发和处理的优先要素。对于判定优先级来说,如果消息没有包含Priority字段,那么处理的时候应当当作”normal”优先级处理。Priority头域不影响通讯资源的优先顺序,比如路由上的包转发的优先级或者访问PSTN网关的优先级。本头域有”non-urgent”,”normal”,”urgent”,和”emergency”取值,另外的取值可以在别处定义。我们强烈建议”emergency”只用于影响到生命、身体、或者财产危急时候才使用。其他情况下, 本头域没有额外的语义。在RFC2076[38]中,定义了”emergency”。
例如:
Subject: A tornado is heading our way!
Priority: emergency。
或者
Subject: Weekend plans
Priority: non-urgent.
20.27 Proxy-Authenticate
Proxy-Authenticate头域用来进行认证使用的。这个头域的用法在[H14.33]中定义。参见22.3节关于本字段的细节讨论。
例如:
Proxy-Authenticate: Digest realm=”atlanta.com”,
domain=”sip:ss1.carrier.com”,qop=”auth”,
nonce=”f84f1cec41e6cbe5aea9c8e88d359”,
opaque=””,stale=FALSE,algorithm=MD5
20.28 Proxy-Authorization
Proxy-Authorization头域允许客户端向一个要求认证的proxy证明自己(或者证明它的使用者)的身份。一个Proxy-Authorization头域包含了与UA认证信息相关的信任书,这个信任书是给proxy和/或者本请求相关的域的。
参见22.3节关于这个头域的定义。
本头域,连通Authorization头域,并不遵循常用的多头域名(多个相同头域名的合并)的规则。虽然不是用逗号分割的列表,这个头域名可以出现多次,并且不能用7.3.1描述的通常规则合并成为一个头域。
例如:
Proxy-Authorization: Digest username=”Alice”,realm=”atlanta.com”,
nonce=”c60f3082ee1212b402a21831ae”,
response=”245f23415f11432b3434341c022”
20.29 Proxy-Require
Proxy-Require头域用来表示请求中一定要求proxy支持的相关的特性。参见20.32关于这个头域的使用。
例子:
Proxy-Require:foo
20.30 Record-Route
Record-Route头域是proxy在请求中增加的,用来强制会话中的后续请求经过本proxy的。本头域的用法在16.12.1节有描述。
例子:
Record-Route:
20.31 Reply-To
Reply-To头域包含了逻辑上返回目的地URI,这个可以和From头域不同。比如,URI可以用来返回未接电话或者未建立的会话。如果用户希望保留匿名,那么这个头域应当从请求中去除或者改变,这样可以避免透露个人隐私信息。
即使”display-name”是空的如果”addr-spec”包含了逗号、问号、或者分号,那么就需要使用”name-addr”的格式来填写。这个语法在7.3.1中定义。
例如:
Replay-To: Bob
20.32 Require
Require头域用于UAC告诉UAS关于要求UAS支持那些特性。虽然这是一个可选的头域,但是如果Require头域存在,那就一定不能掠过不处理。
头域包含一个option tag的列表,这个列表在19.2节中描述。每一个option tag定了一个要处理请求要求UAS必须支持的SIP扩展。通常,这用于定义一个需要支持的扩展头域的集合。复核本规范的UAC应当值包含规范的RFC扩展。
例如:
Require:100rel
20.33 Retry-After
Retry-After头域可以用于500(Server Internal Error)或者503(Service Unavailable)应答,用来标志大约本服务还会处于不可用状态多久。在404(Not Found),413(Request Entity Too Large), 480(Temporarily Unavailable),486(Busy Here), 600 (Busy), 或者603(Decline)应答中用于标志何时被叫方会恢复正常。这个字段的值是一个秒为单位的正整数(十进制),从应答生成开始的一个正整数。
对于回叫的时间,可以有一个附加的说明。”duration”参数标志了被叫方变成正常状态的时间长度。如果没有定义,那么服务可以被看作是永远有效。
例如:
Retry-After: 18000;duration=3600
Retry-After:120 (I’m in a meeting)
20.34 Route
Route头域用于强制一个请求经过一个proxy路由列表。Route头域的使用在16.12.1节定义:
例如:
Route:
20.35 Server
Server头域包含了关于UAS处理请求所使用的软件信息。
服务器的特定软件版本可能会使服务器由于特定软件安全漏洞导致服务器收到攻击。实现上应当使得Server头域是一个可以配置的选项。
例如:
Server:HomeServer v2
20.36 Subject
Subject头域提供了呼叫的一个概览,允许呼叫不用分析会话描述就可以大致过滤。会话描述并不需要和INVITE邀请使用相同的主题标志。
Subject的缩写是s
例如:
Subject: Need more boxes
s: Tech Support
20.37 Supported
Supported头域列举了UAC或者UAS支持的扩展。
Supported头域包含了一个option tag的列表,在19.2节描述的option tag,他们是这个UAS或者UAC所支持的。遵循本规范的UA必须只包含遵循标准RFC扩展的option tag。如果本字段是空的,意味着不支持任何扩展。
Supported头域的缩写是k
例如:
Supported: 100rel
20.38 Timestamp
Timestamp头域描述了当UAC发送请求到UAS的时间戳。
参见8.2.6节关于如何给请求产生一个包含这个头域的应答。虽然没有定义本字段的标准行为,我们允许对扩展应用或者SIP应用获得RTT预计时间。
例如:
Timestamp:54
20.39 To
To头域定义了逻辑上请求的接收者。选项”display-name”意味着展示给客户的界面。”tag”参数提供了对话识别机制。
参见19.3节关于”tag”参数的些界描述。
对于To头域的比较是和对From头域的比较相同的。参见20.10节的比较规则来比较display name,URI和URI参数,以及头域的参数。
To头域的缩写是t。
下边是一个To头域的例子:
To: The Operator
t: sip:[email protected]
20.40 Unsupported
Unsupported头域列出了不被UAS支持的特性列表。参见20.32。
例如:
Unsupported:foo
20.41 User-Agent
User-Agent头域包含了发起请求的UAC信息。本头域的语义在[H14.43]定义。
UA所使用的版本号情况可能会导致由于这个版本的安全漏洞二遭受攻击。所以在实现上应当使得User-Agent头域是可以配置的。
例如:
User-Agent:Softphone Beta1.5
20.42 Via
Via头域是用来描述请求当前经历的路径的,并且标志了应答所应当经过的路径。Via头域的branch ID参数提供了事务的标志,并且用于proxy来检查循环路由。
Via头域包含了用于发送消息的通讯协议,客户端主机名或者网络地址,可能还有接收应答所用的端口号码。Via头域还可以包含参数”maddr”,”ttl”,”received”和”branch”,这些定义在其他节中描述。对于遵循本规范的实现,这个branch参数的值必须用magic cookie”z9hG4bK”打头(8.1.1.7节)。
这里定义的通讯协议是”UDP”,”TCP”,”TLS”,和”SCTP”,”TLS”意思是基于TCP的TLS。当请求发送到一个SIPS URI上时,协议依旧标记着时”SIP”,但是通讯协议是TLS。
Via: SIP/2.0/UDP erlang.bell-telephone.com:5060;branch=z9hG4bK87asdks7
Via: SIP/2.0/UDP 192.0.2.1:5060 ;received=192.0.2.207
;branch=z9hG4bK77asjd
Via头域的缩写是v
在这个例子中,从多源(multi-homed)主机的消息有两个地址,192.0.2.1和192.0.2.207。发送者猜错了发送的网络界面(以为是在192.0.2.1上发送的)。Erlang.belltelephone.com发现了这个不匹配,并且给这个节点的Via增加了一个参数,包含了实际包接收到的地址。
在SIP URI语法下,并不要求填写主机名或者网络地址和端口号。特别是,允许在”:”或者”/”两遍的LWS(线形空白)。例如:
Via: SIP / 2.0 / UDP first.example.com: 4000;ttl=16
;maddr=224.2.0.1 ;branch=z9hG4bKa7c6a8dlze.1
即使本规范要求所有的请求中都包含branch参数,本头域的BNF描述中,branch参数是可选的。这就和RFC2543元素可以进行互操作,因为RFC2543没有添加branch参数。
如果他们的发送协议和sent-by域相等,都有相同的参数集合,并且参数都相等,那么两个Via头域就是相同的。
20.43 警告
Warning头域用来给应答的状态添加附加说明使用的。Warning头域值是在应答中包含的,并且包括了一个3位的警告代码,主机名,和警告正文。
“warn-text”应当是一个自然语言,给个人用户接收应答时候来响应的。这可以通过现有的各种信息来决定这个warn-text,比如用户的位置,Accept-Language域,或者应答重的Content-Language等等。缺省语言是idefault[21]。
下边列出了当前定义的”warn-code”,并且有英文描述的推荐的warn-text。这些井盖描述了会话描述中的各种可能的失败情况。第一个warn-code的数字是”3”表示这是一个SIP规范的警告信息。警告信息300到329是保留用于标志在会话描述中的保留字错误的,330到339是会话描述中基本网络服务相关警告,370到379是关于会话描述重的QoS参数数量相关的警告,390到399是上边未列除的杂项警告信息。
300 Incompatible network protocol:(不兼容的网络协议),One or more network protocols contained in the session description are not available.(在会话描述中的一个或者多个网络协议不适用)
301 Incompatible network address formats(不兼容的网络地址格式):One or more network address formats contained in the session description are not available. (会话描述中的一个或者多个网络地址格式不合法)
302 Incompatible transport portocol(不兼容的通讯协议):One or more transport protocols described in the session description are not available. (会话描述中的一个或者多个通讯协议不存在)。
303:Incompatible bandwidth units(不兼容的带宽单位): One or more bandwidth measurement units contained in the session description were not understood.(会话描述中的一个或者多个带宽单位不支持)。
304 Media type not available(媒体类型不存在): One or more media types contained in the session description are not available. (会话描述中的一个或者多个媒体类型不存在)。
305 Incompatible media format(媒体格式不兼容): One or more media formats contained in the session description are not available.(会话描述中的一个或者多个媒体格式不兼容)。
306 Attribute not understood(媒体属性不支持): One or more of the media attributes in the session description are not supported.(会话描述中的一个或者多个媒体属性不支持)。
307 Session description parameter not understood(会话描述参数不支持): A parameter other than those listed above was not understood.(列出的会话描述参数不支持)。
330 Multicast not available(多点传输不允许): The site where the user is located does not support multicast.(用户定位的这个服务器不支持多点传送)。
331 Unicast not available(Unicast不支持): The site where the user is located does not support unicast communication (usually due to the presence of a firewall)。(用户定位的节点不支持unicast通讯(通常由于在防火墙之后))。
370 Insufficient bandwidth(带宽不足): The bandwidth specified in the session description or defined by the media exceeds that known to be available.(会话描述的带宽要求或者媒体要求的带宽超过限制)。
399 Miscellaneous warning(杂项警告): The warning text can include arbitrary information to be presented to a human user or logged. A system receiving this warning MUST NOT take any automated action.(这个警告信息可以包含给用户的任意信息或者做日志记录。接收到这个警告的系统禁止做任何自动操作)。
1xx和2xx消息是HTTP/1.1使用的。
附加的”warn-code”是IANA定义的,在27.2节有附加说明。
例如:
Warning: 307 isi.edu "Session parameter ’foo’ not understood"
Warning: 301 isi.edu "Incompatible network address type ’E.164’"
20.44 WWW-Authenticate
WWW-Authenticate头域包含了认证信息,参见22.2节有关的详细说明。
例如:
WWW-Authenticate:Digest realm=”atlanta.com”,
domain=”sip:boxesbybob.com”,qop=”auth”,
nonce="f84f1cec41e6cbe5aea9c8e88d359",
opaque="", stale=FALSE, algorithm=MD5