(Linux 高性能服务器) – IP 协议笔记

IP 协议

IP 服务的特点

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

  • 无状态:IP通信双方不同步传输数据的状态信息,因此所有IP数据报的发送、传输和接收都是相互独立、没有上下文关系的。缺点就是无法乱序处理和重复的 IP数据报。
  • 无连接:IP通信双方都不长久地维持对方的任何信息。这样,上层协议每次发送数据的时候,都必须明确指定对方的IP地址。
  • 不可靠:IP协议不能保证IP数据报准确地到达接收端,它只是承诺尽最大努力(best effort)。很多种情况都能导致IP数据报发送失败。比如,某个中转路由器发现IP数据报在网络上存活的时间太长(根据IP数据报头部字段TTL判断,见后文),那么它将丢弃之,并返回一个ICMP错误消息(超时错误)给发送端。又比如,接收端发现收到的IP数据报不正确(通过校验机制),它也将丢弃之,并返回一个ICMP错误消息(IP头部参数错误)给发送端。无论哪种情况,发送端的IP模块一旦检测到IP数据报发送失败,就通知上层协议发送失败,而不会试图重传。因此,使用IP服务的上层协议(比如TCP协议)需要自己实现数据确认、超时重传等机制以达到可靠传输的目的。

IPv4 的头部结构

字段 意义
4位版本号 指定 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则需要最大吞吐量的服务。TOS 的位:
16位总长度(total length) 整个IP数据报的长度,以字节为单位,因此IP数据报的最大长度为65 535(216-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) 用来区分上层协议。/etc/protocols文件定义了所有上层协议对应的protocol字段的数值。其中,ICMP是1,TCP是6,UDP是17。/etc/protocols文件是RFC 1700的一个子集。
16位头部校验和(header checksum) 由发送端填充,接收端对其使用CRC算法以检验IP数据报头部(注意,仅检验头部)在传输过程中是否损坏。
32位的源端IP地址和目的端IP地址 用来标识数据报的发送端和接收端。一般情况下,这两个地址在整个数据报的传递过程中保持不变,而不论它中间经过多少个中转路由器。

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

  • 记录路由(record route),告诉数据报途经的所有路由器都将自己的IP地址填入IP头部的选项部分,这样我们就可以跟踪数据报的传递路径。
  • 时间戳(timestamp),告诉每个路由器都将数据报被转发的时间(或时间与IP地址对)填入IP头部的选项部分,这样就可以测量途经路由之间数据报传输的时间。
  • 松散源路由选择(loose source routing),指定一个路由器IP地址列表,数据报发送过程中必须经过其中所有的路由器。
  • 严格源路由选择(strict source routing),和松散源路由选择类似,不过数据报只能经过被指定的路由器。

查看数据包

sudo tcpdump-ntx-i lo # 抓取本地回路上的数据包 使用参数 -n(数字显示端口,-t:不打印时间戳,-x:16进制显示数据)

IP 127.0.0.1.36230 > 127.0.0.1.telnet: Flags [S], seq 3953640826, win 43690, options [mss 65495,sackOK,TS val 1185603718 ecr 0,nop,wscale 7], length 0
	0x0000:  4510 003c 2077 4000 4006 1c33 7f00 0001
	0x0010:  7f00 0001 8d86 0017 eba7 c57a 0000 0000
	0x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a
	0x0030:  46aa e086 0000 0000 0103 0307

可以发现这个数据包一共 60 字节。其中前20字节是IP头部,后40字节是TCP头部,不包含应用程序数据(length值为0)。具体的信息:

十六进制数 IP 的头部信息
0x4 IP 版本4
0x5 头部长度 5个 32位的字。共 20字节
0x10 TOS 中的最大吞吐量标识置位
0x003c 数据报总长度,一共 60 字节
0x2077 16位标志
0x4 设置了DF位,即进制分片(第一位是保留的)
0x000 分片用,这里不使用
0x40 TTL设置位64
0x06 协议字段为6,参考 /etc/protocols,代表上层的 TCP协议
0x1c33 16 位的头部校验和
0x7f000001 源 IP地址:127.0.0.1 的16进制表示
0x7f000001 目标 IP地址:同上

