SIP交互流程及路由机制

来源:互联网 发布:修改ssh命令默认端口 编辑:程序博客网 时间:2024/05/21 09:04

    Dialog是SIP中的一个关键概念。根据RFC3261,会话是两个UA之间持续一段时间的点到点的SIP连接,即是记录两者已经连接上的相关内容实体,方便在对话中请求进行识别和处理。

    对话都是有对话ID来标识的,包括Call-ID,一个本地标签(From-tag)和一个远端标签(To-tag)。即是说三者确定了某个对话的存在。

    对话中还包括一些对话中的后续消息所需的状态,包括:对话ID、本地序列号、远端序列号、本地URI、远端目的、布尔型标记“secure”和路由集。路由集是一个顺序的URI集,指定发送请求到目的地所需遍历的服务器地址。

会话的状态有初始状态和确认状态。当临时的相应被创建时,即标记对话的三个因素刚齐全时为初始状态;而收到2**的最后响应到达时转为确认状态,如果是其他响应或无响应到达,初始状态终结。

如下所示


图1-1 对话建立过程

1.创建对话

1) UAS

 i.路由集由请求的Record-Route头字段提供,并要保留顺序和URI参数,实时更新,如果下一轮的请求中无Record-Route头字段,则路由集变为空

 ii.本地URI填入回应的Contact头字段

 iii.布尔型标记“secure”,如果请求基于TLS(传输层安全协议),则由Request-URI中的secure参数来提供

 iv.远端目的由请求的Request-URI提供

 v.From-tag可能不存在,则默认为空

2) UAC

 i.路由集必须为响应消息中的Record-Route头字段的URI列表,保持相反的顺序和保留所有的URI参数

 ii.远端目的为响应消息的Contact头字段的URI

 iii.本地序列号为请求消息的CSeq头字段的序列号值

 iv.远端序列号必须为空,当远端UA发送一个本次对话中的请求后该值才能确定

 v.To-tag可能不存在,则默认为空

 

2. 对话中的请求

对话创建后,UA方可能需要建立新的事务,这时发起请求的UA为UAC,这可能跟对话创建之时的角色不同。re-INVITE是一种对话中修改目的URI的重新请求。

1) UAC行为

  i.发起请求一个对话中的请求消息有对话所保存的状态信息来构建。

  1. 请求消息的To头字段的URI必须设置为对话状态的远端URI

  2. 请求消息的To头字段的标签值设置为对话ID的远端标签值

  3. 请求消息的From头字段的URI必须设置为对话状态的本端URI

  4. 请求消息的From头字段的标签值设置为对话ID的本端标签值

  5. 请求消息的Call-ID必须设置为对话的Call-ID 而对话中的其它字段同样有限制:

  6. CSeq序列号。CSeq是按照各自方向严格增1的值,如果为空则设为初始值。

  7. Request-URI由远端目的指定

  8. Route由路由集指定,如果路由集为空,则无Route字段。如果路由集的第一个URI中包含lr参数,UAC必须将Request-URI设置为远端目的URI值;如果路由集的第一个URI中不包含lr参数,UAC必须将Request-URI设置为路由集的第一个URI,且不允许去掉任何参数,同时Route头字段在最后增加一个目的URI。

  9.Contact。对话中任何一个更新目的的请求消息包含一个Contact头字段,Contact字段内URI为对话的远端目的URI。

如果UAC收到对目的刷新请求消息的2**响应时,UAC必须将对话的远端目的URI设置为存在Contact字段的URI值。如果响应为481(呼叫/事务不存在)或408(请求超时),UAC应该终止对话;在无对方响应时也应该终止对话。

2)UAS行为

  i. 如果请求To字段存在标签值。UAS内核会计算与此请求相关的对话标签值,同时与已有的对话标签值比较,如果匹配则为同一个对话中的请求。此时UAS采用与对话外请求消息处理规则相同的流程进行处理;如果不存在匹配的对话,UAS可以拒绝(481)或接受这个请求。

  ii.如果远端序列号为空,则设置为请求消息中CSeq字段的序列号值;如果远端序列号存在并大于请求的CSeq序列号值,则认为请求次序颠倒,回500(服务器内不出错)消息。

 

3. 终止对话

