资讯动态

Linux下GTP-U报文解包与抓包实战:从TEID到序列号排错指南

发布时间:2026/10/9 8:35:56 来源:尧图企业网站定制
简介这是一份面向移动网络开发者的GTP-U协议栈Linux工程源码包解压后包含34个文件由头文件h、C源文件c、编译依赖文件d、目标文件o及makefile组成整体仅130KB适合用于阅读协议实现并完成二次编译。包内围绕用户平面编解码、会话管理和TEID分配等核心模块展开同时涉及GTP-C与GTP-U的分工配合可帮助读者理解4G/5G数据通道在PGW/SGW侧的落地方式。源码结构按inc、src目录组织并附带gtputest等测试用例便于在Linux环境下对照学习、调试或扩展。已有724人学习下载适合具备一定移动通信基础、希望深入GTP-U协议栈代码的研发或测试人员。1. 为什么要在Linux上单独折腾GTP-UGTP-U是LTE和5G核心网用户面的最后一百米封装UE产生的IP包从基站出来经过GTP-U隧道打到核心网反过来的下行数据也包在这层隧道里。Linux从3.17内核开始自带GTP-U设备支持意味着就算你不做核心网开发也能在实验室里用一条ip link add gtp1 type gtp模拟一个用户面端点分析抓包、验证TEID分配、复现丢包。拿到一份叫gtp-u.rar的压缩包时里面要么是pcap抓包要么是一套协议栈源码但很多人第一步就栽在把GTP-C和GTP-U混着看。这篇笔记我按GTP-C与GTP-U的分工、Linux下的解包与抓包、TEID/序列号/扩展头三个坑、再到用脚本自测GTP-U连通性的顺序给你一条能直接照着走的路径。适合核心网测试、嵌入式Linux网络工程师以及信令分析新手。2. GTP-C与GTP-U的分工先看控制面再谈用户面2.1 两个协议管的两件事从UDP端口到消息类型GTP全称是GPRS隧道协议在3GPP框架里被明确拆成控制面和用户面两个独立协议。它们共享同一个基础头格式——版本、协议类型PT、扩展头标志E、序列号标志S、N-PDU标志PN、消息类型、长度和TEID但实际用途完全不同连UDP端口都分开GTP-C在2123端口管会话GTP-U在2152端口管数据。拿到一个抓包文件或者实时流量第一件事永远是用端口号分清当前在跟控制面还是用户面打交道。协议UDP端口使命常见消息类型GTP-C v12123建立/修改/删除承载会话Create Session Request(1), Modify Bearer(34), Delete Session Request(35)GTP-U v12152承载用户IP数据包网元保活Echo Request(1), Echo Response(2), G-PDU(255) /注意这里有个很迷惑人的地方GTP-C的Create Session Request和GTP-U的Echo Request消息类型值都是1。只看消息类型数字是分不清的必须结合端口或者上下文。我遇到过同事拿到一个GTP-U的pcap发现全是消息类型1直接当成控制面去分析结果半天看出不来问题。后来我用tcpdump同时抓2123和2152按端口拆开才恍然那些类型1其实是GTP-U的保活探测包跟会话建立毫无关系。在抓包阶段你可以先把两个端口一起收下来避免后续被“这包怎么这么少”这个黑匣子卡住sudo tcpdump -i any -nn udp port 2123 or udp port 2152 -w gtp-both.pcap-i any抓所有网卡避免物理链路选择问题-nn不做DNS和端口名解析UDP端口会直接以数字形式打印-w落盘后面用Wireshark或tshark慢慢拆。如果确认只关心用户面就把过滤条件收紧成udp port 2152减少磁盘占用。2.2 为什么没有GTP-C就建不起GTP-U会话与TEID的来龙去脉要理解GTP-U先得说清TEID从哪来。TEID全称Tunnel Endpoint Identifier是GTP隧道的接收端分配的目的就是让接收端在一台主机上区分成千上万个并发承载。在LTE附着流程里MME通过S11接口的GTP-C Create Session信令让SGW和PGW建立默认承载。SGW在响应消息的Bearer Context里带上自己分配给这条承载的下行TEIDeNodeB也分配自己的上行TEID两边交换完GTP-U数据面才开始真正搬运UE的IP包。所以GTP-U不是一条静态配置的IP隧道。即使你在Linux上手工把gtp设备创建好了、两端物理IP也通没有GTP-C先协商TEID对端拿到GTP-U包后一查TEID表发现查无此项会直接静默丢弃。这个行为让很多跑实验室的人翻车netlink配置没问题链路也是up的业务就是不通回头查才发现TEID是随手写的根本没与控制面对齐。在抓包里验证TEID来源的方法是同时打开GTP-C和GTP-U两个pcap先找Create Session Response记下它携带的TEID和IP地址再去GTP-U G-PDU头里做比对。具体操作可以先过滤2123端口把GTP-C报文拉出来tshark -r gtp-both.pcap -Y udp.port2123 -T fields -e frame.number -e ip.src -e ip.dst -e gtp.teid值得小心的是GTP-C v1报文头的TEID只是传输层标记真正写进会话上下文的是Create Session Response消息体里Bearer Context下的TEID信息元素。上述命令输出的是头字段不一定等于实际给用户面用的TEID。更可靠的做法是在Wireshark里展开Create Session Response找到Bearer Context - TEID把那个值记录下来再去GTP-U包里核对。这个步骤看似麻烦却是排查GTP-U转发异常的根基。2.3 别把GTP-U当成GRE/VXLAN三个本质差异和VXLAN或GRE相比GTP-U看起来也是“IP包套UDP再套IP”但三个差异让它的排查思路完全不同。第一是TEID的双向独立性。VXLAN的VNI只是一个24位网络标识两端配成一致就行GTP-U则不同一条承载的上行TEID和下行TEID是两个独立值分别由隧道两端各自分配。你在tcpdump里看同一条业务流请求方向的目的TEID可能是0x10000001回包方向的目的TEID却是0x20000002。这不是错误是正常的。第二是序列号。GTP-U头可以带2字节Sequence Number主要用来检测GTP-U层的丢包、重排和重复。GRE和VXLAN都没有这层语义。如果观测到GTP-U包带序列号你可以用它自己计算隧道层的实际丢包率这是内层TCP的RTO或NWIP监视都反映不出来的。第三是扩展头链。GTP-U v1的E位可以指示后面跟一个或多个扩展头扩展头之间通过Next Extension Header Type串起来。VXLAN没有这种结构GRE虽然有很小的扩展能力但远没有GTP-U这么常用。扩展头的存在会让Wireshark解析器频繁误判在抓包解码中是最容易踩坑的点后面第4章专门展开。所以选型结论很直接如果只是做普通隧道打通用GRE或VXLAN完全够。但如果要模拟移动核心网、做UPF的N3/N4接口测试、分析eNodeB和SGW之间的用户面就必须上GTP-U。不要试图用IPIP或GRE替代TEID和保活机制两边根本对不上最后只会把自己的调试时间搭进去。3. 在Linux上拆解gtp-u.rar解压、抓包和解码GTP-U的三步走3.1 解压前先不折腾rar列表、file类型和pcap入口拿到一个名字带GTP-U的rar包可能是同事从抓包机上导出的pcap也可能是一份开源协议栈的源码。不管哪种先不要急着解压。常见做法是用unrar l看清单如果是混合内容l的输出会直接告诉你哪些是.pcap哪些是.c和.h哪些是README。unrar l gtp-u.rar unrar x gtp-u.rar file *.pcapunrar l是列表模式不解压安全又省空间。rar包从Windows机器传过来时路径常带空格所以解压时我习惯带上引号unrar x gtp-u.rar。如果系统里没有unrar用7z x gtp-u.rar也一样7-Zip对RAR的兼容性足以处理常规包。file命令一次性识别文件类型看到pcap capture file就说明是抓包看到ASCII text多半就是README或者配置文件。真实干活的时候我会再多跑一条md5sum gtp-u.rar。压缩包在同事之间传来传去太容易坏先留个校验和解压到一半发现损坏时能第一时间确认不是自己的问题。这在多团队协作的实验室里很常见也是避免“压缩包损坏导致Wireshark打不开”这种哑问题的基本功。3.2 实时抓包tcpdump按端口2152抓GTP-U如果目标Linux主机本身就是核心网网元比如一台UPF或者实验室的SGW抓GTP-U最简单的方式是直接按UDP 2152抓。注意抓包位置必须在隧道内外层都能看到的点否则只能抓到一个方向。这种问题很隐蔽我曾在SGW上只抓了eth0结果下行包走了另一条物理链路白白上演了“GTP-U只出现半个方向”的戏码。基础命令sudo tcpdump -i any -nn -s 256 -c 2000 udp port 2152 -w gtp-u-live.pcap拆开看-s 256是快照长度。GTP-U v1头加上UDP头、IP头总共才不到60字节256字节足够看到完整外层IP头、UDP头、GTP-U头以及内层IP头的前面一部分磁盘和内存开销能大幅下降。-c 2000抓满2000个包自动退出适合无人值守。如果后续要做HTTP或TCP层深度分析建议把-s调大到512或者干脆-s 0全量抓但代价是文件膨胀很快实验室里千兆口满速可能一分钟就能跑出几个GB。抓包时我会再加一个条件只抓GTP-U数据包可以带上and not host 10.0.0.1之类的排除过滤掉大量广播和协议探测包不然tcptrace和后续分析会被噪音淹没。不过第一次抓包建议先全量免得误伤有用信息。3.3 离线解码tshark提取TEID、序列号和IP头解压出来的pcap或刚抓完的pcap最怕用Wireshark打开大文件卡到崩溃。命令行工具tshark在这种场景下比图形界面高效得多。用tshark把GTP-U关键字段直接拉成表格tshark -r gtp-u-live.pcap -Y udp.port2152 -T fields \ -e frame.number -e ip.src -e ip.dst \ -e gtp.message_type -e gtp.teid -e gtp.sequence_number-Y是显示过滤器语法udp.port2152先把用户面流量锁住-T fields把输出改成表格按-e指定的字段打印。gtp.message_type、gtp.teid、gtp.sequence_number是Wireshark的GTP dissector字段名。注意Wireshark这里统一用gtp.前缀不管是GTP-C还是GTP-U靠端口和消息类型区分。在输出结果里G-PDU的消息类型是255Echo Request是1一眼就能识别。如果你拿到的pcap里混合了GTP-C先过滤udp.port2123再单独导出不然TEID列表会被控制面消息干扰。tshark输出的字段如果该包没有序列号就会是空字符串这很正常不要当成解析错误。想要统计同一流IPTEID组合的出现次数可以加一条管道tshark -r gtp-u-live.pcap -Y udp.port2152 gtp.message_type255 \ -T fields -e ip.src -e ip.dst -e gtp.teid \ | sort | uniq -c | sort -rn这条命令统计每个“源IP 目的IP TEID”组合的G-PDU数量能快速看出上下行TEID是否一致、是否多个TEID交错出现。如果预期一个会话只有两个方向两个TEID结果统计出第三、第四个多半是抓包里混了其他用户或TEID分配出了问题。4. GTP-U报文头里最值钱的三个字段TEID、序列号与扩展头4.1 TEID要上下行分开看一个会话至少有四个值再次强调TEID是GTP-U隧道里唯一能区分用户承载的标识。但任何一条承载都不是单一TEID。以S1-U接口为例eNodeB侧和SGW侧的承载上下文各自保存一组TEID上行方向数据包的目的TEID由SGW分配下行方向数据包的目的TEID由eNodeB分配。两边各有一组TEID加上方向区分所以同一个承载在抓包里至少能看到两组不同的TEID。实际操作里很多人以为只要把某个TEID对上就完事。但抓包时如果只按一个TEID过滤会漏掉另一半方向的包。在GTP-C Create Bearer Response里SGW分配的DL TEID和eNodeB分配的UL TEID通常是成对出现的。手工配置GTP-U隧道时必须把“对端接口的TEID”写到自己设备出方向的目的TEID里而不是把自己分配的TEID填进去。这个细节是我见过最多的GTP-U配置错误一个TEID填到两个方向结果一半流量静默丢。验证TEID是否匹配除了用前面的sort | uniq -c统计还可以直接在命令行里把TEID流打出来看tshark -r gtp-both.pcap -Y gtp.message_type255 \ -T fields -e ip.src -e ip.dst -e gtp.teid \ | awk {print $1 - $2 teid$3}awk把字段格式化成连续的流记录上下行TEID的变化能连续观察。如果发现同一TCP流回包的目的TEID和请求的目的TEID完全不同但IP对端相同那是正常的如果连IP对端都变了说明隧道可能跨节点重建了这时候要回头查GTP-C信令里节点地址是否发生了变化。4.2 序列号和N-PDU为什么时而出现时而不出现GTP-U v1头里S位控制Sequence Number是否出现PN位控制N-PDU Number是否出现。S位置1时TEID后面跟2字节序列号PN位置1时TEID后面跟1字节N-PDU编号。这两个标志都是可选的所以收到GTP-U包时不能假设一定有序列号。不少刚用Wireshark的人发现同一段抓包里有的G-PDU有seq有的没有以为自己抓丢包头了其实只是发送端的实现不同。序列号在GTP-U里的用途是检测GTP-U层的丢包、重复和乱序类似TCP seq但不参与确认。N-PDU编号是早期UMTS网络为了配合RLC层重传才加入的在LTE和5G纯IP承载里几乎不会置位。我用tcpdump抓包时如果看到首字节是0x34开头的GTP-U头就说明PN位是1这种包多半来自旧设备或特定RAN共享场景。如果是为了分析用户业务质量优先看有S位的流因为能算隧道层丢包率。需要手工验证时直接用十六进制看头几个字节比翻Wireshark更快tcpdump -r gtp-u-live.pcap -nn -X udp port 2152 | head -30首字节的二进制格式高3位是版本号第4位是PT第5位是spare第6位是E第7位是S最低位是PN。比如0x32 0011 0010意思是版本1、PT1、E0、S1、PN0所以TEID后面要解析2字节序列号。0x30 0011 0000S0、PN0头部就是一个干净的TEID。记住0x30和0x32这两张脸在乱码一样的十六进制流里能快速定位问题。4.3 扩展头触发延后E位翻车现场GTP-U v1的E位为1时头部后面会紧跟Next Extension Header Type字段指向第一个扩展头每个扩展头又有自己的长度字段和下一个扩展头类型。扩展头机制让GTP-U可以不断追加新能力但代价是解析器必须沿着链走。Wireshark对常见扩展头RAN Container、PDCP PDU Number支持不错但遇到厂商私有类型解析器会直接跳到GTP头length指定的位置把扩展头数据当成内层IP内容。一个典型的翻车现场同一个GTP-U pcap换了新版本Wireshark后原来能看到的内层TCP流突然散架变成一堆Malformed Packet。引起这个的原因通常是E位状态变了早期抓包没有扩展头网络升级后核心网设备开始带PDCP PDU Number扩展头但抓包软件还不认识新类型号。解决办法是确认E位的状态查看扩展头类型值。手动剥包时基础头部分总是flags(1) 消息类型(1) 长度(2) TEID(4) 8字节。如果S位为1再加2字节序列号如果PN位为1再加1字节N-PDU如果E位为1先有1字节Next Extension Header Type然后按类型和长度继续加。用xxd看前12字节可以一步步对照xxd -l 12 -c 16 gtp-u-live.pcap看到E位为1时先看后继字节是什么类型。0x80、0x40这类是常见扩展头Wireshark应当能解显示Unknown时不要硬用GTP dissector改成用data过滤器手工剥掉扩展头后再把内层IP交给抓包工具解析。这个技巧在分析GTP-U over IPv6的包时同样适用因为IPv6的外层头长度会变但GTP-U头本身的结构不变。5. GTP-U定位与排错的5个踩坑记录从抓包到绕过5.1 隧道两端都up但ping不通先看回程路由现象gtp隧道设备状态是UP对端外层IP也能ping通但ping内层UE地址不通。原因GTP-U是overlay隧道。业务网段要经过gtp设备转发回程路由如果没指到对端真实IP所在的出接口响应包会走物理路由出去对端就收不到隧道内回包。测试时只配了物理接口默认路由没配隧道网段路由数据包就“有去无回”。解决在两端各自把内层网段路由指到gtp设备上。例如ip route add 10.10.0.0/16 dev gtp1配完后用ip route get 10.10.0.2确认下一跳走的是不是gtp1。这个问题七成是路由不对称不关GTP-U的事。排查优先级永远是先路由后隧道先物理后overlay。5.2 抓了一小时只有GTP-C没有GTP-U检查消息类型和端口现象抓包条件明明写了udp port 2123 or 2152结果看到GTP-C的Create Session、Modify Bearer但一条G-PDU都没有。原因抓包位置虽然把2123和2152都收了但很多用户面流量其实是从另一台转发设备直接旁路走的信令面经过当前主机用户面却不经过。另一种可能是过滤条件写错比如只写了gtp.message_type1而G-PDU的消息类型是255自然什么都抓不到。解决先用tshark -r file.pcap -Y gtp.message_type255确认G-PDU是否存在。如果确实不存在说明抓包位置没有用户面流去拓扑图上找真正经过用户面的链路口。如果只有GTP-C也别白抓可以分析会话建立和TEID分配为后续排查提供依据。5.3 TEID不匹配导致报文被静默丢弃别只看IP层现象物理IP通、隧道网段路由通、GTP设备up但内层ping无响应。抓包看到GTP-U包已经发到对端对端却没有回包。原因对端的GTP-U解包模块查TEID时发现与本地已分配TEID不一致会直接把包丢掉。GTP-U没有类似ICMP的错误通知机制所以表现是静默丢弃。不少设备为了防日志刷屏对TEID查找失败连日志都不打。解决把抓包里GTP-U头的TEID和GTP-C信令里协商的TEID逐一对比。上行包的目的TEID必须等于对端下行TEID下行包的目的TEID必须等于对端上行TEID。用命令列出所有TEID组合tshark -r gtp-both.pcap -Y udp.port2152 gtp.message_type255 \ -T fields -e ip.src -e ip.dst -e gtp.teid | sort | uniq -c对照Create Bearer Response里的TEID信息元素哪个方向对不上一边开Wireshark一边改配置比瞎猜快得多。5.4 大包丢小包通MTU和GTP头长度叠加现象内层ping -l 100没问题ping -l 1400开始丢包。原因GTP-U外层要加GTP头8字节、UDP头8字节、IPv4头20字节合计36字节开销。内层MTU如果是1500加上外层就超过以太网MTU产生分片。分片包在中间网络设备被丢弃就会出现典型的大小包差异。解决调小GTP设备的MTU让内层IP自动分片或让TCP MSS协商到合适值。常见做法ip link set gtp1 mtu 1400如果根因不在两端主机而在中间传输网还得调整核心网承载MTU。排查时可以对比ping -s 1350和ping -s 1500找到阈值再反推开销。大包丢小包通的问题十有八九是MTU叠加而不是GTP-U转发异常。5.5 Wireshark提示UDP checksum错误GSO offload在捣乱现象用tcpdump抓包后Wireshark打开大量“UDP checksum incorrect”红字尤其在高吞吐场景下。原因现代网卡启用UDP TX offload校验和由网卡在实际发出报文时计算tcpdump在软件层拿到的包校验和可能是0或未填充。这是网卡驱动的offload行为不是隧道链路出错。解决临时关闭网卡校验和offload再抓一次sudo ethtool -K eth0 tx off抓完恢复sudo ethtool -K eth0 tx on。或者干脆在Wireshark里关闭UDP校验和校验路径是Preferences - Advanced - Validate UDP checksums去掉勾选。如果只做协议分析直接忽略校验和最稳妥因为offload不影响真实数据内容。6. 进阶用Python脚本构造GTP-U Echo请求测隧道连通性当tcpdump和tshark都确认GTP-U头结构没问题后可以自己构造一个GTP-U Echo Request去探测对端节点是否活着。这比ping外层IP可靠得多因为GTP-U Echo走的是用户面协议栈对端若连接正常但GTP-U解析异常它不会回Echo Response而外层IP的ping还是会通。#!/usr/bin/env python3 构造GTP-U Echo Request并等待Echo Response纯标准库无第三方依赖 import socket import struct def build_gtpu_echo_request(teid0, seq0x1234): flags 0x32 # 版本1 PT1 S1 msg_type 1 # Echo Request length 6 # TEID(4) Sequence Number(2) base struct.pack(!BBHI, flags, msg_type, length, teid) seq_bytes struct.pack(!H, seq) return base seq_bytes def main(): dst_ip 10.0.0.2 dst_port 2152 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) sock.settimeout(2) pkt build_gtpu_echo_request(teid0, seq0x1234) sock.sendto(pkt, (dst_ip, dst_port)) try: resp, addr sock.recvfrom(2048) if len(resp) 2: print(短响应数据不完整) elif resp[1] 2: seq struct.unpack(!H, resp[8:10])[0] print(fGTP-U Echo Response from {addr[0]}:{addr[1]} seq0x{seq:04x}) except socket.timeout: print(timeout: 对端没有回GTP-U Echo Response) if __name__ __main__: main()struct.pack里的!表示大端字节序GTP协议所有字段都是大端。flags0x32代表版本1、协议类型PT1、S位置1因此TEID后面跟2字节序列号。length字段按3GPP定义是TEID及之后可选字段的长度所以填6而不是包头总长10。如果对端是标准实现会回一个类型为2的Echo Response如果超时就需要回到第5章的路由和TEID排查去。把这个脚本放到定时任务或监控脚本里每5秒发一次就能持续检测用户面节点活没活。这种探测比传统ICMP更能反映用户面转发状态因为GTP-U的解析、TEID查找都通过了说明业务通路基本可用。在嵌入式Linux项目里资源有限这个纯标准库脚本也能直接放进去跑。我现在的习惯是先在目标节点上用tcpdump抓一次Echo Request确认发出去了再在对端抓Response这样即使不通也能立刻看出卡在哪一段。希望这个自测脚本能帮你少走弯路也希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