一、为什么 SIP 需要专门讨论路由
SIP 不是一个只靠源地址和目的地址就能完成通信的协议。它在应用层中显式携带了大量与路由相关的信息,例如 Request-URI、Via、Route、Record-Route、Contact 等,因此同一个请求“发给谁”“经过谁”“后续请求走哪条路”,都可能由不同字段共同决定。
也正因为如此,SIP 的路由机制比很多常见应用协议更容易让人混淆。很多抓包里看起来像“地址写重复了”,其实每个字段承担的职责并不一样。
二、先分清几个最核心的字段
1. Request-URI
Request-URI 用来描述当前这个请求逻辑上要送达的目标对象。代理服务器在收到请求后,通常会根据本地路由策略、注册绑定关系或位置服务,把请求继续转发给下一跳。
2. Via
Via 主要记录请求经过了哪些节点,更偏向“响应回程路径”和事务层匹配,不等同于对话层里的后续路由。
3. Contact
Contact 表示一个 UA 希望对端后续直接联系自己的地址。它更像“我之后可被访问的地址”,而不是“当前请求必须经过的路径”。
4. Route
Route 是后续请求转发时显式指定的路由集合。它告诉 UA:这次请求在送往最终目标前,应先经过哪些路由节点。
5. Record-Route
Record-Route 由代理在初始请求经过时插入,用来声明:后续对话内请求仍应把我保留在路径中。它是对话内路由保持一致性的关键机制。
三、初始请求与对话内请求的路由差异
SIP 的路由机制要分成两个阶段理解:
- 初始请求:例如首个 INVITE,重点看 Request-URI 如何被定位、代理如何转发。
- 对话内请求:例如 ACK、BYE、re-INVITE、UPDATE,重点看 Route 集合和远端 Contact 如何共同决定后续路径。
很多人误以为后续请求永远直接发给最初 INVITE 里看到的那个地址,这在很多场景下并不成立。只要中间代理通过 Record-Route 要求自己留在路径上,后续请求就仍然要经过它。
四、Record-Route 和 Route 是如何配合的
在初始请求经过代理时,代理可以插入 Record-Route。等对话建立后,UA 会根据这些 Record-Route 条目生成路由集合,并在后续请求中带上 Route 头域。
所以可以把两者理解为:
- Record-Route:代理在初始阶段声明“后面也要经过我”。
- Route:UA 在后续请求里真正执行这条声明出来的路径。
五、Contact 为什么经常被误解
Contact 很容易被误当成“当前这一跳的目标地址”。实际上它更偏向终端在对话或注册关系中的可达地址声明。
例如:
- 在 REGISTER 中,Contact 表示注册用户可被送达的地址;
- 在 INVITE / 200 OK 中,Contact 常被用于后续对话内请求的目标联系点;
- 但如果路由集合中仍存在代理节点,后续请求不一定直接“跳过中间代理去打 Contact”。
六、松散路由与严格路由
现代 SIP 实现中更常见的是松散路由(Loose Routing),即由 Route 集合控制下一跳,而 Request-URI 更多保留逻辑目标语义。历史上还存在严格路由(Strict Routing)模型,但如今更多是协议理解上的背景知识。
从工程角度看,理解松散路由即可:Route 控制沿途经过谁,Request-URI 表示逻辑上想找谁。
七、为什么后续请求不一定走和初始 INVITE 完全相同的网络层路径
即使逻辑路由链一致,网络层上的实际转发路径也可能受 DNS、负载均衡、NAT、传输层连接复用等因素影响而变化。所以分析 SIP 路由时,要区分:
- 协议层声明的逻辑路径
- 网络层真实发包路径
这也是为什么抓包时不能只看 IP 五元组,还要结合 SIP 头域一起分析。
八、抓包时最值得先看的点
- 当前请求是不是初始请求还是对话内请求
- Request-URI 指向谁
- Route 集合是否存在
- Record-Route 是谁插入的
- Contact 是否在后续请求中被当作远端目标
- Via 体现的响应返回路径是否正常
九、总结
SIP 路由机制之所以显得复杂,是因为它不是由单个字段独立决定,而是由 Request-URI、Route、Record-Route、Contact、Via 等多层信息共同作用。真正理解这套机制,关键在于区分:
- 谁是逻辑目标
- 谁是下一跳
- 谁负责后续请求继续留在路径中
- 谁只是用来描述响应回程或事务匹配
只要把这几个角色拆开看,SIP 的路由行为就会清晰很多。