news 2026/9/8 5:45:19

ICMP数据包构造实战:从报文格式到Scapy抓包验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ICMP数据包构造实战:从报文格式到Scapy抓包验证

简介:这是一份用于演示ICMP(互联网控制消息协议)数据包构造与发送的编程资源,面向学习网络编程、协议分析或从事网络诊断的开发人员,也可作为计算机网络课程实验的参考。资源由C++源文件、头文件及说明文档组成,共3个文件,压缩包仅5KB,代码结构精简,便于直接阅读和编译运行。其中C++源文件负责ICMP报文填充与校验和计算,头文件定义报文结构,说明文档补充了构造流程与抓包验证方法。已有1605人学习下载。通过阅读源码和说明,可以掌握ICMP报文头部的类型、代码字段组织方式,理解差错报告报文和查询报文的区别,以及如何将ICMP报文封装进IP数据报,并借助Wireshark抓包验证构造正确性。该资源能帮助读者从底层理解网络通信机制,为后续实现Ping工具、Traceroute或网络监测功能打下基础。 先说结论:如果你准备在网络排查、协议分析这类场景里自家尝试“造”一个ICMP包,这篇文章可以节省你至少一个下午的折腾时间。

作为常年跟网络抓包、设备联调打交道的人,我对ICMP一直有种“最熟悉的陌生人”的感觉。平时排查不通,第一反应就是ping一下,但真要自己从零构造一个ICMP报文,把Type、Code、Checksum这些字段逐一填对的时候,还是遇到不少细节问题。这篇就基于我实际调试过的经验,把ICMP数据包构造这件事拆开揉碎,从报文格式讲到抓包验证,再到常见设备的调试命令,一次说清楚。

这篇文章更适合这几类人看:刚接手网络维护、手里一堆ping不通故障单的运维新人;正在学TCP/IP协议栈、想用代码验证理论的学生;以及做网络工具开发、需要在程序里自定义探测包的开发者。不管是纯原理学习还是应付具体工作,下面这些内容都能直接落地去试。

1. 动手之前先摸清底细:ICMP报文格式与核心字段

构造数据包这事有个基本逻辑:你得先知道每个字节是干什么的,才能谈“构造”。ICMP虽然看起来简单,但它的报文结构里藏着不少门道。

1.1 ICMP报文整体布局

ICMP报文分为两部分:报文头和数据部分。报文头固定8个字节,由类型(Type)、代码(Code)、校验和(Checksum)三部分组成,后面跟着4字节的“剩余头部信息”,这个剩余头部内容会根据报文类型发生变化。

以最常用的 Echo Request(ping请求包)为例,整体布局是:

偏移量字段长度说明
0Type1字节标识报文类型,Echo Request为8
1Code1字节类型下的细分代码,ping时为0
2Checksum2字节对ICMP报文头和数据部分的校验和
4Identifier2字节用于匹配请求和应答,通常填进程ID
6Sequence Number2字节序号,每发一个包递增
8Data可变载荷数据,可以是任意内容

这里有一个新手容易忽略的点:ICMP报文本身不携带端口号,它依赖Identifier和Sequence Number来区分不同的“会话”。这也是为什么我们在构造时不能随便填这两个字段,否则对端回包时你可能认不出来。

1.2 Type与Code:决定报文“身份”的两个关键字节

真正的ICMP报文类型远不止ping用到的8和0。比如目标不可达是Type 3,它下面又细分了十几个Code,网络不可达是0、主机不可达是1、端口不可达是3、需要分片但设置了不分片标志是4。超时是Type 11,Code 0表示传输过程中TTL耗尽,Code 1表示分片重组超时,traceroute就是这个原理。

构造前一定要想清楚你要模拟的是哪种ICMP行为,然后对照RFC 792把Type和Code填对。填错了对端可能直接丢弃,也可能回复一个意外的差错报文,排查起来非常痛苦。

1.3 校验和计算逻辑

