资讯动态

IPv4报文头部详解:字段、分片与网络排查核心知识

发布时间:2026/10/20 6:27:43 来源:尧图企业网站定制
做网络排查做得久了你会发现一个特别有意思的现象很多人能把TCP的三次握手、四次挥手讲得天花乱坠可一旦你把抓包工具里的一帧数据展开让他逐字节解释一下IPv4头部他反而会卡壳。TCP、UDP这些传输层协议太出名以至于真正负责“把数据从A点搬到B点”的IPv4报文反倒成了最熟悉的陌生人。这篇文章想干的事很单纯把IPv4报文从头到尾拆开每个字段都讲清楚它为什么存在、怎么工作、踩坑时怎么用它。顺带把网络层“尽力而为”这句话背后的设计逻辑翻出来晒一晒——它并不是“随便传一下”恰恰是一堆精巧妥协堆出来的护城河。适合刚入门网络的同学建立完整认知也适合干了几年运维、想补齐细节的人看。1. 逐字段解剖IPv4头部为什么长这样1.1 先看一张完整的地图IPv4头部最小20字节最大60字节。日常抓包里见到的绝大多数报文头部都是20字节也就是没有带选项的标准头。下面这张表列的就是这20字节的完整布局建议先保存下来后面所有内容都围绕它展开字段长度作用备注版本号4比特标明IP协议版本IPv4固定为4用于区分IPv4/IPv6首部长度4比特以4字节为单位标明头部长度最小值5最大值15服务类型8比特DSCP标记与ECN显式拥塞通知现代路由器主要看DSCP总长度16比特整个IP报文长度头部数据单位字节最大65535标识16比特分片时标识同一组报文不分片时也照常赋值标志3比特DF禁止分片、MF还有后续分片最高位保留为0片偏移13比特本分片在原始报文中的位置单位是8字节存活时间8比特每经过一跳减10则丢弃主要防环路协议号8比特标识上层协议1ICMP6TCP17UDP首部校验和16比特只校验IP头部完整性每跳都要重新计算源地址32比特发送方IP地址可被伪造目的地址32比特接收方IP地址也是32比特我入行那年师傅跟我说过一句话这些字段没有一个是多余的。哪怕是最不起眼的某个位背后都能牵出一段网络工程的历史。刚开始我觉得夸张后来踩的坑多了才慢慢信了。1.2 版本号与首部长度两个4比特的讲究版本号占了4比特IPv4就是4IPv6就是6。设计这个字段本意是让同一台设备能同时识别不同版本的IP报文。这个远见在IPv4和IPv6过渡的今天显得非常值钱——虽然NAT和隧道技术让共存变得很复杂但至少设备能从第一个字节就判断出“这是谁家的孩子”。首部长度字段同样占了4比特但它的存在更现实IPv4头部带选项长度是可变的接收端必须知道头部到底有多长才能知道数据从第几个字节开始。这个字段的单位是4字节所以最小值是5对应20字节的标准头最大值是15对应60字节。头部再想长一点这个4比特字段就表达不了了这是IPv4天生的硬限制。这个设计在当时很聪明放到现在却成了转发设备的一个小负担。你想硬件路由器每收到一个包都得先做一次“首部长度乘4”的运算才知道偏转起点在哪。如果每个包都是固定20字节流水线不知道能简化多少。IPv6把头部直接固定在40字节其实就是在向“高速转发”低头用灵活性换性能。1.3 服务类型从没人用的TOS到DSCP和ECN服务类型这个8比特字段是IPv4头部里改朝换代最典型的例子。最初的设计叫TOS分成了3比特的优先级和若干标志位想表达“这个包的优先级高请优先处理”。理想很丰满现实很骨感很长一段时间里几乎没有网络设备真正按这套规则做事TOS字段在生产环境里基本是个摆设。后来人们发现完全不用太浪费于是改造成我们今天熟知的DSCP区分服务编码点。前6比特用于标记服务等级语音、视频、背景流量各归各的队列路由器就可以按优先级进行拥塞管理。后面2比特是ECN显式拥塞通知这个机制更巧妙路由器不丢包而是在拥塞时悄悄把ECN位打上标记接收端看到标记后就通知发送端放慢速度。DSCP和ECN的出现算是给“尽力而为”做了一次外科手术——IP层仍然不保证任何东西但至少允许你表达“我想要什么”让中间设备在力所能及时给你一点优待。这个字段也常被拿来排查问题如果你发现某个应用的流量抓包时DSCP值是0x00但实际网络上存在QoS策略先别急着怀疑网络很可能是应用自己没打标。1.4 总长度、标识、标志、片偏移为分片而生的三位一体总长度字段16比特单位是字节代表整个IP报文的大小。最大值65535字节这就是IP层的理论报文上限。但实际网络里二层链路的MTU一般只有1500字节所以绝大多数情况下IP报文会被限制在1500以内总长度字段很少出现夸张的数值。标识、标志、片偏移这三个字段是三位一体、专为分片服务。标识字段16比特分片时用同一个编号标记一组同源的碎片接收端靠它知道“这些碎片原本是一家人”。标志字段里真正有用的只有两个位DF置1表示“禁止分片”如果报文超过链路MTU就直接丢弃这是路径MTU发现机制的基础MF置1表示“后面还有分片”最后一个分片的MF一定为0。片偏移字段13比特它表达“本片在原始报文中的位置”单位是8字节。为什么偏移量偏偏要用8字节作单位这里有个数学上的讲究片偏移一共13比特最大值是8191。如果单位是1字节最多只能表达8191字节的偏移可IPv4报文最大有65535字节根本不够用。把单位放大到8字节就能覆盖到65528字节的偏移范围几乎够用还不会太浪费精度。代价是每个分片的数据长度必须是8字节的整数倍最后一片除外。1.5 协议号、TTL、首部校验和可靠性的三个小门神协议号字段告诉接收端“我肚子里装的是谁”6是TCP17是UDP1是ICMP。接收端拆掉IP头之后就靠这个字段把数据正确地交给对应的上层协议去处理。它实现了网络层和传输层的“多路分解”相当于一栋楼里的门牌号。TTL字段的官方名字很坑叫“存活时间”实际上它计的是跳数不是秒数。最大255通常初始值用64、128或255。每经过一台路由器减1减到0还到不了目的地路由器就会把包丢掉并发一个ICMP超时的消息。评论区很多人问过“TTL太小会怎样”答案很直接网络中如果出现转发环路TTL是最后一道防环的保险没有它一个坏包能在网络里转圈转到地老天荒。首部校验和字段比较简单粗暴发送端把IP头部按16比特一组做二进制反码求和得到的结果取反填进去接收端收到后把头部连同校验和一起做同样的反码求和正常情况下结果应该全是1。只要有一位翻转结果就不对包会被静默丢弃。注意它只校验头部不管数据。这不是偷懒而是因为数据的完整性由TCP、UDP各自负责IP层只需要保证自己写给别人的“快递面单”是清楚的。1.6 源地址、目的地址与选项字段面单上的寄件人和收件人源地址和目的地址各占32比特是IPv4最核心的信息。路由器就是拿这两个字段做路由表查询决定下一步往哪个端口送。这里必须提醒一句源地址是可以被伪造的。很多攻击都带着假源地址出门所以任何安全设备如果“无条件相信”源地址迟早要出事。选项字段就更有历史感了。早期设计里它支持记录路由、时间戳、甚至源路由——源路由允许发送端自己指定路径这等于把路由器的活儿抢了危险性极大后来基本被所有设备禁用。选项字段把IPv4头部变得可长可短这也是上面讲“首部长度”字段存在的原因。实际抓包里几乎看不到有人用选项头部全是干净的20字节。2. 分片机制IPv4的妥协与智慧2.1 为什么要分片每一段链路都有“窄门”IP层设计目标是把异构网络互联起来可不同二层链路的脾气各不相同以太网帧最大载荷1500字节老式X.25、ATM链路可能更小。如果发端不知道路径上最小MTU是多少就只能在中间让路由器出面解决。于是IPv4定了一个规矩中间设备发现包太大时自己动手把它拆成多片每片独立转发到了终点再由对方拼回去。这个设计把“联网”的复杂度下放给了网络设备保证了源端不必知道全路径的MTU信息。代价是中间设备要多做分片运算而且每一片都要复制一份IP头部开销反而变大。更重要的是分片之后如果丢了一片整组报文全部作废接收端会重组失败这给网络可靠性带来了隐患。2.2 分片到底怎么拆以3000字节报文为例我经常用这个例子讲分片建议收藏。假设一个IP报文头部20字节、数据2980字节要穿过MTU为1500的链路。第一个片头部20字节 数据1480字节总长度1500MF置1片偏移0。为什么数据部分取1480因为片偏移单位是8字节1480除以8等于185正好是整数。 第二个片头部20字节 数据1480字节总长度1500MF置1片偏移185。 第三个片头部20字节 数据20字节总长度40MF置0片偏移370。这里要特别强调一句分片的单位不是“整个报文平摊”而是把原始数据按8字节对齐切开。第三个片只有20字节数据却要扛一个20字节的头部转发效率极低。这就是为什么网络工程师看到分片包会皱眉——分片越多额外开销越大丢包风险越高。2.3 为什么重组只能在终点做你有没有想过为什么路由器遇到分片不直接帮它拼回去省得终点来回折腾答案是各片在网络上走的路径可能完全不同。原因在于IP是无连接的每个片在转发时都是独立的包完全可能走不同路由器、从不同链路到达。中间某台路由器根本没法保证能见到这一组的全部碎片硬要重组就得一直等等到超时就变成一个状态大户这跟IP“无状态转发”的初衷矛盾。所以IPv4规定重组只能在最终目的主机做。目的主机把所有标识号相同的分片收齐之后按片偏移顺序拼好再交给上层协议。如果缺了几片超过重组超时时间直接把已经收到的部分全部丢弃通知也不一定发。分片是这个协议的无奈之处也是它“尽力而为”气质的集中体现。2.4 分片带来的安全问题与PMTU黑洞因为分片会扰乱防火墙的状态检查历史上出现过大量针对分片的攻击手法。经典的是重叠分片攻击攻击者发送偏移重叠的分片使得重组后的数据里某个字节既可能是合法的老内容、也能被伪造的新内容覆盖从而绕过防火墙的规则匹配。这类攻击推动了“状态化重组检查”的出现现代安全设备基本都会把分片先重组检查一遍再转发很多站点干脆直接过滤所有分片流量。再说PMTU黑洞这是运维里极难缠的问题。路径MTU发现机制依赖DF位源端发出DF置位的探测包如果某个链路的MTU不够路由器会丢弃并回一个“需要分片但DF置位”的ICMP消息源端据此调小报文长度。问题在于很多安全设备把ICMP当成洪水猛兽直接过滤掉探测包被静默丢弃源端永远等不到回音就会一直重传。表现就是你小包Ping得通大包Ping不通网页打不开游戏掉线。IPv6干脆取消了中间分片只允许源端自己分片从设计上消灭了这条岔路。3. “尽力而为”的真正含义无连接与端到端原则3.1 无连接每个包都是独立行动的单兵“尽力而为”的第一层含义是IP层是无连接的。每个IP报文在转发时路由器都会独立地查一次路由表做一次选路决策。同一个TCP连接里的两个包前一个可能走A路径后一个可能走B路径。这跟打电话那种“先拨通、再通话”的面向连接方式完全不同它更像在大街上各自赶路的行人没有统一指挥没有预定路线。这种设计的优点很突出路由器不需要为每个连接保存任何状态完全可以做成“来包就转”的流水线网络里某台设备挂了影响的只是正在经过它的包不会拖垮整条连接很久。缺点也很明显报文可能乱序到达、可能重复、可能丢失、甚至可能压根没送到。传输层TCP存在的意义就是给这样一位不靠谱的快递员“擦屁股”。3.2 不可靠不等于没兜底端到端原则的智慧很多人听到“网络层不可靠”就慌觉得怎么能在不靠谱的基础上建互联网。这里必须讲清楚端到端原则尽量把功能放在通信的两端而不是网络中间。IP层只负责“尽力转发”转发不动了也不会主动重传真正需要可靠性的时候由传输层的TCP通过序号、确认、超时重传、滑动窗口把这些全包了。这种分工不是没有道理的。如果让IP层做得太可靠每个路由节点都缓存无确认的数据维护连接状态那网络设备的成本会直线上升转发速度反而被拖垮。而把可靠交到端到端用户手里发送方可以根据真实网络状况灵活调整重传策略接收方也可以按需缓冲。网络层保持简单传输层负责补救这是计算机通信的一次伟大分工。3.3 为什么不能把所有可靠性都塞进网络层你也可以反过来想假如当年IP层对每一个包都做可靠确认会是什么后果首先中间每一跳路由器都得记录“谁发了什么、我确认没确认、我缓存了哪些数据”这种状态量的爆炸式增长会让路由器从一个无状态转发器变成一个沉重的状态机。其次不同链路的时延、带宽差异巨大一个可靠的网络层必须按最慢链路的节奏来整个网络的吞吐量会被木桶效应死死压住。所以“尽力而为”不是躺平而是深思熟虑后的最优解。它让IP成为一个可以在各种物理网络之上灵活运行的“通用层”不管底下是光纤、双绞线还是无线都能跑也让网络设备能做减法、做并发、做线速转发。加上DSCP提供优先队列、ECN管理拥塞、ECMP做等价多路径这套系统早就不是几十年前的蛮力转发了。4. TTL与首部校验和两个最容易忽略的细节4.1 TTL的递减规则为什么主机收到TTL1的包还能正常处理TTL的细节里有一个混淆点必须澄清很多人以为TTL到1就应该被丢弃其实不对。路由器转发时如果发现TTL已经为0或减1后变成0才会丢弃并回ICMP超时而目的主机在收到TTL1的包时只要自己是终点就直接交给上层处理不再关心TTL还剩多少。日常排查中TTL初始值能透露不少信息。不同操作系统的TCP/IP协议栈默认初始TTL不一样常见的就有64、128、255。Ping一个远程主机看回包里的TTL值多少能猜出它穿了大概多少跳或者它跑在什么系统上。比如发出时是128回来时剩118路上就是10跳左右如果回包TTL只有60多半初始值是64的Linux/类Unix设备而且距离我们很近。4.2 首部校验和的计算与增量更新一个把计算量省下来的老办法计算IPv4校验和的算法特别老派但很有效。把头部按16比特切分成一组一组把所有组按“二进制反码求和”的方法加起来也就是普通加法但如果加出进位就把进位循环加回最低位。全部加完后把结果按位取反填进校验和字段。接收端做同样运算时如果头部没坏总和一定是0xFFFF。转发时TTL要减1头部被改动所以校验和必须更新。早期设计里每跳路由器都要重新计算整个校验和这在硬件转发时代是个负担。后来出现了增量更新算法因为TTL只是减1旧校验和与新校验和之间只要做一次修正即可不必把20字节全重算一遍。这属于“为了高速转发抠细节”的典型案例今天很多硬件转发芯片依然沿用这个思路。还有一点容易让人误解抓包时看到IP校验和显示错误先别急着报故障要想想是不是网卡硬件校验和卸载导致的假象。常见的抓包软件抓的是网卡送上来、还没做完校验的数据软件按算法算了一遍觉得对不上就会标红提醒。真正的网络故障是设备传输途中比特翻转这种概率其实很低。4.3 traceroute是怎么用TTL“钓”出每一跳路由器的traceroute的原理简单说就是利用TTL制造一系列“故意失败”的包。先发一个TTL1的UDP探测包第一台路由器转发时减1成0直接丢弃再顺手回一个ICMP超时消息。这个超时消息的源IP地址就是第一跳路由器的地址。然后再发TTL2的包第二跳路由器就会回超时……以此类推一路钓出路径上每台设备。探测包到达目标主机后端口大概率是关着的目标会回一个ICMP端口不可达的消息。这个消息一来你就知道已经到终点路径探测结束。这个工具的价值在于它让“看不见”的网络路径变成了逐跳可见的清单。排查“某一段网络不通”时跑一次traceroute星号出现在第几跳问题就锁定在那一段比挠头猜快多了。5. 抓包实操从十六进制到字段解读5.1 一张教学示例帧的逐字节读取下面这行十六进制是我按真实抓包风格写的演示数据为了方便讲解校验和字段用了占位值45 00 00 3c 1c 46 40 00 40 06 00 00 ac d9 17 8a c0 a8 01 01从左到右拆0x45高4位是版本号4低4位是首部长度5头部20字节。0x00DSCP和ECN都是0该包没有服务标记。0x003c总长度60字节也就是头部20字节加40字节数据。0x1c46标识字段十进制7246。0x40这是标志和片偏移的高字节。0x40换算成二进制是0100 0000其中最高位保留0DF位为1MF位为0片偏移低13位是0。说明这包没分片而且不允许分片。0x00片偏移低字节和上面的低5位组成完整偏移0。0x40TTL64。0x06上层协议是TCP。0x0000示例校验和占位。0xacd9178a源地址换算成点分十进制就是172.217.23.138。0xc0a80101目的地址即192.168.1.1。这个例子我们在脑子里过一遍就明白了“版本长度”共用第一字节、DF位藏在标志字节里的具体形态。平时抓包软件会直接帮你翻译成人话但如果哪天工具坏了、字体乱了能自己上手读hex才是真的把协议吃透了。5.2 用Python手工构造一个IPv4报文验证自己对头部的理解还有一个更极客的办法自己动手把报文拼出来。下面这段Python代码可以构造一个最简单的UDP-over-IPv4报文并在本地发送。核心在于按字段顺序用struct打包再把校验和算对。import socket import struct def checksum(data): if len(data) % 2: data b\x00 s 0 for i in range(0, len(data), 2): s (data[i] 8) data[i1] while s 16: s (s 0xffff) (s 16) return ~s 0xffff def build_ipv4(src, dst, payload): version_ihl 0x45 # IPv4, 20字节头部 tos 0 total_len 20 len(payload) ident 0x1234 flags_frag 0x4000 # DF1 ttl 64 proto 17 # UDP src_ip socket.inet_aton(src) dst_ip socket.inet_aton(dst) header struct.pack(!BBHHHBBH4s4s, version_ihl, tos, total_len, ident, flags_frag, ttl, proto, 0, src_ip, dst_ip) chk checksum(header) header struct.pack(!BBHHHBBH4s4s, version_ihl, tos, total_len, ident, flags_frag, ttl, proto, chk, src_ip, dst_ip) return header payload packet build_ipv4(192.168.1.100, 192.168.1.1, bhello ipv4) s socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) s.sendto(packet, (192.168.1.1, 0))重点提两句打包格式串!BBHHHBBH4s4s是大端序网络字节序的标准写法字段顺序严格对应头部校验和计算函数里的while s 16循环就是处理“进位循环回最低位”的老规矩。这段代码只能用来学习生产环境里请直接相信操作系统的协议栈。5.3 抓包工具里的几个实用过滤表达式做IPv4报文分析时我常用的过滤表达式有这么几个基本能覆盖日常排查ip只看IPv4流量。ip.src 192.168.1.100按源地址过滤。ip.dst 192.168.1.1按目的地址过滤。ip.ttl 10找出TTL已经很低、可能快出问题的包。ip.flags.df 1只看DF位的包排查分片问题时很有用。ip.frag_offset 0只看非首片的分片。分片重组是否正常也可以通过抓包验证抓到同一标识号Identification的多帧数据它们的源地址、目的地址一致但片偏移和MF位不同说明这是一组分片。如果只看到首片、后面没有后续片或者中间缺了几片基本可以判断分片在网络上被丢了就得回头查路径MTU。6. 常见问题与排查技巧实录6.1 MTU黑洞问题小包通、大包不通的经典谜题症状通常很一致Ping一个目标小包秒回大包直接超时网页打开缓慢、图片加载不出来但命令行交互又一切正常。这个问题我遇到不下十次老老实实讲一下标准排查流程。先做最大包长测量。常见系统上Ping加“-l 1472”表示发送1472字节的ICMP数据载荷加上IP头20字节和ICMP头8字节刚好凑满1500字节。如果能通说明端到端MTU至少有1500如果1472不通但1460通说明路径上某个环节MTU更小。再加DF位测试命令里带上“-f”参数情况就更明确了如果加DF后不通、去掉DF后通说明路径上有设备拒绝分片但没把ICMP错误消息回给你PMTU黑洞实锤。处理手段通常是在TCP层开启PMTU探测相关参数或者把会话的MSS压低让TCP报文永远不触碰MTU红线终极解法是整条路径统一协商MTU但这在有多个第三方网络的情况下非常困难。这也是为什么IPv6宁可让源端强制分片、中间设备只管转发彻底绕开这套黑盒逻辑。6.2 TTL耗尽与路由环路超时消息背后的线索如果目标一直在“传输中TTL超时”状态说明TTL被减到0了。可能是路径太长超出255跳这基本不可能更可能是网络出现了路由环路。抓包你会看到同一对源目地址的报文在反复横跳而且TTL值逐跳递减到达同一个路由器后再次出现。这时先看路由表找有没有等价路由写错、静态路由指回自己这种低级错误。环路还有一个伴生现象CPU打满、带宽被垃圾流量塞满。环路里的包会不停复制扩散严重时能拖垮整片区域网络。因此TTL这个字段表面上是“限制包活多大岁数”本质上是“网络保险丝”。排查线路质量时所有故障点的证据都能在ICMP超时消息里找到线索别只盯着Ping的结果看。6.3 校验和错误到底要不要管抓包软件里标红一个IP校验和错误让人忍不住想查。我的建议是分三步走第一确认这个错误是不是网卡校验和卸载造成的假象——办法是看错误是不是集中在接收方向而且应用通信正常第二如果是发送方向看到校验和错误多半是驱动或特别老的硬件问题第三如果错误带着明显的丢包、连接中断才需要怀疑传输链路有比特翻转这时候可以顺着物理链路检查光模块、线缆衰减。另外中间设备修改TTL会导致校验和变化如果设备没有同步更新校验和接收端抓包也会看到错误。这种情况属于设备实现有缺陷通常伴随大量丢包因为接收端会把校验失败的报文直接扔掉。比如老式三层交换机转发时只改TTL、不改校验和的固件问题我就踩过一次升级固件后立刻好了。6.4 分片丢失和重组超时如何判断“缺了一片”应用层症状是大块数据传不完、进度条反复回退但小文件传输正常。抓包去看标识号如果发现某个标识号只出现了一部分分片之后再也没有后续的分片帧那就是中途丢了。再配合上游设备的丢包计数基本能定位到是哪一跳丢的。分片重组超时后接收端会把已经收到的分片全部丢弃这就导致一个反直觉的现象一个15KB的报文被切成11片如果只丢了1片剩下的10片也没用了。应用层看到的结果就是整体重传网络利用率直线下降。所以很多安全设备宁可把分片全部拆开重组检查也不愿放一个不知道会拼出什么的怪物进去。对运维来说最好的策略是让TCP的MSS不要触发分片把分片扼杀在源端。干这行久了我最大的体会是抓包时要先把十六进制当成唯一的事实来源再去看工具给的“友好结果”。有一年排查一个跨地域传输问题抓包软件显示一切正常可应用就是卡得要命。我不信邪逐字节翻了几十个包才发现所有报文都被某台设备强行打了DF位、悄悄改了MSS导致应用层大包永远卡在MTU边缘下不来。如果只盯着协议树看这个问题可能还要拖很久。IPv4报文就这么30多个字节但它浓缩了互联网这么多年的经验与妥协。无论以后IPv6怎么普及把一个IP报文读明白的能力都值得花时间去磨一磨。

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

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

免费获取报价 →
↑