2. IP协议详解

第2章 IP协议详解

1. IP服务的特点

IP协议是TCP/IP协议族的动力,它为上层协议提供无状态、无连接、不可靠的服务。

  • 无状态(stateless)是指IP通信双方不同步传输数据的状态信息
  • 因此所有IP数据报的发送、传输和接收都是相互独立、没有上下文关系的。
  • 这种服务最大的缺点是无法处理乱序和重复的IP数据报。

虽然IP数据报头部提供了一个标识字段(见后文)用以唯一标识一个IP数据报,但它是被用来处理IP分片和重组的,而不是用来指示接收顺序的。

无状态服务的优点也很明显:简单、高效。

  • 我们无须为保持通信的状态而分配一些内核资源,也无须每次传输数据时都携带状态信息。
  • 在网络协议中,无状态是很常见的,比如UDP协议和HTTP协议都是无状态协议。
  • 以HTTP协议为例,一个浏览器的连续两次网页请求之间没有任何关联,它们将被Web服务器独立地处理。

无连接(connectionless)是指IP通信双方都不长久地维持对方的任何信息。这样,上层协议每次发送数据的时候,都必须明确指定对方的IP地址。

不可靠

  • 是指IP协议不能保证IP数据报准确地到达接收端,它只是承诺尽最大努力(best effort)。
  • 很多种情况都能导致IP数据报发送失败。发送端的IP模块一旦检测到IP数据报发送失败,就通知上层协议发送失败,而不会试图重传。
  • 因此,使用IP服务的上层协议(比如TCP协议)需要自己实现数据确认、超时重传等机制以达到可靠传输的目的。

2. IPv4头部结构

2.1 IPv4头部结构

IPv4的头部结构如图2-1所示。其长度通常为20字节,除非含有可变长的选项部分。

4位版本号(version)指定IP协议的版本。对IPv4来说,其值是4。其他IPv4协议的扩展版本(如SIP协议和PIP协议),则具有不同的版本号(它们的头部结构也和图2-1不同)。

4位头部长度(header length)标识该IP头部有多少个32 bit字(4字节)。因为4位最大能表示15,所以IP头部最长是60字节。

8位服务类型(Type Of Service,TOS)包括一个3位的优先权字段(现在已经被忽略),4位的TOS字段和1位保留字段(必须置0)。4位的TOS字段分别表示:最小延时,最大吞吐量,最高可靠性和最小费用。其中最多有一个能置为1,应用程序应该根据实际需要来设置它。比如像ssh和telnet这样的登录程序需要的是最小延时的服务,而文件传输程序ftp则需要最大吞吐量的服务。

16位总长度(total length)是指整个IP数据报的长度,以字节为单位,因此IP数据报的最大长度为65 535(2^16-1)字节。但由于MTU的限制,长度超过MTU的数据报都将被分片传输,所以实际传输的IP数据报(或分片)的长度都远远没有达到最大值。接下来的3个字段则描述了如何实现分片。

16位标识(identification)唯一地标识主机发送的每一个数据报。其初始值由系统随机生成;每发送一个数据报,其值就加1。该值在数据报分片时被复制到每个分片中,因此同一个数据报的所有分片都具有相同的标识值。

3位标志字段的第一位保留。第二位(Don’t Fragment,DF)表示“禁止分片”。如果设置了这个位,IP模块将不对数据报进行分片。在这种情况下,如果IP数据报长度超过MTU的话,IP模块将丢弃该数据报并返回一个ICMP差错报文。第三位(More Fragment,MF)表示“更多分片”。除了数据报的最后一个分片外,其他分片都要把它置1。

13位分片偏移(fragmentation offset)是分片相对原始IP数据报开始处(仅指数据部分)的偏移。实际的偏移值是该值左移3位(乘8)后得到的。由于这个原因,除了最后一个IP分片外,每个IP分片的数据部分的长度必须是8的整数倍(这样才能保证后面的IP分片拥有一个合适的偏移值)。

8位生存时间(Time To Live,TTL)是数据报到达目的地之前允许经过的路由器跳数。TTL值被发送端设置(常见的值是64)。数据报在转发过程中每经过一个路由,该值就被路由器减1。当TTL值减为0时,路由器将丢弃数据报,并向源端发送一个ICMP差错报文。TTL值可以防止数据报陷入路由循环。

8位协议(protocol)用来区分上层协议,我们在第1章讨论过。/etc/protocols文件定义了所有上层协议对应的protocol字段的数值。其中,ICMP是1,TCP是6,UDP是17。/etc/protocols文件是RFC 1700的一个子集。