校验和是最容易翻车的地方。ICMP的Checksum计算范围和IP头校验和不同,它覆盖ICMP报文头加整个数据部分,而且是先置0再计算。具体步骤:

  1. 将校验和字段先置为0;
  2. 将ICMP报文(包括数据部分)按16位一组划分;
  3. 如果总长度是奇数,末尾补一个0字节;
  4. 把所有16位字进行二进制反码求和;
  5. 将结果取反,填入校验和字段。

这地方用Python的Scapy自动处理,问题不大。但如果用C语言的原始套接字裸写,校验和出错是最常见的问题。后面实操部分我会给出一份可以直接用的校验和计算函数。

2. 工具选型与环境准备:为什么我推荐Scapy

构造ICMP包的方案不少,各有各的适用场景。我在实测过程中把三种常见方案都试了一遍,用下来感受差异很明显。

2.1 三种常见实现方案对比

方案优点缺点适用场景
系统ping命令零成本,最可靠无法自定义Type/Code和载荷日常连通性测试
Python Scapy灵活,代码量小,支持网络层全字段定制依赖第三方库,性能一般协议研究、自动化脚本、快速构造任意ICMP包
C语言原始套接字性能高,完全可控,不依赖解释器代码复杂,校验和要自己算,需要root权限高性能探测工具、嵌入式网络设备开发

个人建议是:学习阶段直接上Scapy,因为Scapy已经帮你封装好了完整的数据包组装能力,构造一个ICMP包三行代码就搞定,还自带发送和接收函数,非常适合验证理论。如果你要封装成正式给生产环境用的工具,再考虑用C或Go重写,那属于工程优化范畴。

2.2 环境安装与最小验证

我用的环境是Ubuntu 22.04,Python和Scapy的安装属于标准操作:

sudo apt update sudo apt install python3 python3-pip sudo pip3 install scapy

装完后可以先跑一个小测试,确认Scapy能正常发送和接收报文。测试之前要确保系统允许ping命令正常通行,有的安全策略会禁掉ICMP,那种环境下报文发出去可能没有回应。

运行下面的代码,如果能收到回包,说明环境没问题,就可以往下走了:

from scapy.all import * p = IP(dst="114.114.114.114")/ICMP() reply = sr1(p, timeout=2, verbose=False) if reply: print("收到回复,Type =", reply[ICMP].type) else: print("无回复")

这里有个细节:Scapy在sr1返回的是完整应答包,我们要从应答里取ICMP层去判断Type,直接从结果里打印type就是0了,因为Echo Reply的Type就是0。

3. 从零构造ICMP数据包:三种典型场景实操

环境没问题后,我们正式进入构造环节。我挑了三种有代表性的场景,从最基础的ping包到自定义载荷再到纯手工C语言构造,每层难度递增,但每层都能让你对ICMP的理解加深一步。

3.1 场景一:标准Echo Request(ping包)

最标准的ICMP Echo Request就是ping命令发的包。用Scapy构造非常直观:

from scapy.all import * # 构造一个ICMP Echo Request icmp_packet = IP(dst="192.168.1.1")/ICMP(type=8, code=0) # 发送并等待回复 reply = sr1(icmp_packet, timeout=3, verbose=False) if reply: print("目标地址:", reply[IP].src) print("回复Type:", reply[ICMP].type) print("回复Code:", reply[ICMP].code) else: print("超时无回复")

这里要注意一个点:ICMP(type=8)是请求,ICMP(type=0)是回复。构造时type一定要填8,你填0的话就成了应答包,在正常情况下没人会主动应答一个应答包。

另外前面说到的Identifier和Sequence Number,Scapy会自动帮你填默认值。实际使用中建议手动指定,比如用进程ID做Identifier,这样在抓包工具里便于区分是本机哪个程序发出的报文。

3.2 场景二:带自定义载荷的探测包

