TCP/IP(三):IP 协议——数据报、头部与分片

写在前面

上一篇把链路层讲完了:同一子网里,靠 MAC 地址 + 以太网帧 + 交换机 + ARP,两台设备就能把数据搬过去。但 MAC 只管"一跳",跨网、跨子网、从北京到纽约,链路层就无能为力了。

升级到网络层,主角登场:IP(Internet Protocol)。这一篇先不谈地址怎么划分、路由怎么走(那是下一篇的事),而是把 IP 协议本身讲透——它的设计哲学、IPv4 数据报头部每一个字段、TTL 怎么防环路、分片怎么和 MTU 较劲,最后带一眼 IPv6 的概貌。


一、IP 的设计哲学:尽力而为

IP 的脾性可以用三个词概括:无连接、不可靠、尽力而为

  • 无连接(connectionless):发包前不先建电路、不先握手,抬手就发;
  • 不可靠(unreliable):不保证送达、不保证顺序、不保证不重复;
  • 尽力而为(best-effort):会尽量投递,但出了问题不负责善后——丢了就丢了,乱序就乱序。

这听起来很糟,其实是有意为之。可靠性、顺序、去重这些复杂活,IP 全部甩给上层(一般是 TCP)去干。IP 只负责一件事:看目的地址,把包往目的方向推一跳。这样网络层保持极简、极快,复杂逻辑只在端点上做——这正是互联网能"大而糙地"扩到全球的秘诀。

顺带一提 ICMP(Internet Control Message Protocol),它是 IP 的"信使"。IP 自己不报告错误,遇到问题(TTL 超时、目的不可达、需要分片但 DF 置位)就回一个 ICMP 报文告诉源端。日常用的 pingtraceroute 都靠它。


二、IPv4 数据报头部

IP 层的 PDU 叫数据报(datagram)。一个 IPv4 数据报由头部 + 载荷组成,头部固定部分 20 字节,最长 60 字节(带选项)。RFC 791 定义的头部布局:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    0 |Version|  IHL  | DSCP  | ECN   |      Total Length             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    4 |       Identification          |Flags|   Fragment Offset       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |    TTL        |   Protocol    |    Header Checksum            |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 |                  Source IP Address                            |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   16 |                Destination IP Address                         |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   20 |                      Options (if any)                         |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

逐字段拆解:

字段位宽说明
Version4 位IP 版本,IPv4 这里是 4
IHL4 位头长,以 4 字节为单位。最小 5(=20 字节,无选项),最大 15(=60 字节)。它决定了载荷从哪个字节开始
DSCP6 位差分服务码点,用于 QoS 打标记优先级
ECN2 位显式拥塞通告,路由器用它给端点"我拥塞了"的信号
Total Length16 位整个数据报(头部 + 载荷)的字节数,理论最大 65535
Identification16 位标识符,分片重组时用来认"这几片本来是一家"
Flags3 位bit0 保留(置 0);bit1 DF(Don’t Fragment);bit2 MF(More Fragments)
Fragment Offset13 位本片在原数据报中的位置,以 8 字节为单位
TTL8 位生存时间,每经一跳减 1
Protocol8 位上层协议号,告诉对面"载荷交给谁"(见下表)
Header Checksum16 位只校验头部,不校验载荷
Source IP32 位源地址
Destination IP32 位目的地址
Options可变几乎不用,多数路径会忽略或直接丢

Protocol 字段 常见取值(IANA 维护完整列表):

协议号协议
1ICMP
2IGMP
6TCP
17UDP
41IPv6 封装
47GRE
50ESP(IPsec)
51AH(IPsec)
58ICMPv6
89OSPF

几个容易记错的点:

  • IHL 数的是 4 字节单位,不是字节。所以 5 代表 20 字节、15 代表 60 字节,别和 Total Length 搞混;
  • Header Checksum 只管头部,载荷的完整性交给上层(TCP 有自己的校验和、UDP 在 IPv4 里校验和可选但实际都开);
  • Options 基本退出历史舞台:带选项的包常被路径上的设备丢弃或特殊处理,安全设备尤其敏感。

三、TTL:防环路的保险丝

TTL(Time To Live)名字像"时间",其实是跳数。规则极简:

  • 源端给个初值(常见 64、128 或 255,因系统而异);
  • 每经过一台路由器,TTL 减 1
  • 减到 0 还没到目的地,路由器丢弃该包,并回一个 ICMP Time Exceeded 给源。

它的核心使命是防止环路包永远转圈。要是没有 TTL,一个配错路由的包可能在两台路由器之间无限循环,把链路带宽吃光。

不同系统的默认 TTL 不一样,常被拿来粗略判断对端系统(不完全可靠,可被改):

系统常见默认 TTL
Linux / macOS64
Windows128
网络设备(路由器等)255

这正是 traceroute(Windows 下叫 tracert)的原理:故意发 TTL = 1、2、3 … 的探测包,每一跳的路由器因为 TTL 超时回 ICMP Time Exceeded,把返回的 ICMP 源地址记下来,就描出了整条路径。


四、分片与 MTU

上一篇说过,以太网的 MTU = 1500 字节(载荷上限)。可 IP 数据报最长能到 65535 字节。当 IP 包大于出口链路的 MTU 时,就要分片(fragmentation):路由器把载荷切成几片,每片都套上一个完整的 IP 头,各自独立转发。

分片相关三个字段一起用:

  • Identification:原数据报被切出来的所有分片,共用同一个 Identification,让目的端能认出"这几片是一家的";
  • MF(More Fragments):除最后一片外都是 1,最后一片是 0——告诉目的端"分片到这就齐了";
  • Fragment Offset:本片数据在原载荷中的偏移,以 8 字节为单位

