CentOS7 利用 pyshark 解析 QUIC 报文实战指南
Centos7利用pyshark解析QUIC报文最近在做网络协议分析遇到一个真让人头疼的场景C端应用大量走到UDP 443端口但tcpdump抓下来全是密文仔细一看原来都是QUIC流量。CentOS 7上想快速把QUIC报文里的版本号、包类型、DCID这些关键信息提出来做统计一开始用tshark文本输出grep半天发现字段对不齐、长连接一多就一脸懵后来换成pyshark写脚本半小时就把活干完了。这篇文章把整个过程、踩过的坑和最后落地的代码都整理出来适合运维、后端开发、网络分析和安全方向的同学参考。1. 为什么要专门解析QUIC报文1.1 QUIC在当下的实际地位QUIC是建立在UDP之上的一套传输层协议HTTP/3的默认底座就是它。现在主流浏览器、视频App、游戏SDK都在往HTTP/3迁移特征是源端口或者目的端口集中在UDP 443但打开包看内容又不像普通DNS也能一眼看懂因为除了少数明文帧QUIC的头部和负载大多做了加密和帧序列化处理。过去排查网络问题大家习惯盯TCP三次握手、TLS ClientHello这些明文线索。可到了QUIC时代这套经验基本失效。我曾经遇到一个场景某应用升级到HTTP/3后部分用户反馈“连接失败”抓包看到UDP 443端口有大量重传但看不到任何TLS交互过程因为QUIC把TLS协商都揉进了UDP负载里。如果只是按端口过滤根本定位不到问题。这时候必须把QUIC报文里的版本号、包类型、连接ID这些元信息抽出来分析。这里说的“解析QUIC报文”不等于解密HTTP/3内容而是在协议层把“这是什么版本的QUIC”“这是Initial包还是Handshake包”“连接ID是什么”“包号是多少”这类信息读取出来。这些元信息本身就足够用来做故障定位、版本统计和异常流量识别。1.2 为什么选pyshark而不是直接调tsharktshark本身可以把QUIC字段输出成文本比如tshark -r quic.pcap -Y quic -T fields -e quic.version -e quic.packet_type单条命令看起来挺方便但真到实际使用会非常痛苦。一是文本输出格式依赖字段顺序一次想统计版本号、包类型、DCID、SCID、包号5个字段命令行会变得又长又难维护二是结果要写入监控系统或者跟业务日志做关联时还得再写一层shell解析加个正则匹配就能让人折腾半天三是长连接场景下每个流都有几十上百个包想按连接聚合统计纯文本处理基本不现实。pyshark解决的是“解析引擎复用、结果结构化”的问题。它本身不重新实现协议解码而是把tshark作为底层引擎借助Wireshark强大的协议栈把每个包解析成带属性的Python对象。拿到对象后直接访问属性就行packet.quic.version packet.quic.packet_type packet.quic.short_dcid相比命令行输出这是质的差别。数据可以直接进pandas做分布统计也可以写进数据库甚至挂到实时告警管道里。用pyshark基本等于给QUIC分析按了一个可编程的钩子而且保留了Wireshark协议的兼容性不需要自己造轮子去解UDP负载格式。1.3 这个方案能帮你解决什么这套方案的典型场景有几个批量分析历史pcap里QUIC版本分布、追踪某个客户端连接ID完整路径、区分握手阶段包类型、自动从大流量日志中提取异常Initial重传。比如想统计线上UDP 443的QUIC版本分布手工抽样不可能覆盖全量脚本批量跑一遍就能出表。给不同基础读者的一个简单定位如果你只是偶尔用Wireshark看一眼单个包那本文的代码可以当工具箱备用如果你需要定期处理一批pcap或者准备做自动分析那这套pyshark环境就是你基础设施的一部分。文章后面会按“环境安装、代码实现、排错记录”三条线展开跟着走基本能落地。2. 环境准备CentOS 7上的依赖安装2.1 CentOS 7最容易卡住的问题CentOS 7听起来是老系统但生产环境里数量仍然不少。在它上面搭pyshark解析QUIC有三个特别容易卡住的地方。第一是系统自带的Python版本是2.7而pyshark新版本需要Python 3.6以上直接pip install pyshark都会提示语法不支持或者依赖冲突。第二是Yum默认源里的Wireshark版本非常老老到可能压根不支持QUIC解析。第三是在线抓包时非root用户没有权限打开网卡程序一跑就报“Couldnt run /usr/sbin/dumpcap...”。这些问题不看经验真不好整。我见过同事在CentOS 7上装了半天pyshark最后发现tshark打印不了任何内容仔细看版本号是1.10连QUIC协议树都没有自然解析失败。所以环境准备的核心原则是先把协议解析底座搞定再装Python侧依赖。2.2 安装tshark与libpcap先确认当前tshark版本tshark --version如果版本低于3.0基本可以断定不支持完整QUIC解析。Wireshark 3.0之后才对QUIC有比较稳定的协议支持建议尽量到3.4以上。CentOS 7默认源里的wireshark-cli版本通常很老可以引入epel源试试sudo yum install -y epel-release sudo yum install -y wireshark-cli sudo yum install -y libpcap-devel装完再执行一次tshark --version。如果版本仍然偏低就得靠第三方源或者源码编译。我实际用过源码编译依赖需要cmake、glib2-devel、libpcap-devel、flex、bison等过程虽然长但能保证拿到新版本。编译安装路径建议保留默认否则后续pyshark调用tshark时还得配置环境变量。这里多说一句libpcap是抓包底库pyshark本身不直接调用它但tshark和dumpcap在离线解析和在线抓包时依赖它。很多问题表面上像Python代码出错实际上是系统缺少这个库所以顺手装上是值得的。源码编译的简略流程sudo yum install -y cmake gcc gcc-c flex bison glib2-devel libpcap-devel wget wireshark源码包 tar xvf wireshark-*.tar.xz cd wireshark-* mkdir build cd build cmake .. make -j$(nproc) sudo make install sudo ldconfig编译耗时取决于机器性能一般几分钟到十几分钟。编译完成后重新打开终端确认tshark --version已经变成新版本。2.3 安装Python环境与pysharkCentOS 7上我建议给项目单独建一个Python 3环境不要动系统自带的Python 2。可以用Miniconda也可以从源码编译Python 3或者用Software Collections的方式安装。以Miniconda为例wget Miniconda安装包 bash Miniconda3-latest-Linux-x86_64.sh source ~/.bashrc python3 --version有了Python 3之后创建虚拟环境并安装pysharkpython3 -m venv quic_env source quic_env/bin/activate pip install pyshark如果离线环境没有外网可以在一台能联网的机器上提前打包pip download pyshark -d ./offline_packages再把整个目录传到目标机器上离线安装。因为pyshark基于tshark解析它并没有太多底层C扩展所以离线安装的兼容性问题比较少。装完测试一下python3 -c import pyshark; print(pyshark.__version__)接着确认pyshark能找到tsharkpython3 -c import pyshark; print(pyshark.tshark.tshark.get_tshark_version())这一步如果报错说明tshark不在PATH里。解决方式是在调用前设置环境变量或者在代码里指定tshark路径import pyshark pyshark.tshark.tshark.TSHARK_PATH /usr/local/bin/tshark初次配置容易忽略这个环节等到代码运行报“Could not determine tshark version”时才想起来补其实提前检查能省很多时间。3. 核心实现用pyshark解析QUIC报文3.1 场景A离线解析pcap文件最常用的场景就是拿到一个pcap文件批量提取QUIC元信息。建议先用tshark验证一下文件里确实有QUIC协议tshark -r quic.pcap -Y quic -c 5能打出内容说明tshark版本和文件都没问题。接下来写Python脚本import pyshark import csv def parse_quic_pcap(pcap_path, output_csv): cap pyshark.FileCapture( pcap_path, only_summariesFalse, keep_packetsFalse, use_jsonTrue ) with open(output_csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame_time, quic_version, packet_type, dcid, scid]) for packet in cap: try: quic_layer packet.quic except AttributeError: continue frame_time getattr(packet.frame_info, time, N/A) version getattr(quic_layer, version, N/A) packet_type getattr(quic_layer, packet_type, N/A) dcid getattr(quic_layer, short_dcid, None) or getattr(quic_layer, dcid, N/A) scid getattr(quic_layer, scid, N/A) writer.writerow([frame_time, version, packet_type, dcid, scid]) print(解析完成结果已写入, output_csv) if __name__ __main__: parse_quic_pcap(/data/quic.pcap, /data/quic_result.csv)几个参数值得说明。only_summariesFalse表示保留完整协议树这样才能访问packet.quic。如果设成True只能拿到汇总信息像“源IP”“目的IP”“长度”协议层字段全没了。keep_packetsFalse特别重要。默认是True也就是说所有包对象都会被保留在列表里解析一个几GB的文件时内存刷刷往上涨可能直接OOM。设成False后FileCapture变成一个流式生成器一个包解析完就释放内存占用稳定在较低水平。use_jsonTrue能明显提升解析速度。pyshark底层调用tshark时通过JSON输出比文本输出更容易转成对象实测同体量pcap解析时间能缩短不少。运行结果大概长这样frame_time, quic_version, packet_type, dcid, scid 2025-01-12 10:00:01, 0x00000001, Initial, 7a1f..., 9c33... 2025-01-12 10:00:02, 0x00000001, Handshake, 9c33..., 4d2e...可以看到版本号0x00000001是标准的QUIC v1Initial包用于连接建立Handshake包用于密钥协商。把这些信息存成CSV后用pandas一行命令就能做版本分布或包类型统计import pandas as pd df pd.read_csv(/data/quic_result.csv) print(df[quic_version].value_counts())3.2 场景B在线抓包并实时解析在线场景通常是为了抓“正在发生”的流量比如某次现网问题复现时采集UDP 443的QUIC包。用pyshark的LiveCapture可以做到边抓边解析。先看一个基础版本import pyshark capture pyshark.LiveCapture( interfaceeth0, bpf_filterudp port 443, output_file/data/live_capture.pcapng ) print(开始抓包请触发业务请求...) capture.sniff(timeout60) capture.stop() for packet in capture._packets: try: quic_layer packet.quic version getattr(quic_layer, version, N/A) packet_type getattr(quic_layer, packet_type, N/A) dcid getattr(quic_layer, short_dcid, None) or getattr(quic_layer, dcid, N/A) print(fversion{version}, type{packet_type}, dcid{dcid}) except AttributeError: continue线上环境要注意几点。第一sniff(timeout60)是阻塞式抓包60秒时间到了自动停止。如果业务请求不在这个时间窗口内出现可能什么都没抓到。所以在大流量环境建议把timeout调大或者直接放到定时任务里持续抓一段时间。第二bpf_filter只过滤了UDP 443但没有判断是不是真QUIC。UDP 443上还有其他协议比如DTLS、RTP如果用packet.quic访问不到就会被except AttributeError跳过不影响整体流程。反之如果某些加密流量刚好也被判断成解析失败那也没关系元信息统计不会因此中断。第三在线抓包对权限要求很严格。普通用户直接跑大概率报The capture session could not be initiated on interface eth0这是因为dumpcap需要root权限或者特殊能力。最简单的做法是sudo -E python3 live_capture.py或者把当前用户加入wireshark组并调整dumpcap权限sudo groupadd wireshark sudo usermod -aG wireshark $USER sudo chgrp wireshark /usr/sbin/dumpcap sudo chmod 750 /usr/sbin/dumpcap改完需要重新登录终端才生效。这里提醒一句给dumpcap设置了750权限后wireshark组内用户都能抓包生产环境要确认组内成员可信。3.3 关键字段提取与参数说明QUIC报文里最值得关注的是版本号、包类型、短连接ID和长连接ID几个字段。版本号代表协议版本常见的是QUIC v1也就是0x00000001早期还有Google的Q039、Q043、Q046等版本号。技术改造时经常需要按版本分组统计从而判断线上客户端是否已经统一到新版本。包类型决定了这个包在连接的哪个阶段。长包头模式下典型类型有Initial、Handshake、Retry、0-RTT短包头模式下一般是1-RTT包。通过包类型可以排查连接是否卡在Initial重传或者Handshake阶段是否异常。比如Initial重传特别多通常意味着服务端没响应或者UDP丢包严重。连接ID字段中DCID是目标连接IDSCID是源连接ID。同一连接上DCID和SCID在握手过程中会交换用DCID可以追踪一段UDP流中的完整连接路径。实际分析时通过DCID还能关联客户端发送的多个包即使IP地址被负载均衡伪装过DCID也相对稳定。要准确拿到字段不能完全靠猜。pyshark的属性名其实是Wireshark显示的字段名去掉quic.前缀之后重新构造的。想查所有可用字段可以用tshark自带元数据tshark -G fields | grep -i quic | grep -E version|packet_type|dcid|scid结果会输出类似P quic version QUIC Version P quic long.packet_type QUIC Long Packet Type P quic packet_type QUIC Packet Type P quic short_dcid QUIC Short DCID P quic dcid QUIC DCID P quic scid QUIC SCID看到这些字段后在pyshark里对应属性名要把.去掉换成_例如long.packet_type对应long_packet_typeshort_dcid对应short_dcid。不过不同tshark版本的命名略有差异最可靠的方式是解析时打印一下层对象的所有属性for packet in cap: quic_layer packet.quic print(dir(quic_layer))然后从输出的属性列表中挑选需要的字段名。这个方法几乎适用于Wireshark支持的所有协议不止QUIC。因为字段名在不同版本之间可能有细微差别我建议所有访问属性的代码都用getattr(quic_layer, version, N/A)这种写法。这样即使字段不存在也不会让整个解析过程崩掉。4. 常见问题与排查实录4.1 tshark版本太旧导致QUIC解码失败这个错误最有迷惑性。pyshark能正常读取文件但遍历包时始终拿不到packet.quic每次都落到except AttributeError看起来像代码写错了其实底层tshark根本不认识QUIC协议。判断方法很简单先用tshark命令行解析同一个文件tshark -r quic.pcap -Y quic -c 1如果输出为空或者报“No such protocol”基本就是tshark版本问题。CentOS 7默认源里老版本的wireshark对QUIC支持非常有限很多早期draft版本甚至完全没有协议定义。解决办法就是升级tshark。前面已经给了epel和源码编译两种方案这里再补充一个验证习惯改完环境后立刻执行tshark -G protocols | grep -i quic能搜到“QUIC”就说明协议树已经就绪。4.2 权限、无权限读取网卡的问题在线抓包时最常见的报错是Couldnt run /usr/sbin/dumpcap in child process: Permission denied或者接口打开失败。很多人第一反应是去看Python代码实际上和代码没有关系。我在生产环境遇到过一种情况用服务账号跑计划任务抓包脚本一直失败日志里全是权限报错。最后检查发现服务账号不在wireshark组里dumpcap又是750权限根本没有执行权限。稳妥的做法还是把账号加到wireshark组同时确认dumpcap所在路径which dumpcap如果路径在/usr/sbin/dumpcap修改权限时的路径也要保持一致。修改过权限之后重新登录或者执行newgrp wireshark让组权限生效。如果机器安全要求高也可以考虑用setcap赋予抓包能力sudo setcap cap_net_raw,cap_net_admineip /usr/sbin/dumpcap这样避免把整个组权限放开但这种能力配置需要谨慎评估风险。4.3 大pcap文件内存爆掉很多人第一次跑pyshark解析大文件脚本写着写着进程就被系统OOM Kill了。看代码会发现默认创建FileCapture时会把每个包都保存在capture._packets或内部缓存里文件一大会话内存瞬间飙升。解决方法是加keep_packetsFalse。这个参数之前提过再强调一次是因为它太容易被忽略。开启后pyshark使用迭代器方式逐个解包当前包处理完就被回收。用下面的写法可以验证内存变化python3 -c import resource print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss) 在大文件解析脚本里建议每隔一段时间输出一次已处理包数和当前RSS方便观测内存是否异常增长。配合生成器解析10GB级别pcap时RSS也能稳定在几百MB量级。还有一个容易忽视的细节如果代码里把每个包追加到自己的全局列表比如写all_packets.append(packet)那不管pyshark怎么释放你这边又给留住了。正确做法是在循环体里同步完成统计和输出不要预留全量列表。4.4 QUIC版本号与包类型提取不到有时候字段不是完全取不到而是取出来的值为空或者名称对不上。比如packet.quic.version返回None第一反应可能是“版本号没有抓到”其实可能是这个包根本没有version字段。短包头格式的1-RTT包确实不携带version字段只有长包头格式的Initial/Handshake/Retry包里才有。所以统计版本号时对于短包直接标记为“1-RTT/短包头”不要强求一个version值。还有早期QUIC版本字段和现在的字段名不同。某些draft版本在Wireshark里显示为quic.version另一些显示为quic.long.packet_type或quic.header_form。想确认字段名最靠谱的是打印一层属性quic_layer packet.quic print(dir(quic_layer))输出大概包含version、packet_type、long_packet_type、short_dcid、scid等。字段列表会随tshark版本变化但不会凭空消失只要打印出来就一目了然。批量解析时我自己的习惯是在代码里加一个兜底函数def get_quic_field(layer, field): return getattr(layer, field, None) or getattr(layer, field.upper(), None)虽然不能完全覆盖所有命名差异但至少不会因为一个字段缺失就中断整批任务。4.5 UDP 443不全是QUIC最后一个问题不算报错更多是统计陷阱。用udp port 443过滤流量时里面可能是QUIC也可能是其他基于UDP的加密协议。直接遍历全量包时会发现很多包没有quic层这是正常的。要提高纯度可以先判断包的前几个字节特征。QUIC v1长包头的第一个字节固定为0b11000000开头也就是0xC0以上的值短包头则是0x40到0x7F之间。当然这只是一种启发式判断不是100%可靠但能快速筛掉很多噪音。代码里可以按这个思路粗筛def looks_like_quic(packet): raw packet.raw_mode # 注意这会增加内存谨慎用于大流量 if len(raw) 2: return False first_byte raw[0] # 长包头 0xC0~0xFF短包头 0x40~0x7F return (first_byte 0x80) ! 0 and (first_byte 0x3F) ! 0不过在离线pcap这种大文件解析里不建议开raw_mode因为会缓存原始字节。更建议直接依赖Wireshark协议树判断如果当前包没有quic层就跳过。毕竟Wireshark已经在内部做了协议识别比自己猜字节可靠得多。最后分享一点体会我实际用下来最大的感受是大部分人并不是被协议本身难住而是卡在pyshark字段命名和底层tshark版本的不稳定上。记住一个组合技能就够了先用tshark -G fields查字段名再用dir(packet.quic)核对属性名基本不会出错。另外一个小技巧如果一段QUIC报文在pyshark里始终解析不对先用桌面版Wireshark打开同一份pcap定位到特定包在协议树里看它显示的字段层级再回pyshark里找对应属性。这个流程能解决90%的字段提取问题。解析QUIC不只是为了看元信息更重要的是把它接到监控和告警链路里真正发挥批量分析的价值。