3、IP协议详解:IPv4报文结构、IP分片与重组、子网划分与CIDR、IPv6基础与调试

IP协议,说白了就是网络世界的「地址系统」。没有它,数据包就像没有收件人的快递,满世界乱跑。我做了这么多年嵌入式网络开发,调试IP层的问题是最多的——不是报文格式错了,就是分片没处理好,要么就是子网掩码配错了。今天咱们就把IP协议这块硬骨头啃下来。

3.1 IPv4报文结构:别被20字节吓到

IPv4报文头部固定20字节,后面跟选项字段(最多40字节)。我刚开始看这个结构时,觉得字段太多了。其实真正需要关注的,就那么几个。

字段 长度(位) 说明
版本 4 IPv4就是4,IPv6就是6
首部长度 4 单位是4字节,最小5(20字节)
总长度 16 整个IP报文长度,最大65535字节
标识 16 分片时用,同一报文分片标识相同
标志 3 DF(不分片)、MF(还有分片)
片偏移 13 分片在原报文中的偏移,单位8字节
生存时间 8 TTL,每经过一个路由器减1
协议 8 上层协议:1=ICMP,6=TCP,17=UDP
首部校验和 16 只校验头部,不校验数据
源IP地址 32 发送方IP
目的IP地址 32 接收方IP
我的调试习惯:抓包时先看「总长度」和「首部长度」。如果总长度小于首部长度,那报文肯定坏了。我曾经在调试一个LwIP协议栈时,发现报文总是被丢弃,最后定位到是首部长度字段写错了——写成了6(24字节),但实际只有20字节。

3.2 IP分片与重组:嵌入式设备最容易踩的坑

为什么需要分片?因为链路层有MTU限制。以太网MTU是1500字节,Wi-Fi通常是1500,有些PPP链路只有576。IP层发现报文超过MTU,就得切碎它。

分片规则其实不复杂:

  • DF=1时,不分片。如果报文太大,直接丢弃并返回ICMP错误
  • DF=0时,可以分片。每个分片都有相同的标识字段
  • 除了最后一个分片,其他分片的MF=1
  • 片偏移是8字节的倍数
注意:分片重组只在接收端进行。中间路由器只负责分片,不负责重组。我曾经遇到过一个坑:某个嵌入式设备发送的UDP报文超过MTU,但DF位被置1了。结果报文被路由器丢弃,设备还没收到ICMP错误——因为ICMP被防火墙过滤了。排查了整整两天。

重组时,接收端需要:

  1. 根据标识字段,把属于同一报文的分片归到一起
  2. 根据片偏移,按顺序排列
  3. 检查MF位,直到收到MF=0的分片
  4. 如果某个分片超时未到,丢弃整个报文

嗯,这里要注意:嵌入式设备内存有限,重组缓冲区不能太大。我一般建议设置成最大报文长度65535字节,但实际中很少用到这么大。如果内存紧张,可以限制只重组小于2048字节的报文。

3.3 子网划分与CIDR:别再死记硬背了

子网划分,说白了就是把一个大网络切成小网络。CIDR(无类别域间路由)则是用「IP地址/前缀长度」的方式来表示网络。

举个例子:192.168.1.0/24,表示前24位是网络号,后8位是主机号。这个网络有256个地址,但可用的是254个(去掉网络地址和广播地址)。

我刚开始学的时候,总是记不住子网掩码。后来发现一个规律:

  • /24 对应 255.255.255.0
  • /16 对应 255.255.0.0
  • /8 对应 255.0.0.0
  • /30 对应 255.255.255.252(只有2个可用地址,常用于点对点链路)
实用技巧:在嵌入式设备中配置IP时,我习惯用CIDR表示法。比如配置一个静态IP:192.168.1.100/24,比写子网掩码255.255.255.0更直观。很多轻量级协议栈(如LwIP)都支持这种格式。

子网划分的实战场景:

  • 公司有4个部门,每个部门不超过50台设备
  • 分配一个/24网络(256个地址),切成4个/26子网(每个64个地址)
  • 每个子网可用地址62个,够用了

你想想看,如果不划分子网,所有设备都在一个广播域里,ARP广播满天飞,网络性能肯定受影响。

3.4 IPv6基础与调试:迟早要面对

IPv6地址128位,写成8组16进制数。比如:2001:0db8:85a3:0000:0000:8a2e:0370:7334。可以省略前导0,连续0可以用::代替(只能一次)。

IPv6和IPv4最大的区别:

  • 没有广播地址,用组播代替
  • 没有NAT,理论上每个设备都有公网IP
  • 自动配置(SLAAC)比DHCP更常用
  • 头部固定40字节,没有选项字段(用扩展头部代替)
我的经验:调试IPv6时,最常用的命令是ping6traceroute6。在嵌入式设备上,我习惯先检查链路本地地址(fe80::/10)是否配置成功。如果链路本地地址都不通,那全局地址肯定也通不了。

IPv6的调试要点:

  1. 先确认设备是否支持IPv6(很多嵌入式协议栈默认只开IPv4)
  2. 检查邻居发现协议(NDP)是否正常工作
  3. 确认路由器通告(RA)是否到达设备
  4. 测试链路本地地址通信

我曾经调试过一个物联网项目,设备死活获取不到IPv6地址。抓包一看,路由器通告报文到了,但设备没响应。最后发现是协议栈的NDP实现有bug——没有正确处理RA报文中的前缀信息选项。修了3行代码就好了。

避坑指南:我曾经在调试IPv6时,发现设备能ping通链路本地地址,但ping不通全局地址。排查了半天,发现是防火墙把ICMPv6的「邻居请求」报文给过滤了。IPv6的邻居发现依赖ICMPv6,千万别把ICMPv6全禁了。

最后说一句:IPv6迟早要全面铺开。现在嵌入式设备如果不支持IPv6,过几年可能就卖不出去了。我建议你在新项目中,至少把IPv6的链路本地通信做起来,这是最低要求。