重组(reassembly)只在目的端做,中间路由器不重组。目的端靠 (源 IP, 目的 IP, Protocol, Identification) 四元组把同一组的分片归到一起,按 Offset 拼回去;MF = 0 的那片到齐、且中间没有空洞,就算拼完。

为什么 Offset 以 8 字节为单位?因为 13 位的 Offset 域,乘以 8 才能覆盖 16 位的最大长度:2^13 × 8 = 65536,正好够表示最大 65535 字节的数据报。这也意味着每片的载荷长度必须是 8 的倍数(最后一片除外)。

DF 标志与 PMTUD:DF = 1 表示"不要分片"。如果路由器发现包超过 MTU 又被置了 DF,它会丢弃该包,并回一个 ICMP “需要分片” 报文(Fragmentation Needed),把"下游 MTU 是多少"也带上。源端收到后调小包重发——这就是 PMTUD(Path MTU Discovery,路径 MTU 发现)的核心。现代网络强烈推荐用 PMTUD + DF = 1 来避免分片,原因下面说。

分片为什么能避则避:

  • 一片丢,全丢:任一分片在路上丢了,整个数据报就拼不齐,上层(TCP)只能整体重传,放大了丢包代价;
  • 防火墙难处理:分片后第一条之外的片没有 L4 头部,安全策略和 NAT 很难判断,历史上是著名的绕过手法;
  • 重组负担在目的端:占用内存、可能被用来打"重组炸弹"。

所以实践里:应用尽量别发超大包、TCP 配合 PMTUD、UDP 应用自己控制报文大小,能不分片就不分。


五、IPv6 概览

IPv4 地址(32 位)早不够用了,IPv6 把地址扩到 128 位,号称"给每粒沙子都分一个地址"。但 IPv6 不只是"地址变长",它对头部做了大幅简化。IPv6 基本头部固定 40 字节,没有选项、没有头部校验和、分片相关字段也从基本头里挪走了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     0 |Version| Traffic Class |          Flow Label                   |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     4 |    Payload Length            |  Next Header  |   Hop Limit    |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     8 |                                                               |
       +                                                               +
       |                                                               |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    24 |                                                               |
       +                                                               +
       |                                                               |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  字节 8~23:Source Address(128 位);字节 24~39:Destination Address(128 位)

字段对照 IPv4 看更清楚:

IPv6 字段位宽对应 IPv4 / 说明
Version4 位这里是 6
Traffic Class8 位对应 IPv4 的 DSCP + ECN
Flow Label20 位IPv6 新增,标识一条流,便于做 QoS
Payload Length16 位只算载荷,不含 40 字节基本头(IPv4 的 Total Length 是含头的)
Next Header8 位对应 IPv4 的 Protocol,但还能指向扩展头链(见下)
Hop Limit8 位对应 TTL,名字更准确(每跳减 1)
Source / Dest Address各 128 位16 字节的 IPv6 地址

IPv6 相对 IPv4 的几个关键变化:

头部更简、转发更快。固定 40 字节、没有头部校验和(校验交给上层和链路层 FCS),路由器转发时不用每跳重算校验和,转发效率更高。

扩展头链代替 Options。IPv6 把可选功能(逐跳选项、路由、分片、IPsec 的 ESP/AH、目的选项)做成一条扩展头链,用 Next Header 一个接一个串起来,最后才接上层协议。基本头永远在前、固定 40 字节,路由器只需看基本头就能转发,绝大多数扩展头只有目的端(或指定节点)才处理。

中间路由不再分片。IPv6 规定路由器不负责分片,只有源端能分(通过分片扩展头)。如果路由器发现包超过下游 MTU,直接丢弃并回 ICMPv6 Packet Too Big,源端据此调小重发(PMTUD)。IPv6 还规定每条链路最低 MTU = 1280 字节,低于这个值必须做适配。

Hop Limit 取代 TTL,机制完全一样(每跳减 1、到 0 丢弃),只是换了个更诚实的名字。

NDP 取代 ARP。IPv6 没有 ARP 协议,地址解析改由 NDP(Neighbor Discovery Protocol,邻居发现协议,RFC 4861)完成——它基于 ICMPv6,用组播(而非广播)来查询邻居的链路层地址。好处是:不再有 ARP 那种全链路广播风暴,也顺带把 ARP 欺骗的老坑换成了新的安全模型(NDP 有 SEND 等机制可加固)。


小结

  • IP 是无连接、不可靠、尽力而为的网络层协议,可靠性交给上层(TCP);
  • IPv4 头部固定 20 字节(可扩到 60),关键字段:Version / IHL(4 字节单位)/ Total Length / TTL / Protocol / 头部校验和 / 源 / 目的 IP,分片靠 Identification + Flags + Fragment Offset 三个字段;
  • TTL 每跳减 1,到 0 丢弃并回 ICMP Time Exceeded,是防环路的保险丝,也是 traceroute 的原理;
  • IPv4 数据报长度超过出口链路 MTU 且没有禁止分片时,源主机或中间路由器可能分片;重组只在目的端进行。现代实践倾向用 DF = 1 + PMTUD 避免分片(一片丢则全丢、安全设备难处理);
  • IPv6 头部固定 40 字节、无校验和、扩展头链代替选项、中间路由不分片、最低 MTU 1280、Hop Limit 取代 TTL、NDP 取代 ARP

下一篇,我们正式进入 IP 地址的世界——TCP/IP(四):IP 地址、子网划分与路由 :地址分类、子网掩码与 CIDR、私有地址、路由表怎么选下一跳。


参考:

Licensed under CC BY-NC-SA 4.0