Skip to content

SIP 协议基础与 IP 电话原理

在进入 IMS 之前,先理解 SIP 和 IP 电话的基本工作方式会更容易。
本页从“传统电话系统”过渡到“纯 SIP 系统”,再连接回 IMS。

从传统电话到 IP 电话

  • 传统 PSTN 语音多基于电路交换,一次通话占用固定链路
  • IP 电话(VoIP)基于分组交换,把“信令控制”和“媒体传输”分开
  • 常见分工是:SIP 负责信令RTP 负责语音媒体

这也是后续理解 VoLTE/VoNR 的关键:IMS 主要增强和规范的是 SIP 信令控制链路。

什么是 SIP

SIP(Session Initiation Protocol)是文本协议,风格类似 HTTP,采用请求/响应模型。

SIP URI 格式

SIP 用 URI 标识用户和服务,基本格式:

sip:user@domain

例如 sip:alice@example.com。但在实际抓包中你会看到更复杂的形式,特别是 VoLTE 通话中的 INVITE Request-URI 和 To/From 头:

sip:+8613800001234@ims.mnc044.mcc460.3gppnetwork.org;user=phone

这里有两个常见参数:

user=phone:表示 @ 前面的部分是一个电话号码,而不是普通用户名。SIP Proxy 看到 user=phone 后知道应按电话号码路由规则处理(如查号码前缀、匹配 E.164 格式),而不是当作 SIP 账户名去 usrloc 里查找。

phone-context:当号码不是全球唯一的(非 E.164 格式,如本地分机号)时,phone-context 指明号码所属的域或拨号上下文,帮助 Proxy 理解号码含义。完整形式如:

sip:1001;phone-context=example.com@ims.example.com;user=phone

这里 1001 是一个本地号码,phone-context=example.com 说明它属于 example.com 这个拨号域。

还有一种等价的 tel: URI 格式(RFC 3966),有时会出现在 IMS 信令中:

tel:+8613800001234
tel:1001;phone-context=example.com

SIP Proxy 通常会将 tel: URI 转换为带 user=phonesip: URI 再处理。

URI 示例含义
sip:alice@example.com普通 SIP 用户
sip:+861380000@ims.example.com;user=phoneE.164 电话号码(全球唯一)
sip:1001;phone-context=home.example.com@ims.example.com;user=phone本地号码,属于指定拨号域
tel:+861380000tel URI,需转为 sip URI 路由

常见 SIP 角色

  • UA(User Agent):终端(手机、软电话)发起和接收 SIP 消息
  • Registrar:处理 REGISTER,维护用户当前 Contact
  • Proxy:按路由策略转发请求/应答
  • Redirect Server:不转发请求,返回新的目标地址给请求方

常见 SIP 方法

方法用途
REGISTER用户向 Registrar 注册当前可达地址
INVITE发起会话(语音/视频)
ACK确认最终应答(通常是 2xx)
BYE主动结束已建立会话
CANCEL取消尚未建立成功的呼叫
OPTIONS能力探测与连通性检查
UPDATE会话中更新 SDP(不创建新对话)
SUBSCRIBE订阅某个事件的状态通知
NOTIFY服务器向订阅者推送事件状态变化
PRACK确认临时应答(可靠 1xx 的 ACK)

常见 SIP 响应码

类别含义常见码
1xx临时应答(呼叫处理中)100 Trying, 180 Ringing
2xx成功200 OK
3xx重定向302 Moved Temporarily
4xx客户端请求错误401 Unauthorized, 404 Not Found, 486 Busy Here
5xx服务器侧错误500 Server Internal Error
6xx全局失败603 Decline

纯 SIP 架构(不含 IMS)

上图是最小化 VoIP 思路:终端先注册,再通过代理按位置库路由通话。

SIP 注册流程(REGISTER)

实际部署中经常会加鉴权挑战:

  • 首次 REGISTER 可能返回 401 Unauthorized
  • UA 带 Digest 凭据重发 REGISTER
  • 校验成功后返回 200 OK

SIP 呼叫建立流程(INVITE Trapezoid)

可靠临时应答:PRACK 与 100rel

在标准 SIP 中,180 Ringing 等 1xx 临时应答是不可靠的——它们通过 UDP 发送后没有确认机制,丢了就丢了。对于基本的"对方在响铃"提示这无所谓,但 VoLTE 通话中,183 Session Progress 临时应答常携带 SDP Answer(用于提前建立媒体路径、播放回铃音),丢失会导致通话无法正常建立。

