sip 中 transport && transcation 协议层
参考:
- rfc3261
1. 概述
sip(Session Initiation Protocol,会话初始化协议)协议应用广泛,比如软电话、音视频会议、可视对讲、安防监控等领域。
sip 协议主要由 rfc3261 文档描述其各种标准行为,这篇文章主要记录自己对于 sip 协议中 transport layer(传输层)和 transcation layer(事务层)的理解。
2. sip 协议分层
sip 是一个应用层协议,工作在 tcp/udp 传输层协议之上,其典型协议结构如下:
其中,syntax and encoding 这一层用于实现对基于文本的 sip 消息的语法解析与编码:
- 当收到 sip 消息时,需要根据 sip 语法 parse(解析) 字节流,得到一些列易于访问的结构(struct)供上层逻辑使用。
- 上层逻辑需要发送 sip 消息时,都是先填写一些易于访问的结构(struct),然后需要将这些结构对象根据 sip 语法编码(encoding) 出文本/字节流 才能发送到网络中。
实际上个人觉得 syntax and encoding layer 应该在 transport layer 之上。
3. transport layer
transport layer 类似于一个应用层网络库,向上对 tcp/udp 协议进行封装,提供接口以便上层逻辑更好的使用。transport layer 可以提供如下功能:
- 加密传输(TLS)功能。对 sip 报文采用 TLS 进行加密,提高安全性;对收到的报文进行解密,得到 plaintext 报文
- TCP 传输时,处理粘包问题
- 选择正确发送数据的方式,此功能较为复杂,下面几节进行描述
3.1 client transport layer
client 用于发送请求,接收响应,sip 服务默认监听在 5060 端口,sip over tls 默认监听 5061 端口。在 rfc3261 18.1 节中对此有详细描述。
3.1.1 发送请求
不考虑 tls,client 必须选择是通过 udp 还是 tcp 进行数据发送,为此,有如下规则:
- 如果指定了使用 udp 发送,但是待发送的报文有超过 MTU 被 ip 分片的风险,那么 transport layer 将自动采用 tcp 发送
- 如果 tcp 连接失败,则继续采用 udp 发送
- 如果用户指定了使用 tcp 发送,则使用 tcp 进行发送
- 如果用户没有指定发送方式,则默认采用 udp 发送,然后参考第一条规则
3.1.2 接收响应
- 如果请求是采用 tcp 发送的,那么响应也应该通过同一条 tcp 连接发送回来
- 如果 tcp 连接中途断开了,那么 server 端会尝试重连,重连的目标 ip 和 port 参看下面的 响应消息路由 一节
- 如果请求是采用 udp 发送的,响应参看下面的 响应消息路由 一节
3.2 server transport layer
server 用于接收请求,发送响应,在 rfc3261 18.2 节中对此有详细描述。
3.2.1 接收请求
- server 端的监听端口必须同时提供 udp 和 tcp 服务,以兼容 client 发送消息的规则
- 为 via 头填充 received 参数
3.2.2 处理 via 头
client 发送的请求会加上一个 via 头来说明 client 的地址信息,server 收到请求后,会检查 via 头,并决定是否在 via 头上面加上 received 参数:
- server 端无论是 tcp 还是 udp 接收数据,都能知道请求的源 ip 地址和 port,received 参数即源 ip 地址
- 如果 via 头的 host 是一个域名,不是一个 ip 地址,那么会添加 received 参数
- 如果 via 头的 host 是一个 ip 地址,但是与源 ip 地址不一致,也会添加 received 参数
在 rfc3261 18.2.1 章对此有详细描述,下面是一个示例:
Consider a request received by the server transport which looks like,
in part:
INVITE sip:bob@Biloxi.com SIP/2.0
Via: SIP/2.0/UDP bobspc.biloxi.com:5060
The request is received with a source IP address of 192.0.2.4.
Before passing the request up, the transport adds a "received"
parameter, so that the request would look like, in part:
INVITE sip:bob@Biloxi.com SIP/2.0
Via: SIP/2.0/UDP bobspc.biloxi.com:5060;received=192.0.2.4
下面会出现一个 sent-by 的参数,此参数即 via 头中的 host 和 port 信息。
3.2.3 消息响应规则
不考虑 tls,暂时也不考虑 rport 机制,rfc3261 定义了发送响应的如下规则:
- 如果 client 是通过 tcp 发送的请求,那么 server 优先通过同一个 tcp 连接发送响应
- 如果 tcp 连接已经断开了,且 via 头有 received 参数,那么 server 会尝试与 received 参数指定的 ip 地址和 sent-by 参数指定的 port 建立一个新的 tcp 连接
- 如果 tcp 连接已经断开了,且 via 头没有 received 参数,那么 server 会尝试与 sent-by 参数指定的 ip 地址和 sent-by 参数指定的 port 建立一个新的 tcp 连接、
- 如果 tcp 连接已经断开了,但是 sent-by 没有指定 port,那么默认发到 5060 端口
- 如果 tcp 连接建立失败,返回错误给用户
- 如果 client 是通过 udp 发送的请求,且 via 头有 received 参数,那么 server 会发送响应到 received 参数指定的 ip 地址和 sent-by 参数指定的 port 上
- 如果 client 是通过 udp 发送的请求,且 via 头没有 received 参数,那么 server 会发送响应到 sent-by 参数指定的 ip 地址和 sent-by 参数指定的 port 上
- 如果 client 是通过 udp 发送的请求,但是 sent-by 没有指定 port,那么默认发到 5060 端口
- 如果 udp 发送消息失败,返回错误给用户
可见,消息响应发送规则不像发送请求一样,可以由 transport 决定切换传输层协议(tcp or udp),响应消息必须与请求使用相同的传输层协议。
4. transaction layer
事务层工作在 transport layer 之上,用于处理事务关系。
在 sip 中,一个事务是指:一个请求 + 零个或多个中间响应 + 一个或多个最终响应。
属于同一个事务的 sip 消息通过 via 头的 branch 属性来进行区分,如:
Via: SIP/2.0/UDP erlang.bell-telephone.com:5060;branch=z9hG4bK87asdks7
通过对比 branch 属性值,就能知道是否是同一个事务的不同消息。
一般用户编程不会直接用到事务层的接口,但是事务层提供有如下功能:
- 自动将消息进行事务匹配,然后将事务分类信息传递给 transaction user 层
- 进行消息重传
事务消息可以简单分为两类:
- 非 invite 事务,例如 message、register、bye 等,他们的特点是没有 ack 报文。因此他们的事务结构比较简单,从 message 请求开始一个事务,回复 100 trying,回复 2xx、4xx、5xx 等结束事务
- invite 事务,较为复杂,在下面进行描述
4.1 invite 事务
一个呼叫失败的例子:
Alice Bob
| INVITE |
|----------------------->|
| 100 Trying |
|<-----------------------|
| 180 Ringing |
|<-----------------------|
| CANCEL |
|----------------------->|
| 200 OK |
|<-----------------------|
| 487 Request Terminated |
|<-----------------------|
| ACK |
|----------------------->|
- invite 请求开始一个事务
- server 回复的 1xx 在 invite 事务中,但是后面的消息就需要小心了
- 这时 client 发过来一个 cancel,此 cancel 是一个独立的事务的开始,server 回复 200ok 结束这个 cancel 事务
- cancel 后,server 回复 487 呼叫失败,此回复属于 invite 事务。接着 client 发送 ack,此 ack 属于 invite 事务
一个呼叫成功的例子:
Alice Bob
| INVITE |
|----------------------->|
| 100 Trying |
|<-----------------------|
| 180 Ringing |
|<-----------------------|
| 200 OK |
|<-----------------------|
| ACK |
|----------------------->|
- invite 请求开始一个事务
- server 回复的 100 trying 和 3xx 都在 invite 事务中
- server 回复 2xx 结束这个 invite 事务
- 接着 client 发送 ack,此 ack 属于一个独立事务
4.2 ACK 事务
需要结合上面的描述。ack 只出现在 invite 呼叫中。判断一个 ack 是否属于某个 invite,有如下规则:
- 如果呼叫失败,则 ack 属于 invite 事务
- 如果呼叫成功,ack 自己属于一个独立事务。
4.3 非 invite 消息的重传
在 rfc3261 17.1.2 章节中,对此有非常详细的描述。
非 invite 消息,例如 message、register、bye 等,这类消息的特点是没有 ACK 报文。分析重传时,需要区分发送端和接收端。
发送端重传规则如下:
- 如果是 tcp 传输,不进行重传,下面的情况都是 udp 下的
- 前 4 次重传间隔分别为 500ms、1s、2s、4s,后面重传间隔都是 4s
- 如果收到 1xx 临时响应,则后面的重传间隔都是 4s
- 从发出请求开始,超过 32s 都没有最终回应,则事务超时,返回超时错误给 transport user 层(不区分udp、tcp)
接收端重传规则如下:
- 接收到请求后,将请求消息上报给 transport user 层,等待 transport user 层回复临时响应或最终响应
- 如果等待过程中,有 client 重传请求,不会将重传请求消息上报给 transport user 层
- transport user 层回复 1xx 临时响应,什么都不会改变
- transport user 层回复最终响应,事务进入一个 32s 的最后计时
- 在 32s 计时过程中 transport user 层再次回复最终响应,则回报一个错误
- 在 32s 计时过程中,如果这时有 client 重传请求,则重传请求
- 如果 32s 计时完成,则事务彻底结束。如果在这之后有 client 重传请求,则属于一个新的请求事务
4.4 invite 消息重传
在 rfc3261 17.1.1 章节中,对此有非常详细的描述。
分析重传时,需要区分发送端和接收端。
发送端重传规则如下:
- 如果是 tcp 传输,不进行重传,下面的情况都是 udp 下的
- 在收到 100 trying 之前,经历 7 次重传,每次重传间隔为 2^n * 500 ms(n=[0, 6])
- 经历 32s 都没有收到 100 trying 或者最终应答,事务超时,返回 transport user 超时错误
- 在 32s 间收到 100 trying,则停止 32s 定时器,这时 invite 事务再也不会超时,会一直等待 server 最终应答(无论是成功的还是失败的)
- 如果收到最终应答,由 transcation user 发送 ack 响应。如果因为 ack 丢失,接收端重发最终响应,发送端也需要重发 ack 响应,直到 64s 超时(从接收到第一个最终响应开始计时),最后发送 bye 结束 invite 会话
接收端重传规则如下:
- 接收到请求后,将请求消息上报给 transport user 层,等待 transport user 层回复 100 trying 或最终响应
- 如果 200 ms 内 transport user 层没有回复 100 trying 或最终响应,事务层必须自动回复 100 trying
- 回复 100 trying 后(无论是事务层自动回的还是 transport user 层回复的),invite 事务将一直等待 transport user 发出最终响应
- 如果等待过程中,有 client 重传请求,不会将重传请求消息重复上报给 transport user 层,而是自动回复 100 trying
- 后面的重传规则取决于最终响应是成功(2xx)还是失败(3xx-6xx)响应,下面分别讨论
接收端 2xx 成功响应后重传规则如下:
- 成功响应后,invite 事务结束,如果因为 2xx 丢包或其它原因没有收到 ack 响应,transcation layer 会进行重传。前 4 次重传间隔分别为 500ms、1s、2s、4s,后面重传间隔都是 4s
- 在 64s 后超时,最后需要发 bye 结束 invite 会话
接收端 3xx-6xx 失败响应后重传规则如下:
- 失败响应后,收到 ack 之前,会进行重传。前 4 次重传间隔分别为 500ms、1s、2s、4s,后面重传间隔都是 4s
- 在 32s 后终止 invite 事务