标准的ping包数据部分是固定的56字节内容,但ICMP对载荷部分并没有强制要求。这给了我们一个很重要的应用场景:在一些网络质量监测系统里,会用自定义载荷验证链路的数据完整性。

实际构造时可以这样:

from scapy.all import * # 构造一个携带自定义内容的ICMP包 payload = b"network-check-data-2024" packet = IP(dst="192.168.1.1")/ICMP(type=8, code=0)/payload reply = sr1(packet, timeout=3, verbose=False) if reply: # 对比回显的data部分是否一致 if payload in bytes(reply[Raw].load): print("载荷验证通过") else: print("载荷不一致,链路存在数据篡改风险") else: print("超时")

这里需要补一个背景:ICMP Echo Reply会把Echo Request里的数据部分原样带回,正是因为这种机制,我们才能通过比对载荷内容来评估链路质量。曾经有客户反映某个跨地市专线业务时断时续,但普通ping一直是通的,后来就是用超长自定义载荷的ICMP包发现的MTU问题。

3.3 场景三:C语言版原始套接字构造

再上一点难度,用C语言的原始套接字手工构造。说实话现在很多年轻同行已经不怎么用这种底层写法了,但我一直觉得这是理解协议的最好方式,因为你必须亲自动手处理每一个字段。

核心代码框架如下:

#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <arpa/inet.h> #include <unistd.h> // ICMP校验和计算 unsigned short cal_checksum(void *addr, int len) { unsigned short *buf = (unsigned short *)addr; unsigned int sum = 0; while (len > 1) { sum += *buf++; len -= 2; } if (len == 1) { sum += *(unsigned char *)buf; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)(~sum); } int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage: %s <target_ip>\n", argv[0]); return 1; } int sockfd; char packet[64]; struct iphdr *ip_hdr = (struct iphdr *)packet; struct icmphdr *icmp_hdr = (struct icmphdr *)(packet + sizeof(struct iphdr)); struct sockaddr_in dst_addr; // 创建原始套接字,协议类型为ICMP sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sockfd < 0) { perror("socket error"); return 1; } memset(packet, 0, sizeof(packet)); // 填充IP头 ip_hdr->ihl = 5; ip_hdr->version = 4; ip_hdr->ttl = 64; ip_hdr->protocol = IPPROTO_ICMP; ip_hdr->saddr = inet_addr("0.0.0.0"); // 由系统自动填充 ip_hdr->daddr = inet_addr(argv[1]); ip_hdr->tot_len = htons(sizeof(packet)); // 填充ICMP头 icmp_hdr->type = ICMP_ECHO; icmp_hdr->code = 0; icmp_hdr->un.echo.id = htons(getpid()); icmp_hdr->un.echo.sequence = htons(1); icmp_hdr->checksum = 0; icmp_hdr->checksum = cal_checksum(icmp_hdr, sizeof(struct icmphdr)); // 填充目标地址 dst_addr.sin_family = AF_INET; dst_addr.sin_addr.s_addr = inet_addr(argv[1]); // 发送 if (sendto(sockfd, packet, sizeof(packet), 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)) < 0) { perror("sendto error"); return 1; } printf("ICMP Echo Request sent to %s\n", argv[1]); close(sockfd); return 0; }

编译并执行:

gcc -o myping myping.c sudo ./myping 192.168.1.1

注意这个程序需要root权限执行,因为SOCK_RAW原始套接字的创建本身就需要特权。

这里再解释一下为什么要手动算checksum。很多初学者第一次跑C语言版构造的ICMP包,发现对端不回包或者回包被丢弃,八九不离十是校验和错了。IP头里的校验和是为了校验IP头本身的完整性,ICMP头里的校验和是为了校验整个ICMP报文的完整性,两者计算内容和范围完全不同,别混为一谈。

4. 构造后的验证与抓包排查

包发出去不叫完事,抓包确认自己构造的包格式正确才算真正闭环。这一部分我结合自己的踩坑经历,讲讲验证环节的几个关键点。

4.1 用Wireshark验证自建包的格式

