sip 消息路由
参考:
- rfc3261
1. 概述
sip 消息路由分为请求消息路由和响应消息路由,根据经过的路径,又可以分为代理服务器消息路由和 B2BUA 服务器消息路由。本篇博文主要记录自己对消息路由的理解。
注意,本篇文章不涉及 nat 穿越,关于 sip 下的 nat 穿越,参看:https://www.cnblogs.com/moonwalk/p/15968117.html
2. 响应消息路由
我们知道一个 sip 消息从 uac 端发送开始,就会在 via 头中新添一条记录(来自 rfc3665):
INVITE Alice -> Proxy 1
INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
via 头记录了请求发送自哪里,比如上面的示例,请求发送自 client.atlanta.example.com(也可以是 ip),5060 端口,使用 tcp 传输层协议。
每经过一个代理服务器,都会新添一条 via 记录,并放置在最上面,表示是最新的 via(来自 rfc3665,经过了 2 个代理服务器,到达 uas,这里给 top-via 增加了 received= 属性):
INVITE Proxy 2 -> Bob
INVITE sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1;received=192.0.2.222
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9;received=192.0.2.101
在响应消息(临时响应或最终响应)返回的时候,uas 会拿到 top-via,如上的示例会得到 Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1;received=192.0.2.222
,然后将响应消息根据 via 指定的 192.0.2.222:5060/tcp 方式返回:
180 Ringing Bob -> Proxy 2
SIP/2.0 180 Ringing
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
;received=192.0.2.222
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
注意,uas 并没有将 top-via 删除,而是全部 via 都返回。第 2 个代理服务器(Proxy 2)收到 180 ringing 后,会将 ss2.biloxi.example.com 的 top-via 删除,再根据新的 top-via 路由给第 1 个代理服务器(Proxy 1):
180 Ringing Proxy 2 -> Proxy 1
SIP/2.0 180 Ringing
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9;received=192.0.2.101
综上,响应消息完全就是根据 via 头来返回的,uac 和途径的代理服务器必须记录 via 头,否则可能响应消息无法正确路由回来。
3. 请求消息路由
路由简单来说就是确定消息下一跳的地址。
请求消息路由必须区分呼叫方和被叫方之间途径的是代理服务器还是 B2BUA 服务器。
3.1 AOR(address of record) 地址与 contact 地址
aor 地址用于唯一标识一个用户,contact 地址是用户的各个联系地址。
alice 拥有一部手机,一部家庭电话,一部公司电话,在 alice 向 sip 服务器注册的时候,可以这样注册:
REGISTER sip:domain.com SIP/2.0
From: alice <sip:alice@domain.com>;tag=a73kszlfl
To: alice <sip:alice@domain.com>
Contact: <sip:alice@mobile.domain.com>,
<sip:alice@home.domain.com>,
<sip:alice@office.domain.com>
Expires: 3600
那么,to header 里的 uri sip:alice@domain.com
就是 alice 的 aor 地址,3 个 contact 地址会绑定到 aor 地址,信息记录在定位服务器数据库中。
在 sip 服务器(代理服务器、B2BUA 服务器)根据定位服务器路由 sip:alice@domain.com
时,就会找到 3 个 alice 的具体联系地址。
这些 contact 地址拥有过期时限,一般为 3600 秒,因此 alice 需要每隔一段时间就需要刷新注册。
注册时的 contact 地址与 invite 时的 contact 地址不一样,invite 时的 contact 地址主要用于被叫方会话内请求设置的目的地址。比如 alice 通过 sip:alice@mobile.domain.com
移动电话呼叫 bob,那么 bob 发送 bye 的时候就会发到 sip:alice@mobile.domain.com
,而不是发到 sip:alice@domain.com
。
注册服务器、定位服务器、代理服务器关系简单如下所示(http://telecomtechsource.blogspot.com/2015/06/lesson-5-sip-registrar-server-and.html):
3.2 uac 请求消息路由
user agent 在发送请求之前,可以通过 request-uri 和 route header 来确定下一跳的地址,route header 具有高优先级,没有 route header 时只能通过 request-uri 来确定下一跳地址。
3.2.1 request-uri
一般 uac 在呼叫某个 uas 之前,会知道 uas 的 uri(例如 alice@domain.callee.com),但是这个 domain.com 域具体指向哪台 sip 服务器(ip、端口等)是不知道的。这时可以构造 request-uri:
INVITE sip:alice@domain.callee.com SIP/2.0
3.2.2 没有 route header
如果 uac 没有设置 route header,那么 user agent 只能通过 request-uri 进行路由:
- 如果是域名,需要查询配置的 dns/location server 服务器,得到 ip 地址和端口(默认5060)、传输协议(默认 udp)
- 如果是 ip,直接发到这个 ip 即可
3.2.3 有 route header
uac 端配置 route header 分为几种情况:
- 通过 invite 呼叫的 Record-Route 得到路由集,会话内请求将路由集填写到 route header 中去
- 初始请求时,uac 配置了 outbound proxy
- 初始请求时,uac 配置了 B2BUA server
根据 Record-Route 得到路由集,通过逗号分割,依次对应每一跳服务器的地址:
Route: <sip:proxy.caller.com;lr>,
<sip:proxy.callee.com;lr>
在 uac 端配置了 outbound proxy,确定下一跳的地址:
Route: <sip:proxy.caller.com;lr>
在 uac 端配置了 B2BUA server,确定下一跳的地址:
Route: <sip:b2bua.com;lr>
3.3 代理服务器的路由
代理服务器的路由分为严格路由(strict routing)和松散路由(louse routing):
- 严格路由(strict routing),请求的 request-uri 中的 domain 必须是本服务器负责的域,必须通过 route header 进行路由
- 松散路由(louse routing),请求的 request-uri 中的 domain 不需要是本服务器负责的域,可以通过 route header 和 request-uri 进行路由
对于松散路由,在 rfc3261 16.12 节有详细描述:
- 首先检查 request-uri 的 domain 是否是自己负责的域,如果不是,不进行任何处理。如果是,需要通过定位服务器查找目标用户的联系地址(contact address),并替换 request-uri
- 然后查看 top-route-header 是否指向的是自己,如果是,移除之(本路由节点已经到达)
- 再查看新的 top-route-header,如果还有,路由到下一跳地址;如果没有,通过 dns/定位服务器查找 request-uri 指向的地址,然后路由过去
3.3.1 forking
这里有一个 forking 的概念,即对于松散路由服务器,通过定位服务查找到的目标用户联系地址可能有多个(例如 alice 有手机、家庭座机、公司座机等,alice 通过 REGISTER 将信息上报给注册服务器/定位服务器),这时只能进行多次呼叫,有两种策略:
Parallel Forking,即同时呼叫多个 contact,谁先回应 200ok,就建立与谁的最终通过,其它的呼叫发送 CANCEL 取消呼叫(https://www.tutorialspoint.com/session_initiation_protocol/session_initiation_protocol_forking.htm):
Sequential Forking,即一个个串行呼叫,直到打通或者全部失败为止(https://www.tutorialspoint.com/session_initiation_protocol/session_initiation_protocol_forking.htm):
3.4 B2BUA 服务器的路由
背靠背服务器不同于代理服务器,其是具体业务相关的。代理服务器更多是按照 rfc3261 规定的内容进行消息路由,最多提供 nat 穿透、负载均衡等功能。而 b2bua 服务器具有很大的自由度,能够根据业务需求设计各种功能,其路由规则根据业务来决定。
当一个请求发送到 b2bua 服务器后,服务器会根据请求类型和负载内容(sip body),来决定是否路由消息还是直接回应响应。其中一种典型功能是 3pcc,即第三方呼叫请求(来自 rfc3725):
A Controller B
|(1) INVITE no SDP | |
|<------------------| |
|(2) 200 offer1 | |
|------------------>| |
| |(3) INVITE offer1 |
| |------------------>|
| |(4) 200 OK answer1 |
| |<------------------|
| |(5) ACK |
| |------------------>|
|(6) ACK answer1 | |
|<------------------| |
|(7) RTP | |
|.......................................|
上图中 Controller 即是 b2bua 服务器,其提供了 rpc 接口,第三方用户可以通过 rpc 接口(by http/ws 等),建立 A 和 B 之间的流媒体通信:
- b2bua 给 A 发送一个没有 sdp 的 invite
- A 回应 200ok 并携带 offer1
- b2bua 发送 invite 给 B 并携带 offer1
- B 回应 200ok 并携带 answer1
- b2bua 回应 ack 给 B,对 B 的呼叫完成,这时 B 会开始发送流媒体数据给 offer1 中说明的地址,并准备接收流媒体数据从 answer1 中说明的地址
- b2bua 回应 ack 给 A 并携带 answer1,这时 A 会开始发送流媒体数据给 answer1 中说明的地址,并准备接收流媒体数据从 offer1 中说明的地址