RFC 3262 引入了 100rel(reliable 1xx)机制来解决这个问题。

工作原理

要点:

  • 被叫在 1xx 应答中加 Require: 100rel 头和 RSeq 序号,表示"这条临时应答需要确认"
  • 主叫收到后发 PRACK(Provisional Response ACKnowledgement),携带 RAck 头引用对应的 RSeq
  • 如果主叫不发 PRACK,被叫会重传该 1xx,直到收到 PRACK 或超时

为什么在 VoLTE 中常见

VoLTE UE 几乎都使用 100rel,因为 183 Session Progress 在 VoLTE 通话中承担重要角色:

  • 携带 SDP Answer,让双方提前协商媒体参数
  • 触发 QoS 资源建立(专用承载/QoS Flow)
  • 允许在正式接听前就播放回铃音或彩铃(Early Media)

这些都依赖 183 的可靠送达,因此 100rel + PRACK 是 VoLTE 的标准做法。

WebRTC 兼容性问题

WebRTC 客户端(如 JsSIP)通常不支持 PRACK。当 VoLTE UE 发来带 Require: 100rel 的 183,而 WebRTC 侧无法回 PRACK 时,VoLTE UE 会超时等待并最终挂断。Signal6A 的 Gateway Kamailio 通过在回复路由中剥离 RequireRSeq来解决这个问题——把可靠临时应答降级为普通临时应答,让 WebRTC 客户端能正常处理。详见 Signal6A 网关架构 - 应答处理

SIP 事件订阅:SUBSCRIBE 与 NOTIFY

除了 REGISTER(注册)和 INVITE(通话),你在 Wireshark 中很可能还会看到 SUBSCRIBENOTIFY 消息。这是 SIP 的事件通知框架(RFC 6665),用于让一方订阅另一方的状态变化。

基本流程