抓包工具最常用的是Wireshark,启动抓包后应用过滤条件:

icmp

如果你构造的是标准Echo Request,抓到后展开ICMP层,应该能看到Type为8,Code为0,Checksum显示为correct。如果Checksum那里显示incorrect或者wrong,那就说明构造的包在格式上还有问题。

最好把发送端抓包和接收端抓包对比看。我自己调试时发现,从本机发送的包在Wireshark里看到的源IP可能不是你预期的那一个,比如你本机有多块网卡、多个IP,内核会自动选择路由出口,源IP由系统决定。这是正常现象,不需要太纠结。

4.2 常见误区:为什么设置了udp过滤却还能看到ICMP

热搜词里有一条很典型:“Wireshark添加了滤波条件udp但是我还是抓到了icmp的数据”。这个问题我见过不少回帖问过,实际上是因为Wireshark的显示过滤器分为两种:一种是显示过滤器(Display Filter),一种是在抓包前设置的捕获过滤器(Capture Filter)。

如果你在Wireshark最上方的显示过滤器里输入udp,理论上它只显示UDP报文,不会把ICMP显示出来。如果还是能看到ICMP,最常见的原因是输入过滤器的时候把语法弄错了,比如输入成了udp port 53,但抓包文件里同时存在DNS(UDP 53)和DNS相关的ICMP差错报文,某些差错报文里会携带原始DNS报文的内容,Wireshark会把这部分ICMP报文显示出来,导致看起来像“过滤失败”。

另一个可能原因是抓包是在远程接口上做的,本地Wireshark打开的抓包文件里过滤器没有正确生效,这时重新在显示过滤栏输入icmp并回车确认一下语法是否正确就可以。

如果确实想从源头只抓UDP包、不要任何ICMP,应该在抓包选项里用捕获过滤器写:

udp

捕获过滤器是BPF语法,在Wireshark主界面选择“捕获”菜单,设置里填入上面这个表达式。这样在网卡层面就把ICMP丢弃了,根本不会进入抓包结果。

4.3 网络设备端调试:telnet debug icmp

很多情况下问题出在交换机、路由器等网络设备上,这时需要在设备上开启调试。常见厂商设备的操作思路类似,这里以华为设备为例,命令过程是:

<设备> system-view [设备] undo info-center enable [设备] quit <设备> debugging ip icmp <设备> terminal monitor <设备> terminal debugging

打开之后,设备收发ICMP报文时控制台会打印详细信息。这个功能在工程师圈子里常被称为“debug icmp”,你可以直观看到设备是否收到了ICMP请求、回包失败的原因、丢弃报文的可能位置。这是排查“为什么我手工构造的ICMP包设备不回”的利器。

使用完一定要记得关掉:

<设备> undo debugging ip icmp <设备> undo terminal monitor

注意在设备吞吐量大的业务高峰期临时开debug要谨慎。实际维护中我曾经在核心交换机上开过debug icmp,结果日志刷新速度快到屏幕根本看不清,不得已直接重启了终端会话才缓解。调试完立刻关掉是最稳妥的操作。

5. ICMP构造的实际应用场景与扩展

学会了构造ICMP包,能做的事情远不止“造一个ping包”。结合我自己的工作经历,说几个比较有价值的应用方向。

5.1 常规运维场景

最直接的应用是做网络连通性和质量检测平台。传统ping工具一次只能测一个目标,配合Scapy可以轻松写一个并发探测脚本,同时检测多个网络节点,把往返时延、丢包率汇总成报表。我在之前的项目里就是拿Scapy写了个内部监控脚本,定时探测十几个核心网元的ICMP可达性,发现问题自动告警,相当省事。

MTU问题排查也是重要场景。对于某些全链路MTU被调低,但普通ping(默认载荷只有56字节)又测不出来的链路,可以用超长ICMP载荷来试探实际可用MTU值。构造一个1400字节的ICMP包,然后不断降低载荷大小,观察哪个长度开始不再出现fragmentation needed消息,就能推断出路径上的最小MTU。