16位头部校验和(header checksum)由发送端填充,接收端对其使用CRC算法以检验IP数据报头部(注意,仅检验头部)在传输过程中是否损坏。

32位的源端IP地址和目的端IP地址用来标识数据报的发送端和接收端。一般情况下,这两个地址在整个数据报的传递过程中保持不变,而不论它中间经过多少个中转路由器。关于这一点,我们将在第4章进一步讨论。

IPv4最后一个选项字段(option)是可变长的可选信息。这部分最多包含40字节,因为IP头部最长是60字节(其中还包含前面讨论的20字节的固定部分)。可用的IP选项包括:

❑ 记录路由(record route),告诉数据报途经的所有路由器都将自己的IP地址填入IP头部的选项部分,这样我们就可以跟踪数据报的传递路径。

❑ 时间戳(timestamp),告诉每个路由器都将数据报被转发的时间(或时间与IP地址对)填入IP头部的选项部分,这样就可以测量途经路由之间数据报传输的时间。

❑ 松散源路由选择(loose source routing),指定一个路由器IP地址列表,数据报发送过程中必须经过其中所有的路由器。

❑ 严格源路由选择(strict source routing),和松散源路由选择类似,不过数据报只能经过被指定的路由器。

2.2 使用tcpdump观察IPv4头部结构

为了深入理解IPv4头部中每个字段的含义,我们从测试机器ernest-laptop上执行telnet命令登录本机,并用tcpdump抓取这个过程中telnet客户端程序和telnet服务器程序之间交换的数据包。具体的操作过程如下:

1
2
3
4
5
6
7
sudo tcpdump -ntx -i lo  #抓取本地回路上的数据包
telnet 127.0.0.1 #开启另一个终端执行telnet命令登录本机
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Ubuntu 9.10 ernest-laptop login: ernest #输入用户名并回车
Password: #输入密码并回车

此时观察tcpdump输出的第一个数据包,其内容如代码清单2-1所示。

代码清单2-1 用tcpdump抓取数据包

1
2
3
4
5
6
IP 127.0.0.1.41621 > 127.0.0.1.23: Flags[S], seq 3499745539, win 32792,
options[mss 16396,sackOK,TS val 40781017 ecr 0,nop,wscale 6], length 0
0x0000: 4510 003c a5da 4000 4006 96cf 7f00 0001
0x0010: 7f00 0001 a295 0017 d099 e103 0000 0000
0x0020: a002 8018 fe30 0000 0204 400c 0402 080a
0x0030: 026e 44d9 0000 0000 0103 0306

该数据包描述的是一个IP数据报。由于我们是使用telnet登录本机的,

  • 所以IP数据报的源端IP地址和目的端IP地址都是“127.0.0.1”。
  • telnet服务器程序使用的端口号是23(参见/etc/services文件),而telnet客户端程序使用临时端口号41621与服务器通信。关于临时端口号,我们将在第3章讨论。
  • “Flags”、“seq”、“win”和“options”描述的都是TCP头部信息,这也将在第3章讨论。
  • “length”指出该IP数据报所携带的应用程序数据的长度。

这次抓包我们开启了tcpdump的-x选项,

  • 使之输出数据包的二进制码。
  • 此数据包共包含60字节,其中前20字节是IP头部,后40字节是TCP头部,不包含应用程序数据(length值为0)。

由表2-1可见,telnet服务选择使用具有最小延时的服务,并且默认使用的传输层协议是TCP协议。这些都符合我们通常的理解。这个IP数据报没有被分片,因为它没有携带任何应用程序数据。接下来我们将抓取并讨论被分片的IP数据报。

3. IP分片

当IP数据报的长度超过帧的MTU时,它将被分片传输。分片可能发生在发送端,也可能发生在中转路由器上,而且可能在传输过程中被多次分片,但只有在最终的目标机器上,这些分片才会被内核中的IP模块重新组装。

IP头部中的如下三个字段给IP的分片和重组提供了足够的信息:数据报标识、标志和片偏移。

  • 一个IP数据报的每个分片都具有自己的IP头部,它们具有相同的标识值,但具有不同的片偏移。并且除了最后一个分片外,其他分片都将设置MF标志。
  • 此外,每个分片的IP头部的总长度字段将被设置为该分片的长度。