关键要素:

  • Event 头:指定订阅的事件类型(如 regpresencemessage-summary
  • Expires 头:订阅有效期(秒),到期前需重新 SUBSCRIBE 续订
  • 首条 NOTIFY:服务器接受订阅后必须立即发一条 NOTIFY,告知当前状态
  • 取消订阅:发送 Expires: 0 的 SUBSCRIBE 即可

在 IMS 中常见的 SUBSCRIBE

在 VoLTE 实验中你看到的 SUBSCRIBE 通常属于以下几种:

Event 类型谁发起谁响应用途
regUES-CSCF注册状态订阅:UE 注册成功后订阅自己的注册状态,S-CSCF 在注册变化时发 NOTIFY
presenceUEPresence Server在线状态:查询联系人是否在线(实验环境中较少用到)
message-summaryUE语音信箱 AS语音留言通知(MWI):有新留言时推送通知

其中 reg 事件是最常见的——VoLTE UE 注册成功后几乎都会立即发一条 SUBSCRIBE Event: reg。这是 3GPP 规范要求的:UE 需要知道自己的注册是否仍然有效,如果 S-CSCF 重启导致注册丢失,NOTIFY 会通知 UE 重新注册。

实验中的现象

在 Signal6A 或 Kamailio IMS 部署中,S-CSCF 目前对 reg SUBSCRIBE 回复 200 OK 但不发送完整的 NOTIFY(参见 Signal6A 已知问题)。某些 UE 对此容忍,某些可能因未收到 NOTIFY 而掉注册。

SIP 路由机制:Via 与 Record-Route

上面的流程中,请求经过多个 Proxy 转发,响应又能原路返回——这是靠 SIP 头域中的路由机制实现的。理解这些头域对后续阅读 CSCF 和 Kamailio 配置非常有帮助。

Via:响应的回程路径

每个 Proxy 转发请求时,会在消息顶部添加一条自己的 Via 头。被叫端收到的 INVITE 可能包含多条 Via,从上到下依次是最近的 Proxy 到最远的发起方:

Via: SIP/2.0/TCP proxyB.example.com;branch=z9hG4bK-002
Via: SIP/2.0/TCP proxyA.example.com;branch=z9hG4bK-001
Via: SIP/2.0/UDP caller.example.com;branch=z9hG4bK-000

当被叫端发送响应(如 200 OK)时,每个 Proxy 按最顶部的 Via 找到上一跳,转发后移除自己那条 Via,这样响应就沿原路一跳一跳回到发起方。

branch 参数是每条 Via 的事务标识符,Proxy 用它来将响应匹配到对应的请求事务。

Record-Route 与 Route:对话内请求的路径

Via 只解决"响应怎么回来"。但通话建立后,后续的对话内请求(如 BYEre-INVITE)怎么知道还要经过哪些 Proxy?这靠 Record-RouteRoute 头域配合完成。

建立阶段:Proxy 插入 Record-Route

Proxy 在转发 INVITE 时可以插入 Record-Route 头,声明"后续对话内请求也必须经过我"。经过多个 Proxy 后,被叫收到的 INVITE 中会积累多条:

Record-Route: <sip:proxyB.example.com;lr>
Record-Route: <sip:proxyA.example.com;lr>

200 OK 会把这些 Record-Route 原样带回给主叫。至此双方都知道了完整的 Proxy 链。

对话内:UA 发 Route 头

通话建立后,UA 发送 BYE、re-INVITE 等对话内请求时,会把之前收到的 Record-Route 反转后作为 Route 头放入请求中。这就是你在抓包中看到的 Route 头——它告诉网络"这条请求要依次经过哪些 Proxy"。

以主叫发 BYE 为例:

BYE sip:callee@10.0.0.2 SIP/2.0
Route: <sip:proxyA.example.com;lr>
Route: <sip:proxyB.example.com;lr>

每个 Proxy 收到后执行 loose_route():检查最上面的 Route 是不是自己,如果是就剥掉自己那条,按下一条 Route(或 R-URI)继续转发。

;lr 参数

Route/Record-Route URI 中的 ;lr(loose-routing)表示该 Proxy 支持 RFC 3261 的松散路由。现代 SIP 系统普遍使用 ;lr,在 Kamailio 中对应 loose_route() 函数。

IMS 中的 Service-Route

在实验中你可能注意到,手机发 INVITE 时已经带着 Route 头,但这并不是来自之前的 Record-Route——因为 INVITE 是新的对话,还没有 Record-Route 可反转。

这是因为 IMS 引入了 Service-Route:UE 注册成功时,S-CSCF 在 200 OK 中返回 Service-Route 头(指向 S-CSCF 自己),P-CSCF 也可能追加一条。UE 收到后保存下来,在后续所有新请求(INVITE 等)中自动以 Route 头插入,保证新对话的请求也经过完整的 CSCF 链。

INVITE sip:bob@ims.example.com SIP/2.0
Route: <sip:pcscf.example.com;lr>
Route: <sip:scscf.example.com;lr>

头域总结

头域谁添加何时使用作用
Via每个 Proxy 转发请求时响应回程让响应沿原路返回,每跳剥离一条
Record-Route希望留在信令路径上的 ProxyINVITE 建立对话时告知双方 UA "后续请求要经过我"
RouteUA 根据 Record-Route 或 Service-Route 生成发送请求时指定请求要依次经过的 Proxy 列表
Service-RouteS-CSCF(IMS 扩展)注册成功的 200 OK 中让 UE 后续所有新请求都经过 CSCF 链
ContactUA 或 Proxy建立对话时告知对端自己的直接可达地址

与 IMS / Kamailio 的联系

在 IMS 中,P-CSCF 和 S-CSCF 都会插入 Record-Route(Kamailio 中调用 record_route()),这样通话建立后的 BYE、re-INVITE 仍然经过 CSCF 链,CSCF 才能做媒体控制(如 rtpengine 拆除)和计费。收到对话内请求时,Kamailio 调用 loose_route() 按 Route 头逐跳转发。这些都是标准 SIP 机制——IMS 只是强制要求 CSCF 必须使用它们,并通过 Service-Route 扩展到新对话。详见 CSCF 三大网元详解

SIP 事务模型:请求、响应与 CANCEL 的路由规则

前面讲了 Via 控制响应回程、Record-Route/Route 控制对话内请求。但在实际排障中,你还需要理解更底层的问题:不同类型的 SIP 消息到底遵循什么路由规则? 响应是自动走回来的还是需要路由?CANCEL 是走新路径还是复用已有路径?ACK 为什么有时候经过 Proxy 有时候不经过?

这些问题的答案来自 SIP 的事务模型

什么是 SIP 事务

SIP 事务(Transaction)是一次请求-响应的完整交互。一个事务包含:

  • 一个请求(如 INVITE、REGISTER、BYE)
  • 该请求的所有响应(临时响应 1xx + 最终响应 2xx/3xx/4xx/5xx/6xx)
  • 对于 INVITE 事务,还包括对非 2xx 最终响应的 ACK

事务由 Via 头中的 branch 参数唯一标识。每个 Proxy 转发请求时生成新的 branch,收到响应时通过 branch 匹配到对应的请求事务。

三类消息的路由规则

SIP 中所有消息可以分为三类,每类有完全不同的路由机制:

类别消息路由方式路由依据
响应100 Trying, 180 Ringing, 200 OK, 486 Busy, ...沿 Via 逐跳回退Via 头(事务层自动处理)
事务内请求CANCEL, ACK(对非 2xx 响应)与原始请求走相同路径匹配原请求的事务状态
对话内请求BYE, re-INVITE, ACK(对 2xx 响应), UPDATE按 Route 头独立路由Route 头(来自 Record-Route)

这三类的关键区别是:谁决定消息往哪发?

  • 响应:由事务层自动按 Via 转发,Proxy 不需要做路由决策
  • CANCEL:由事务层匹配到原 INVITE 事务,发往相同目标
  • 对话内请求:由 UA 根据 Route 头构造,走正常的路由流程

响应的路由:Via 自动回退

这在 Via 章节已经介绍过基本原理。这里补充几个重要细节:

响应不经过 request_route 在 Kamailio 中,request_route 只处理请求。响应由 tm 模块(事务管理)自动按 Via 转发。你可以用 onreply_route 来修改经过的响应(如剥离头域、改写 SDP),但不需要也不应该手动路由响应——事务层已经知道该往哪发。

100 Trying 是特殊的。 Proxy 收到 INVITE 后通常立即向上游发送自己的 100 Trying(表示"我收到了,正在处理"),而不是转发下游的 100 Trying。从上游(主叫侧)来看,它收到的 100 Trying 来自直接相邻的 Proxy,而非被叫。

1xx 临时响应(180 Ringing, 183 Session Progress)正常转发。 它们沿 Via 逐跳回退到主叫。在多 Proxy 环境中(如 IMS 的 CSCF 链),被叫发出的 180 Ringing 会经过 P-CSCF → S-CSCF → I-CSCF → Gateway → 主叫,每一跳都由 tm 模块自动转发。

CANCEL 的路由:绑定到 INVITE 事务

CANCEL 用于取消一个正在进行中(已发出 INVITE 但未收到最终响应)的呼叫。它的路由规则是 SIP 中最容易误解的部分之一。

CANCEL 不是一个独立路由的请求。 它必须匹配到一个已存在的 INVITE 事务。具体来说:

  • CANCEL 的 Call-ID、From-tag、CSeq 编号和 R-URI 必须与原 INVITE 相同
  • CANCEL 的顶层 Via branch 必须与原 INVITE 相同
  • Proxy 收到 CANCEL 后,在事务层查找匹配的 INVITE 事务,将 CANCEL 转发到与 INVITE 相同的目标

CANCEL 完成后会产生两个响应:

  1. 200 OK — 对 CANCEL 本身的确认("我收到了你的取消请求")
  2. 487 Request Terminated — 对原 INVITE 的最终响应("原来的通话请求已被终止")

CANCEL 只能取消未完成的事务

CANCEL 只对正在处理中的 INVITE(已发送但未收到最终响应)有效。如果被叫已经接听(200 OK 已发出),CANCEL 不起作用——此时需要发 BYE 来结束已建立的通话。这是"取消呼叫"和"挂断电话"的本质区别。

ACK 的两种路由方式

ACK 是 SIP 中路由规则最复杂的消息,因为对 2xx 和对非 2xx 响应的 ACK 走完全不同的路径

ACK 对 2xx(通话建立成功):端到端,走对话路由

当被叫接听(200 OK),主叫发出的 ACK 是一个新事务,走对话路由——和 BYE 一样,按 Route 头(来自 200 OK 的 Record-Route 反转)逐跳转发:

这个 ACK 经过所有插入了 Record-Route 的 Proxy。这也是为什么 Kamailio 的 WITHINDLG 路由中需要处理 ACK——它和 BYE 一样是对话内请求,经过 loose_route() 转发。

ACK 对非 2xx(通话失败/拒绝/取消):事务层自动,逐跳

当 INVITE 收到 4xx/5xx/6xx 最终响应(如 486 Busy、487 Terminated、603 Decline),每个 Proxy 自动在事务层生成 ACK 发给下游,同时将该错误响应转发给上游。这个 ACK 不需要路由配置,完全由 tm 模块处理。

为什么 ACK 要区分对待?

设计原因是可靠性。2xx 响应表示通话建立成功——这个 ACK 必须端到端送达被叫,确认通话已建立。如果中间 Proxy 不在对话路径上(没有 Record-Route),ACK 就直接从主叫发到被叫。

非 2xx 响应表示通话失败——不需要端到端确认,只需要每一跳逐级确认收到了错误响应。Proxy 自动完成这件事。

完整呼叫流程中的消息分类

把以上规则应用到一个完整的呼叫流程中:

消息所属类别路由方式
INVITE初始请求Proxy 按路由逻辑决定(request_route
100 Trying响应Proxy 自动生成,不转发下游的
180 Ringing响应沿 Via 逐跳自动回退
200 OK响应沿 Via 逐跳自动回退
ACK(对 200)对话内请求按 Route 头路由(loose_route()
BYE对话内请求按 Route 头路由(loose_route()

在 IMS CSCF 链中的实际路径

将这些规则应用到 IMS 的 CSCF 链中——以 VoLTE UE 呼叫 WebRTC 用户为例,展示通话建立和取消两种场景中各类消息的路径:

场景 A:正常接听

每个节点的 onreply_route 可以修改经过的响应(如 Gateway 剥离 100rel 头、处理 SDP),但不需要决定响应往哪发——tm 模块自动按 Via 转发。

场景 B:主叫取消

CANCEL 在每个 Proxy 节点上都由 tm 模块匹配到对应的 INVITE 事务后自动转发。在 Kamailio 的 request_route 中你只需要:

if (is_method("CANCEL")) {
    if (t_check_trans())    // 找到匹配的 INVITE 事务
        t_relay();          // 转发 CANCEL
    exit;
}

不需要做任何路由决策——目标地址已经由 INVITE 事务决定了。

小结:排障思维

理解这三类路由规则后,排障时可以按类型定位问题:

症状可能的消息类型问题排查方向
主叫听不到回铃音180 Ringing 响应未到达检查 Via 路径上每一跳是否有状态转发(t_relay());检查防火墙是否阻断了响应
CANCEL 后对方仍在响铃CANCEL 未匹配到 INVITE 事务检查 CANCEL 是否到达了所有 Proxy;检查 t_check_trans() 是否成功
通话接通但无法挂断BYE 路由失败检查 Record-Route 是否正确插入;检查 loose_route() 是否成功
ACK 未到达被叫ACK(对 2xx)路由失败检查 Route 头是否正确(来自 Record-Route 反转);检查 WITHINDLG 路由

SDP 与媒体协商(Offer/Answer)

SIP 消息中的媒体参数通常由 SDP 承载:

  • m=:媒体类型与端口(如音频)
  • c=:连接地址(IP)
  • a=:媒体属性(编解码、方向、RTCP、加密参数等)

典型模式是:

  1. 主叫在 INVITE 携带 SDP Offer
  2. 被叫在 200 OK 返回 SDP Answer
  3. 双方据此确定编解码和媒体端口

RTP:媒体面

SIP 只管“怎么建立/结束会话”,不直接承载语音流。
语音通常走 RTP(或加密版 SRTP),并使用与 SIP 不同的端口和路径。

这也是为什么很多系统会出现“信令通了但没声音”:信令面(SIP)和媒体面(RTP)需分别排障。

关于 RTP 的详细工作原理、音频编解码(AMR-WB、Opus 等)、以及 rtpengine 如何作为媒体代理改变通话的媒体路径,请参见 RTP 与实时音频传输

SIP 与 IMS 的关系

IMS 不是替代 SIP,而是在运营商级网络里对 SIP 做体系化增强:

  • 增加 CSCF 链路(P-CSCF / I-CSCF / S-CSCF)
  • 用 Diameter 对接 HSS 做鉴权和用户资料管理
  • 与 PCRF/PCF 联动 QoS 策略
  • 增加 IPsec/sec-agree 等移动网络安全机制

继续阅读:

Released under the MIT License.