5.2 特殊场景与注意事项

有一点必须在最后强调:ICMP数据包构造能力属于双刃剑。用得好,它是高效的网络诊断工具;用不好,轻则触发网络设备的安全策略,重则涉及网络攻击行为。比如利用ICMP载荷隐藏数据的内容,虽然技术上行得通,但在大多数应用场景下属于违规行为,只应在自己的实验环境或者经过明确授权的网络测试中使用。

我在实践中的体会是,构造ICMP包的核心价值不在于“包本身”,而在于“你完全理解了报文里每个字节的含义之后,遇到问题时多了一种定位思路”。以后再遇到ping不通,你不再只会反复执行ping命令,而是能通过抓包、分析报文、构造特定探测包,一步步确认问题究竟出在哪一层。这种能够从底层视角观察和解决问题的能力,才是掌握数据包构造带来的真正提升。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 5:45:19

沙场春点兵:全链路压测与系统容量评估实战手册

每年开春我都会给系统做一次大检阅&#xff0c;今年正好赶上项目第20个迭代节点&#xff0c;于是有了这场“沙场春点兵”。这里的“沙场”不是别的&#xff0c;就是线上生产环境的那几十个核心服务&#xff1b;“兵”则是每个接口、每条链路、每台机器、每份缓存数据。说白了&a…

作者头像 李华
网站建设 2026/9/8 5:45:05

缠论自动识别dll源码解析:笔段中枢算法与通达信集成实战

简介&#xff1a;这份压缩包提供了一套基于缠论理论的DLL源码实现&#xff0c;面向股票、期货等市场的量化分析开发者&#xff0c;覆盖笔、段、中枢三大核心概念&#xff0c;包含笔的分型处理与方向判断、段的划分规则以及中枢区间的识别逻辑&#xff0c;可直接编译生成动态链接…

作者头像 李华
网站建设 2026/9/8 5:44:54

opencode实战指南:从安装配置到Skills与Playwright调试

把 AI 编程助手从“玩具”用到“生产力”&#xff0c;我今年折腾了一圈&#xff0c;从最开始玩 Codex CLI&#xff0c;到后来切到 Claude Code&#xff0c;最后在 opencode 上彻底安下心来。说实话&#xff0c;opencode 这段时间在网络上的热度很高&#xff0c;但它不像某些工具…

作者头像 李华
网站建设 2026/9/8 5:44:26

穿越机外场PID调参实战:从暴力超调到稳定飞行

1. 这篇文章真正要解决的问题 穿越机玩家应该都有过这种体验&#xff1a;飞机装好了、图传调通了、飞控能解锁&#xff0c;结果一推油门上天&#xff0c;飞机不是疯狂点头&#xff0c;就是横滚方向来回抽动&#xff0c;甚至一给大油门直接翻滚炸机。这时候老玩家会告诉你&#…

作者头像 李华
网站建设 2026/9/8 5:44:11

吴恩达团队aisuite:一体化机器学习工具链实战指南

如果你正在寻找一个能够显著提升机器学习项目效率的工具&#xff0c;那么 Andrew Ng&#xff08;吴恩达&#xff09;团队开源的 aisuite 绝对值得你深入了解。很多开发者都面临这样的困境&#xff1a;模型训练完成后&#xff0c;评估、调试、可视化、部署等一系列后续工作繁琐…

作者头像 李华
网站建设 2026/9/8 5:43:21

开源终端AI编程工具opencode完全上手指南:从安装到生产实战

最近我把主力AI编程工具从Claude Code换成了opencode&#xff0c;不是一时兴起&#xff0c;而是连续踩了几天配置的坑之后&#xff0c;终于觉得这个开源项目值得认真聊一聊。opencode是一个跑在终端里的AI编码代理&#xff0c;你可以接入任意自己喜欢的模型&#xff0c;用自然语…

作者头像 李华