先交代一下背景:我在某运营商的IMS专项里遇到过一种很尴尬的故障——用户投诉“打电话听不到彩铃,只能听到嘟嘟声”,底下的同事抓了一周包也没定位,最后发现不是核心网的问题,而是主叫侧终端在183到来之前先收到了180,自己把本地回铃音放出来了。从那之后我养成了一个习惯:判断放音正不正常,不再靠耳朵,而是靠Wireshark。
很多人处理放音问题,第一反应是去听录音文件、翻媒体服务器日志,但信令里其实已经把答案写得很清楚。彩铃、回铃音、广告音、语音提示,这些放音在IMS网络里的共同技术特征是early media。真正决定它能不能被用户听到的,是183 Session Progress里带不带SDP、媒体方向对不对、UPDATE有没有把早媒体切换成正常通话媒体、200 OK有没有让会话收敛。这套链路拆解一遍,比盲听十段录音都有用。
这次我就借一个典型的IMS放音场景,把183、UPDATE、200 OK这条信令链路完整拆一遍。刚接触SIP的读者可以从原理部分看起,已经在做VoIP运维和测试的朋友可以直接跳到第3节看包级拆解,后面还附了我自己的过滤模板和排障顺序。
1. 动手之前:搞清楚放音信令到底在传什么
1.1 “嘟嘟”声与彩铃本质上不是一回事
先厘清一个基本概念:你平时电话里听到的“嘟嘟”声,在SIP信令上通常叫做本地回铃音。它可以是交换机侧生成本地播放,也可以是终端在收到180 Ringing之后自己产生的。关键问题是,这种本地回铃音不产生RTP媒体流,也就是说,你在抓包里根本看不到它。所以只听“嘟嘟”声,只能说明呼叫流程走到了振铃阶段,不能说明网络侧放音功能是正常的。
彩铃、语音提示这类放音属于early media,也就是在最终应答200 OK之前,由网络侧的媒体服务器通过RTP协议,把音频数据包实时送到主叫侧。SIP信令上最典型的标志就是183 Session Progress,它通常携带SDP,告诉主叫侧“接下来需要用哪个IP、哪个端口、哪种编码来接收媒体”。如果终端正确处理,就会播放网络侧送来的音频;如果终端实现不严谨,就可能在183之前收到180后直接播放本地回铃音,这正好解释了为什么很多用户抱怨“有嘟嘟声,没彩铃”。
1.2 183、UPDATE、200 OK各自的任务
183 Session Progress属于1xx临时响应,它的含义是“呼叫还在进行中,但已经有媒体能力可以协商了”。它绝对不等于被叫已经接通,所以主叫客户端不能把183当作呼叫建立信号,界面上不应该显示“通话中”。
UPDATE方法定义在RFC 3311,它的价值在于:最终响应还没发出之前,如果需要对正在进行的会话参数做修改,比如把单向的放音流切换成双向通话流,就可以用UPDATE。为什么这里不直接使用re-INVITE?因为在早期对话也就是early dialog里,INVITE事务还没有收到最终响应,不少实现不允许在这个阶段发起常规的re-INVITE。UPDATE就是专门为“最终应答前更新会话”设计的动作。
200 OK是INVITE事务的最终响应,代表被叫侧接受呼叫,SIP对话正式建立。主叫收到200 OK后回ACK,媒体进入正常通话。站在放音的角度看,这条200 OK就是早媒体会话结束的边界线。
1.3 为什么抓包要同时抓信令和媒体
如果只过滤SIP消息,你只能看到各方“协商了什么”,看不到放音是否真的送到主叫手机。比如网络侧发了183且SDP完整,但媒体包到达主叫侧时被防火墙丢了,用户听到的还是本地嘟嘟声。这时候SIP链路看什么都正常,RTP统计里却一条流都没有。只凭信令判断,很可能把问题误判到核心网头上。
所以抓包时要把SIP和RTP一起抓。过滤表达式里要么同时保留sip和rtp,要么用UDP端口范围覆盖媒体端口。后面第2节会具体讲命令怎么写,第3节会讲怎么看RTP统计。
2. 抓包环境准备,别在开局就漏包
2.1 抓包点怎么选才能不被冤枉
抓包点选错了,后面所有分析都建立在错误假设上。我接排障单时,第一步先确认这个包要在哪里抓,而不是着急开Wireshark。
- 主叫侧终端抓包:能确认终端有没有收到RTP、有没有正确处理SDP方向,适合排查终端本地放音、编解码不匹配、NAT穿越等问题。
- 信令面镜像口:能看到完整SIP事务,适合定位是媒体服务器没触发放音,还是P-CSCF/S-CSCF没有把183转发下去。
- 媒体面镜像口:把媒体服务器到主叫方向的流量镜像下来,确认RTP实际有没有发出。
日常排障我习惯先抓SBC/P-CSCF侧,因为这里信令和媒体都能同时看到。有个非常常见的坑:镜像口只配置了信令端口5060,没有把动态RTP端口也镜像出来,结果Wireshark里全是SIP、一个RTP包都没有,然后开始怀疑媒体服务器没发媒体。建议抓包前先跟网络同事确认一下镜像策略覆盖了哪些端口。
2.2 用tcpdump分场景抓包,命令其实很简单
测试环境里直接在AS或者SBC侧抓包,我最常用的命令是这样:
tcpdump -i any -s 0 -w /tmp/ims_media.pcap -v 'udp port 5060 or udp portrange 10000-20000'-s 0意思是抓完整帧,不截断。RTP端口范围按实际SDP动态端口调整。如果现场已经知道呼叫双方IP,直接用主机过滤:
tcpdump -i eth0 -s 0 -w /tmp/ims_a_call.pcap host 192.0.2.10 and not port 22这样避免把SSH控制流量也抓进来。生产环境抓包要特别谨慎,SIP和RTP流量大,文件增长很快。可以加上-G 3600 -W 12做小时轮转,每个文件1小时,保留12个文件,这样不会把磁盘写爆。抓包操作必须在有授权的网络环境里做,不要对着无关流量乱抓。
2.3 几分钟定位183/UPDATE/200 OK的过滤条件
Wireshark打开抓包文件后,很多人习惯直接输入sip,结果看到几百条消息头都大了。更高效的做法是按状态码和方法过滤:
- 只看183响应:
sip.Status-Code == 183 - 只看UPDATE请求:
sip.CSeq.method == "UPDATE" - 只看200响应:
sip.Status-Code == 200 - 按单个呼叫聚合:
sip.Call-ID == "abc123@192.0.2.1" - 组合过滤:
sip && (sip.Status-Code == 183 || sip.Status-Code == 200 || sip.CSeq.method == "UPDATE")
定位方法很简单:先打开Telephony→VoIP Calls,找到目标呼叫的起始时间,双击进去会生成一个包含这个呼叫全部消息的视图。也可以从任意一条SIP消息里右键复制Call-ID,再作为过滤条件使用。这样后续所有分析都限制在同一个呼叫内,不会混入其他呼叫的干扰包。
2.4 时间戳与解码设置,细节决定排障效率
Wireshark默认显示绝对时间,跨网元看消息顺序时非常不直观。我每次分析信令都会先把View→Time Display Format改成Seconds Since Previous Displayed Packet,这样每条消息之间的间隔一眼就能看出来。比如183之后隔了300毫秒才来UPDATE,这些时间关系往往比状态码本身更能说明问题。
如果网元之间走的是SIP over TLS或DTLS-SRTP,Wireshark里只能看到TLS Application Data,看不到SIP明文。这时需要拿到TLS密钥,在Edit→Preferences→Protocols→TLS里配置,或者用Wireshark的Decode As把流量强制解码成SIP。没有密钥的话,最现实的办法是让网元临时关掉加密抓一条测试包,或者在应用层导出信令日志。
3. 183/UPDATE/200 OK实战拆解:一次完整的放音会话
3.1 一个可以复现的模拟呼叫结构
下面用一组文档地址IP来还原现场,避免直接暴露生产环境。主叫UE-A是192.0.2.10,网络侧媒体服务器/AS是192.0.2.20,被叫UE-B是192.0.2.30。这个案例里183没有要求100rel,所以不需要PRACK。如果你的环境里183带了Require: 100rel,流程里会多出PRACK和对应的200 OK,不影响主轴。
主叫UE-A 192.0.2.10 网络侧/媒体服务器 192.0.2.20 被叫UE-B |-------- INVITE (SDP offer) ---------->| |<------------- 100 Trying ------------------| |<-- 183 Session Progress (SDP answer, a=sendonly) ---| |<-- UPDATE (SDP update, a=sendrecv) ----------| |-------- 200 OK (UPDATE) --------------->| |<-- 200 OK (INVITE, final SDP) ----------| |-------- ACK --------------------------->|被叫B振铃期间,AS通过183把放音媒体送给主叫A。被叫摘机后,AS通过UPDATE把媒体方向从单向放音切换成双向通话。INVITE的200 OK随后到达,主叫回ACK,整个early media会话收敛。
3.2 从183读放音的关键字段
183这条消息,不要只看状态行里有“183”就够了,真正重要的是SDP部分。最关键的几个字段我一般这样看:
| 字段 | 示例值 | 观察点 |
|---|---|---|
| Status-Line | SIP/2.0 183 Session Progress | 是否为183 |
| From/To tag | tag=main; tag=callee | 早期对话的两个tag |
| CSeq | 1 INVITE | 说明它是INVITE的临时响应 |
| Call-ID | abc123@192.0.2.1 | 聚合呼叫的唯一标识 |
| Content-Type | application/sdp | 是否携带SDP |
| Connection Information | c=IN IP4 192.0.2.20 | 媒体流向的IP |
| Media Description | m=audio 10010 RTP/AVP 97 | 媒体端口和RTP payload类型 |
| Attribute | a=sendonly | 放音方向 |
很多人在SDP方向属性上栽跟头。SDP方向属性是从发送这条SDP的一方视角来描述的。如果AS下发的183里带a=sendonly,意思是AS只发送,主叫只接收,这正是放音到主叫侧的姿势。如果看到a=recvonly,方向就反了,AS在等主叫发媒体,主叫当然听不到放音。根据RFC 4566的默认规则,没有方向属性时默认是a=sendrecv,所以AS只想发的时候必须显式写sendonly。
3.3 UPDATE是早对话里的“改配置”动作
在183建立了early dialog、但INVITE最终响应还没到的时候,媒体参数要变化,UPDATE就是标准动作。实战中,AS给主叫发UPDATE,请求行通常是UPDATE sip:user@192.0.2.10 SIP/2.0,CSeq会是2 UPDATE,与INVITE的CSeq严格区分。
看UPDATE时重点是两个地方。第一是CSeq里的method必须是UPDATE,不要和后面的200 OK INVITE搞混。第二是SDP的变化,典型变化是方向属性从a=sendonly变成a=sendrecv,同时媒体端口可能改变,编解码列表也可能从纯语音改成语音加DTMF。这意味着从这一刻起,主叫和AS之间的媒体流从“只听不放音”变成“双向通话”,原本的彩铃流应该被停掉。
如果UPDATE发送后没有得到200 OK响应,哪怕INVITE最终成了,主叫侧也可能停在一个不健康的媒体状态。比如有些终端不支持UPDATE,会回501 Not Implemented或405 Method Not Allowed,这就要查终端能力协商或者AS的异常处理策略。
3.4 200 OK与媒体边界:别把两个200 OK混在一起
这次实战里会出现两个200 OK,一个是UPDATE的200 OK,一个是INVITE的200 OK。两者在Wireshark里显示的状态行完全一样,不仔细看CSeq很容易搞混。
| 事务 | CSeq示例 | 含义 | 过滤方法 |
|---|---|---|---|
| UPDATE 200 OK | 2 UPDATE | AS发起的UPDATE已成功,媒体参数切换完成 | sip.Status-Code == 200 && sip.CSeq.method == "UPDATE" |
| INVITE 200 OK | 1 INVITE | 呼叫被接受,INVITE事务最终成功 | sip.Status-Code == 200 && sip.CSeq.method == "INVITE" |
正确区分这两个200 OK,是这个标题里的隐藏考点。你排查“放音没有停止”类问题时,如果只看到状态行200 OK就去查AS,很容易查错方向。先用CSeq.Method过滤,确认是INVITE的200 OK还是UPDATE的200 OK,再去对应看媒体状态。
收到INVITE的200 OK后,主叫回ACK,SIP对话正式建立,AS应当停止放音媒体。如果此时RTP流还在继续朝主叫方向发送,那就是媒体没有收敛,需要到AS侧查放音任务释放逻辑。
3.5 用RTP统计确认放音真的送到了
信令分析到最后,我总会打开Telephony→RTP→RTP Streams看一眼实际媒体流。在RTP Streams窗口中,能看到每条流的源IP、目的IP、SSRC、Payload类型、包数、丢包数和抖动。
实操步骤是:找“192.0.2.20 → 192.0.2.10”这条流,媒体端口应该和183 SDP里m=audio的端口一致。正常放音通常是20毫秒一个RTP包,包数会随着放音时长持续增长。如果RTP Streams窗口里根本没有这条源IP的流,说明媒体没有进入抓包点,要么是媒体服务器没发,要么是镜像口没有覆盖媒体端口。如果能看到RTP流但丢包率很高,再看包间隔,出现接近一秒的空洞,基本可以确定物理链路或者网元转发有排队问题。
RTP包内容不一定需要听,先看统计就能判断八成问题。Wireshark也支持对RTP流做播放,但实际排障里播放音频往往很失真,统计数字反而更可靠。
4. 放音异常排查:听不到、只响一声、提前接通的诊断思路
4.1 我遇到过的三类典型症状
症状一:完全听不到彩铃或回铃音。先过滤sip.Status-Code == 183。如果183都没有,重点查AS有没有触发放音,或者P-CSCF有没有放行这个1xx响应。如果有183但没有RTP,重点查SDP里的IP端口能不能通、镜像口有没有覆盖媒体端口。如果有RTP但终端没声音,重点查编解码匹配和终端本地回铃音逻辑。
症状二:只响了一声就安静了。这种情况我见过好几次,常见原因有两种:一是媒体服务器只播了一个提示音文件,播完就停了,而后续正常通话媒体没有接上,根因可能在AS放音脚本;二是UPDATE来得太早,主叫刚收到放音媒体就被切到了双向通话,但通话双方其实都还没准备好,终端表现就是“一声之后没动静”。判断方法是在RTP流上做时间轴对照,看RTP持续时长和UPDATE时间点是否吻合。
症状三:主叫界面很快就显示“通话中”,但双方没声音。这是典型的终端把183当成了最终应答,过早进入通话态。信令上有个隐蔽特征:主叫在183之后直接回ACK,而不是等INVITE的200 OK。查这个问题时,不只要看核心网,还要看终端应用对early media状态机的处理逻辑。
4.2 信令正常但听不到音,先别怀疑核心网
有次在一个测试环境里,SIP流程完全漂亮:183带SDP、UPDATE正常、200 OK也到了,但就是听不到彩铃。最后发现AS下发的183里方向属性是a=recvonly,AS视图上它只接收不上行媒体,但实际放音媒体又往主叫方向发,两个行为互相矛盾。终端严格按照SDP方向属性判断,认为这条路只能接收,于是把收到的RTP当垃圾丢弃,用户自然听不到声音。
这种问题在抓包里很容易被忽略,因为RTP流确实存在,只按IP和端口过滤完全看不到异常。所以每次看SDP,我都会把方向属性单独列出来,和RTP实际传输方向对比。SDP方向属性反了,比IP错误还要隐蔽,而且很多网元侧配置检查不出来。
4.3 一个排查顺序表,按顺序查能省一半时间
我自己总结的“听不到放音”排查顺序如下:
| 层级 | 检查点 | 操作方法 |
|---|---|---|
| 信令面 | 183是否到达主叫侧 | sip.Status-Code == 183,检查目的IP |
| 媒体协商 | SDP里的IP、端口、编码、方向属性 | 对照SDP和实际RTP流 |
| 媒体面 | RTP是否到达抓包点 | Telephony→RTP→RTP Streams |
| 终端处理 | 本地回铃音、early media状态机 | 终端日志或设备信令跟踪 |
这个顺序不一定每个问题都走完,但每跨过一层就能排除一种可能性。不要一上来就怀疑终端,也不要一上来就把锅扣给核心网。先看信令,再看媒体,终端放到最后。
4.4 跨网元抓包的两个细节
两段抓包来自不同网元时,不同机器的系统时间经常不同步,导致绝对时间对比没有意义。我的习惯是抓包时同步记录一段相对事件,比如在命令行里打印几秒钟的日志或者发一个测试呼叫,用事件序列对齐两边的包,而不是依靠时间戳强行对齐。
另一个细节是前面提过的镜像端口问题。有次我只抓到SIP、看不到RTP,差点判定AS没放音,后来才发现交换机的镜像策略只覆盖了5060端口,动态RTP端口根本没进镜像。排查时如果RTP Streams窗口为空,先问自己一个问题:这个抓包点到底能不能抓到RTP?确认不了这一点,后续分析全部没有意义。
5. 实操心得与过滤模板
5.1 我常用的Wireshark过滤条件模板
下面这些过滤条件是我平时直接复制用的,省得每次重新拼:
# 单呼叫全量信令加媒体 sip.Call-ID == "abc123@192.0.2.1" || rtp # 只看183和所有200响应 sip && (sip.Status-Code == 183 || sip.Status-Code == 200) # 只看UPDATE请求和响应 sip.CSeq.method == "UPDATE" # 精确区分两种200 OK sip.Status-Code == 200 && sip.CSeq.method == "INVITE" sip.Status-Code == 200 && sip.CSeq.method == "UPDATE" # 按媒体服务器IP过滤信令和媒体 ip.addr == 192.0.2.20 && (sip || rtp)过滤模板只是起点。真正的排障效率来自把“过滤条件+时间关系+SDP对比”组合起来用,而不是一条条件包打天下。
5.2 能帮你少走弯路的三个小习惯
第一个习惯是抓包文件一定按Call-ID提前收敛。大型抓包里可能有几十个呼叫混在一起,直接过滤sip只会越看越乱。先用VoIP Calls找到目标呼叫,再用Call-ID过滤,分析范围从“几十兆流量”缩小到“几条消息”,所有时序关系都清楚了。
第二个习惯是给Wireshark列表加SIP方法、状态码、CSeq这些列。默认的Info列信息确实够用,但加了CSeq.Method和Status-Code列之后,一屏就能看到呼叫状态机是怎么走的,不需要点开每一条消息看详情。这在分析长信令链路时特别实用。
第三个习惯是养成“对时间戳”的敏感度。收到183和收到RTP之间的间隔、183到UPDATE之间的间隔、UPDATE到200 OK之间的间隔,这些数据比日志更有说服力。放音和通话切换出现毛刺,往往就是这些间隔里藏着问题。我现在接到“听不到放音”的投诉,第一反应不是去听录音,而是把包抓下来,按这套流程扫一遍:183有没有、SDP方向对不对、RTP有没有持续在走、UPDATE有没有切断媒体。你下次遇到类似问题,可以试试按这个顺序来,基本都能找到真正的根因。