简介:围绕S1数据转发测试整理的docx小结,面向移动通信核心网、无线侧测试人员及协议分析学习者,聚焦切换过程中用户面数据连续性验证。内容从切换前后的数据流向入手,梳理SGW、源eNodeB与目标eNodeB间的转发路径,结合TEID与Sequence Number说明GTP隧道标识和包序判断;并按测量报告、切换决策、接纳控制、HANDOVER COMMAND、RRC重配置、数据转发、UE同步至资源释放的主线,拆解各信令节点的观察重点。文中列出Data Forwarding检查点,包括End Marker的触发与转发、源小区数据传送地址向源与目标转发地址切换,以及HANDOVER NOTIFY后目标小区数据传送地址的变化,便于对照抓包判断转发是否完整。包内共1个docx文件,约769KB,适合作为测试记录与排错参考。已有305人学习,可帮助初、中级测试人员建立S1切换数据转发的检查思路。
1. 一次切换后业务静默 30 ms:S1 data forwarding 的测试到底在测什么
上个月在一套模拟项目里做切换压测,UE 从 source eNB 切到 target eNB,信令面一路顺畅,HANDOVER NOTIFY 按时落地,可下行速率曲线在切换点还是凹了一块,静默大约 30 ms。信令全绿、业务掉坑,这类问题最后多半要回到 S1 data forwarding 的转发链路里找原因——抓包一看,SGW 发出的 End Marker 停在源侧没有被转发出去,目标侧一直在等那个“没有更多数据”的信号。S1 data forwarding 就是切换期间在源侧和目标侧之间搬用户面报文的那段机制,测的是切换过程中有没有丢包、乱序、多转。正在做切换特性验证、S1 接口测试,或者要定位切换掉话和速率凹坑的工程师都绕不开它,尤其是手里已经有抓包、却不知道从哪几个字段下手的同学。
2. 数据流向的三段路:从 sgw→source eNB 到 sgw→target eNB 的路径切换
2.1 切换前、切换中、切换后:三条下行路径
把一次切换拆成时间轴,下行用户面其实只有三段路,测试的全部难点在于第二段路要走多久、走哪条隧道、什么时候收尾。切换前,SGW 把下行 G-PDU 打到源 eNB 为这个 E-RAB 分配的 S1-U 下行 TEID 上,文档里记的是0x01001008,这是源小区正常的数据传送隧道,业务全程走它,跟切换流程没有关系。这段时间的检查点是“源小区 DL 传送 TEID 上的包必须连续”,一旦在测量报告之前就出现空洞,问题不在转发,要去查空口和承载配置。
切换执行阶段是整条链路最容易翻车的地方。SGW 收到 MME 的路径切换相关信令之前,仍然把下行数据发往源 eNB;但源 eNB 已经通过 RRCConnectionReconfiguration 让 UE 往目标小区走了,它不能把这些包继续往空口上灌,只能在自己的转发隧道上重新封装,送出去。如果源和目标之间没有直接可达的 IP 路径,这些包要先回到 SGW,再由 SGW 转到目标 eNB,于是路径就变成了 sgw→source eNB→sgw→target eNB。文档里0x0000115b和0x01000a09这两个值就是这段绕行路上的两个端点:前者是源侧转发隧道用的标识,后者是目标侧为转发分配的隧道标识。
切换成功后,SGW 收到 HANDOVER NOTIFY 触发的路径切换完成,把下行改到目标小区正常的数据传送隧道,文档里记的是0x01000a08。注意这个值和0x01000a09只差一个低位,很多人在抓包里一眼扫过去就混了,结果把转发阶段的包算进了切换后,得出一堆“切换后仍然乱序”的假结论。两者的角色完全不同:0x01000a09只在转发窗口内有效,0x01000a08是切换后长期承载业务的隧道。
| 角色 | 文档中的值 | 分配方 | 谁往这个隧道发 | 生效时刻 |
|---|---|---|---|---|
| 源小区 DL 传送 TEID | 0x01001008 | 源 eNB | SGW | 切换前,长期 |
| 源侧转发 TEID | 0x0000115b | 源 eNB(间接转发时由 SGW 侧对接) | 源 eNB | RRC 重配之后 |
| 目标侧转发 TEID | 0x01000a09 | 目标 eNB | 源 eNB 或 SGW | 切换准备完成、源侧收到 HANDOVER COMMAND 之后 |
| 目标小区 DL 传送 TEID | 0x01000a08 | 目标 eNB | SGW | HANDOVER NOTIFY 之后 |
表里的四个值来自这份测试小结的记录,实际抓包里以gtp.teid字段为准。测试时把这四个值提前写进过滤脚本的常量表,比每次现场翻文档靠谱得多。
2.2 直接转发与间接转发:选型依据不是偏好,是可达性
数据转发有两条实现路径,测试方法完全不一样,混着测就会得出互相矛盾的结论。直接转发是源 eNB 拿到目标 eNB 分配的转发隧道标识后,直接把用户面报文封进去发给目标 eNB,中间的 SGW 完全不参与;间接转发是源 eNB 把报文发回 SGW,由 SGW 再转给目标 eNB,也就是文档里那条“绕一圈”的路径。判定走哪条,核心看源 eNB 与目标 eNB 之间有没有可用 IP 通路,以及切换类型。
S1 切换场景下,源和目标往往属于不同厂家的设备池,中间隔着安全域和防火墙策略,直接转发经常不具备条件,常见做法是走间接转发。X2 切换时源和目标之间本来就有 X2 接口,直接转发的概率高得多。但这不是铁律,S1 切换也允许直接转发,前提是核心网侧给源 eNB 下发的透明容器里带上了可直达的目标侧转发地址。所以在测试用例设计时,不要按“S1 = 间接转发”去写死预期,应该从 HANDOVER REQUEST ACKNOWLEDGE 里的传输层地址去判断本次实际走的是哪条。
| 对比项 | 直接转发 | 间接转发 |
|---|---|---|
| 报文路径 | source eNB → target eNB | source eNB → SGW → target eNB |
| 依赖条件 | 源与目标 IP 可达 | 源与 SGW、SGW 与目标均可达 |
| 转发隧道数量 | 1 条(源到目标) | 2 条(源到 SGW、SGW 到目标) |
| 多一跳带来的影响 | 无 | 增加时延与乱序概率 |
| 常见场景 | X2 切换、同池同域 | S1 切换、跨域跨厂家 |
| 测试关注点 | 目标侧转发 TEID 是否用对 | 两段隧道是否都建起来、End Marker 是否逐跳传递 |
如果现场抓包发现只有一条转发隧道的流量、且目的 IP 直接就是目标 eNB 的地址,那就是直接转发;如果看到源侧发出的转发包目的地址是 SGW,再由 SGW 发出另一组包到目标,就是间接转发。这个判断只需要看ip.dst和gtp.teid两列,一分钟能确认。
2.3 End Marker 的语义:它不是业务包,是转发链路的收尾信号
End Marker 这个包里没有用户数据,它的作用只有一个:告诉对端“我这边没有需要转发的数据了,你可以停止转发业务”。文档里写得很清楚,SGW 在把下行路径切到 target 之前,向 source 发一个 End Marker,source 收到后把它在转发隧道上继续送给 target,源侧用户面的转发任务到这里就算完成。少了这一步,目标侧不知道转发流什么时候结束,要么一直等着,要么提前把转发隧道的包丢掉,两种都会造成切换点的业务空洞。
在 GTP-U 里,End Marker 的类型值是 0xfe,普通 G-PDU 是 0xff。这两个值必须记牢,很多人写了gtp.message == 0之类的过滤条件,自然是抓不到。另外,End Marker 这个包的 TEID 字段按规范是填 0 的,部分实现会填隧道值,所以过滤时最好不要只靠 TEID,直接用消息类型更稳。
End Marker 是双向的,上行和下行各有一个,测试时容易只盯下行。上行方向由源 eNB 在切换前向 SGW 发 End Marker,表示上行转发结束;下行方向由 SGW 在切换前向源 eNB 发。两个方向的时间点不一致是正常的,但只要有一个方向的 End Marker 丢了,对应的那半边业务就会在切换点出现一段“多发”或者“早停”。我在模拟项目里遇到的那次 30 ms 静默,根因就是下行 End Marker 在源侧被丢弃,目标侧一直到 RRC 重配完成之后才开始正常收包。
测试 End Marker 的关键检查有三条:一是有没有发,源侧和 SGW 侧各抓一次;二是有没有转发,看源侧转发隧道上是否出现同类型的包;三是发完之后还有没有业务包,正常情况 End Marker 之后该方向的转发隧道上不应再出现 G-PDU。第三条最容易漏测,也最容易暴露源侧“以为切完了”提前停转发的问题。
3. TEID 与 Sequence Number:GTP-U 隧道端点的识别与乱序判定
3.1 TEID 是本地标识,不是全局地址
TEID 用来标记 GTP 隧道的端点,一条承载的每个方向各有一个 TEID,由接收侧分配、对端使用。这意味着同一个业务在源侧和目标侧看到的 TEID 必然不同,而且同一个 TEID 值在不同网元上可能被复用。测试时把 TEID 当成“会话 ID”去全局追,是最常见的误解之一。文档里那四个十六进制值之所以要单独列出来,就是因为它们分别属于不同的端点和不同的阶段,离开上下文单看一个值说明不了任何问题。
正确做法是建立“隧道四元组”:源 IP、目的 IP、TEID、方向。一条下行转发隧道的四元组是源 eNB 地址 + SGW 地址 + 源侧转发 TEID;对应的反向隧道四元组完全不同。抓包分析时先按四元组归类,再看每类的包数和时间窗口,比按单个 TEID 过滤可靠得多。S1AP 侧传递的 GTP-TEID 字段和 GTP-U 头里的 TEID 必须能对上,对不上就说明承载映射错了,这一类错误在跨厂家组网里出现频率不低。
还有一类边界要提前知道:TEID 值 0 是保留的,用于 End Marker 和 Echo 这类没有承载语义的消息。所以看到gtp.teid == 0的包不要当成异常,先看消息类型。同样,如果某个本该有业务的隧道上出现了 TEID 0 的 G-PDU,那才是真的异常,多半是转发实现里把隧道标识填错了。
3.2 Sequence Number 与 PDCP SN 不是一回事
GTP-U 头部带一个可选的 Sequence Number,只有在 E、S、PN 标志位置位时才存在。它由发送侧为每条隧道独立维护,逐包递增,16 位回绕,作用是对端做乱序检测和重复包丢弃。这个字段在转发测试里非常有用,因为转发隧道是新建的,SN 从哪个值开始、是否连续,直接反映了源侧转发实现的质量。
需要特别提醒的是,GTP-U 的 SN 和 PDCP 层的 COUNT 是两套完全独立的编号。前者只在本跳 GTP 隧道内有效,出了这个隧道就没有意义;后者是端到端的,靠 SN Status Transfer 在源和目标之间同步。测试时经常有人拿 GTP-U 的 SN 去比对 SN Status Transfer 里的 PDCP SN,发现对不上就报缺陷,实际上是两回事。文档里第 3 点提到的“sequence number 是数据流量”,指的就是 GTP-U 这一层的计数,它服务于转发链路的连续性检查,不承担端到端排序职责。
真正要检查的是三件事:转发隧道上的 SN 是否单调递增且无明显跳变;切换后目标小区新隧道上的 SN 起始值是否符合实现预期(有的实现从 0 开始,有的延续计数);同一 SN 是否在短时间内重复出现。第三条如果命中,基本可以判定为转发路径上存在重传或双份投递,配合抓包点的位置可以快速定位到是源侧重复发还是 SGW 重复转。
3.3 用 tshark 把隧道、序列号和 End Marker 一次导出来
现场抓包通常是多个网元同时抓,时间基准不一致,先做一次字段级导出比直接看 Wireshark 界面高效得多。下面三条命令分别解决“全量导出”“只看 End Marker”“按 TEID 聚合”三件事。
# 1) 全量导出 GTP-U 关键字段,输出 CSV 供后续脚本处理 tshark -r ho_s1u.pcapng -n -Y "gtp" \ -T fields -E header=y -E separator=, \ -e frame.number -e frame.time_relative \ -e ip.src -e ip.dst -e udp.srcport -e udp.dstport \ -e gtp.message -e gtp.teid -e gtp.seq \ > gtp_s1u.csv # 2) 只保留 End Marker(GTP-U 消息类型 0xfe),确认两个方向都发了 tshark -r ho_s1u.pcapng -n -Y "gtp.message == 0xfe" \ -T fields -e frame.number -e frame.time_relative \ -e ip.src -e ip.dst -e gtp.teid # 3) 按 TEID 聚合 G-PDU,输出包数与首末时间,用于对齐四个隧道 tshark -r ho_s1u.pcapng -n -Y "gtp.message == 0xff" \ -T fields -e gtp.teid -e frame.time_relative \ | awk '{c[$1]++; if(!($1 in f)) f[$1]=$2; l[$1]=$2} END{for(t in c) printf "%s,%d,%.3f,%.3f\n", t, c[t], f[t], l[t]}' \ | sort -t, -k3 -n第一条命令的-Y "gtp"是显示过滤器,只保留 GTP 协议报文;-e gtp.message输出消息类型,0xff 是业务包、0xfe 是 End Marker;-e gtp.seq输出序列号,若该包没置 S 位,这一列会是空值,空值本身也是一个检查点,说明发送侧没开序列号。第二条命令把 End Marker 单独拎出来,重点看ip.src和ip.dst是不是符合预期的转发路径,间接转发下应该能看到两段。第三条命令用 awk 做聚合,f[]记录每个 TEID 的首包时间、l[]记录末包时间,排序后一眼就能看出哪个隧道的时间窗口偏了。
导出之后不要急着下结论。先确认抓包点,S1-U 上的抓包只能看到 SGW 与 eNB 之间的流量,如果本次是直接转发,源到目标的转发流量根本不经过 S1-U 抓包点,导出的结果里会缺一大块,此时应该去 X2-U 或源与目标之间的互联链路上补抓。抓包点选错是转发测试里成本最高的错误,因为所有后续分析都会建立在残缺数据上。
4. 从 HANDOVER REQUIRED 到 HANDOVER NOTIFY:转发参数是怎么传下来的
4.1 信令序列与每一步的观察点
切换流程的编号在文档里已经列全了,从 0 的 Area Restriction Provided 一直到 17 的 Release Resources。测试时把这串编号贴在抓包软件的书签栏,按号找包,比凭记忆翻快得多。真正与转发强相关的只有其中几步:4 的 HANDOVER REQUIRED、5 的 HANDOVER REQUEST、7 的 HANDOVER REQUEST ACKNOWLEDGE、8 的 Handover Command、10 和 11 的 Status Transfer、15 的 HANDOVER NOTIFY。
| 步骤 | 消息 | 方向 | 转发相关的观察点 |
|---|---|---|---|
| 2 | Measurement Reports | UE → 源 eNB | 触发切换的无线条件,与转发无关但决定时间窗 |
| 4 | HANDOVER REQUIRED | 源 eNB → MME | 源侧提出的目标小区与承载列表 |
| 5 | HANDOVER REQUEST | MME → 目标 eNB | 携带源侧的上行转发隧道信息 |
| 6 | Admission Control | 目标 eNB 内部 | 决定哪些承载被接纳 |
| 7 | HANDOVER REQUEST ACKNOWLEDGE | 目标 eNB → MME | 目标侧 eNB UE S1AP ID、目标小区数据隧道标识、目标小区数据转发隧道标识 |
| 8 | Handover Command | MME → 源 eNB | 源侧从这里拿到转发目标地址 |
| 9 | RRC Conn. Reconf. | 源 eNB → UE | 这一包发出后,源侧下行应切到转发隧道 |
| 10/11 | eNB/MME Status Transfer | 源 → MME → 目标 | PDCP SN 与 HFN 的同步 |
| 15 | HANDOVER NOTIFY | 目标 eNB → MME | 触发路径切换,SGW 改发目标小区隧道 |
| 16/17 | UE Context Release / Release Resources | MME → 源 eNB | 源侧资源释放,转发隧道随之拆除 |
第 7 步是整条链路的参数源头。目标 eNB 在完成资源分配和切换入的承载接纳处理后,如果资源满足、小区接入允许,就给 MME 回 HANDOVER REQUEST ACKNOWLEDGE,里面带目标侧分配的 eNB UE S1AP ID,以及接纳成功的每个 EPS 承载对应的目标侧数据传送地址,具体是两个值:目标小区数据隧道标识,用于切换完成后 SGW 直接发;目标小区数据转发隧道标识,用于切换期间接收转发数据。这两个值必须成对出现,缺一个就意味着该承载只能转不能接,或者只能接不能转。
第 8 步源 eNB 收到 HANDOVER COMMAND,从中解析出接纳成功的承载信息和目标侧转发地址,到这一刻它才具备开始转发的条件。测试时经常看到源侧在收到 HANDOVER COMMAND 之前就开始往转发隧道发包,这种提前转发在目标侧会因为隧道未建好而被丢弃,表现为切换前的一段丢包。判断方法很简单:把 HANDOVER COMMAND 的时间戳和转发隧道首包的时间戳一比,前者必须早于后者,反过来就是实现顺序错了。
4.2 源侧收到 HANDOVER COMMAND 之后做哪几件事
源侧的动作可以拆成四步,顺序不能乱。第一步是停止在源小区空口上发送新的下行数据,避免 UE 已经走了还在旧空口上灌包;第二步是把自己缓冲区里的在途报文通过转发隧道送出去;第三步是发 SN Status Transfer,把 PDCP 的计数状态告诉目标侧;第四步是在转发隧道上发出或转发 End Marker,宣告转发结束。四步里任何一步顺序颠倒,都会在切换点留下痕迹。
缓冲区大小是个容易忽略的参数。缓冲区太小,UE 一走,在途报文直接丢;缓冲区太大,切换时延被拉长,End Marker 迟迟发不出去,目标侧等得久。现场调优一般会结合业务类型给一个折中值,语音类偏小、数据类偏大,具体数值各厂家不一样,测试时不要在文档里写死,记录实际配置值即可。真正需要验证的是“缓冲区里的包有没有全部转出去”,这个可以用转发隧道上的包数减去源小区隧道在切换时刻之后的包数来估算。
SN Status Transfer 这一步的坑在于它走的是控制面,不经过用户面转发隧道,所以只抓 S1-U 是看不到的。要在 S1-MME 上同时抓,才能把 PDCP 状态同步和用户面转发对齐。文档里第 10、11 两步分别对应源 eNB 发和 MME 转,中间隔着一个 MME,时间差通常在几毫秒量级,如果超过几十毫秒,说明 MME 处理或者链路有问题,会直接影响目标侧对重复包的判断。
4.3 四个 TEID 的时序检查点
把四个 TEID 的生效时刻做成一条时间线,是这套测试里最值得固化下来的动作。切换前只有0x01001008上有下行流量;RRC 重配之后,0x0000115b上开始出现包,同时0x01001008上的包逐渐减少;源侧收到转发数据后,0x01000a09上出现对应流量;HANDOVER NOTIFY 之后,0x01000a08接棒,0x01000a09上的流量归零。任何一条不符合,就是一处缺陷。
用一段 Python 把导出的 CSV 按这四段时间切开,比人工比对可靠。下面的脚本读的是上一节导出的gtp_s1u.csv,按 TEID 归类并输出每个隧道的首末包时间与包数。
import csv from collections import defaultdict # 四个 TEID 角色,取自测试小结记录,实际使用时替换为现场值 ROLE = { "0x01001008": "src_dl_normal", # 源小区正常下行隧道 "0x0000115b": "src_fwd", # 源侧转发隧道 "0x01000a09": "tgt_fwd", # 目标侧转发隧道 "0x01000a08": "tgt_dl_normal", # 目标小区正常下行隧道 } stat = defaultdict(lambda: {"cnt": 0, "first": None, "last": None}) with open("gtp_s1u.csv", newline="", encoding="utf-8") as f: for row in csv.DictReader(f): teid = (row.get("gtp.teid") or "").lower() if not teid: continue key = "0x%08x" % int(teid, 16) # 统一成 8 位十六进制,避免大小写与前导零问题 t = float(row["frame.time_relative"]) s = stat[key] s["cnt"] += 1 if s["first"] is None or t < s["first"]: s["first"] = t if s["last"] is None or t > s["last"]: s["last"] = t for teid, s in sorted(stat.items(), key=lambda kv: kv[1]["first"]): print("%s,%s,%d,%.3f,%.3f" % ( teid, ROLE.get(teid, "unknown"), s["cnt"], s["first"], s["last"]))脚本只做三件事:把 TEID 归一化成 8 位小写十六进制,避免0x1001008和0x01001008被当成两个隧道;统计每个隧道的包数与首末时间;按首包时间排序输出。逻辑很朴素,但它把“四个隧道是否按预期顺序出现”这件事变成了可以直接看的一行行数据。ROLE字典里出现unknown就要警惕,说明抓到了预期之外的隧道,可能是其他业务的流量串进来,也可能是承载映射错了。
参数上唯一需要注意的是frame.time_relative是相对文件首包的时间,不是绝对时间。多文件对比时必须先做时间基准对齐,否则跨网元的先后关系全是错的。对齐方法在最后一章展开。
5. 避坑与排查:五类翻车现场的现象、成因与处置
5.1 抓不到 End Marker,业务却在切换点丢最后一个包
现象:S1-U 抓包里搜遍gtp.message == 0xfe一个都没有,但切换点的速率曲线确实有一个窄坑,重传计数上涨。原因通常有两种,一是抓包点不在 End Marker 经过的路径上,间接转发时 End Marker 会在源与 SGW 之间、SGW 与目标之间各出现一次,只在其中一段抓就会漏;二是过滤条件写错,用 TEID 去过滤,而 End Marker 的 TEID 字段按规范填 0,直接过滤不到。解决方式是先改用消息类型过滤,确认两个方向、两段路径都覆盖,再回去看 TEID。如果确实一个都没发,就要查源侧和 SGW 的转发停止逻辑,看是不是在 RRC 重配后就直接停了转发而没有走收尾流程。
5.2 把 TEID 当成 IP 地址填进配置或脚本
现象:脚本里按0x01001008去找对应的网元,结果匹配不上;或者把四个十六进制值当成四个 IP,画出来的拓扑完全对不上。原因是文档里这几个值写的是“地址”,实际对应的是 GTP-U 头里的隧道端点标识,不是 IP。解决方式是在任何分析脚本里都把 TEID 和 IP 分成两列处理,四元组的形式是源 IP、目的 IP、TEID、方向,缺一不可。这个坑我踩过一次,当时用 TEID 去 grep 抓包文本,把一串完全无关的十六进制也匹配进来了,结论整整错了半天。
5.3 HANDOVER REQUEST ACKNOWLEDGE 里承载不全,源侧没检查
现象:多承载业务在切换后只有部分业务恢复,其余业务在目标侧一直起不来。原因是目标侧接纳控制只接纳了部分 EPS 承载,HANDOVER REQUEST ACKNOWLEDGE 里只带回了成功的那些,源侧如果按“全部成功”去处理,没被接纳的承载仍然往转发隧道上发,目标侧没有对应隧道,全丢。解决方式是在源侧解析 HANDOVER COMMAND 时逐承载比对,对未接纳的承载停止转发并记录原因值,测试用例里也要专门构造资源受限场景去覆盖这一条。文档里第 6 步“Admission Control”被单独列出来,就是为了提醒这一步的结果会改变后续转发行为。
5.4 拿 GTP-U 的 Sequence Number 去对 PDCP 的 SN
现象:测试报告里写“转发隧道序列号与状态转移消息不一致”,研发一看就退回。原因是两层编号体系被混用了,GTP-U 的 SN 是逐跳的、每隧道独立、16 位回绕;PDCP 的 COUNT 是端到端的,靠 SN Status Transfer 同步。解决方式是分析时明确标注每个 SN 属于哪一层,GTP-U 的 SN 只用来看转发隧道内的连续性,端到端的排序问题去看 PDCP 状态和空口侧的重传统计。这一条几乎是新人必踩,写进测试规范能省不少来回。
5.5 抓包点选错,整份数据分析建立在残缺样本上
现象:导出的 CSV 里只有源小区正常隧道和目标小区正常隧道,转发隧道一条都没有,于是得出“本次切换没有发生数据转发”的结论。原因是本次走的是直接转发,流量根本不经过 S1-U,而抓包只布在 S1-U 上。解决方式是在测试执行前先确认转发模式,从 HANDOVER REQUEST ACKNOWLEDGE 里的传输层地址判断,再决定抓包点布在 S1-U、X2-U 还是源与目标之间的互联链路。多网元同时抓时统一用绝对时间或者做 NTP 对齐,否则后面的时间窗比较没有意义。
6. 用一份回放脚本验收转发:四个 TEID 的时间窗对齐与判定阈值
把前面几章的动作串起来,验收一份切换抓包其实就三步:对齐时间、切分窗口、套阈值。时间对齐这一步最容易被跳过,也最容易让结论失真。多网元抓包时,我用相对时间做一次线性校正:以 HANDOVER REQUIRED 这个信令包为锚点,把各文件的相对时间平移到同一基准,再去做隧道窗口比较。锚点选它是因为它在源侧和 MME 侧都能抓到,且时间戳精度足够。
窗口切分沿用四个 TEID 的角色表。判定阈值我一般会设这么几条,作为回归基线而不是硬标准,具体项目按业务类型调整:源侧转发隧道的首包必须晚于 HANDOVER COMMAND,早于则判顺序错误;目标侧转发隧道的末包必须早于 HANDOVER NOTIFY,晚于则说明路径切换后仍有转发流量,属于多转;下行 End Marker 必须出现在目标侧转发隧道上,且其后该隧道上不再有 G-PDU;源小区正常隧道的末包与目标小区正常隧道的首包之间的间隔,就是切换中断时长,超过设定门限就报警。
def check_windows(stat, sig, max_gap_ms=100.0): """stat: {teid_role: (cnt, first, last)};sig: 关键信令时间字典""" errs = [] src_fwd = stat.get("src_fwd") tgt_fwd = stat.get("tgt_fwd") src_dl = stat.get("src_dl_normal") tgt_dl = stat.get("tgt_dl_normal") # 1) 转发必须发生在 HANDOVER COMMAND 之后 if src_fwd and src_fwd[1] < sig["ho_cmd"]: errs.append("src_fwd starts before HANDOVER COMMAND") # 2) 转发必须在 HANDOVER NOTIFY 之前收尾 if tgt_fwd and tgt_fwd[2] > sig["ho_notify"]: errs.append("tgt_fwd still active after HANDOVER NOTIFY") # 3) 切换中断时长,用两条正常隧道的间隙估算 if src_dl and tgt_dl: gap_ms = (tgt_dl[1] - src_dl[2]) * 1000.0 if gap_ms > max_gap_ms: errs.append("interrupt gap %.1f ms exceeds %.1f ms" % (gap_ms, max_gap_ms)) return errs这段函数只做断言,不做统计,输入是上一节脚本产出的隧道窗口和人工填入的信令时间点。max_gap_ms是中断门限,默认给 100 ms 是个宽松值,语音场景通常要压到 50 ms 以内,数据场景可以放到 200 ms 以上,按业务定。返回的字符串列表为空才判通过,非空就按提示回抓包定位。代码短,但它把“有没有转发、转得对不对、断了多久”三件事固化成了可重复执行的判断,比每次靠眼睛看曲线稳定。
一个具体技巧:把ROLE字典和这份断言函数放进同一个模块,每份抓包只改输入路径和信令时间,跑完直接出结论。我还会额外打印一条“未知隧道”清单,凡是没在角色表里出现的 TEID 都列出来,因为串扰流量往往就是从这里暴露的。从那以后我每次做切换验证,都强制先跑一遍时间窗对齐再动手看图,哪怕只有一份抓包也不跳过,省下来的返工时间远比写脚本的时间多。希望帮到你。
本文还有配套的精品资源,点击获取