初始状态的对话不依赖于发起请求的具体方法,只要收到一个非2**的终止响应即可将其终止;确认状态的对话的终止与确切方法相关,BYE方法终止一次会话并终止与其相关的对话。


SIP学习笔记 





学习 SIP 协议最快捷的方法是通过范例来学习,

找到了一个完整的呼叫流程,let's Go

INVITE

主叫方Tesla首先发起 INVITE 消息到被叫方MarconiINVITE 消息包含会话类型和一些呼叫所必须的参数。会话类型可能是单纯的语音,也可能是网络会议所用的多媒体视频,还可能是游戏会话。下面是消息体范例,我们来详细分析各个字段的意义。

INVITE sip:marconi@radio.org SIP/2.0
    < 请求方法、请求地址(RequestURI)、SIP 版本号(目前都是 SIP/2.0
    <
 请求地址一般就是被叫方地址,跟 MSN 中好友 eMail 地址类似

Via: SIP/2.0/UDP lab.high-voltage.org:5060;branch=z9hG4bKfw19b
    <SIP 版本号(2.0)、传输类型(UDP)、呼叫地址、
    <
branch是一随机码,它被看作传输标识
    <Via 字段中地址是消息发送方或代理转发方设备地址,一般由主机地址和端口号组成
    <
=传输类型可以为 UDPTCPTLSSCTP

Max-Forwards: 70
    <=最大跳跃数,就是经过 SIP 服务器的跳跃次数,主要是防止循环跳跃
    <
=每尽管一台代理服务器,该整数减一

To: G. Marconi <sip:Marconi@radio.org>
From: Nikola Tesla <sip:n.tesla@high-voltage.org>;tag=76341
    <=表示请求消息的发送方和目标方
    <
=如果里面有用户名标签,地址要求用尖括号包起来
    <
=对于 INVITE 消息,可以在 From 字段中包含 tag,它也是个随机码

Call-ID: 123456789@lab.high-voltage.org
    <=呼叫ID是由本地设备生成的,全局唯一值。每次呼叫该值唯一不变
    <
=对于用户代理发送 INVITE 消息,本地将生成 From tag  Call-ID 全局唯一码,被叫方代理则生成 To tag 全局唯一码。这三个随机码做为整个对话中对话标识dialog indentifier)在通话双方使用。

 

CSeq: 1 INVITE
    <CSeq,又叫命令队列Command Seqence),每发送一个新的请求,该数自动加1
以上几个字段是所有 SIP 消息体所必须的,其它头字段有些是可选的,有些在特定请求也是必须

Subject: About That Power Outage...
Contact: <sip:n.tesla@lab.high-voltage.org>

    <