以太网帧的MTU是1500字节(可以通过ifconfig命令或者netstat命令查看),因此它携带的IP数据报的数据部分最多是1480字节(IP头部占用20字节)。

考虑用IP数据报封装一个长度为1481字节的ICMP报文(包括8字节的ICMP头部,所以其数据部分长度为1473字节),则该数据报在使用以太网帧传输时必须被分片。

需要指出的是,ICMP报文的头部长度取决于报文的类型,其变化范围很大。后面的例子用到了ping程序,而ping程序使用的ICMP回显和应答报文的头部长度是8字节。

为了看清楚IP分片的具体过程,考虑从ernest-laptop来ping机器Kongming20,每次传送1473字节的数据(这是ICMP报文的数据部分)以强制引起IP分片,并用tcpdump抓取这一过程中双方交换的数据包。具体操作过程如下:

1
2
sudo tcpdump -ntv -i eth0 icmp  #只抓取ICMP报文
ping Kongming20 -s 1473 #用-s选项指定每次发送1473字节的数据

下面我们考察tcpdump输出的一个IP数据报的两个分片,其内容如下:

1
2
3
1. IP(tos 0x0, ttl 64, id 61197, offset 0, flags[+], proto ICMP(1), length 1500) 192.168.1.108 > 192.168.1.110: ICMP echo request, id 41737, seq 1, length 1480

2. IP(tos 0x0, ttl 64, id 61197, offset 1480, flags[none], proto ICMP(1), length 21) 192.168.1.108 > 192.168.1.110: icmp

这两个IP分片的标识值都是61197,说明它们是同一个IP数据报的分片。第一个分片的片偏移值为0,而第二个则是1480。很显然,第二个分片的片偏移值实际上也是第一个分片的ICMP报文的长度。第一个分片设置了MF标志以表示还有后续分片,所以tcpdump输出“flags[+]”。而第二个分片则没有设置任何标志,所以tcpdump输出“flags[none]”。这两个分片的长度分别为1500字节和21字节,这与图2-2描述的一致。

最后,IP层传递给数据链路层的数据可能是一个完整的IP数据报,也可能是一个IP分片,它们统称为IP分组(packet)。

4. IP路由

IP协议的一个核心任务是数据报的路由,即决定发送数据报到目标机器的路径。

4.1 IP模块工作流程

我们从右往左来分析图2-3。当IP模块接收到来自数据链路层的IP数据报时,它首先对该数据报的头部做CRC校验,确认无误之后就分析其头部的具体信息。

如果该IP数据报的头部设置了源站选路选项(松散源路由选择或严格源路由选择),则IP模块调用数据报转发子模块来处理该数据报。如果该IP数据报的头部中目标IP地址是本机的某个IP地址,或者是广播地址,即该数据报是发送给本机的,则IP模块就根据数据报头部中的协议字段来决定将它派发给哪个上层应用(分用)。如果IP模块发现这个数据报不是发送给本机的,则也调用数据报转发子模块来处理该数据报。

数据报转发子模块将首先检测系统是否允许转发,如果不允许,IP模块就将数据报丢弃。如果允许,数据报转发子模块将对该数据报执行一些操作,然后将它交给IP数据报输出子模块。

IP数据报应该发送至哪个下一跳路由(或者目标机器),以及经过哪个网卡来发送,就是IP路由过程,即图2-3中“计算下一跳路由”子模块。IP模块实现数据报路由的核心数据结构是路由表。这个表按照数据报的目标IP地址分类,同一类型的IP数据报将被发往相同的下一跳路由器(或者目标机器)。我们将在后面讨论IP路由过程。

IP输出队列中存放的是所有等待发送的IP数据报,其中除了需要转发的IP数据报外,还包括封装了本机上层数据(ICMP报文、TCP报文段和UDP数据报)的IP数据报。

图2-3中的虚线箭头显示了路由表更新的过程。这一过程是指通过路由协议或者route命令调整路由表,使之更适应最新的网络拓扑结构,称为IP路由策略。

4.2 路由机制

要研究IP路由机制,需要先了解路由表的内容。我们可以使用route命令或netstat命令查看路由表。在测试机器ernest-laptop上执行route命令,输出内容如代码清单2-2所示。

代码清单 2-2 路由表实例

1
2
3
4
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
192.168.1.0 * 255.255.255.0 U 1 0 0 eth0

该路由表包含两项,每项都包含8个字段,如表2-2所示。

