SIP路由
本文主要讨论的是跟SIP消息发送的路由选择和相关头域。SIP消息传输路径依靠的是路由集和目标地址(remote target URI)。主要参考的是RFC中的第八章(一般用户代理行为)、第十二章(对话)、第十三四章(会话)、十六章(Proxy行为)。
重要概念Route Set: 路由集。路由集合是一个顺序的SIP或者SIPS URI。这些URI描述了传递一个请求所必须经历的代理列表。一个路由集可以是自适应的,因为包头中包含了Record-Route(记录路由),也可以是依赖配置得到的。(不包括目标地址)
相关头域说明Request-URI:目标用户或者服务的地址。Via:Via头域是用来描述请求当前经历的路径的,并且标志了应答所应当经过的路径。Via头域的branch ID参数提供了事务的标志,并且用于proxy来检查循环路由。只有当需要通过选择传输设备到达下一个节点(hop)的时候,才需要在头域中包含Via域。Contact:Contact头域提供了一个URI,指明了请求的资源或请求发起者,这个URI的含义取决于是在请求还是在应答中。Record-Route:Record-Route头域是proxy在请求中增加的,用来强制会话中的后续请求经过本proxy的。Route:Route头域用于强制一个请求经过一个proxy路由列表。
严格路由和松路由处理1 严格路由:Route头域中的值中包含lr参数部分为严格路由。如果Route头域的第一个值为严格路由,那么UA或Proxy必须根据下列步骤修改这个请求:- 必须把Request-URI放在Route头域中的最后一个值。-必须把第一个Route头域的值放在Request-URI中,并且从Route头域中删去。2 松路由:Route头域中的值中不包含lr参数部分为松路由。如果Route头域的第一个值为松路由,那么UA或Proxy必须根据下列步骤修改这个请求:- 如果跟自身URI相符,则把第一个Route头域的值删掉。
一般UA行为1 UAC1.1 发起请求时:(对话之外)Request-URI(必选):最开始的Request-URI头域应该是TO头域的的值。在一些特定的情况下,预先设置的路由集(route-set)会影响消息中的Request-URI:如果预设路由集的第一项是严格路由,则Request-URI值被修改为路由集的第一个值。Via(必选):当UAC创建一个请求,它必须在头域中添加一个Via域。Contact(必选):Contact头域提供了访问后续请求的特定UA实例的联系方法。Route(可选):预先设置路由集。
此时路由集为预先设置的路由集。
1.2 发起请求时:(对话之内)
此时路由集为应答的路由集。
Route:由应答重构的路由集,即对话的路由集,如果是Route由Record-Route创建的,请求中必须包含该头域。Contact:对话的远端地址。在对话中的请求可以包含Record-Route和Contact头域。不过,虽然他们会修改remote target的URI,但是这些请求也不会导致对话的路由集被改变。需要注意的是,虽然UAC会尝试新的地址(处理3xx应答的时候),但是它依旧使用对话内的路由集合来构造请求的Route头域。
1.3 处理应答remote target URI必须设置成为应答中的Contact头域的URI。当UAC收到一个刷新target请求的2xx应答的时候,如果对话的remote target URI存在,那么它必须用这个应答的Contact头域的值来替换对话的remote target URI,并以此值修改对话的目标地址。如果应答中含有Record-Route头域,则将该头域的顺序重头到尾颠倒,但内容不变,此时得到的结果为新的路由集,并以此值修改对话的路由集;如果在应答中没有Record-Route头域,那么这个路由集必须设置成为空集合。这个路由集合即便是空的,为了以后的对话中的请求,也要覆盖任何预先存在(pre-existing)的路由集合。
此时路由集为对话的路由集。
可见路由集是在对话建立中完成,在该对话中该路由集不变,而对话的目标地址属性却是可修改的。
2 UAS当UAS接收到一个target刷新请求的时候,如果请求中存在Contact头域,它必须用Contact头域中的URI来替换对话的remote target URI。UAS必须拷贝所有的请求中的Record-Route头域到应答中去(包括URI,URI参数,和其他任何Record-Route头域的参数,无论UAS是不是认识的参数都需要原样拷贝),并且必须维持这些参数的顺序。并按照此方法构建对话的路由集。UAS必须增加一个Contact头域给应答。这个Contact头域包含一个UAS在后续对话请求中接收请求的地址(这个包含了给INVITE请求的2xx应答的ACK请求处理的地址)。请求中的Via头域原封不动的移到应答中。
UAS存储路由集和远端目的地址是为了在对话中发起新的请求时选择路径用。
Proxy行为1 请求消息必须按照以下步骤:1.1 路由信息预处理根据前面严格路由的做法,proxy必须检查请求中的Request-URI部分。如果Request-URI包含了一个本proxy早先放在Record-Route头域中的值(参见16.6,4),proxy必须用Route头域中的最后一个值来替换Request-URI,并且从Route头域中删去这个值。如果Route头域的第一个值就是这个proxy,那么proxy必须从请求中把它移去。1.2 确定请求的目的如果Request-URI的区域并非本proxy负责的区域,那么Request-URI必须放在目标集合中,并且作为唯一一个目标URI。如果请求的目标集合没有像上边讲述的这样预先设定,那么这就意味着proxy是负责Request-URI所指明的区域的,并且proxy可以用任何机制来决定往哪里发送这个请求。通过这些机制,我们可以有一个可能的目的地列表,他们的URI被增加到目的地集合。1.3 请求转发当目的地集合不是空的时候,proxy可以开始转发这个请求。有状态的proxy可以按照任意的顺序处理这个目的地集合。在拷贝好的请求中的Request-URI必须用目的地的URI进行替换。如果希望保留这个请求创建的对话中,后续的请求依旧是要经过本proxy,那么本proxy必须增加一个Record-Route头域值在这个拷贝中,并且增加的这个头域值应当是在其他现存的Record-Route头域之前。如果这个请求已经是一个对话的一部分,proxy如果希望以后这个对话的请求依旧经过本proxy,那么proxy应当增加一个Record-Route头域值。以上可知,普通的终端操作中,这些Record-Route头域值不会对终端使用的路由集合造成任何影响。proxy必须在请求的拷贝中增加一个Via头域值,并且在其他Via头域值之前增加。2 应答消息proxy从应答中移去Via头域中最上的值。如果在这个应答中没有这个Via头域值,那么应答的含义就是说这个应答不应当被这个proxy转发。
如何选择发送消息的下一节地址请求消息消息发送的地址为第一个Route头域(如果存在),或者在请求的Request-URI(如果Route头域不存在)。在建立对话的请求中,如果请求没有包含Route头域,那么就没有对额外的目的地有什么其他的限制了。这个就提供了一个简单的外发(outbound)proxy的事前路由的选择。但是,用这样的方法配置一个外发proxy是不推荐的;应该由单个UPI规定的预先设定的路由集来指定外发proxy。如果请求包含了Route头域,请求应该发送到Route头域最上边的一个位置,但是请求也可能被发给由RFC3261约定的Route或者Request-URI所指定的服务器(同RFC2543定义的相反)。特别的,一个配置了外发proxy的UAC应该首先尝试把请求发送给由第一个Route头域值指定的位置,而不是采用把所有消息都发给外发proxy的策略。这就保证了外发的proxy通过不增加Record-Route头域而不参与后续请求的路径。这个也允许让不能分析第一个Route URI的终端,把请求交给外发proxy来发送。应答消息消息发送的地址为请求消息的第一个Via的URI。
参考RFC3261
存在的问题因为当前实现方案中,Request-URI和Route都作为当前路由选择的因素,而为了向后兼容RFC2543,严格路由在路由过程中修改Request-URI,导致了路由选择的混乱。虽然现在使用了松动的路由选择方式可以解决这个问题(严格区分Request-URI和Route的作用域,前者为目标地址,而Route为到达目标要经过的代理列表,后者不应影响前者),但该方式只存在于对话内的请求,而该修改实现可以参见draft-rosenberg-sip-ua-loose-route。