Contact  INVITE 消息所必须的,它用来路由到被叫设备地址,也称为用户代理(UA
Content-Type: application/sdp
Content-Length: 158

    <
=最后两位附属字段说明消息体类型以及字段长度

 

v=0    <SDP版本号,目前都是 0
o=Tesla 2890844526 2890844526 IN IP4 lab.high-voltage.org    
<=主叫源地址,类型等
s=Phone Call    
<=主题
c=IN IP4 100.101.102.103    
<=连接
t=0 0    
< 时间戳
m=audio 49170 RTP/AVP 0   
<=媒体
a=rtpmap:0 PCMU/8000    
<=媒体属性

    <=从上面 SDP 消息体我们可以得出下面信息
    <
=连接 IP 地址:100.101.102.103
    <
=媒体格式:audio
    <
=端口号:49170
    <
=媒体传输类型:RTP
    <
=媒体编码:PCM u Law
    <
=采样率:8000 Hz

180 Ringing

当被叫方接收到 INVITE 请求消息后,将回复 180 Ringing。顾名思义,就是发回铃音,提示主叫方电话已连接上了,正等待被叫应答。被叫方接收到 INVITE 消息后也会发生响铃或者其它有呼入提示,这由被叫方设定(我们可以把它想象成我们自己设定手机铃声)。对于 180 响应又被称为消息及时响应,它是一种用来测试被叫状态的一种响应。因此它所包含的信息不多,具体 180 响应消息如下:

SIP/2.0 180 Ringing
Via: SIP/2.0/UDP lab.high-voltage.org:5060;branch=z9hG4bKfw19b
;received=100.101.102.103    
<=这里增加一个 received 参数,标识接收方 IP 地址
To: G. Marconi <sip:marconi@radio.org>;tag=a53e42    <=上已提到,To tag 做为被叫方标识
From: Nikola Tesla <sip:n.tesla@high-voltage.org>;tag=76341  <=要求很发送方 From tag 一致
Call-ID: 123456789@lab.high-voltage.org
CSeq: 1 INVITE
Contact: <sip:marconi@tower.radio.org>
Content-Length: 0
    <=对于 180 Ringing 响应,基本上就是将 INVITE  ViaToFromCall-ID  CSeq 内容复制过来,对于首行标出 SIP版本号,响应代码(180)和动作原因(reason phrase
    <
=注意这里 From  To 地址,因为它们用来指定呼叫方向,因此这里的 200 OK 响应并没有将地址对调,仍然保持原样。一点不同的是 To 头字段添加了由被叫方 Marconi 生成的 tag 标识

200 Ok

被叫响铃后,如果被叫用户 Marconi 接起电话,则发出 200 OK 响应。这个响应除了做为接通指示之外,还有一个功能是用来指定被叫允许的连接媒体格式,让主叫方确认是否可以接收该媒体。
消息体如下

SIP/2.0 200 OK
Via: SIP/2.0/UDP lab.high-voltage.org:5060;branch=z9hG4bKfw19b
;received=100.101.102.103
To: G. Marconi <sip:marconi@radio.org>;tag=a53e42
From: Nikola Tesla <sip:n.tesla@high-voltage.org>;tag=76341
Call-ID:
 123456789@lab.high-voltage.org
CSeq: 1 INVITE
Contact: <sip:marconi@tower.radio.org>
Content-Type: application/sdp
Content-Length: 155
    <=头字段部分基本同上

v=0
o=Marconi 2890844528 2890844528 IN IP4 tower.radio.org
s=Phone Call
c=IN IP4 200.201.202.203
t=0 0
m=audio 60000 RTP/AVP 0
a=rtpmap:0 PCMU/8000

    <=从上面 SDP 消息体我们可以得出下面信息
    <
=终端 IP 地址:200.201.202.203
    <
=媒体格式:audio
    <
=端口号:60000
    <
=媒体传输类型:RTP
    <
=媒体编码:PCM u Law
    <
=采样率:8000 Hz

ACK

通话前最后一步是主叫方确认 200 OK响应。该项确认证明连接被允许,即将使用另一种协议开始媒体连接。这另一种协议是上面在SDP 消息段中所协商好的 RTP 格式。该 ACK 响应内容如下:

ACK sip:marconi@tower.radio.org SIP/2.0
Via: SIP/2.0/UDP lab.high-voltage.org:5060;branch=z9hG4bK321g
Max-Forwards: 70
To: G. Marconi <sip:marconi@radio.org>;tag=a53e42
From: Nikola Tesla <sip:n.tesla@high-voltage.org>;tag=76341
Call-ID: 
123456789@lab.high-voltage.org
CSeq: 1 ACK
Content-Length: 0

BYE

通话完毕后,由被叫方 Marconi 首先挂机,发送 BYE 请求命令。注意这回由 Marconi 做为主叫方了,因此 Via 字段和FromTo  INVITE 字段有所不同。其实也就是倒置。

BYE sip:n.tesla@lab.high-voltage.org SIP/2.0
Via: SIP/2.0/UDP tower.radio.org:5060;branch=z9hG4bK392kf
Max-Forwards: 70
To: Nikola Tesla <sip:n.tesla@high-voltage.org>;tag=76341
From: G. Marconi <sip:marconi@radio.org>;tag=a53e42
Call-ID: 123456789@lab.high-voltage.org
CSeq: 1 BYE
Content-Length: 0

200 OK

BYE 之后,要求被叫方发 200 Ok 确认,也就是让主叫知道被叫已经知道你挂断了。(注意这里所说的主被叫角色已经倒过来了)打个比方,通话之后,有一方要求挂机,另一方需要知道它已经挂机了。

SIP/2.0 200 OK
Via: SIP/2.0/UDP tower.radio.org:5060;branch=z9hG4bK392kf
;received=200.201.202.203
To: Nikola Tesla <sip:n.tesla@high-voltage.org>;tag=76341
From: G. Marconi <sip:marconi@radio.org>;tag=a53e42
Call-ID:
 123456789@lab.high-voltage.org
CSeq: 1 BYE
Content-Length: 0

到此,就是最简单的呼叫过程。该过程简单在于两个终端之间没有其它设备,完全的点对点连接,它们之间只需要知道对方 IP 地址即可。现实生活中这种呼叫形式是很少见的。

SIP路由字段和机理


From: 如果一个SIP消息中没有Contact或者Record-Route头域,那么callee就会根据From头域产生后续的Request。比如:如果Alice打一个电话给Bob,From头域的内容是 From:Alice<sip:alice@example.org>。那么Bob打给Alice时就会使用 sip:alice@example.org作为To头域和Request-URI头域的内容。 

Contact: 后续Request将根据Contact头域的内容决定目的地的地址,同时将Contact头域的内容放到Request-URI中。它还可以用来指示没有在Record-Route头域中记录的Proxies的地址。同时它还可以被用在Redirect servers和REGISTER requests 和responses。 
Record-Route:Record-Route字段实际上用于帮助UA建立Route Set,当UA发送Request时会用Route Set来设置上面提到的Route字段。当一个Request消息经过Proxy Server时,如果该Proxy Server希望通知UA相关的后续消息都能通过它来转发,此时它就会在消息中添加Record-Route字段,内容为自己的地址信息。当UAS发送Resposne消息时它将复制Request中的Record-Route字段,而UAC在Response消息中检测到Record-Route字段时,它就会用该字段的路由信息更新自己的Route Set
Via
Via头域是被服务器插入request中,用来检查路由环的,并且可以使response根据via找到返回的路。它不会对未来的request 或者是response造成影响。 

总的来说,如果有Route,request就应该根据Route发送,如果没有就根据Contact头域发送,如果连Contact都没有,就根据From头域发送。


Service-Route:Service-Route在S-CSCF向UE发送REGISTER成功应答时设置,作用和Record-Route类似,用于帮助UE建立Route Set,这样UE注册后的消息(例如INVITE)通过设置Route字段无需经过I-CSCF可直接送达S-CSCF。

Path :只能用于用户向注册服务器发送的Register请求。
          1如果某代理服务器希望发往用户的任何后续请求仍能经过自己,就可以在Register请求中插入一个Path字段并赋值为自身的URI。
          2如果要求拓扑隐藏,经过I-CSCF的时候要把这个 I 添加到Path字段中 

To 字段总是包含被呼叫方的地址(通过sip代理时是公用地址,点对点时是真实ip),要注意的是区别该标题头和sip消息请求行中的Request-URI。To在信令路径中不会被代理改变,然而Request-URI包含的是信令路径中下一跳的地址,因此在路途中被每个代理改变。

总的来说,SIP中存在两种路由场景:
1,请求消息的路由
2,响应消息的路由
 
其中,响应消息的路由非常简单,就是完全依靠Via来完成的。
【说明】一个SIP消息每经过一个Proxy(包括主叫),都会被加上一个Via头域,当消息到达被叫后,Via头域就记录了请求消息经过的完整路径。被叫将这些Via头域原样copy到响应消息中(包括各Via的参数,以及各Via的顺序),然后下发给第一个Via中的URI,每个Proxy转发响应消息前都会把第一个Via(也就是它自己添加的Via)删除,然后将消息转发给新的第一个Via中的URI,直到消息到达主叫。
下面谈SIP请求消息的路由。
 
首先我们要搞清楚什么是严格路由和松散路由。
 
严格路由(Strict Routing):
可以理解为比较“死板”的理由机制,这种路由机制在SIP协议的前身RFC 2534中定义,其机制非常简单。
要求接收到的消息的request-URI必须是自己的URI,然后它会把第一个Route头域“弹”出来,并把其中的URI作为新的request-RUI,然后把该消息路由给该URI。
 
松散路由(Louse Routing,lr):
该路由机制较为灵活,也是SIP路由机制的灵魂所在,在RFC 3261中定义。
下面介绍一下一个松散路由的Proxy的路由决策过程:
 
1,Proxy首先会检查消息的request-URI是不是自己属于自己所负责的域。如果是,它就会通过定位服务将该地址“翻译”成具体的联系地址并以此替换掉原来的request-URI;否则,它不会动request-URI。
 
2,Proxy检查第一个Route头域中的URI是不是自己的,如果是,则移除之。
 

3、Loose Router首先会检查Request URI是否为自己:如果不是,则不作处理;如果是,则取出Route字段的最后一个地址作为Request URI地址,并从Route字段中删去最后一个地址。

4、Loose Router其次会检查下一跳是否为Strict Router:如果不是,则不作处理;如果是,则将Request URI添加为Route的最后一个字段,并用下一跳Strict Router的地址更新Request URI。

可以看到步骤3、4其实是Loose Router为了兼容Strict Router而做的额外工作。

前面都是准备工作,下面该进行真正的路由了。如果还有Route头域,则Proxy会把消息路由给该头域中的URI,否则就路由给request-URI。
 
对于前面的规则,可以简单总结为一句话:Route的优先级高于request-URI的。
 
好,了解了两种路由机制,我们再来了解一下Route和Record-Route。
如果说Via是为了给一个请求消息的响应消息留后路,那么Record-Route就是为了给该请求消息之后的请求消息留后路。
 
    而在一个请求消息的传输过程中,Proxy也可能(纯粹自愿,如果它希望还能接收到本次会话的后续请求消息的话)会添加一个Record-Route头域,这样当消息到达被叫后里面就有会有0个或若干个Record-Route头域。被叫会将这些Record-Route头域并入路由集,并并入自己的路由集,随后被叫在发送请求消息时就会使用该路由集构造一系列Route头域,以便对消息进行路由。
    然后,被叫会像上面对待Via头域一样,将Record-Route头域全部原样copy到响应消息中返回给主叫。
    主叫收到响应消息后也会将这些Record-Route头域并入路由集,只是它会将其反序。该会话中的后续请求消息的Route头域就会通过路由集构造。
【注意】Record-Route头域不用来路由,而只是起到传递信息的作用。
Record-Route头域不是路由集的唯一来源,路由集还可以通过手工配置等方式得到。
 
 
只是描述还是比较抽象,下面就以RFC 3261中的两个实例来解释一下。
 
路由示例1:
 
场景:
两个UE间有两个Proxy,U1 -> P1 -> P2 -> U2,并且两个Proxy都乐意添加Record-Route头域。
 
消息流:
【说明】由于我们在此只关心SIP路由机制,因此下面消息中跟路由机制无关的头域都省略了。
 
U1发出一个INVITE请求给P1(P1是U1的外拨代理服务器):
      INVITE sip:callee@domain.com SIP/2.0
      Contact: sip:caller@u1.example.com
 
P1不负责域domain.com,消息中也没有Route头域,因此通过DNS查询得到负责该域的Proxy的地址并且把消息转发过去。这里P1在转发前就添加了一个Record-Route头域,里面有一个lr参数,说明P1是一个松散路由器,遵循RFC3261中的路由机制。
      INVITE sip:callee@domain.com SIP/2.0
      Contact: sip:caller@u1.example.com
      Record-Route: <sip:p1.example.com;lr>
P2负责域domain.com,因此它通过定位服务得到callee@domain.com 对应的设备地址是callee@u2.domain.com ,因此用新的URI重写request-URI。消息中没有Route头域,因此它就把该消息转发给request-URI中的URI,转发前它也增加了一个Record-Route头域,并且也有lr参数。
      INVITE sip:callee@u2.domain.com SIP/2.0
      Contact: sip:caller@u1.example.com
      Record-Route: <sip:p2.domain.com;lr>
      Record-Route: <sip:p1.example.com;lr>
位于u2.domain.com的被叫收到了该INVITE消息,并且返回一个200 OK响应。其中就包括了INVITE中的Record-Route头域。
      SIP/2.0 200 OK
      Contact: sip:callee@u2.domain.com
      Record-Route: <sip:p2.domain.com;lr>
      Record-Route: <sip:p1.example.com;lr>
被叫此时也就有了自己的路由集:
      (<sip:p2.domain.com;lr>,<sip:p1.example.com;lr>)
 
并且它本次会话的远端目的地址设置为INVITE中Contact中的URI:caller@u1.example.com,此后被叫在该会话中的请求消息就发到这个URI。同样,被叫在200 OK响应中也携带了自己的联系地址,主叫收到该响应消息后也会把本次会话的远端目的地址设置为:callee@u2.domain.com,此后主机在该会话中的请求消息就发到这个URI。
同样,主叫也有了自己的路由集,只是跟被叫的是反序的:
      (<sip:p1.example.com;lr>,<sip:p2.domain.com;lr>)
 
 
通话完毕后,我们架设主叫先挂机,则主叫发出BYE请求:
      BYE sip:callee@u2.domain.com SIP/2.0
      Route: <sip:p1.example.com;lr>,<sip:p2.domain.com;lr>
可以看到,BYE的Route头域正是主机的路由集构造来的。
由于p1在第一个Route中,因此BYE首先发给P1。
 
P1收到该消息后,发现request-URI中的URI不属于自己负责的域,而消息有Route头域,并且第一个Route头域中的URI正是自己,因此删除之,并且把消息转发给新的第一个Route头域中的URI,也就是P2:
      BYE sip:callee@u2.domain.com SIP/2.0
      Route: <sip:p2.domain.com;lr>
P2收到该消息后,发现request-URI中的URI不属于自己负责的域(P2负责的是domain.com,而不是u2.domain.com),第一个Route头域中的URI正是自己,因此删除之,此时已经没有Route头域了,因此就转发给了request-URI中的URI。
 
被叫就会收到BYE消息:
      BYE sip:callee@u2.domain.com SIP/2.0
 
路由示例2:
如果说上面的示例主要关注的是路由流程,那么本示例关注的则是严格路由与松散路由的区别。
 
场景:
U1->P1->P2->P3->P4->U2
其中,P3是严格路由的,其余Proxy都是松散路由的,并且4个Proxy都很乐意增加Record-Route头域。
 
消息流:
我们直接给出了到达被叫的INVITE消息:
      INVITE sip:callee@u2.domain.com SIP/2.0
      Contact: sip:caller@u1.example.com
      Record-Route: <sip:p4.domain.com;lr>
      Record-Route: <sip:p3.middle.com>
      Record-Route: <sip:p2.example.com;lr>
      Record-Route: <sip:p1.example.com;lr>
 
这中间的其他消息我们就不过问了,直接看一下被叫最后发出的BYE消息大概是什么样子:
      BYE sip:caller@u1.example.com SIP/2.0
      Route: <sip:p4.domain.com;lr>
      Route: <sip:p3.middle.com>
      Route: <sip:p2.example.com;lr>
      Route: <sip:p1.example.com;lr>
 
因为P4在第一个Route里,因此被叫将BYE消息发给了P4。
 
P4收到该消息后,发现自己不负责域u1.example.com,但是第一个Route头域中的URI正是自己,因此删除之。P4还发现新的第一个Route头域中的URI是一个严格路由器,因此它把request-URI中的URI添加到最后一个Route的位置,并且将第一个Route“弹出”并且覆盖原来的request-URI。然后将消息转发给当前的request-URI,也就是P3。
      BYE sip:p3.middle.com SIP/2.0
      Route: <sip:p2.example.com;lr>
      Route: <sip:p1.example.com;lr>
      Route: <sip:caller@u1.example.com>
 
P3收到该消息后,直接把消息作出如下变换并且发给P2:
      BYE sip:p2.example.com;lr SIP/2.0
      Route: <sip:p1.example.com;lr>
      Route: <sip:caller@u1.example.com>
P2收到该消息后,发现消息中的request-URI是自己的,因此在进一步处理先首先对消息做如下变换:
      BYE sip:caller@u1.example.com SIP/2.0
      Route: <sip:p1.example.com;lr>
然后,P2发现自己不负责域u1.example.com,第一个Route中的URI也不是自己的,因此将消息转发给该URI,也就是P1。
 
P1收到该消息后,发现自己不负责域u1.example.com,但是第一个Route头域中的URI正是自己,因此删除之。消息变成下面的样子:
      BYE sip:caller@u1.example.com SIP/2.0

原创粉丝点击