代码清单2-2所示的路由表中,第一项的目标地址是default,即所谓的默认路由项。该项包含一个“G”标志,说明路由的下一跳目标是网关,其地址是192.168.1.1(这是测试网络中路由器的本地IP地址)。另外一个路由项的目标地址是192.168.1.0,它指的是本地局域网。该路由项的网关地址为*,说明数据报不需要路由中转,可以直接发送到目标机器。

那么路由表是如何按照IP地址分类的呢?或者说给定数据报的目标IP地址,它将匹配路由表中的哪一项呢?这就是IP的路由机制,分为3个步骤:

1)查找路由表中和数据报的目标IP地址完全匹配的主机IP地址。如果找到,就使用该路由项,没找到则转步骤2。

2)查找路由表中和数据报的目标IP地址具有相同网路ID的网络IP地址(比如代码清单2-2所示的路由表中的第二项)。如果找到,就使用该路由项;没找到则转步骤3。

3)选择默认路由项,这通常意味着数据报的下一跳路由是网关。因此,对于测试机器ernest-laptop而言,所有发送到IP地址为192.168.1.*的机器的IP数据报都可以直接发送到目标机器(匹配路由表第二项),而所有访问因特网的请求都将通过网关来转发(匹配默认路由项)。

4.3 路由表更新

路由表必须能够更新,以反映网络连接的变化,这样IP模块才能准确、高效地转发数据报。route命令可以修改路由表。我们看如下几个例子(在机器ernest-laptop上执行):

1
2
3
4
sudo route add-host 192.168.1.109 dev eth0
sudo route del-net 192.168.1.0 netmask 255.255.255.0
sudo route del default
sudo route add default gw 192.168.1.109 dev eth0
  • 第1行表示添加主机192.168.1.109(机器Kongming20)对应的路由项。这样设置之后,所有从ernest-laptop发送到Kongming20的IP数据报将通过网卡eth0直接发送至目标机器的接收网卡。
  • 第2行表示删除网络192.168.1.0对应的路由项。这样,除了机器Kongming20外,测试机器ernest-laptop将无法访问该局域网上的任何其他机器(能访问到Kongming20是由于执行了上一条命令)。
  • 第3行表示删除默认路由项,这样做的后果是无法访问因特网。
  • 第4行表示重新设置默认路由项,不过这次其网关是机器Kongming20(而不是能直接访问因特网的路由器)!经过上述修改后的路由表如下:
1
2
3
4
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
Kongming20 * 255.255.255.255 UH 0 0 0 eth0
default 192.168.1.109 0.0.0.0 UG 0 0 0 eth0

这个新的路由表中,第一个路由项是主机路由项,所以它被设置了“H”标志。我们设计这样一个路由表的目的是为后文讨论ICMP重定向提供环境。

通过route命令或其他工具手工修改路由表,是静态的路由更新方式。对于大型的路由器,它们通常通过BGP(Border Gateway Protocol,边际网关协议)、RIP(Routing Information Protocol,路由信息协议)、OSPF等协议来发现路径,并更新自己的路由表。这种更新方式是动态的、自动的。

5. IP转发

前文提到,不是发送给本机的IP数据报将由数据报转发子模块来处理。路由器都能执行数据报的转发操作,而主机一般只发送和接收数据报,这是因为主机上/proc/sys/net/ipv4/ip_forward内核参数默认被设置为0。我们可以通过修改它来使能主机的数据报转发功能(在测试机器Kongming20上以root身份执行):

1
# echo 1 > /proc/sys/net/ipv4/ip_forward

对于允许IP数据报转发的系统(主机或路由器),数据报转发子模块将对期望转发的数据报执行如下操作:

1)检查数据报头部的TTL值。如果TTL值已经是0,则丢弃该数据报。

2)查看数据报头部的严格源路由选择选项。如果该选项被设置,则检测数据报的目标IP地址是否是本机的某个IP地址。如果不是,则发送一个ICMP源站选路失败报文给发送端。

3)如果有必要,则给源端发送一个ICMP重定向报文,以告诉它一个更合理的下一跳路由器。

4)将TTL值减1。

5)处理IP头部选项。

6)如果有必要,则执行IP分片操作。

6. 重定向

6.1 ICMP重定向报文

我们在1.1节讨论过ICMP报文头部的3个固定字段:8位类型、8位代码和16位校验和。ICMP重定向报文的类型值是5,代码字段有4个可选值,用来区分不同的重定向类型。本书仅讨论主机重定向,其代码值为1。

ICMP重定向报文的数据部分含义很明确,它给接收方提供了如下两个信息:

