资讯详情

资讯详情

深入浅出IP协议:从IPv4报文头到分片机制与IPv6差异

搞传输层讲了TCP和UDP这么久今天把视角往下移一层来到网络层的主角——IP协议。说它“熟悉”是因为凡是在网络上跑的设备几乎都逃不开一个IP地址说它“陌生”是因为很多人背得出IP地址的格式却说不清一个数据包在IP层到底经历了什么更别提报头里那些字段分别负责什么。这篇文章我打算从协议定位、报文头、寻址逻辑、分片机制、抓包验证和故障排查六个角度把IPv4一次讲透并在关键节点对比IPv6说说两者在思路上最根本的差异。适合正在学网络协议的人也适合做后端开发、运维平时被各种网络问题折磨的朋友。1. 先把IP放在网络栈里看清它的角色1.1 三层是“端到端”的边界我们常说的TCP/IP体系里IP处于第三层也就是网络层。它的上一站是传输层TCP/UDP下一站是链路层以太网、WLAN。这层的位置决定了它要解决的问题不管底层是光纤、双绞线还是无线也不管中间隔着多少个路由器IP负责把一个个数据报从源主机送到目的主机。这个过程就是“端到端”的传输。注意这里的“端到端”是主机到主机。TCP说的是进程到进程IP说的只是设备到设备。一台服务器上跑着好几个服务IP负责把包送到这台服务器至于该交给哪个进程那是TCP/UDP的端口字段去管的事。所以IP协议本身不关心上层是HTTP、SSH还是DNS它就是一台“分拣运输机”把货物按门牌号送到小区门口后续怎么分配到各家各户它不参与。这也解释了为什么IP协议设计得如此简洁它只保证“尽力而为”地把数据报送到目的地址不建立连接、不确认收到、不重传、不排序。这些可靠性工作全部留给上层TCP。IP的哲学是“网络层尽量简单智能放到端点去”这也是互联网能大规模扩展的基础。1.2 无连接与尽力而为不是偷懒而是设计很多人第一次听说“IP不可靠”会很困惑既然不可靠为什么底层还在用它这里有个重要的思维转换可靠性不该由网络中间设备保障而应由通信两端保障。IP的设计是“无连接”的也就是说每个数据报都是独立的路由器按包转发前一个包和下一个包完全可以走不同的路径。这样做的最大好处是网络对故障有天然的忍受力——某条链路断了路由器只要更新路由表后面的包就能自动改道不需要提前建立一条“专属通道”。如果像打电话那样先建立会话再传输中间断线整个通信就要重来这在规模巨大、路径复杂的互联网上是不可接受的。“尽力而为”则意味着路由器遇到处理不过来的包直接丢弃TTL减到0直接丢弃找不到下一跳直接丢弃。丢包后会怎样交给上层发现、重传。这种“各司其职”的分工让路由器可以做得简单高效也让TCP可以放心地做拥塞控制。我在实际排查中见过不少“把可靠性下沉到网络层”的私有协议最终几乎都因为网络中间设备不予配合而变得极其难维护IP这种“笨但通用”的设计反而最稳妥。1.3 为什么有了MAC地址还要IP地址这是很多初学者问的第一个问题。MAC地址烧在网卡里全球唯一为什么还要IP地址可以这么理解MAC地址是“物理身份”IP地址是“逻辑位置”。以太网帧在同一个二层网络内传输时靠MAC地址定位目标但互联网是由无数个二层网络拼接起来的。数据包要跨越不同的二层网络必须有一种“地址”能表达“这个目标在哪个网络里、怎么汇聚过去”这就是IP地址的职责。IP地址有层级结构前缀相同的地址会被路由聚合成一条路由条目。比如10.1.1.1和10.1.1.2都归属10.1.0.0/16这个前缀路由器只需维护一条“去10.1.0.0/16往这边走”的表项不需要记住几百万台具体设备。而MAC地址是扁平结构没有任何聚合性理论上只能靠泛洪和广播在一个小范围内使用。我在一个上千台设备的园区网里维护过交换机的MAC地址表那种扁平寻址带来的表项膨胀和广播风暴会让你深刻理解为什么三层需要IP这种结构化地址。2. IP头里的每个字段都是被逼出来的2.1 版本、首部长度、TOS4个字节讲身份标准IPv4报文头固定20字节加上可选项最多60字节。前4位是版本号IPv4对应数值4二进制0100看到这个字段就知道后续要按IPv4的格式解析。版本之后跟着4位首部长度IHL单位是4字节。常见的值是5表示20字节如果出现7则实际长度是28字节。这个字段很重要因为IP头不是定长的解析者必须先知道头有多长才能找到载荷起点。我抓包时偶尔能看到IHL为0的异常包那种包基本是扫描器或畸形流量直接判断为攻击特征。第5个字节是服务类型TOS最初设计用来表示优先级和延迟要求经过多次演变现在主要被拆成DSCP差分服务代码点和ECN显式拥塞通知。DSCP是QoS服务质量的关键可以在路由器上按这个字段给语音、视频等流量打不同优先级。但说实话很多内网并没有真正启用端到端QoS我在没有配置DSCP信任的网络里见过大量携带0x00标记的普通流量这个字段更多是“预留能力”日常排查时几乎不会碰。2.2 总长度、标识、标志、片偏移为分片留的一手紧跟着的16位是总长度指示整个IP数据报头载荷的字节数。16位上限是65535理论最大一个IP包可以接近64KB。但实际链路MTU很少超过1500于是IP层必须面对一个棘手问题一个大于下游链路MTU的包怎么发出去答案就是分片。分片涉及三个紧密配合的字段。标识16位标识原始数据报将同一个大包切出的所有分片共享这个标识值方便接收端重新归组。标志3位里最关键的是DF禁止分片和MF更多分片。DF置1表示“此包不能被拆分”MF表示“后面还有分片”而最后一个分片的MF为0。片偏移13位记录当前分片在原始报文里的位置单位是8字节。13位之所以对应8字节粒度是因为13位最大表示8191而8191×865528刚好覆盖16位总长度的最大值。这是协议设计里非常经典的“用位数换范围”的做法。我强烈建议你记住“片偏移单位是8字节”这点后面验证分片时会反复用到。2.3 TTL、协议、校验和、地址每跳都在变的东西TTL生存时间是8位字段每经过一台路由器减1减到0就丢弃并向上层反馈ICMP超时。它存在的直接原因是防环假如网络里出现路由环路一个包会无限转圈。有了TTL包最多存活255跳就会“寿终正寝”。日常ping看到“Time to live exceeded”时基本就是在提醒你要么路由环了要么跳数太多超过了TTL初值。协议字段表示上层协议编号ICMP是1、TCP是6、UDP是17。接收端拿到包后根据这个字段决定把载荷交给哪个模块处理类似“货单上写着这批货归哪个部门”。16位头部校验和只校验IP头本身不校验载荷。为什么不做全包校验因为IP层每经过一跳TTL就会变校验和必须重新计算。如果校验范围覆盖整个包每个路由器都要对几十KB的数据做CRC性能开销不可接受。把校验限定在20字节的头部再把数据可靠性交给上层TCP/UDP和二层链路是工程上很务实的取舍。源地址和目的地址各占32位不必多说。需要注意正常情况下源地址字段是逐跳不变的不会因为经过路由器而改写。被NAT改写是另一个话题后面单独讲。2.4 可选字段为什么被边缘化IPv4头部的第21到60字节可以放可选项最早设计了记录路由、时间戳、源路由等能力。源路由让发送者指定整条路径曾被用于网络探测但很快成为攻击者绕过访问控制的手段几乎所有路由器都默认丢弃带源路由选项的包。记录路由和时间戳因为性能损耗大、实现复杂在现网里也很难看到。我自己的观点是IPv4可选项基本可以当成“历史遗留设计”来了解实际排查中遇到带特殊选项的包反而要警惕。IPv6彻底重新设计了这条路子把所有可选项移出主头部放进独立的扩展头只让端节点处理绝大部分扩展头中间路由器默认不解析既保留了灵活性又避免了IPv4可选项带来的转发性能黑洞。3. 寻址逻辑从分类地址到CIDR3.1 分类地址与子网划分IPv4地址最早采用分类寻址A类首位是0B类是10C类是110D类是组播E类保留。A类地址段是1.0.0.0到126.255.255.255B类是128.0.0.0到191.255.255.255C类是192.0.0.0到223.255.255.255。每类都有固定的网络位和主机位这种方案用起来极其浪费。比如A类地址理论上容纳约1600万台主机有几个场景真需要在一个二层网络里放1600万台设备于是出现了子网掩码让管理员从主机位借用若干比特作为子网位。计算子网时有两个边界值要严格记住主机位全0是网络地址全1是广播地址这两个地址都不能分配给设备。设置子网掩码时写错一位就会把网段切错轻则互访不通重则流量跑到广播域里造成干扰。我维护过一段“古老”的C类网段192.168.1.0/28的配置可用主机只有14个。当时觉得太少后来想想小广播域反而好排障——出问题影响面小抓包也清爽。子网划分的本质就是“控制广播域大小方便路由聚合”没有别的更高深意图。3.2 CIDR、最长前缀匹配与路由汇聚分类寻址废弃之后1993年推出无类域间路由CIDR。它不再看你是A/B/C类而是用斜杠直接写前缀长度例如192.168.1.0/24。关键是路由表匹配规则变成“最长前缀匹配”一个目的地址同时命中多条路由时前缀越长的路由优先级越高。为什么必须最长匹配因为有了CIDR后路由表里可以同时存在172.16.0.0/16和172.16.1.0/24两个条目。去172.16.1.5的包显然走更精确的/24路由而不是大的/16。如果按传统最短匹配或按出现顺序匹配整个网络就会走错方向。我在配置路由时吃过一次亏添加了一个/24的明细路由却忘了检查原有/16汇总路由是否还在导致部分流量被旧路由“截胡”排查了大半天最后用ip route show逐条比对才发现顺序问题。所以现在养成了习惯任何路由变更先看完整路由表再想清楚最长匹配会走到哪一条。CIDR还有个好处是路由汇聚。原来多条C类路由可以汇总成一条/16甚至更短的前缀减少路由表条目。一个20多个C类段的局域网汇聚成一段/20后核心路由表立刻干净很多。3.3 私网地址、NAT与地址消耗的现实IPv4地址总数约43亿可分配的公网地址远没有这么多。为了缓解压力协议规定了几段私网地址10.0.0.0/8约1677万地址、172.16.0.0/12约104万、192.168.0.0/16约6.5万。这些地址可以在局域网内随意使用但公网路由器一律不转发来自这些源的流量。于是NAT技术登场了。家用路由器把内网私网地址映射到出口公网地址同时通过端口号区分不同内网主机。很多年前我配置过一台老式宽带猫它把整栋楼几百个用户的地址都转换成一个公网IP再用不同端口映射回来。这种机制缓解了地址枯竭但也给网络带来了一些长期问题。NAT破坏了端到端的透明度外部设备无法主动访问内网主机很多P2P场景需要额外的手段打通连接。更隐蔽的是它会影响IP层之上的某些协议如果报文里有加密的完整性校验且校验范围覆盖IP地址那么NAT改写地址字段后校验值就对不上了这也是大家常听说的“某些协议过不了NAT”的缘由。我个人认为IPv6普及前的漫长过渡期里NAT会一直存在但是所有做网络的人都应该明白NAT是妥协产物不是优化设计真正彻底的出路是IPv6。3.4 IPv6对比有什么根本不同IPv6把地址扩展到128位数量多到不需要NAT。它去掉了广播用组播和任播替代头部长固定为40字节去掉了头部校验和路由器不再分片只由源节点分片用邻居发现协议替代ARP。几个关键差异值得细说。首先IPv6头部没有校验和理由很简单上层协议有校验二层帧也有校验IP层再算一遍纯属重复劳动。其次IPv6路由器遇到超过MTU的包不会像IPv4那样自己分片而是直接丢弃并回复“包太大”让源主机把包改小后重发。把分片工作推到端点减少了中间设备的状态和负载。第三IPv6地址用冒号十六进制表示例如2001:db8::1连续零组能用双冒号压缩一个地址里只能出现一次双冒号。我认为理解IPv6不能停留在“地址变长”这种表面认知它的核心变化是把很多历史包袱清理掉了。只是现实里IPv6过度到端到端连接还有很长的路要走但网络新人直接学IPv6其实是站在更干净的设计起点上。4. 分片与MTUIP层最容易被误解的机制4.1 MTU是什么为什么会有分片MTU最大传输单元是二层链路能承载的最大数据载荷以太网标准MTU是1500字节。一个IP包在源主机发出时可能只有几十字节比如TCP的ACK包但也可能大到几KB比如大文件传输中的数据包。当这个包要从以太网口转发到另一个MTU更小的链路时比如某些广域网链路MTU为1480IP层就必须把超过1480载荷的包拆成多段再分别发送。分片本身不复杂复杂的是它造成的问题。第一任何一个分片丢失接收端只能丢弃所有已收到的分片整个原始数据报都要由上层重传。这意味着一个分组被切成10片只要丢1片就要重传全部10片放大丢包的成本。第二分片重组只在目的主机进行中间路由器不重组。如果路径上不同方向的MTU不一致就会出现大量重组开销。4.2 分片与重组的规则详解以ping -s 3000为例实际发出的是3008字节的IP载荷8字节ICMP头加3000字节数据。在1500字节MTU的链路上IP层会把它切成三片第一片IP头20字节载荷1480字节MF为1片偏移为0第二片同样载荷1480字节MF为1片偏移是185因为1480除以8第三片载荷48字节MF为0片偏移是370因为2960除以8。片偏移换算需要乘8。185×81480370×82960正好对应前两片的累计载荷。发送端切分时还要求非最后一个分片的载荷必须是8的倍数这是为了能用13位偏移精确表示任意位置。很多抓包新手看到片偏移30、100这种数字会愣住其实直接用数字乘以8就是它在原始报文里的字节偏移地址。重组顺序靠片偏移保证归组靠标识字段。如果同一标识的分片来得七零八落接收主机会一直等超过一定时限就丢弃整组。这个“等”会占用内存所以历史上出现过不少分片攻击利用大量不完整分片耗尽设备资源。现网里很多防火墙会直接丢弃分片包或者对分片做严格速率限制这也是分片如今在实际网络中越来越少见的直接原因。4.3 路径MTU发现和“黑洞”问题既然分片这么贵TCP协议族实际是尽量避免分片的。TCP在三次握手时协商MSS最大报文段大小让每个TCP报文段都小于路径MTU迫使IP包不用分片。这个协商依赖“路径MTU发现”机制发送方把IP头的DF置1一旦某个中间链路MTU不够路由器会丢弃并返回ICMP“需要分片”的报错发送方收到后把MSS调低重发。听起来很优雅但现实里充满坑。很多中间设备尤其是安全设备为了防攻击直接丢弃这些ICMP报错不发任何通知。发送方发出大包后毫无音讯TCP重传反复失败或超时直到把TCP段长降到足够小才恢复。表现就是“打不开网页、但ping小包是通的”。这类问题被称为MTU黑洞排查方法很简单用DF置1的ping测试逐步降低载荷大小找到刚好能通过的最大值。在我处理过的故障里局域网内很少遇到MTU黑洞它高发在需要额外封装的场景。排查思路上记住一个规律见鬼的网络问题先怀疑MTU因为它总能制造“说不上原因”的间歇性故障。5. 实操抓两个包把IP协议看穿5.1 环境与抓包命令纸上谈兵没意思。我建议你在自己的Linux机器上实际抓一次包来验证上面的理论。先准备一个简单的环境一台内网机器比如地址为10.20.30.40一台公网测试服务器用文档保留网段203.0.113.10作为示例保证能ping通即可合法使用。第一个命令是用tcpdump抓取ICMP包sudo tcpdump -ni eth0 icmp参数说明-n表示不解析主机名-i eth0监听指定网卡icmp过滤协议类型。然后另开一个终端执行ping -c 4 203.0.113.10在tcpdump输出里会看到类似下面的行13:22:01.123456 IP 10.20.30.40 203.0.113.10: ICMP echo request, id 3792, seq 1, length 64 13:22:01.123689 IP 203.0.113.10 10.20.30.40: ICMP echo reply, id 3792, seq 1, length 64第一步就完成了你直观看到了一个IP头的摘要。注意中间的IP和“”符号这是tcpdump帮你解析了IP头后的结果左边是源地址右边是目的地址长度64是IP载荷大小不含IP头。5.2 ping包里的IP头逐字段解读把上面这个ICMP包用Wireshark打开展开Internet Protocol Version 4一行你会看到我之前说的所有字段。我这里用文字做个逐行说明0100 .... Version: 4 .... 0101 Header Length: 20 bytes (5) Differentiated Services Field: 0x00 Total Length: 84 Identification: 0x4feb (20459) Flags: 0x0000 Fragment offset: 0 Time to live: 64 Protocol: ICMP (1) Header checksum: 0x7c08 [validation disabled] Source Address: 10.20.30.40 Destination Address: 203.0.113.10Total Length是84因为20字节IP头8字节ICMP头56字节的ping数据加起来正好84。Identification是随机值两台不同机器、不同时间抓出来的数值都不一样但这个字段在分片场景下必须相同。TTL是64这是Linux系统的默认初始值Windows一般是128。Protocol字段这里为1ICMP如果是访问网页这里通常就是6TCP或17UDP。校验和通常显示“validation disabled”因为现在的网卡大多开启了校验和卸载能力包在抓取时校验和由网卡硬件填充软件解析不一定实时验证。如果看到校验和字段显示“bad”大概率不是网络上真出错而是网卡卸载、桥接或抓包工具自身导致的误报。判断校验和问题要结合丢包和延迟症状不要一看到bad就激动。5.3 用Wireshark验证一次分片为了实际看到分片执行一次大载荷pingping -c 3 -s 3000 203.0.113.10在Wireshark里过滤icmp ip[6:2] 0x1000可以快速找到同一个标识的分片组或者直接看IP层的Fragment offset排序。你会看到三个IP包它们的Identification相同但Total Length依次是1500、1500、68。第一个分片的Flags显示More fragments1Fragment offset是0第二个同样More fragments1offset是185第三个More fragments0offset是370。验证片偏移公式185 × 8 1480370 × 8 2960正好对上。这个实验做一次远比背十遍文档记得牢。实际上我在教新人的时候一定会让他们亲手ping一次3000字节然后在抓包里找这三个包凡是能独立把片偏移和载荷解释清楚的人IP分片这个知识点基本就再也不会忘了。6. 排障速查IP协议相关的六类经典问题6.1 TTL超时与路由环路ping某个地址收到“Time to live exceeded”时第一反应是看traceroute的结果。逐跳查询会发现包的TTL在某几跳之间反复递减到0或者始终停在同一台设备IP上。这就是环路迹象通常是静态路由写反、动态路由协议收敛失败导致。处理手法不复杂登录最后一跳可用的路由设备查看去往目的网段的路由表确认出接口和下一跳是否合理。我曾遇到一个双出口的网络核心交换机两条默认路由分别指向两个网关数据包在两个网关之间来回弹跳TTL很快耗尽。把默认路由改成单条并加上浮动路由问题立刻消失。这也是TTL设计最成功的地方没有它环路会让数据包无限消耗带宽。6.2 MTU黑洞的判定与处理症状很典型网页加载不完整、一部分图片出不来、大文件传一半卡死但ping小包正常、TCP连接能建立。判定方法如下在Linux上执行ping -M do -s 1472 203.0.113.10 ping -M do -s 1473 203.0.113.10-M do表示设置DF禁止分片。如果1472通、1473不通说明本机到目标这个方向的MTU是1500147228正好等于1500。如果1472都不通就要逐步减小用二分法快速定位合适值。找到之后不是把目标主机MTU改了完事而应该思考中间链路的实际MTU并在出接口上配置对应的MTU。很多环境里最稳妥的手段是调整TCP MSS让所有TCP报文段的载荷小于路径允许值这样即使链路MTU略小TCP也永远不需要IP分片。我实际遇到的MTU黑洞案例中有一半以上最终确认是中间安全设备不放行ICMP报错导致。你可以在遭遇MTU黑洞时用tcpdump抓取出接口是否出现type 3 code 4的ICMP包如果没有基本就是这类报错被吞掉了。这时最好的办法是在经过的设备上显式设置合理的MTU值不要指望路径MTU发现能自己治好。6.3 IP冲突、校验和错误与其它IP冲突是老问题。症状是设备不定时掉线刷新ARP时地址在几台设备间横跳。排查时先看本机ARP表再用抓包工具过滤arp request确认哪个MAC在不断宣告该IP。通常发生在有人手动配置静态IP、而该IP同时又被DHCP分配出去的场景统一规划DHCP保留地址是根治办法。校验和错误在真实网络中较少是物理层问题。如果Wireshark显示大量IP校验和bad同时MAC帧的FCS也错才可能是网线或光模块故障如果只有IP校验和bad而FCS正常多半是抓包工具或网卡驱动对校验和无序包的错误报告这时候多看TCP重传率比纠结校验和更有意义。还有一种常见情况是私网地址被错误发布到公网路由。内部网络访问某公网地址不通或一个公网IP在内网被当成私网使用根源往往出在新接入设备把默认路由指向了错误的网关导致带私网源地址的包被送到了公网边界。处理这类问题的核心套路是先确认源、目的地址合法性再逐层检查路由表最后看NAT映射是否覆盖了实际流量。我在处理这些IP层问题时最大的体会是不要把IP层看得太玄。它的状态少、逻辑直接绝大多数疑难杂症只要把抓包结果里的IP头字段逐一读出来问题范围就能缩小一大半。分片、TTL、地址冲突、路由选择每一个都有明确的字段和计可循剩下的就是耐心比对。最后分享一个我自己的习惯排查网络问题时我会第一时间把抓包窗口里每个不认识的IP层字段都查一遍而不是急着看TCP层。因为TCP的所有重传、乱序、连接异常往往只是IP层某个小问题在上层的投影。把IP协议读懂了你就拥有了定位一大半网络故障的底层能力。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →