ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python网络协议报文解析与可视化:从Scapy抓包到Matplotlib出图

Python网络协议报文解析与可视化:从Scapy抓包到Matplotlib出图 简介一套基于Python的网络协议报文解析与可视化工具及配套资料附带完整文档与全部源码面向计算机相关专业学生、教师及企业开发者尤其适合人工智能、通信工程、自动化、电子信息、物联网等专业用于毕业设计、课程设计或项目初期立项演示也适合新手循序渐进学习。工具实现了网络协议报文的解析、分类与可视化展示代码组织清晰可在现有基础上二次修改便于拓展其他协议格式。压缩包共8个文件包括两个Python源码、交互式示例笔记、详细说明文档、配置文件及授权文件等整体仅21KB轻量精炼。该项目为个人高分源码已获导师指导认可答辩评审分达95分代码全部测试运行成功功能稳定可用。目前已有90人浏览学习读者可获得可直接运行的解析脚本、演示笔记、项目说明与配置资料快速掌握Scapy库在报文分析中的实际应用并可直接用于课程作业、毕业设计或项目实践。1. 用Python做网络协议报文解析与可视化这到底是什么能省多少事做网络协议报文解析很多人第一反应是打开Wireshark右键导出一下觉得不就完事了吗。可真到要批量分析、要出图表、要和业务数据打通的时候Wireshark那套手工界面根本扛不住。标题里这个“基于Python的网络协议报文解析及可视化工具”本质上是把抓包、解析、特征提取、画图做进一条流水线读入pcap/pcapng或实时网卡数据按协议栈拆字段再把时间序列、流量分布、协议占比可视化。适合不想每次手动导出一堆CSV、想在公司内网复用自己的解析逻辑、甚至要做自动化告警的工程师。这篇文章把我自己搭这套工具的路径拆开讲从选型到踩坑最后给验证技巧保证你照着能跑跑完能改。2. 报文解析的底层思路与工具选型从socket到scapy先定解析框架2.1 为什么首选Scapy而不是手工解析网络报文说到底就是一串字节。所谓解析就是按协议规范把这串字节切成一个个字段MAC地址占6字节IP头里有版本、TTL、源地址TCP头里有端口、序号、标志位。如果只用Python标准库一条原始socket recv出来的数据都要自己切做三次握手的场景就得写几百行struct.unpack还不一定有现成的CRC校验。所以先不要自己造轮子在Python生态里解析网络报文有个绕不开的库叫Scapy。常见做法是用Scapy读pcap文件它能自动识别以太网、IP、TCP、UDP这些常见协议把每个字段映射成对象属性。选型对比下来有几个候选方案原理适合场景缺点裸socket struct.unpack手动按协议头切字节固定的私有协议、嵌入式串口代码量大协议一变就要改dpkt纯Python轻量解析只做读pcap、提取字段API偏底层不支持发包Scapy对象化协议栈支持解析/构造/抓包网络协议分析与工具原型大文件解析慢内存占用高pyshark调用tshark内核解析结果与Wireshark一致需要保持和Wireshark完全一致依赖外部tshark性能受限于tshark我一般会选Scapy做主解析dpkt做备选。为什么不是pyshark因为它本质是给tshark套了一层壳你机器上必须装Wireshark才能用在服务器上部署很不方便。Scapy是纯Python包pip install scapy就能装常见协议都认识还自带抓包功能。下面是最小解析代码from scapy.all import rdpcap, IP, TCP, UDP pkts rdpcap(capture.pcap, count500) # 只读前500包 for pkt in pkts: if IP in pkt: src pkt[IP].src dst pkt[IP].dst if TCP in pkt: sport pkt[TCP].sport dport pkt[TCP].dport flags pkt[TCP].flags print(f{src}:{sport} - {dst}:{dport} flags{flags}) # 非IP帧比如ARP此时不会走IP分支rdpcap是全量读入内存count参数控制读取数量适合先探路。pkt[IP].src是Scapy把IP头解析成层对象后的字段访问方式flags是TCP标志位。跑这个脚本能快速看到数据包里有没有IP协议、有哪些端口在通信。注意如果pcap里有非IP帧直接pkt[IP]会抛异常所以要先if IP in pkt判断层是否存在。这也是新手最容易踩的坑后面避坑章会说。2.2 解析模块的目录结构与公共函数实际工具不能只有一个脚本我一般这样组织目录protocol_tool/ ├── pcap_reader.py # 读pcap/pcapng统一入口 ├── protocols/ # 协议解析扩展包 │ ├── __init__.py │ ├── eth_ip_tcp.py # 传统IP协议解析 │ ├── modbus_rtu.py # Modbus RTU解析 │ ├── can_bus.py # CAN报文解析 │ └── iec698.py # 电网698协议解析示例 ├── visualizer.py # matplotlib画图 ├── utils/ │ ├── byte_tools.py # 字节转换工具 │ └── time_utils.py # 时间戳处理 ├── config.yaml # 解析参数配置 └── output/ # 输出csv/png公共函数里最常用的就是字节转十六进制、大小端转整型、MAC/IP地址格式化。比如写一个hexdump用来在调试时打印报文原始字节# utils/byte_tools.py def hexdump(data: bytes, base0, width16) - str: 按Wireshark风格输出hexdump便于和抓包对照 lines [] for i in range(0, len(data), width): chunk data[i:iwidth] hex_part .join(f{b:02x} for b in chunk) ascii_part .join(chr(b) if 32 b 126 else . for b in chunk) lines.append(f{basei:04x} {hex_part:{width*3}} {ascii_part}) return \n.join(lines) if __name__ __main__: print(hexdump(b\x01\x02abc))width控制每行显示多少字节Wireshark默认是16也可以设成8方便看串口报文。左侧偏移量能帮助定位解析到第几个字节。调试私有协议时先用这个函数把报文打出来再对着协议文档数偏移量比用print(data)直接打印不可读的bytes要直观得多。3. 手写一套可复用的报文解析与可视化工具核心代码与参数说明3.1 用Scapy解析PCAP文件并提取协议字段现在开始做真正的解析工具。假设手上有一批pcap文件来自公司网关、CAN总线采集盒或串口服务器。目标是把每条报文的协议类别、源目地址、端口、时间戳、关键载荷统一提出来存成结构化数据。常见做法是写一个读写分离的解析函数只返回逻辑结果不直接打印。# pcap_reader.py from scapy.all import PcapReader, IP, TCP, UDP, Raw from datetime import datetime def parse_pcap(file_path, bpf_filterNone, max_pktsNone): 逐包读取pcap返回解析记录列表 records [] with PcapReader(file_path) as reader: for idx, pkt in enumerate(reader): if max_pkts and idx max_pkts: break # BPF过滤在scapy层自己实现避免先读全包 if bpf_filter and not _simple_bpf_match(pkt, bpf_filter): continue rec {} rec[index] idx rec[time] float(pkt.time) # Epoch时间戳 rec[src_mac] pkt.src if hasattr(pkt, src) else None rec[dst_mac] pkt.dst if hasattr(pkt, dst) else None if IP in pkt: rec[src_ip] pkt[IP].src rec[dst_ip] pkt[IP].dst rec[proto] pkt[IP].proto if TCP in pkt: rec[sport] pkt[TCP].sport rec[dport] pkt[TCP].dport rec[flags] str(pkt[TCP].flags) if UDP in pkt: rec[sport] pkt[UDP].sport rec[dport] pkt[UDP].dport if Raw in pkt: rec[payload_len] len(pkt[Raw].load) # 只保留前64字节做指纹避免大载荷撑爆内存 rec[payload_hex] pkt[Raw].load[:64].hex() records.append(rec) return recordsPcapReader是迭代器逐包解析不会一次性把整个文件读进内存处理几个GB的pcap是关键。pkt.time是Scapy帮我们转好的Epoch浮点秒可以直接和采集系统的日志对齐。_simple_bpf_match是自己写的一个简易过滤函数比如只关心TCP端口。注意我这个parse_pcap返回的是列表遇到超大文件建议改成yield生成器后面避坑再说。参数说明max_pkts用于快速验证设成1000能秒出结果bpf_filter传字符串如tcp and port 8080时我这里简化成只做端口匹配如果你想用完整BPF语法建议内部直接调用tcpdump或PcapReader的filter参数。实际工作中我倾向于在采集时就按端口过滤减少无效流量。3.2 把解析结果落成CSV并用Matplotlib画时序图解析出来的记录如果只print在终端谈不上可视化。下一步是把记录写成CSV再用Matplotlib画流量时序图和协议占比图。这也是标题里“可视化工具”的核心落地。# visualizer.py import csv from collections import Counter from datetime import datetime, timezone import matplotlib.pyplot as plt def records_to_csv(records, out_csv): 将解析记录写入CSV字段顺序由fieldnames控制 if not records: return fieldnames records[0].keys() with open(out_csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(records) print(fsaved {len(records)} records to {out_csv}) def plot_timeline(records, out_png, bucket_sec1): 按bucket_sec聚合每秒包数/字节数画折线 bucket_times [int(r[time] // bucket_sec) for r in records] counter Counter(bucket_times) if not counter: return start min(counter) end max(counter) x list(range(start, end 1)) y [counter.get(t, 0) for t in x] # 转换为本地时间字符串 x_time [datetime.fromtimestamp(t * bucket_sec, tztimezone.utc) for t in x] plt.figure(figsize(12, 4)) plt.plot(x_time, y, linewidth1) plt.title(Packet Count per Second) plt.xlabel(Time (UTC)) plt.ylabel(Packets) plt.grid(True) plt.tight_layout() plt.savefig(out_png, dpi100) print(fsaved timeline to {out_png})bucket_sec是聚合窗口网络画像建议设为1秒长时采集可以设60秒避免曲线毛刺过多。timezone.utc必须显式指定否则datetime.fromtimestamp会用本地时区导致和采集端时间对不上。CSV用utf-8-sig编码Excel打开不会乱码这是给同事看报告时必备的小教养。画完时序图还得画协议占比图。协议占比最适合饼图但Wireshark按包数统计我们的工具可以同时统计“包数”和“字节数”两者差异能提示小包高频攻击或大包传输场景。def plot_proto_pie(records, out_png): proto_counter Counter(r.get(proto) for r in records if r.get(proto)) labels [] sizes [] for proto, cnt in proto_counter.most_common(8): # 取top8 labels.append(f{proto}({cnt})) sizes.append(cnt) plt.figure(figsize(6, 6)) plt.pie(sizes, labelslabels, autopct%1.1f%%, startangle140) plt.title(Protocol Distribution) plt.savefig(out_png, dpi100)proto字段在3.1里取了IP层的协议号。6是TCP17是UDP1是ICMP。如果你发现大量协议号是自己不认识的说明采集到了私有协议这时候就要扩展解析逻辑。注意most_common(8)把长尾协议全部丢了如果你想看小概率协议可以把8改大或改用柱状图。3.3 扩展支持CAN/Modbus/698这类非IP协议标题里的“网络协议”不一定都是IP工业现场常见的是CAN、Modbus RTU、CDT以及电网的698协议。这些报文在pcap文件里通常封装在Linux SocketCAN或TCP/UDP载荷里Scapy默认不认识需要自己写解析扩展。这也是“可复用”的关键点。设计上我会在protocols/目录里给每个协议写一个解析类统一实现parse(raw_bytes)方法返回结构化dict。以Modbus RTU为例它的报文结构是地址(1字节) 功能码(1字节) 数据(N字节) CRC(2字节低字节在前)。# protocols/modbus_rtu.py def parse_modbus_rtu(raw: bytes) - dict: 解析Modbus RTU报文帧校验CRC是否合法 if len(raw) 4: return {valid: False, reason: frame too short} addr, func raw[0], raw[1] crc_received int.from_bytes(raw[-2:], little) # 接收到的CRC crc_calc _crc16_modbus(raw[:-2]) return { valid: crc_recalc crc_received, slave_addr: addr, function: func, data_len: len(raw) - 4, data_hex: raw[2:-2].hex(), crc_ok: crc_calc crc_received, # 字段名和上面valid二选一 } def _crc16_modbus(data: bytes) - int: crc 0xFFFF for b in data: crc ^ b for _ in range(8): if crc 1: crc (crc 1) ^ 0xA001 else: crc 1 return crc注意我代码里写了两处valid和crc_ok实际使用时统一用valid就好避免二次判断。crc16_modbus是固定算法0xA001是多项式这是Modbus RTU的行业标准不要自己发明。如果你解析CAN报文常见做法是先取11位或29位ID再拆数据域里的信号位CAN和Modbus的区别是它没有地址和功能码而是ID加8字节数据。对于电网698协议报文更长而且有复杂TLV结构建议按帧头、长度、控制域、数据域拆分先不急着解业务字段先把帧完整性验证了。CDT报文则是固定帧长按报文类型决定字段偏移。这些非IP协议的共同点是不要在解析函数里做业务判断只输出“这个字段是什么”把业务判断放到上层。这样你的工具换个项目还能用这也是最容易被忽视的软件工程边界。4. 避坑指南报文解析里那些让新手一夜回到解放前的坑4.1 链路层头部长度不一致导致卦错字段现象用Scapy解析时IP层源目地址全对但是TCP端口看起来像乱码。原因pcap文件里有带VLAN tag的帧Scapy会把Ethernet头解析成两层而你手动pkt[IP]时Scapy自动跳过了VLAN但如果你自己按tcpdump的偏移量读就容易多算4字节。解决不要自己算偏移始终用pkt[IP]这类语义化访问实在要手动切先检查pkt.haslayer(VLAN)然后把偏移加4。另外Linux抓包常见的“any”设备头长度2字节普通网卡头长度14字节跨机器比对pcap时一定要看链路层类型。4.2 Scapy对分片重组和TCP流切分不靠谱现象解析大流量pcap时某些TCP负载只有一半或者HTTP页面缺内容。原因IP分片和TCP分段是两回事Scapy默认不重组分片也不做TCP流重组它只把每个包当成独立网络层报文。解决如果做文件还原或HTTP解析先开启IP(defragTrue)或者直接用scapy.pipetool里的TCPReassembler。我在实际项目中用pkt[IP].defrag()配合pkt[TCP].payload做最简单重组但这只对完整TCP会话有效乱序流还得用tcpreorder。如果业务要求百分百准确直接调用tshark的-z follow,tcp,ascii更省事。4.3 时间戳精度和时区问题让可视化曲线变形现象画出来的每秒包数曲线和采集端日志对不上峰谷错位。原因Scapy源码里pkt.time是浮点数但格式是unix epoch秒不同抓包工具对微秒处理不一样pcapng可能带有纳秒精度而pcap老格式只有微秒。解决统一用float(pkt.time)然后按int(time // bucket_sec)聚合时区一律用UTC不要用本地时区否则凌晨有抓包结果的话曲线会歪。另外不要把pkt.time直接当整数去重因为同一秒内多个包会重复我见过有人把时间当主键结果记录数少于实际包数后面排查半天。4.4 编码与字节序大小端搞错全盘皆错现象解析CAN报文时车速总是理不清数值要么大得离谱要么是负数。原因CAN信号传输中常见的Intel字节序小端和Motorola字节序大端混着用。Modbus RTU也是寄存器值可以是高字节在前或低字节在前。解决写工具时把字节序参数化显式传给解析函数比如int.from_bytes(field, byteorderlittle)。并且单独写一个endian_utils.py处理8/16/32/64位整型转换。我的血泪经验是在字段名里直接加后缀_le、_be一眼能看出哪个是哪个调试起来不用再去翻协议文档。4.5 大文件解析内存暴涨可视化直接卡死现象读一个1.5GB的pcaprdpcap直接把内存吃满机器卡到swap解析跑了10分钟还没完。原因rdpcap是全量载入把每个包都变成Scapy对象1GB文件轻松吃4GB内存。解决解析阶段用PcapReader迭代器我在3.1里用了已经避免全量载入但如果我把记录全存进列表再返回内存也会涨。正确做法是把记录分批写成CSV或者用yield生成器。可视化阶段瓶颈在Matplotlib画几百万个点此时要先用Counter聚合结果只保留几百个桶再画图就不会卡。另外建议优先用--max_pkts参数做小样本验证确认逻辑正确后再跑全量。5. 让工具更实用实时捕获、协议识别与验证的进阶技巧5.1 用pyshark做实时抓包并回灌进现有可视化静态pcap分析只适合事后复盘做监控预警就需要实时抓包。我常用pyshark的LiveCapture接口观察当前网卡流量不需要root权限但依赖tshark。加了这层之后整个工具就从一个“解析器”变成“监控平台”了。import pyshark def stream_capture(interfaceeth0, duration60): cap pyshark.LiveCapture(interfaceinterface) for pkt in cap.sniff_continuously(packet_count100): # 判断协议后直接落库不做列表缓存 print(pkt.highest_layer, pkt.length)interface传网卡名Windows上可能叫“以太网”或\Device\NPF_{...}。packet_count限制数量避免无限抓。实时抓包拿到的对象和pyshark的解析结果必须转成字符串类型才能和其他模块对接否则内存里留着对象指针每秒钟泄漏一堆。5.2 用一条命令验证解析结果与Wireshark是否一致解析工具最怕自己认为对、实际和Wireshark对不上。我的验证方法是随机抽取100条记录输出关键字段然后和Wireshark的“显示列”对照。不用手动一个个看我写了个快速比对脚本读取CSV后直接打印不匹配的行。# verify.py # 用法python verify.py records.csv wireshark_export.csv import csv, sys def main(rec_csv, ws_csv): rec {f{r[index]}: r for r in csv.DictReader(open(rec_csv))} ws {f{r[index]}: r for r in csv.DictReader(open(ws_csv))} for idx in rec: if idx not in ws: print(fmissing in wireshark: {idx}) continue if rec[idx][src_ip] ! ws[idx][src_ip]: print(fmismatch {idx}: rec{rec[idx][src_ip]} ws{ws[idx][src_ip]})关键点两端都导出“序号”并且要保证包序号对应同一个包。Wireshark的“首包编号”默认从1开始而Scapy的索引从0开始所以需要对齐偏移。如果发现大量不匹配先检查你的协议解析逻辑而不是怀疑Wireshark。5.3 日常维护中养成的三个验证习惯第一每次改协议解析函数之前固定一组“黄金pcap”这组文件里包含正常帧、异常帧、分片帧和边界帧改完必须重新跑回归保证历史结果不受影响。第二遇到新增协议先用Wireshark看三层报文结构把偏移量和字段长度写进注释再动手写解析。第三所有时间值都以UTC存储展示时再转本地时区避免时区问题从源头污染数据。我早期图省事在解析时直接转本地时间结果统计曲线在夏令时切换当天愣是多出来3600个点查了整整一个下午才找到原因。从那以后我所有工具内部一律UTC只有画图最后一个环节才转字符串。这套习惯让我现在接手任何报文解析项目都很少返工希望帮到你。本文还有配套的精品资源点击获取
返回列表