❑ 引起重定向的IP数据报(即图2-4中的原始IP数据报)的源端IP地址。

❑ 应该使用的路由器的IP地址。

接收主机根据这两个信息就可以断定引起重定向的IP数据报应该使用哪个路由器来转发,并且以此来更新路由表(通常是更新路由表缓冲,而不是直接更改路由表)。

/proc/sys/net/ipv4/conf/all/send_redirects内核参数指定是否允许发送ICMP重定向报文,而/proc/sys/net/ipv4/conf/all/accept_redirects内核参数则指定是否允许接收ICMP重定向报文。一般来说,主机只能接收ICMP重定向报文,而路由器只能发送ICMP重定向报文。

6.2 主机重定向实例

4.3节中,我们把机器ernest-laptop的网关设置成了机器Kongming20,5节中我们又使能了Kongming20的数据报转发功能,因此机器ernest-laptop将通过Kongming20来访问因特网,比如在ernest-laptop上执行如下ping命令:

1
2
3
4
5
6
7
$ ping www.baidu.com
PING www.a.shifen.com(119.75.217.56) 56(84) bytes of data. From Kongming20(192.168.1.109): icmp_seq=1 Redirect Host(New nexthop:192.168.1.1)

64 bytes from 119.75.217.56: icmp_seq=1 ttl=54 time=6.78 ms --- www.a.shifen.com ping statistics---

1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 6.789/6.789/6.789/0.000 ms

从ping命令的输出来看,Kongming20给ernest-laptop发送了一个ICMP重定向报文,告诉它请通过192.168.1.1来访问目标机器,因为这对ernest-laptop来说是更合理的路由方式。当主机ernest-laptop收到这样的ICMP重定向报文后,它将更新其路由表缓冲(使用命令route -Cn查看),并使用新的路由方式来发送后续数据报。上面讨论的重定向过程可用图2-5来总结。

7. IPv6头部结构

IPv6协议是网络层技术发展的必然趋势。它不仅解决了IPv4地址不够用的问题,还做了很大的改进。比如,增加了多播和流的功能,为网络上多媒体内容的质量提供精细的控制;引入自动配置功能,使得局域网管理更方便;增加了专门的网络安全功能等。

7.1 IPv6固定头部结构

IPv6头部由40字节的固定头部和可变长的扩展头部组成。图2-6所示是IPv6的固定头部结构。

4位版本号(version)指定IP协议的版本。对IPv6来说,其值是6。

8位通信类型(traffic class)指示数据流通信类型或优先级,和IPv4中的TOS类似。

20位流标签(flow label)是IPv6新增加的字段,用于某些对连接的服务质量有特殊要求的通信,比如音频或视频等实时数据传输。

16位净荷长度(payload length)指的是IPv6扩展头部和应用程序数据长度之和,不包括固定头部长度。

8位下一个包头(next header)指出紧跟IPv6固定头部后的包头类型,如扩展头(如果有的话)或某个上层协议头(比如TCP,UDP或ICMP)。它类似于IPv4头部中的协议字段,且相同的取值有相同的含义。

8位跳数限制(hop limit)和IPv4中的TTL含义相同。

IPv6用128位(16字节)来表示IP地址,使得IP地址的总量达到了\(2^{128}\) 个。

32位表示的IPv4地址一般用点分十进制来表示,而IPv6地址则用十六进制字符串表示。IPv6地址用“:”分割成8组,每组包含2字节。可以使用零压缩法来将其简写,也就是省略连续的、全零的组。不过零压缩法对一个IPv6地址只能使用一次。

7.2 IPv6扩展头部

可变长的扩展头部使得IPv6能支持更多的选项,并且很便于将来的扩展需要。它的长度可以是0,表示数据报没使用任何扩展头部。一个数据报可以包含多个扩展头部,每个扩展头部的类型由前一个头部(固定头部或扩展头部)中的下一个报头字段指定。目前可以使用的扩展头部如表2-3所示。

注意 IPv6协议并不是IPv4协议的简单扩展,而是完全独立的协议。用以太网帧封装的IPv6数据报和IPv4数据报具有不同的类型值。第1章提到,IPv4数据报的以太网帧封装类型值是0x800,而IPv6数据报的以太网帧封装类型值是0x86dd(见RFC 2464)。


2. IP协议详解
http://binbo-zappy.github.io/2024/12/16/Linux高性能服务器编程-游双/2-IP协议详解/
作者
Binbo
发布于
2024年12月16日
许可协议