SIP/IMS 呼叫信令分析(通用版)
面向运营商核心网/IMS/主叫管控平台的运维与优化工程师。输入抓包 + 现象描述,输出可回溯到帧号的定位报告。
一、输入与约定
| 项 | 要求 |
|---|---|
| 输入文件 | .pcap / .pcapng(可含 RTP/RTCP);SIP over UDP/TCP/TLS 均可 |
| 可选输入 | 投诉号码、现象(单通/无回铃/失败/掉话)、时间窗、关注网元 IP |
| 处理原则 | 默认本地解析;对外交付前按脱敏规则掩码号码,原始文件不出本地 |
| 证据规则 | 每条结论必须挂帧号;抓包里没出现的值一律写「未出现」,禁止推断 |
二、环境准备
export PATH="/usr/bin:/bin:/c/Windows/System32:$PATH" # Git Bash 下避免 ls/dirname command not found
tshark 路径探测(按序尝试,取第一个存在):
for p in "/c/Program Files/Wireshark/tshark.exe" "/d/Program Files/Wireshark/tshark.exe" \
"/usr/bin/tshark" "/Applications/Wireshark.app/Contents/MacOS/tshark"; do
[ -x "$p" ] && { TS="$p"; break; }
done
命令统一加 2>/dev/null 过滤 shim 噪音;字段导出统一用 -T fields -E separator=, -E quote=d。
三、分析流程(P0–P5,按序执行)
P0 取证可行性评估(必做,先于一切结论)
"$TS" -r in.pcapng -q -z io,phs # 协议分层:确认有无 RTP/RTCP
"$TS" -r in.pcapng -q -z conv,ip # IP 会话对:网元角色初判
必须写明取证边界:
- 无 RTP → 只能做信令面判断,单通方向无法直接证明,需补充媒体面证据;
- SIP over TLS(5061)且无密钥 → 只能做传输层统计,需声明"报文内容不可解析";
- 多机合并抓包 → 时间源可能不同步,跨文件时延类 KPI 需标注误差;
- IP 分片:前一片显示
data(ip.flags.mf=True),tshark 在后一片重组出完整 SIP,不要把 data 帧误判为丢包; - 无 SIP / 无 SDP(纯媒体抓包):PT 为动态映射且未提供映射表时,语音不可解码,只能走 P6b 包级分析,并明确声明"不能做波形、频谱与能量判定";
- RTP 未被 tshark 自动识别(协议树显示
data而非rtp):多为端口非标准,需-d udp.port==<port>,rtp强制解码后再过滤,否则-Y rtp结果为空; - 文件被截断(
capinfos提示cut short):尾部报文可能缺失,结论须标注"以已捕获部分为准"。
P1 全局呼叫清单(Call-ID 维度)
"$TS" -r in.pcapng -Y sip -T fields -E separator=, -E quote=d \
-e frame.number -e frame.time_epoch -e ip.src -e ip.dst \
-e sip.Call-ID -e sip.CSeq -e sip.Method -e sip.Status-Code -e sip.from.user -e sip.to.user > sip.csv
按 sip.Call-ID 聚合,每通呼叫输出:起止时间、主被叫、网元路径、最终态、失败码、接续时长、通话时长。
同步统计 KPI:呼叫总数、应答率、失败码分布(4xx/5xx/6xx 分类)、重传率、平均接续时延。
P2 单呼叫时序还原 + 时序图
对目标 Call-ID 取全量消息,生成 ladder 图:
python scripts/call_ladder.py sip.csv --call-id <Call-ID> -o ladder.svg
逐通核对关键节点:INVITE → 100 → 183/180 → PRACK/200 → UPDATE → 200 OK → ACK → 媒体 → BYE/CANCEL。
记录每跳时延:呼叫建立时延、振铃时延、应答时延、媒体通路建立时延。
P3 媒体协商对照(单通/串话的核心证据)
"$TS" -r in.pcapng -Y 'sdp' -T fields -E separator=, -E quote=d \
-e frame.number -e ip.src -e ip.dst -e sip.CSeq -e sip.Method -e sip.Status-Code \
-e sdp.connection_info.address -e sdp.media.port -e sdp.media.format -e sdp.media_attr > sdp.csv
按"主叫 offer / 平台→被叫 INVITE / 被叫 183 / 平台→主叫 183 / UPDATE 200 / 应答 200 / re-INVITE"逐段列出 c= 与端口,判定:
- 媒体锚定是否对称:平台只对一侧改锚(典型:仅向被叫发 re-INVITE 携带平台地址,主叫侧 200 OK 不带 SDP 且无 re-INVITE)→ 被叫语音被拉走,主叫听不到被叫。re-INVITE 帧与 200 OK 帧必须成对引用。
- 编解码交集是否为空、端口为 0(hold/拒收)、SRTP crypto 属性是否匹配、
a=sendrecv/sendonly/recvonly/inactive是否异常。
P4 IMS 扩展解析(VoLTE/IMS 场景必做)
"$TS" -r in.pcapng -Y sip -T fields -E separator=, -E quote=d -e frame.number \
-e sip.P-Asserted-Identity -e sip.P-Preferred-Identity -e sip.P-Charging-Vector \
-e sip.P-Access-Network-Info -e sip.P-Early-Media -e sip.P-Visited-Network-ID \
-e sip.Service-Route -e sip.Path -e sip.User-Agent -e sip.Authorization > ims.csv
- 网元角色:用
Record-Route/Via栈还原 P-CSCF → S-CSCF → AS → 对端,识别 SBC 拓扑隐藏(Via 被改写、Contact 私有地址)。 - 身份与归属:
P-Asserted-Identity与 From 不一致 → 需核实是否合法代呼/平台代发。 - 鉴权:401/407 +
nonce/AKAv1-MD5响应缺失 → 注册或呼叫被拒;重复 401 说明鉴权向量问题。 - 早媒体:
P-Early-Media授权头缺失或 183 未透传 → 无回铃/无彩铃。 - 固网/互联互通:SIP-I 中 ISUP 封装,
-Y 'isup'取原因值,按 Q.850 映射(见知识库 F 类)。 - 100rel / precondition:
Require: 100rel未满足或 precondition 资源预留失败 → 183 后无 PRACK、接续中断。
P5 异常扫描(全量,不只看目标呼叫)
| 检查项 | 判定方法 |
|---|---|
| ACK 丢失 | ACK 的 Via 端口为 0(sip.msg_hdr 搜 :0;branch)→ 200 重传、会话超时 |
| 重传风暴 | 同一 CSeq 出现多次,间隔≈500ms(T1)→ 对端未接受/处理慢 |
| 响应超时 | 请求后 >500ms 无响应(UPDATE/PRACK 尤甚) |
| 终端不一致 | User-Agent / +sip.instance IMEI 在同一呼叫内变化 |
| 异常拆线 | CANCEL 早于 200、BYE 原因值异常、会话中途 RTCP timeout |
| 报文全文取证 | -Y "frame.number==N" -T fields -e sip.msg_hdr -e sip.msg_body(比 -V 省 token) |
P6 媒体面分析(增值模块:语音还原 / 频谱 / 单通硬证据)
触发条件:抓包含 RTP,且现象为单通、静音、杂音、回声、按键收不到。信令面只能给疑点,媒体面才能下最终结论。
"$TS" -r in.pcapng -Y rtp -T fields -E separator=, -E quote=d \
-e frame.number -e frame.time_epoch -e ip.src -e ip.dst \
-e rtp.ssrc -e rtp.p_type -e rtp.seq -e rtp.payload > rtp.csv
python scripts/rtp_media_report.py rtp.csv --outdir out
python scripts/rtp_media_report.py --selftest --outdir out # 无抓包时自检
产出(零第三方依赖,纯标准库实现 FFT):
- 每个方向一条
audio_<src>-<dst>.wav(8kHz 单声道,可直接试听); envelope.svg—— 双向能量包络对比,横轴时间、纵轴 dBFS,含语音门限线;spectrogram_<方向>.svg—— 频谱图(纵轴 0–4kHz,亮度即能量),可据此判断静音、单频杂音、回声、断续;media_report.json—— 包数 / 丢包率 / 时长 / 平均能量 / 有效语音占比。
单通判定规则:
| 情形 | 判据 | 结论 |
|---|---|---|
| 一侧有效语音 ≥25%,反向 ≤5% | 能量差 >35dB | 疑似单通,写明"哪一侧听不到哪一侧" |
| 双向均 ≥15% | — | 未发现单通特征,需转向 codec/回声/终端 |
| 双向均 <15% | — | 语音偏弱,复核编解码协商、锚定地址、增益 |
注意:
rtp.payload为 hex 文本,大文件必须先按 Call-ID / 时间窗裁剪再导出;- G.711(PT 0/8)可直接还原;AMR / EVS / Opus 需另行转码,不得按 G.711 解码(报告中须标注编解码类型与限制);
- 语音内容与号码同属客户敏感数据,默认本地处理,交付前脱敏,不随报告外发原始音频;
- DTMF RFC2833 按键比对、收号失败定责 → 转
rtp-voice-restore-dtmf技能。
P6b 包级分析(无 SDP / 动态 PT / 编码未知时的兜底路径)
适用场景:抓包只有媒体面、无 SIP 协商信息,或 PT 为动态类型(如 96–127)且未给出映射。此时不得解码语音,但可以给出 QoS 与行为结论。
# 端口非标准时先强制解码,否则 -Y rtp 为空
"$TS" -r in.pcapng -d "udp.port==<port>,rtp" -Y rtp -T fields -E separator=, -E quote=n \
-e frame.number -e frame.time_relative -e ip.src -e udp.srcport \
-e ip.dst -e udp.dstport -e rtp.p_type -e rtp.seq -e rtp.timestamp \
-e rtp.ssrc -e rtp.payload > rtp.csv
python scripts/analyze_rtp.py rtp.csv # 流清单 / 丢包 / 乱序 / 抖动 / DTMF
python scripts/gen_timeline.py rtp.csv -o timeline.svg --title "<标题>"
CSV 输入格式(两种都支持,脚本自动识别):
| 导出方式 | 说明 |
|---|---|
| 上面这条命令(无表头) | 按默认列顺序解析,如需自定义顺序加 --columns t,src,dst,pt,seq,… |
导出时加 -E header=y |
按列名解析,列顺序任意,多列/缺列均无影响 |
方向(A/B 泳道)自动判定:脚本构建无向图 → 取叶子节点作为通话两端 → 按到端点的跳距给每条有向边打标签。
因此"终端↔平台↔媒体资源"这类三点中转拓扑,会把 终端→平台 与 平台→媒体资源 合并为同一个端到端方向,无需手工配置 IP。
通用性纪律:脚本中禁止出现任何具体 IP、号码、SSN/IMEI。需要区分案件时用命令行参数或数据自动推断,不得写死。
可得出结论(不依赖解码):
| 维度 | 说明 |
|---|---|
| 丢包 / 重复 / 乱序 | 按 rtp.seq 连续性判定;注意 d==1 正常、d==0 重复、d>1 丢包、d>=0x8000 乱序(方向别搞反) |
| 发送间隔抖动 | min/mean/max,判断是否匀速(20ms 节拍) |
| 载荷长度分布 | 区分满速率语音帧 / SID(DTX 静音描述符)/ 事件包 |
| DTMF 还原 | PT 若承载 RFC2833,按 [event, E\|R\|Vol, dur_hi, dur_lo] 解析,合并同 event 连续包为一次按键 |
| 双向活跃度 | 两方向包数与帧类型对比;稀疏不等于单通 —— 需先排除 DTX 与按键行为 |
| 转发对称性 | 中转节点两侧包数是否一一对应,判断平台是否丢包 |
| RTCP | SR 中的发包/字节计数、丢包率、jitter;SDES CNAME 常含终端标识(须脱敏) |
判定纪律:仅凭"某方向包数少"不能下单通结论,必须先用 DTX/SID 与 DTMF 解释;若解释成立,结论应为"未发现单通特征",并写明需补信令面才能最终定责。
四、故障模式知识库(结论必须挂帧号 + 对号入座)
| 类 | 故障 | 典型特征 |
|---|---|---|
| A 单通 | 媒体锚定不对称 | 单侧 re-INVITE、主叫 200 OK 无 SDP |
| A 单通 | 媒体地址不可达 | c= 为私网/NAT 地址、防火墙未放通端口段 |
| B 无回铃 | 早媒体未透传 | 183 丢失、P-Early-Media 授权缺失、PRACK 未支持 |
| B 无回铃 | 放音资源异常 | 平台侧 183 有 SDP 但无 RTP、MRF 未响应 |
| C 呼叫失败 | 403/404/480/486/487/488/503/504/408 | 按码定因:鉴权/路由/用户忙/无应答/媒体协商失败/过载/超时 |
| D 掉话 | 会话中途拆线 | BYE 原因值、媒体流中断、UPDATE 失败、会话定时器过期 |
| E 注册鉴权 | REGISTER 401/407 循环 | nonce 过期、AKA 校验失败、UICC 不匹配 |
| F 串话错号 | 号码/会话错配 | Call-ID 复用、号码变换规则错误、SIP-I 原因值与现象不符 |
| G 编解码 | 488/606、DTMF 失败 | codec 交集为空、2833 与 inband 混用、SRTP 密钥协商失败 |
严重度分级:P1 阻断业务(呼叫不可达/大面积失败)→ P2 影响体验(单通/无回铃/掉话)→ P3 隐患(重传、参数不规范)。
五、交付格式
Markdown 报告固定章节:
- 分析概要(文件、时间窗、规模、取证边界声明)
- 网元地址角色表(含判定依据列)
- 呼叫清单表(Call-ID / 主被叫 / 起止 / 最终态 / 失败码 / 时长)
- 时序图(
ladder.svg,或内嵌 ASCII 时序) - 媒体协商对照表(每跳
c=、端口、codec、帧号) - IMS 扩展头解析表(P-header、Service-Route、鉴权)
- 异常项表(严重度 P1/P2/P3 + 帧号 + 证据)
- 结论(单通方向须写清"哪一侧听不到哪一侧")
- 整改建议表(动作 / 责任面 / 验证方法)
- 待确认事项(拆分「已确认项」与「待确认事项」)
机器可读旁路:sip.csv / sdp.csv / ims.csv 随报告一并交付,便于二次统计。
六、脱敏规则(对外交付前执行)
- 号码:保留前 3 后 4,中间掩码(
138****1234); - IP:核心网内网地址保留(分析必需),终端侧地址可掩码末段;
- 域名/局名:保留,用于定位;
- 语音与 RTP 载荷:不导出,只在需要时给出还原方法(见
rtp-voice-restore-dtmf技能)。
七、能力分层
| 层 | 覆盖范围 | 说明 |
|---|---|---|
| 基础(免费) | P0–P5 信令面全量分析 + 时序图 + 异常定级 | 可在本地离线完成 |
| 增值(付费) | P6 媒体面(语音还原 + 频谱 + 单通硬证据)、批量呼叫 KPI 趋势、故障库与多厂商 UA 特征持续更新、定制报告模板 | 依赖后端服务与知识库 |
若本技能以付费模块分发:P6 及深水知识库部分仅在取得有效授权后执行,未授权时输出能力清单与报价说明,不输出分析结果。
八、边界声明
- 无媒体面(RTP)时,单通方向只能给出疑点,不能下最终定责结论,必须附取证清单。
- 抓包含 RTP 且需语音还原、RFC2833 按键比对、收号失败定责 → 转
rtp-voice-restore-dtmf技能。 - 所有示例与演示数据必须在报告中标注来源;不得用构造数据冒充现网结论。
九、案例速览(主叫单通)
> 案例数据为脚本构造的演示数据,非现网结论,仅用于说明证据链组织方式。
- 现象:接通后主叫听不到被叫;呼叫流程无失败响应、BYE 正常,仅看 KPI 无法发现问题。
- 信令面证据:帧 2410 应答后仅向被叫侧发 re-INVITE 改锚(40020 → 40022);主叫侧 200 OK(帧 2404)无 SDP 且未收到 re-INVITE → 两侧媒体锚定不对称。
- 媒体面证据:双向能量比对,一侧有效语音 100%(−6.9 dB)、反向 0%(−80.0 dB),能量差 > 35 dB → 自动判定疑似单通。
- 定级:P1 媒体锚定不对称;另有 P2 INVITE 重传 515 ms、P2 UPDATE 响应 630 ms。
- 排除项:IMEI 全程一致、P-Asserted-Identity 与 From 一致,排除终端更换与代呼。
- 产物:
ladder.svg时序图、10 章定位报告、envelope.svg双向能量包络、spectrogram_*.svg频谱图、audio_*.wav还原语音、media_report.json。
十、案例速览(真实抓包:纯媒体面,无 SDP)
> 真实抓包实测,用于说明编码未知时的包级分析路径(P6b)。
- 输入:1746 包 / 12.22 s,全 UDP,无 SIP 信令;PT=98(语音)、PT=105(事件)均为动态映射 → 语音不可解码。
- 拓扑:终端
10.191.186.168↔ 平台/媒体锚点10.191.176.207↔ 对端媒体资源10.191.176.155,平台两侧包数一一对应(605→608、263→263),转发忠实。 - QoS:全部 5 条流 0 丢包 / 0 重复 / 0 乱序;A 方向发送间隔 19.37–20.71 ms;RTCP SR 报告丢包 0、jitter 54/58。
- 关键还原:DTMF 序列
5977#拨了两次(3.914 s–10.421 s),终端侧与平台侧序列完全一致,无丢键。 - 判定:B 方向仅 10 个满速率语音帧 + 53 个小包 + 200 个 DTMF 包,稀疏性由 DTX + 按键行为完整解释 → 未发现单通特征。
- 纪律:仅凭"某方向包数少"不得下单通结论;本例若误判,会把正常的 IVR 交互当成故障。
- 遗留:开局 SSRC 切换、RTCP 单向且仅 2 个 SR、文件被截断 —— 均列入待确认,不作为结论。
十一、常见错误 FAQ(踩过的坑,逐条对照)
> 以下每条都源于真实排查教训,违反会直接导致错误结论。
| # | 症状 / 错误做法 | 根因 | 正确做法 |
|---|---|---|---|
| 1 | 只看某一方向包数少就判"单通" | 忽略了 DTMF 交互期与 DTX 静音期(实测某案 Low 侧 263 包中 DTMF 占 76%) | 先做 PT 与载荷长度分类,Low 侧 DTMF 占比 > 30% 时属正常收号交互,不得判单通 |
| 2 | -Y rtp 返回空,以为没有媒体流 |
RTP 走非标准端口,Wireshark 未自动解码 | 先 tshark -q -z conv,udp 看会话,再 -d udp.port==<port>,rtp 强制解码 |
| 3 | 丢包统计虚高(tshark 报 0,自己算出几百) | 按到达顺序判序号缺口,"后发先至"被记成丢包 | 丢包用序号集合缺口法(与 tshark 口径一致);乱序/重复另按到达顺序统计 |
| 4 | DTMF 按键数被放大 3 倍 | RFC2833 的 end 包会重发(实测重发 3 次) | 只统计带 end 标志的代表包,按 (event, duration 递增) 去重 |
| 5 | 抖动指标虚高(如 43 ms) | 混入了 SID/DTX 小包,节拍本来就不均匀 | 只对满速率语音帧(len >= 40 字节)计算抖动 |
| 6 | AMR/EVS/Opus 按 G.711 解出来是噪音 | 动态 PT 未通过 SDP 确认编解码 | 无 SDP 时一律走 P6b 包级分析,声明语音不可解码,不得猜测解码 |
| 7 | 脚本对别人的抓包输出空图/报错 | 曾把具体 IP 写死在脚本里(历史教训) | 拓扑与方向一律运行时自动推断;脚本中禁止出现任何具体 IP/号码 |
| 8 | 换一种 tshark 导出格式后脚本崩 | 依赖固定列顺序且无表头 | 本技能脚本同时兼容有/无表头,有表头时列顺序任意;不匹配会给出明确报错 |
| 9 | IP 分片的前一片显示 data,误判为丢包 |
只有后一片能重组出完整 SIP | 见到 ip.flags.mf=True 就到后续分片找完整报文 |
| 10 | 用 -V 全量展开导致 token 耗尽 |
输出包含全部协议层 | 用 -T fields -e <具体字段>,逐帧取证只用 -Y frame.number==N |
十二、自检与回归测试
python test/test_all.py # 29 项断言,零依赖,秒级完成
覆盖:CSV 三种输入格式一致性、丢包/重复/乱序判定、DTMF 去重与顺序、端点自动识别(直连 + 三点中转)、端到端方向划分、SVG 产物合法性、缺列容错。
修改任何脚本后都应先跑通过再交付。