搞网络排障这些年,我见过太多人一抓包就犯晕:满屏十六进制,不知道从哪看起。其实IPV4报头就是把网络层所有“控制信息”打包的地方,寻址、分片、防环、协议识别全靠它。很多看似玄乎的问题——UDP大包发不出去、某些路径MTU不对、traceroute在某跳停顿——只要把报头字段读明白,三分钟就能定位。这篇文章不想当RFC的搬运工,而是想从一个天天和报文打交道的人的角度,把IPV4报头的20字节默认结构彻底讲透,并结合抓包、排障、MTU这些实际场景,告诉你每个字段到底是怎么用的。无论你是刚入行的运维、在备考网络认证,还是写网络程序的老会被分片坑到的开发者,这篇都应该能帮你少走很多弯路。
1. 从一次ping不通说起:IPV4报头为什么值得啃透
1.1 一个真实场景:抓包前两眼一抹黑
有一回远程处理一个跨三层访问故障,客户的业务系统在服务器A,客户端在另一个网段。路由协议都正常,ping网关也通,但访问业务端口就是不行。客户催得急,我让现场同事在服务器上抓包,得到的反馈是“有请求进来,也有回包出去”。这就很有意思了:请求到了,回包也发了,为什么客户端没收到?
后来我让同事把抓包文件发过来,一层层看下去才找到线索。服务器发出的回包里,IPV4报头的TTL字段已经接近耗尽,数据包在返回路径上被某个设备丢弃,而且这个设备没有回ICMP超时消息。也就是说,业务方眼里“一切正常”的数据包,实际上在半路已经死了。这个问题的定位,靠的就是IPV4报头里一个很小的字段——TTL。
这个经历让我养成了一个习惯:遇到网络问题,先看报头,再谈其他。IPV4报头虽然只有20字节起步,但它浓缩了网络层几乎所有的决策信息。你不会看它,抓包就是看天书;你会看它,很多故障就是一眼的事。
1.2 IPV4报头在网络协议栈里的位置
先简单对齐一下位置。网络分层里,IP层处在二层之上、四层之下。链路层负责在同一个物理链路内传帧,传输层的TCP/UDP负责端到端的可靠传输和端口区分,而网络层的IP,负责的是“从源地址到目的地址”这一整条路径上的寻址和转发。
IPV4报头就是这个名字里“报头”二字的实体——它像信封上的信息:发件人地址、收件人地址、邮资戳记、限时要求、防拆标记。而TCP/UDP报头更像是信纸里的开头落款和正文内容。你检查一封信投递问题,得先看信封写得对不对,再考虑信纸内容有没有问题。抓包分析也一样,先剥开IPV4报头,基本能确定“这封信该往哪走、还能走多远、是不是被拆成了几封寄出”。
1.3 这篇文章适合谁、能解决什么问题
如果你正在做网络运维、技术支持,或者备考网络类认证,IPV4报头是绕不过去的硬知识。但我要强调的是,它不只是考试内容。写网络程序的开发同事也会遇到:明明UDP包发出了,对方怎么收不到?多半和分片、DF标志有关。这些都不难,难的是你不知道报头里哪个字段在起作用。
这篇文章我会把20字节的默认报头逐字段讲透,解释每个字段为什么这么设计,然后带你实际抓一次包,亲手把二进制字段“翻译”成人话。接着重点讲分片、MTU黑洞、TTL超时这些实战价值最高的细节。最后用一张表告诉你,排障时到底优先看哪几个字段。
2. 20字节默认报头:逐字段拆解设计背后的道理
先放一张完整结构表,后面逐个展开:
| 字段 | 长度 | 作用 |
|---|---|---|
| Version | 4位 | IP版本号,IPv4固定为4 |
| IHL | 4位 | 报头长度,单位4字节,默认5 |
| TOS | 8位 | 服务类型,现代主要用于DSCP/ECN |
| Total Length | 16位 | IP报文总长度(报头+数据) |
| Identification | 16位 | 标识符,分片重组用 |
| Flags | 3位 | DF/MF等标志 |
| Fragment Offset | 13位 | 片偏移,单位8字节 |
| TTL | 8位 | 生存时间,每跳减1 |
| Protocol | 8位 | 上层协议号 |
| Header Checksum | 16位 | 报头校验和 |
| Source Address | 32位 | 源IP地址 |
| Destination Address | 32位 | 目的IP地址 |
| Options | 0~40字节 | 可选字段,现代网络中基本不用 |
2.1 版本号与首部长度:最开始的4位+4位
IPV4报头第一个字节通常写成0x45。这个字节拆开来看,高4位是版本号,低4位是IHL。0x45等于二进制0100 0101,前4位0100就是IPv4的版本号4,后4位0101等于5,表示报头长度是5个32位字,也就是20字节。
为什么要用4位来表示“单位是4字节”的报头长度?因为IP层做转发处理时,希望所有字段都按照32位对齐,这样CPU读取多个字节时效率最高。IHL最大能表示15,所以报头长度最大是15乘以4等于60字节,也就是默认20字节加上最多40字节的选项字段。
很多初学者最容易忽略的是IHL字段的“可变性”。正常抓包看到的IPV4报文IHL基本都等于5。一旦遇到带选项的报文,IHL变大,路由器就需要额外计算报头长度,处理性能会下降。这也是后面要说的“选项字段为什么被嫌弃”的根本原因之一。
2.2 服务类型与总长度:从“质量控制”到DSCP/ECN
服务类型TOS字段一共8位,早期设计者想用它来表达“这个包是否重要”,比如延迟敏感、高吞吐、高可靠性之类的诉求。但实际用起来后发现,运营商和大型网络需要更精细的QoS分类,于是后来把前6位定义成了DSCP(差分服务编码点),后2位定义成了ECN(显式拥塞通知)。
DSCP在语音、视频业务里非常常见。你可以把DSCP理解成“快递单上的服务等级标贴”,EF标记通常给语音,AF41这种给视频,普通流量就是BE,也就是0。路由器看到不同的DSCP,会放进不同的队列,决定先转发谁。ECN是后2位,当路由器发生拥塞时,不再直接丢包,而是把ECN位标记为“拥塞遇到过”,让接收端通知发送端降速。这种机制在TCP里能明显改善拥塞时的吞吐。实际抓包时,在Wireshark里看到的DSCP字段值,就是从这8位里拆出来的。
总长度字段是16位,单位是字节,最大能表示65535字节。它表示整个IP报文(报头+载荷)的总大小。需要注意:IP总长度最大值是65535,但数据链路层MTU通常只有1500左右,真正能跑出65535字节的IP报文非常少见,除非是多层链路内部传递或使用了巨型帧。你在Wireshark里看到一个IP报文的总长度是60、74、1514之类的数值,而不是65535,这个是正常的。
2.3 标识、标志、片偏移:分片三件套是怎么配合的
这是IPV4报头里最需要整体理解的一组字段。16位的标识符、3位的标志字段、13位的片偏移字段,它们共同配合,完成IP分片和重组。
为什么需要分片?因为每种数据链路层技术都有MTU限制。以太网是1500字节,如果上层要发一个3000字节的UDP报文,IP层就必须把它拆成若干个“小报文”分别发送。拆出来的每个分片都有相同的标识符——16位Identification。接收端看到相同标识符的多个分片,就知道它们属于同一个原始报文。
3位Flags的具体含义是:第一位保留(必须为0),第二位DF(Don't Fragment,禁止分片),第三位MF(More Fragments,后面还有分片)。DF置1时,如果报文超过路径MTU,路由器不会拆包,而是直接丢包并回一个ICMP错误。MF用来标记是不是最后一个分片:除了最后一片,前面的分片MF都等于1,最后一片MF等于0,表示到此结束。
片偏移字段是13位,但它标记偏移量的单位不是字节,而是8字节。为什么设计成8字节?因为13位最大只能表示8192,如果单位是1字节,最大只能覆盖到8192字节的报文,远不够65535。把单位定为8字节之后,可表示范围扩大到65528字节,刚好覆盖IP报文的最大长度。这个设计不是随意的,而是为了精度和覆盖率之间取了一个折中。
2.4 TTL、协议号、校验和:三个容易被低估的字段
TTL(Time To Live)8位,设计初衷是“生存时间”,早期用秒作为单位,实际使用中简化成了“跳数”。每经过一台路由器,TTL就减1,减到0还没送到目的地,路由器就丢弃这个包,并返回一个ICMP超时报文给源端。这个机制解决了路由环路问题——没有TTL的话,一个被错误路由反复转发的包会在网络里永远打转。
协议号字段8位,它告诉你IP报头后面跟的是哪种上层协议。常见值:1是ICMP,6是TCP,17是UDP,47是GRE,50和51分别对应ESP和AH。很多新手把“协议号”和“端口号”混在一起,其实它们在不同层:IP报头里只有协议号,TCP头里才有端口号。抓包想过滤“TCP流量”,在Wireshark里可以写tcp,底层其实就是根据协议号6来识别的。
报头校验和16位,它校验的是“IP报头本身”,不校验后面的数据。为什么数据部分不用IP层管?因为TCP和UDP层都有自己的校验和,IP层再做一遍就重复了。而且路由器转发时TTL每次都会变,校验和必须跟着重算,如果IP校验和连数据一起算,每台路由器还要反复读取整个报文,性能代价太大。所以范围只限报头,这是转发速度和安全性的折中。
2.5 源地址、目的地址与选项字段:灵活性的代价
源地址和目的地址各32位,这个没什么好说的,就是IPv4地址本体。正常情况下每个IP报文都必须有这两个字段,没有它们,路由器根本无法工作。
选项字段则有点“历史遗留”的味道。它设计初衷是灵活的:可以记录路由路径、填写时间戳、指定某些特殊转发要求。但它有两个硬伤:第一,报文头会变得不固定,路由器处理选项字段时要额外判断和解析,严重影响转发性能;第二,很多选项字段可以被恶意利用,比如源路由选项可以让数据包“绕开”网络管理员配置的路径,这是安全上绝对不可接受的。
所以现在的网络设备大多默认丢弃带选项的包,尤其是公网上,你几乎看不到带选项的IPV4报文。如果你用Wireshark在现网环境抓到了带Options的包,大概率是网络扫描、探测类流量,要引起警惕。选项字段加填充字段最多40字节,所以IPV4报头默认20字节,最大60字节,IHL字段在这里起到了“报头有多长”的告知作用。
3. 亲手抓一次包:把报头字段从二进制里“剥”出来
3.1 环境准备:用Wireshark抓一个ping包
讲完理论,一定要上手抓包验证,否则记不牢。环境不复杂,一台装了Wireshark的电脑就行,Windows或Linux都无所谓。Wireshark抓包需要底层驱动,Windows上用Npcap,Linux上直接用libpcap。
打开Wireshark,选择“WLAN”或“以太网”这样的接口,点击开始捕获。然后在命令行执行一条ping命令,比如:
ping -c 4 223.5.5.5Linux用-c控制次数,Windows可以用ping -n 4。ping返回后回到Wireshark,在显示过滤栏输入icmp,就能看到四条请求和四条应答。双击任意一条ICMP请求包,底下会出现一个十六进制对比面板,上面是解析后的协议树,下面是原始字节。
3.2 十六进制里的0x45:第一个字节说了两件事
在Wireshark的字节面板里,你会看到类似这样的开头:
0000 00 1c 42 ... 08 00 45 00 00 3c ...前面14个字节是以太网帧头,从第15个字节开始才是IPV4报头。大多数ping包的第一个IP字节都是0x45。
0x45用二进制展开是0100 0101。把8位拆成两组:0100就是版本号4,0101就是IHL等于5。IHL等于5表示报头长度是5个32位字,5乘以4等于20字节,正好是IPV4默认报头长度。就这么一个字节,同时告诉我们两件事:这是一个IPv4包,报头没有选项字段。
再往后看几个字节:0x00 0x00通常是TOS和部分DSCP字段,接下来是总长度。抓包时如果看到类似00 3c,换算成十进制就是60,第三个字节数值等于60,说明这个IP报文总长度是60字节,这是绝大多数不带数据的ICMP echo请求的典型长度。
3.3 Wireshark解析面板和原始字节的对应关系
在Wireshark的协议树里,你展开Internet Protocol Version 4这一行,会看到它把每个字段都拆了出来:
- Version: 4 —— 对应第一个字节高4位
- Header Length: 20 bytes —— 对应第一个字节低4位
- Total Length: 60 —— 对应第3、4字节
- Identification: 0xXXXX —— 对应第5、6字节
- Flags: 0x0 —— 3位标志位
- Fragment Offset: 0 —— 13位片偏移
- Time to Live: 64 —— 第9字节
- Protocol: ICMP (1) —— 第10字节
- Header Checksum: 0xXXXX —— 第11、12字节
- Source Address: 你的IP —— 第13到16字节
- Destination Address: 对端IP —— 第17到20字节
你一边看十六进制原码,一边看解析面板,就能建立极强的对应认知。比如第9字节是0x40,也就是十进制的64,这就是Linux主机常见的初始TTL。第10字节是0x01,代表ICMP协议。第13字节开始是c0 a8对应192.168之类的私网地址,看到这种对应关系,你以后读二进制报文就不会慌乱。
Wireshark还有一个很有用的功能:在过滤栏输入ip.version == 4,会自动把所有IPv4报文标出来,用来快速筛选。做协议分析时,建议把Wireshark的Preferences里的“Validate the IPv4 checksum if possible”选项打开,它会在校验和正确时打上勾。
3.4 用Python手算一次报头校验和
看校验和正确与否是抓包基本功。IPV4校验和算法不复杂,核心步骤是:把报头按16位为一组,全部做二进制反码求和,再对结果取反。为了演示,我写个简单的Python函数,输入报头字节,输出计算出的校验和:
def ip_checksum(header: bytes) -> int: if len(header) % 2: header += b'\x00' s = 0 for i in range(0, len(header), 2): word = (header[i] << 8) + header[i+1] s += word if s & 0xffff0000: # 有进位就回卷 s = (s & 0xffff) + (s >> 16) return (~s) & 0xffff使用的时候需要注意,计算发送端的校验和时,需要把报头里的校验和字段先置为0,再加上整个报头做反码求和。接收端校验时,则直接用“包含校验和字段的完整报头”做反码求和,结果应该是0xffff,如果结果不是0xffff,就说明报头在传输中发生了改变。
我自己在排查时常用这个脚本验证可疑报文的校验和。但有一个非常关键的实战提醒:Wireshark抓到本机发送的包时,经常会出现“Header checksum incorrect”的红色标红,这未必是链路错误,很可能只是网卡开启了checksum offload,真正发出的数据包在网卡硬件里才补上正确的校验和。这个话题等会儿在排障章节详细说。
4. 分片、DF标志和MTU黑洞:报头里最值钱的实战细节
4.1 IP为什么要分片:MTU与路径MTU
MTU是数据链路层能承载的最大帧大小。以太网通常是1500字节,也就是说,IP报文不能超过1500字节,否则链路层放不下。当IP层要发送一个大于出接口MTU的报文时,如果IPV4报头里的DF标志为0,就可以进行分片。
分片在哪发生?可能发生在源主机,也可能发生在中间的某台路由器。如果一条路径上不同链路MTU不同,比如从1500的以太网进入1400的隧道,那路由器就必须把这个IP报文拆开。分片后的每个片都是一个独立的IP报文,有相同的标识符和不同的片偏移。
这里要理清一个概念:路径MTU(Path MTU)是整条源到目的路径上最小的那个MTU。它不是你单台交换机接口上配置的MTU,而是整条链路里最细的那截“水管”。只要有一个接口MTU小,大包过不去。
4.2 DF标志下的“黑洞”问题:当PMTUD失效
现代操作系统出于安全和效率考虑,默认策略是设置DF=1,也就是“尽量别分片”。那大包怎么发出去?靠PMTUD(Path MTU Discovery):发送端先按较大MTU发包,如果路由器觉得包超过下游MTU,会向源端回一个ICMP type 3 code 4报文,附带“需要分片,但我没分,因为你的DF置了1”的通知。发送端收到后,就把自己的发送MTU调小,再继续探测。
这个机制正常工作时非常好用,但有一个致命漏洞:很多网络安全设备为了“防ICMP攻击”,会直接丢ICMP type 3 code 4报文。发送端发出去的大包被丢了,回收不到通知,还会傻傻地重发同样大小的大包,直到超时。这个现象就叫“MTU黑洞”或“PMTUD黑洞”。
典型表现就是:小包能通,ping大包不通,某些应用打不开。比如网页加载时TCP握手是正常的,但一到传输大数据段就卡死。TCP握手包只有几十字节,MTU足够;数据包可能超过路径MTU,一旦DF置1又收不到ICMP通知,就会无限重传。
4.3 分片包长什么样:用偏移量和MF标志看穿分片
分片报文在Wireshark里非常好认。假设一个UDP报文的上层载荷有3000字节,加上UDP头8字节后,IP层要载荷3008字节。以太网MTU是1500,去掉20字节IP头,每个分片最多能带1480字节的载荷。这个1480刻意选取,是因为它刚好是8的倍数,分片的偏移量好对齐。
这个3008字节的报文会被拆成三个分片:
- 第一个分片:载荷1480字节,Fragment Offset=0,MF=1
- 第二个分片:载荷1480字节,Fragment Offset=185,MF=1
- 第三个分片:载荷48字节,Fragment Offset=370,MF=0
片偏移为什么是185和370?因为偏移单位是8字节,1480除以8等于185。第三个分片偏移是370,因为前两个分片已经带了2960字节的载荷,370乘以8等于2960,偏移量指向剩余载荷的起始位置。最后一个分片MF=0,表示重组到这里就结束了。
调试的时候,见到Fragment Offset大于0或者MF=1的报文,就要高度关注分片问题。UDP本身不保证可靠传输,IP分片只要丢一片,整个原始报文就无法重组,接收端会把已经收到的一整组分片全部丢弃。这就是为什么大UDP包在网络上特别容易“送不到”的原因。
4.4 一条命令测路径MTU的实操笔记
实战中,判断路径MTU最直接的办法是用ping带DF标志加大包。Linux和Windows命令略有差异,但原理一致:
Linux:
ping -M do -s 1472 223.5.5.5Windows:
ping -f -l 1472 223.5.5.5这个1472不是随便写的。以太网1500字节,减去20字节IP头,再减去8字节ICMP头,得到1472字节。这样设置,整个IP报文是1500字节,正好等于标准以太网MTU。如果你ping 1472字节的包通了,说明路径MTU至少是1500。如果收到类似“Frag needed and DF set”的提示,说明路径上有接口的MTU小于1500。
我习惯用二分法逐步测:先从1472测,不通就降到1400,再不通降到1300,找到刚好能通的最大值,再加上28字节就是实际的路径MTU。注意ICMP本身可能会有额外开销,复杂网络里隧道封装会降低有效MTU,所以这招虽然不是100%精确,但排障时非常高效。
5. 抓包排障时,报头字段到底怎么帮我定位问题
5.1 TTL超时与traceroute原理
TTL字段最重要的应用就是traceroute。原理不复杂:traceroute从源端发出一个TTL=1的报文,第一台路由器收到后,TTL先减1变成0,发现自己不能再转发,就丢掉报文并返回一个ICMP Time Exceeded报文,源端就知道第一跳地址。接下来源端发TTL=2的报文,第一跳减到1后继续转发,第二跳减到0后返回ICMP Time Exceeded,这样逐跳递增,就能把整条路径都描出来。
实际抓包中,如果看到连续几个ICMP Time Exceeded,就看原始报头里的TTL字段,能判断是哪一跳开始出问题的。另外,很多系统工具还可以用TTL初始值反推主机类型:Linux默认初始TTL=64,Windows默认128,很多网络设备默认255。不过经过NAT或多跳转发后TTL会被重置或递减,所以只能作为辅助判断,不能当作定论。
5.2 校验和报错时别急着骂网卡
Wireshark里看到“Header checksum incorrect”红色标红,很多人第一反应是“网络有坏包”。但根据我的经验,这个红色有相当一部分是假报警。最常见的原因是发送端的网卡开启了硬件校验和卸载(Checksum Offload)。网卡硬件在报文真正发出去前才把校验和算好填进去,而Wireshark是在驱动层抓包的,抓到的是还没被硬件填充校验和的版本,自然算不对。
遇到这种情况,可以分两步判断:第一步,关掉网卡的IP/TCP/UDP校验和卸载功能再重新抓包;第二步,看接收方向上是否也有大量校验和错误。如果只是本机发送方向报错,通常是offload造成的。如果是接收方向也报错,再结合底层是否有CRC错误来综合判断是不是链路真的有问题。
还有一点要注意:IP校验和只覆盖IPV4报头,不覆盖后面的数据。TCP/UDP数据部分出错了,TCP层或UDP层会有自己的校验和报错。因此看到IP校验和错误,不要立刻怀疑数据被篡改,它只说明“IP头本身可能有问题”。
5.3 协议号决定上层,别在IP层找端口
很多刚接触抓包的人会在过滤栏里写ip.port = 80,然后发现没有这个字段。IPV4报头里真的没有端口号。端口号在TCP头或UDP头里,IP层只有协议号,告诉你上层是谁。协议号和端口号是两个完全不同的维度:协议号是“哪类协议”,端口号是“这个协议里的哪个服务”。
排障时要分层拆解。比如业务方说“TCP 8080端口连不上”,你在抓包时应先确认IP层有没有把报文发到正确的目的地址,再确认IP报头里的Protocol字段是6(TCP),最后才去看TCP头里的目的端口是不是8080。如果一开始就往TCP端口上找,可能忽略了IP层地址错误导致的问题。
我常用的抓包过滤写法是:先ip.addr==目的IP,再tcp.port==8080,把范围逐步收窄。不要一上来就tcp.port==8080,那样会漏掉中间环节的异常。
5.4 报头选项字段:为什么现代网络基本不用它
前面提到选项字段会造成报头长度可变,影响路由器处理性能。这里把安全因素也说透:IP选项里的源路由选项,可以指定“这个包必须经过哪几个路由器”,如果被恶意利用,攻击者可以让报文绕过正常的访问控制路径。所以主流网络安全设备普遍选择丢弃带选项的IP包。
另一个影响是IHL不再是5,报文头变长后,某些只按固定长度读取报头的硬件设备可能处理出错。这在一个高速转发环境里是不可接受的。
因此实战中你看到的IPV4报文,几乎都是IHL=5的20字节标准报头。如果哪天抓到一个IHL大于5的普通业务包,我建议你多留个心眼:要么是特殊网络探测工具,要么是配置异常的设备在发包,顺着源地址查一查总会发现点东西。
6. 从IPv4到IPv6:读懂报头演进,才真正懂报文设计
6.1 固定20字节换来的硬件转发速度
理解了IPV4报头的字段后,再看IPv6的设计就顺理成章。IPv4的IHL字段是因为选项字段才存在的,选项字段又是为了“灵活性”设计的。但在实际网络里,这种灵活性几乎没带来正面收益,反而成了性能和安全的包袱。
现代路由器转发报文,尤其是核心设备,动辄每秒处理几百万个包。硬件转发希望每次处理的格式是固定、可预期的。IPv6把报头固定成40字节,没有IHL字段,没有校验和,没有中间分片相关字段,路由器转发的动作变得非常机械:查路由表,改跳数字段,重新走。这只是一种“减法”,但直接换来了更稳定的转发能力。
6.2 IPv6是怎么删掉校验和与分片字段的
IPv6报头里没有IPV4的Header Checksum,原因是三层以下已经有链路层的FCS校验,三层以上有TCP或UDP的校验和,再做一次IP头校验属于重复劳动。去掉之后,每台路由器都少一次校验和计算,这对高带宽设备是实打实的性能收益。
分片字段在IPv6里也不再出现在基础报头中,而是挪到了扩展头里,并且规定只有源主机能做分片,中间路由器不允许分片。如果遇到MTU不足的情况,路由器直接丢包并向源端报告。这个改动让中间设备不再承担分片计算工作,也让报头保持固定长度。IPv4里那套“标识+标志+片偏移”三件套,在IPv6基础报头里彻底看不到了,要看分片就得去看分片扩展头。
6.3 排障时优先看哪几个字段:一张个人经验表
我把平时排障时优先关注的字段整理了一下,方便你快速对照:
| 故障现象 | 优先查看字段 | 判断逻辑 |
|---|---|---|
| 数据包发不到目的地 | 源地址、目的地址、TTL | 地址对不对;TTL减到0说明中途丢了 |
| 大包通、小包通不了 | DF、MF、Fragment Offset、Total Length | 看是否分片、DF是否置1、总长度是否超MTU |
| 应用层协议识别混乱 | Protocol | 确认IP头标的上层协议是否正确 |
| 怀疑报头损坏 | Header Checksum | 校验和是否正确,但这要结合硬件卸载判断 |
| 中间链路丢包 | TTL和ICMP Time Exceeded | 定位到具体第几跳 |
这几组字段不要孤立地看,很多问题是组合出来的。比如看到DF=1、总长度又超过路径MTU,同时收不到ICMP type 3 code 4,那基本就是MTU黑洞。看到MF=1且Fragment Offset有值,就说明这是分片包,后续还要继续等同一个Identification的其余分片。
最后说点个人体会吧。IPV4报头这套结构,我在书里背过很多遍,真正理解它是在一次一次抓包中完成的。遇到网络问题不要急着重启设备,先抓包,把报头字段对着十六进制读一遍,你会发现所谓“玄学故障”九成都有明确答案。闲下来也可以拿Python写个解析器,把每个字段打出来,比看文档有用得多。希望这篇长文能帮你把IPV4报头彻底吃透,下回抓包时,一眼就能看出问题在哪。