IPv4分组 (IP Packet) 由首部 (header) 和数据段 (data) 两部分组成。个人习惯上将数据段称为载荷payload.


1. IPv4分组的格式

IPv4分组的header由14个元素组成,包括13个必须项和1个可选项。

图片来源:维基百科

版本号 (Version)

4位长度, 可表示0-15中的任意值。IPv4的版本号为4 (0100₂).

网际首部长度 (Internet Header Length, IHL)

首部长度指出当前分组的header长度,4位长度, 最大可表示15。每位对应4 Bytes (单位值), 因此header长度最多为:

1111₂ x 4 (Byte) = 15₁₀ x 4 (Byte) = 60 Bytes

从上图可看出header的前20 Bytes是固定的,而可选项options是可变的,因此首部长度的最小值为 20 / 4 (Byte) = 5₁₀ = 0101₂

由于受到IHL单位是4 Bytes的约束,可选项options的长度必须为4 Bytes的整数倍,不满4的地方则需要用0进行填充 (padding)。

区分服务代码 (Differentiated Services Code Point, DSCP)

6位长度。起初该字段被定义为服务类型 (type of service, ToS), 之后改为区分服务DiffServ. 实时数据流用到该字段,其中一个例子是 Voice over IP, VoIP, 用于交互式声音服务 (即IP电话)。

DiffServ 提供聚合功能,根据DS值将多个流 (同类型的IP分组) 聚合成少量流,从而简化路由器的转发机制。DiffServ与RSVP作用相仿,都是用于提升网络层的交付效率,但比后者要简单的多。

显式拥堵通知 (Explicit Congestion Notification, ECN)

2位长度,允许在不丢包的情况下,显式地通知网络拥堵。只有当通讯的两端和所在网络也支持的情况下,才能使用该服务。

分组总长度 (Total Length)

16位长度, 表示当前IP分组的总长度,单位为Byte,最大可表示 2¹⁶ - 1 = 65535 Bytes。由于header最小为20 Bytes, 因此 Total Length 的最小值为20。

如果当前IP分组是一个分片,则该字段表示当前分片的总长度。和分片偏移值配合可将分片还原成原IP分组。

id值 (Identification)

用于识别单个IP分组的标识符,长度为16位, 可表示 0-65535.

如果一个IP分组被分成多片(甚至是多次分片),所有的分片的id值都相同。接收分片的主机/路由器会根据这个字段将IP分组进行重组。

据维基:有的研究希望增加id值的作用,例如在伪装网络下追踪IP分组的来源信息,但类似的研究已被禁止。

分片标识 (Flags)

IP分组标识,包含3位,即可以设置3个标识,但首位仍未分配使用,必须为0.

第二位表示 Don’t fragment, DF, 即禁止分组

第三位表示 More fragment, MF, 即有更多分组

分片偏移 (Fragment offset)

表示当前IP分组(分片)payload的首字节,在原分组(完整数据报)中的索引值。该字段长度为13-bit, 单位是8-Byte, 因此对数据报进行分组时,必须以8-Byte为单位。

存活时间 (Time to live, TTL)

表示当前分组允许转发的跳数,每次路由减一,TTL为0时丢弃该分组,用于避免分组在网络中发生无限转发,浪费网络资源。长度为8位。

原表示IP分组存活的秒数,后来随着分组转发效率的提高而改为更实用的跳数。

协议 (Protocol)

长度为8位, 表示当前IP分组的payload所对应的协议。

首部校验和 (Header checksum)

长度为16位,用于检验当前IP分组的header数据在传输中是否出错。由于路由器在转发分组时,该分组的TTL必然减少、分片信息和IP地址 (NAT转换) 等信息可能会发生变化,因此路由器需要重新计算checksum.

checksum的计算:将header按2字节的长度划分为多个整数,例如没有选项值的header为20字节,则划分为10个整数,将checksum字段视为0。将10个整数求和得到sum后,再将sum取反即可得到checksum值并填入header。

checksum的验证:同样按2字节的长度,将接收到的IP分组的header划分为多个整数 (checksum字段按接收值处理), 同样对这些整数求和,得到的结果为0xFFFF则分组首部数据正常。

需要注意:1. 划分的多个整数均视为无符号整数(无符号位/非负);2. 求和操作如果产生了进位并溢出,则必须将溢出部分加入checksum (如果仍产生进位,则也要进行相加操作)

具体过程可参考下面的示例。

源地址 (Source address)

32位的IPv4地址,在经过 NAT 转换时会发生改变。

目的地址 (Destination address)

32位的IPv4地址,在经过 NAT 转换时会发生改变。

可选项 (Options)

整个可变长度的选项部分,长度必须为4 (Byte) 的整数倍。不足的部分需要在低位用0补足。

2. 一些示例

checksum的计算和验证

以下是wiki上的一个以十六进制表示的header checksum示例,长度为32字节。第11和12字节的 b861 为该header的checksum值:

4500 0073 0000 4000 4011 b861 c0a8 0001
c0a8 00c7 0035 e97c 005f 279f 1e4b 8180

checksum的计算过程为:

4500 0073 0000 4000 4011 0000 c0a8 0001
c0a8 00c7 0035 e97c 005f 279f 1e4b 8180

先将checksum部分视为0,然后将各字段的值相加(将进制改为16,计算方式与十进制加法相同),得到结果为 2479c。由于该值已大于2字节,因此按16位的长度截取成2479c两部分,并将这两部分相加,得到479c + 2 = 479e(如果仍产生进位,则重复该步骤),最后将479e取反得到b861

checksum的校验过程:

和计算的过程相似,区别在于checksum按接收值算进结果里而不是视作0:

4500 + 0073 + 0000 + 4000 + 4011 + b861 + c0a8 + 0001 + c0a8 + 00c7 = 2fffd

求和的结果大于16位,同样要将超出部分拆分并相加:2 + fffd = ffff

得到结果为 ffff (或者再执行一次取反得到0x0000) 则表示该IP分组的header是合法的。

分片示例

由于数据链路层的MTU (最大传输单元) 规定为1500字节,因此大于该长度的IP分组需要进行分片 (以及在接收处进行组装)。

当一个id值为x的IP分组需要分片传输时,其所有分片的id值均为x, 且除了最后一个分片,所有分片的Flags标签的More Flagment标识均为true. 同时通过分片偏移值指出当前分片的数据段对应原分组数据段的位置 (索引值)。

例如:一个分片的header显示总长800 bytes, header长20 bytes, 分片漂移值为185,则可计算出该分片的数据长度为780 bytes, 且首字节位于为原数据字节数组的 185 * 8 (偏移值的单位) = 1480(即该分片的数据对应完整IP分组的索引值1480开始的长780字节的一段数据)。

3. 与其他网络层级的关联

运输层

IPv4分组为了提高转发效率,只对header部分进行验证。其他基于IPv4的协议都需要自行验证数据的完整性,但是过程和IPv4的checksum的计算和校验过程是相同的。

数据链路层

虽然数据链路层规定帧的总长在64-1500字节之间,但以太网帧本身并没有字段记录数据段的长度。当接收节点检测到没有电压变化时表示帧数据已结束,可通过FCS (4 bytes) 来反推数据部分的结束点并提取payload数据提交到网际层。

至少64字节是 CSMA/CD 协议的要求,但实际上这个协议仅用在半双工传输媒介(用同一条线路进行收发会出现冲突),而全双工传输媒介由于有各自的收发通道,因此无需使用 CSMA/CD 协议。

4. 参考文档