现在来介绍一下SIP中的路由机制。总的来说,SIP中存在两种路由场景:1、请求消息的路由;2、响应消息的路由 ,响应消息的路由非常简单,就是完全依靠Via来完成的,具体请见我关于RFC3261中会话流程的分析。
下面我们只谈SIP请求消息的路由。 首先,我们要搞清楚什么是严格路由和松散路由。
严格路由(Strict Routing):可以理解为比较“死板”的理由机制,这种路由机制在SIP协议的前身RFC 2534中定义,其机制非常简单。要求接收到的消息的request-URI必须是自己的URI,然后它会把第一个Route头域“弹”出来,并把其中的URI作为新的request-RUI,然后把该消息路由给该URI。
松散路由(Louse Routing,lr):该路由机制较为灵活,也是SIP路由机制的灵魂所在,在SIP的RFC 3261中定义。
松散路由的Proxy的路由决策过程:
1、Proxy首先会检查消息的request-URI是不是自己属于自己所负责的域。如果是,它就会通过定位服务将该地址“翻译”成具体的联系地址并以此替换掉原来的request-URI;否则,它不会动request-URI。
2、Proxy检查第一个Route头域中的URI是不是自己的,如果是,则移除。
3、如果还有Route头域,则Proxy会把消息路由给该头域中的URI,否则就路由给request-URI。
对于以上的3条规则,我们可以简单总结为一句话:Route的优先级高于request-URI的。
了解了两种路由机制,我们再来了解一下Route和Record-Route。如果说Via是为了给一个请求消息的响应消息留后路,那么Record-Route就是为了给该请求消息之后的请求消息留后路。
首先,我们来介绍下via头域的用法。一个SIP消息每经过一个Proxy(包括主叫),都会被加上一个Via头域,当消息到达被叫后,Via头域就会记录请求消息经过的完整路径。被叫将Via头域原样copy到响应消息中(包括各Via的参数,以及各Via的顺序),然后下发给第一个Via中的URI,每个Proxy转发响应消息前都会把第一个Via(也就是它自己添加的Via)删除,然后将消息转发给新的第一个Via中的URI,直到消息到达主叫。
在一个请求消息的传输过程中,Proxy也可能(纯粹自愿,如果它希望还能接收到本次会话的后续请求消息的话)会添加一个Record-Route头域,这样当消息到达被叫后里面就有会有0个或若干个Record-Route头域。被叫会将这些Record-Route头域并入路由集,并入自己的路由集,随后被叫在发送请求消息时就会使用该路由集构造一系列Route头域,以便对消息进行路由。 然后,将Record-Route头域全部原样copy到响应消息中返回给主叫。主叫收到响应消息后也会将这些Record-Route头域并入路由集,只是它会将其反序。该会话中的后续请求消息的Route头域将会通过路由集构造。
注意:Record-Route头域不是用来路由,而只是起到传递信息的作用。Record-Route头域不是路由集唯一来源,路由集还可以通过手工配置等方式得到。
以上的描述还是比较抽象,下面就以两个实例来解释一下。
例子是一个基本的SIP四边传送,U1->P1->P2->U2,使用proxy来传送。下边是过程。
U1 发送:
INVITE sip:callee@domain.com SIP/2.0
Contact: sip:caller@u1.example.com
发给P1,P1是一个外发的proxy。P1并不管辖domain.com,所以它查找DNS并且发请求到那里。它也增加一个Record-Route头域值:
INVITE sip:callee@domain.com SIP/2.0
Contact: sip:caller@u1.example.com
Record-Route: <sip:p1.example.com; lr>
P2收到这个请求。这是domain.com所以它查找位置服务器并且重写Request-URI。它也增加一个Record-Route头域值。请求中没有Route头域,所以它解析一个新的Request-URI来决定把请求发送到哪里。
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的被叫方接收到这个请求并且返回一个200OK应答:
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>
u2的被叫方并且设置对话的状态的remote target URI为:
sip: caller@u1.example.com并且它的路由集合是:
(<sip:p2.domain.com;lr>,<sip:p1.example.com;lr>)
这个转发通过P2到P1到U1。现在U1设置它自己的对话状态的remote target URI为:sip:calle@u2.domain.com并且它的路由集合是:
(<sip:p1.example.com;lr>,<sip:p2.domain.com;lr>)
由于所有的路由集合元素都包含了lr参数,那么U1构造最后的BYE请求:
BYE sip:callee@u2.domain.com SIP/2.0
Route:<sip:p1.example.com;lr>,<sip:p2.domain.com;lr>
就像其他所有的节点(包括proxy)会做的那样,它会使用DNS来解析最上的Route头域的URI值,这样来决定往哪里发送这个请求。这就发到了P1。P1发现Request-URI中标记的URI不是它负责的域,于是它就不改变这个Request-URI。然后看到它是Route头域的第一个值,于是就从Route头域中移去,并且转发这个请求到P2:
BYE sip:callee@u2.domain.com SIP/2.0
Route: <sip:p2.domain.com;lr>
P2也发现它自己并非负责这个Request-URI的域(P2负责的是domain.com并非u2.domain.com),于是P2并不改变它。它看到自己在Route的第一个值,于是移去这个,并且向u2.domain.com转发(根据在Request-URI上查找DNS):
BYE sip:callee@u2.domain.com SIP/2.0
穿越一个严格路由proxy
在这个例子中,对话建立通过4个proxy,每一个增加Record-Route头域值。第三个proxy是由严格路由实现的(RFC 2543)。
U1->P1->P2->P3->P4->U2
INVITE请求到达U2包括了:
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>
并且U2返回了一个200 OK。接着,U2根据第一个Route头域值发送下边的BYE请求到P4:
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并不管辖Request-URI指出的域,于是就不更改这个Reqeust-URI。它发现自己在第一个Route头域中,于是把自己从Route头域移除。然后准备发送请求到现在的第一个Route头域值:sip:p3.middle.com,但是它发现这个URI并没有包含lr参数,于是在发送前,它把这个请求更改成为:
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是它放在Record-Route头域中的值,于是在进一步处理前,它把这个请求改写为:
BYE sip:caller@u1.example.com SIP/2.0
Route: <sip:p1.example.com; lr>
P2自己并不管辖u1.example.com,于是它根据Route头域的值,转发这个请求到P1。
P1发现自己在Route头域的最上,于是把自己移除,得到:
BYE sip:caller@u1.example.com SIP/2.0
由于P1并不管辖u1.example.com并且没有其他的Route头域,P1会基于Request-URI转发这个请求到u1.example.com。
重写Record-Route头域值。
在这里例子中,U1和U2是在不同的私有域空间中,并且他们通过proxy P1开始一个对话,这个P1作为不同私有namespace的一个网关存在。
U1->P1->U2
U1发送:
INVITE sip:callee@gateway.leftprivatespace.com SIP/2.0
Contact: <sip:caller@u1.leftprivatespace.com>
P1使用自己的定位服务并且发送下边的信息到U2:
INVITE sip:callee@rightprivatespace.com SIP/2.0
Contact: <sip:caller@u1.leftprivatespace.com>
Record-Route:<sip:gateway.rightprivatespace.com;lr>
U2发送200 OK应答回给P1:
SIP/2.0 200 OK
Contact: <sip:callee@u2.rightprivatespace.com>
Record-Route: <sip:gateway.rightprivatespace.com;lr>
P1重写它的Record-Route头域参数,提供成为U1能够使用的参数,并且发送给P1:
SIP/2.0 200 OK
Contact: <sip:callee@u2.rightprivatespace.com>
Record-Route:<sip:gateway.leftprivatespace.com;lr>
稍后,U1发送接下来的BYE到P1:
BYE sip:callee@u2.rightprivatespace.com SIP/2.0
Route: <sip:gateway.leftprivatespace.com;lr>
P1转发到U2:
BYEsip:callee@u2.rightpriatespace.com SIP/2.0