IP 分片

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

IP头部中的如下三个字段给IP的分片和重组提供了足够的信息:数据报标识、标志和片偏移。一个IP数据报的每个分片都具有自己的IP头部,它们具有相同的标识值,但具有不同的片偏移。并且除了最后一个分片外,其他分片都将设置 MF标志。此外,每个分片的IP头部的总长度字段将被设置为该分片的长度。

使用 ifconfig 可以查看 MTU 大小。

当 IP的数据报大小超过了 1500 字节的时候。就会进行分片。考虑 ICMP 协议(ping 程序使用)。

Windows 发送数据包给虚拟机 ping -l 1473 192.168.100.102。-l 指定发送的 ICMP 数据包数据部分的字节数。由于 MTU 的限制,IP 一次性可以发送的 数据部分是 1480 字节。在 Windows 和 Linux 中 ping 的 ICMP 报头都是 8字节。虚拟机的使用 tcpdump -ntv -i ens33 抓包的结果:

IP (tos 0x0, ttl 128, id 25456, offset 0, flags [+], proto ICMP (1), length 1500)
    192.168.100.101 > 192.168.100.102: ICMP echo request, id 1, seq 1, length 1480
IP (tos 0x0, ttl 128, id 25456, offset 1480, flags [none], proto ICMP (1), length 21)
    192.168.100.101 > 192.168.100.102: ip-proto-1

可以看到 IP分片数据包的标志相同都是“23456”。第一个数据包中 flags + 表示 MF 被置位,第二个数据包的 flags none 表示没有更多的分片的数据包了。

我们可以看出:

  • 第一个数据包大小:20(IP 报头) + 8(ICMP 头) + 1472(ICMP 数据)。
  • 第二个数据包:20 + 1(注意分片省去了重复的 ICMP 报头)

IP 路由

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

Linux 使用 route 可以查看网卡的路由表:

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         gateway         0.0.0.0         UG    20100  0        0 ens33
192.168.100.0*   0.0.0.0         255.255.255.0   U     100    0        0 ens33

具体的含义是:

  • 号是自己加去的给定的数据包的目标地址,匹配的步骤如下:
  1. 查找路由表中和数据报的目标IP地址完全匹配的主机IP地址。如果找到,就使用该路由项,没找到则转步骤2。
  2. 查找路由表中和数据报的目标IP地址具有相同网路ID的网络IP地址(比如代码清单2-2所示的路由表中的第二项)。如果找到,就使用该路由项;没找到则转步骤3。
  3. 选择默认路由项,这通常意味着数据报的下一跳路由是网关。

重定向

ICMP 重定向报文

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重定向报文。他们的值是 10

实现主机重定向(未完)

注意,由于需要访问互联网,所以我把两台机器之间的桥接用的网卡(环回适配器)与 WLAN 共享。所以 Windows 主机的 IP地址变为 192.168.137.1,而 Linux 虚拟机修改为 192.168.137.2。网关为 192.168.137.1。

  1. 修改路由表。还是之前的机器,在 Linux 系统中修改路由表,可以把数据直接发送到 Windows 的物理机上:
sudo route del -net 192.168.137.0 netmask 255.255.255.0
sudo route del default
sudo route add -host 192.168.137.1 dev ens33
sudo route add default gw 192.168.137.1 dev ens33

这样就可以删除默认的网关,直接与 Windows 物理机通信。修改后的路由表:

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.137.0   0.0.0.0         255.255.255.0   U     100    0        0 ens33
DESKTOP-4KGFP6B 0.0.0.0         255.255.255.255 UH    0      0        0 ens33
  1. 测试 Ping 互联网上的主机

ICMP 转发的流程:

IPv6

IPv6 并不是 IPv4 的扩展而是一个全新的协议。具体的细节可以参考 RFC2464

参考&引用