Linux高性能服务器编程 学习笔记 第二章 IP协议详解

本章从两方面探讨IP协议:

1.IP头部信息。IP头部出现在每个IP数据报中,用于指定IP通信的源端IP地址、目的端IP地址,指导IP分片和重组,指定部分通信行为。

2.IP数据报的路由和转发。IP数据报的路由和转发发生在除目标机器外的所有主机和路由器上,它们决定数据报是否应该转发以及如何转发。

由于32位的IPv4地址即将全部用完,因此人们开发出了新版本的IPv6协议。

IP协议为上层协议提供无状态、无连接、不可靠的服务。

无状态(stateless)指所有IP数据报的发送、传输、接收都是相互独立、没有上下文关系的,这种服务的缺点是无法处理乱序和重复的IP数据报,如发送端发送出的第N个IP数据报可能比第N+1个IP数据报后到达接收端,而相同源地址和目的地址的IP数据报也可能经过不同路径到达接收端,也会出现同一个IP数据报多次到达接收端(正常情况下,同一个IP数据报不会多次到达接收端,只有在网络故障的情况下才会出现),这样接收端的IP模块无法检测到乱序和重复,因为这些IP数据报之间没有任何上下文关系。接收端的IP模块只要接收到了完整的IP数据报(如果IP分片的话,IP模块会先将其重组),就将其数据部分(如TCP报文段、UDP数据报、ICMP报文)上交给上层协议,从上层协议来看,这些数据就可能是乱序、重复的,而面向连接的上层协议,如TCP,则能自己处理乱序、重复的报文段,从而递交给TCP的上层的内容绝对是有序的、正确的。

IP数据报头部提供了一个标识字段用来唯一标识一个IP数据报,但它是用来处理IP分片和重组的,而非用来指示接收顺序的。

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

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

不可靠指的是IP协议不保证IP数据报准确地到达接收端,它只是承诺尽最大努力(best effort),很多情况都会导致IP数据报发送失败,如某个中间路由器发现IP数据报在网络上存活的时间太长(根据IP首部中的TTL字段判断),那么该路由器将丢弃此IP数据报,并返回一个ICMP错误消息(超时错误)给发送端;又比如接收端发现收到的IP数据报不正确(通过校验机制),接收端也会将该IP数据报丢弃,并返回一个ICMP错误消息(ICMP首部参数错误)给发送端。而发送端的IP模块一旦检测到IP数据报发送失败,就通知上层协议发送失败,而不会试图重传,因此,使用IP服务的上层协议(如TCP)需要自己实现数据确认、超时重传等机制以达到可靠传输的目的。

IP首部结构如下图,其长度通常为20字节,除非含有可变长度的选项部分:

4位版本号字段指定IP协议的版本,对IPv4来说,其值为4。

4位首部长度字段标识IP头部长度,以4字节为单位,因此IP头部最长为2 4 ^{4} 4*4=60字节。

8位服务类型字段(Type Of Service,TOS)包括一个3位的优先权字段(现已被忽略)、4位的TOS字段和1位保留字段(必须置0)。4位的TOS字段分别为:最小延时、最大吞吐量、最高可靠性、最小费用,其中最多只能有一位置1,应用程序可根据实际需要来设置它,如像ssh和telnet这样的登录程序需要最小延时服务,而ftp则需要最大吞吐量服务。而目前的IPv4使用DiffServ(Differentiated Services,区分服务)和ECN(显式拥塞通知,Explicit Congestion Notification)等更现代的QoS(服务质量,Quality of Service,即对服务质量的要求)机制所取代。

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

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

3位标志字段的第一位保留,必须置0;第二位表示禁止分片(DF,Don't Fragment),如果设置了此位,IP模块将不对数据报进行分片,此时,如果IP数据报长度超过MTU,IP模块将丢弃该数据报并返回一个ICMP差错报文;第三位表示更多分片(MF,More Fragment),除了数据报的最后一个分片外,其他分片都要将其置为1。

13位分片偏移字段(fragmentation offset)是分片相对分片前的IP数据报的数据部分开始处的偏移,以8字节为单位,原因为该字段只有13位,而整个IP数据报长度字段有16位,因此需要以8字节为单位偏移值才能覆盖整个IP数据报,因此,除了最后一个IP分片外,每个IP分片的数据部分的长度必须是8字节的整数倍。

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

8位协议字段(protocol)用来区分上层协议,/etc/protocols文件定义了所有上层协议对应的protocol字段的值,其中,ICMP是1,TCP是6,UDP是17。RFC 1700中规定了协议号的分配,/etc/protocols文件中的内容一般是RFC 1700中规定的协议号分配的子集。

16位头部校验和字段(header checksum)由发送端填充,接收端对其进行检验(仅检验头部)以确定IP头部是否在传输过程中被损坏。

32位源端IP地址和目的端IP地址用来标识发送端和接收端,一般,这两个地址在整个数据报的传递过程中保持不变,而无论它经过了多少中间路由器。

选项字段是可变长的可选信息,最多包含40字节,因为IP头部最长为60字节,减去前面固定部分20字节。部分IP选项:

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

2.时间戳(timestamp)选项:告诉每个路由器将数据报被转发的时间填入IP头部的选项部分,这样就可测量途径的路由之间的数据报传输的时间。

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

4.严格源路由选择(strict source routing)选项:类似松散源路由选择,但数据报只能经过被指定的路由器。

其他IP头部选项可参考RFC 791,但其他选项很少被使用,使用松散源路由选择和严格源路由选择的例子可能只有traceroute程序。作为记录路由选项的替代品,traceroute程序使用UDP和ICMP实现了更可靠的记录路由功能,可参考RFC 1393。

我们在ernest-laptop上执行telnet命令登录该主机本身,并用tcpdump抓取此过程中telnet客户和telnet服务器之间交换的数据包,具体操作如下:

tcpdump输出的第一个数据包:

该数据包描述的是一个IP数据报,由于我们是使用telnet登录本机的,所以IP数据报的源IP地址和目的端IP地址都是127.0.0.1。telnet服务器使用端口号23(参见/etc/services文件),而telnet客户使用临时端口号41621与服务器通信。Flags、seq、win、option描述的都是TCP头部信息。length指出该IP数据报所携带的应用程序数据的长度为0。

本次抓包我们开启了tcpdump的-x选项,使其输出数据包的二进制码,此数据包共含60字节,其中前20字节是IP首部,后40字节是TCP首部,不包含应用程序数据。以下是IP头部中的每个字节含义:

上表中第二列的空格子表示我们不关心相应字段的十进制值。由上表可见,telnet服务选择具有最小延时的服务,且默认使用的传输层协议是TCP协议。这个IP数据报没有被分片,且没有携带任何应用程序数据。

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

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

以太网帧的MTU是1500字节,可通过ifconfig命令查看:

以太网MTU为1500字节,它指定的是可以在单个帧中传输的最大数据量,不包括帧头和帧尾,因此一个以太网帧可以携带的IP数据报的最大长度为1500字节,减去20字节的IP固定首部长度,得到IP数据报的数据部分最多是1480字节。我们用IP数据报封装一个长度为1481字节的ICMP报文(包括8字节ICMP头部和1473字节数据部分),该IP数据报在使用以太网帧传输时必须被分片,如下图所示:

上图中,长为1501字节的IP数据报被拆分成两个IP分片,第一个IP分片长度为1500字节,第二个IP分片长度为21字节,每个IP分片都包含自己的IP头部(20字节),且第一个IP分片的IP首部设置了MF标志,而第二个IP分片的IP首部没有设置该标志,因为它已经是第二个分片了。原始IP数据报中的ICMP头部内容被完整地复制到了第一个IP分片中,第二个IP分片不包含ICMP头部信息,因为IP模块重组该ICMP报文的时候只需要一份ICMP头部信息,重复传送这个信息没有用处。1473字节的ICMP报文数据的前1472字节被IP模块复制到第一个IP分片中,使其总长度为1500字节,从而满足MTU的要求,而多出的最后1个字节被复制到第二个IP分片中。

ICMP报文的头部长度取决于报文的类型,变化范围很大,上图中以8字节为例,ping程序使用的ICMP回显和应答报文的头部长度是8字节的。

我们在ernest-laptop上ping Kongming20,每次传送1473字节的数据(即ICMP报文的数据部分长1473字节)以强制引起IP分片,并用tcpdump抓取这一过程双方交换的数据包,具体操作如下(tcpdump的-n选项表示显示数值形式的IP地址和端口号,-v选项显示更详细的输出):

tcpdump会输出一个IP数据报的两个分片,其内容如下:

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

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

IP协议会决定数据报到目标机器的路径,即数据报的路由。

IP模块的基本工作流程:

从右往左分析上图,当IP模块收到来自数据链路层的IP数据报时,它首先对该数据报的头作校验,确认无误后就分析其头部的信息。

如果该IP数据报的头部设置了源站选路选项(松散源路由选择或严格源路由选择),则IP模块调用数据报转发子模块来处理该数据报,这是因为经过某个源路由后,该路由地址会从选项中被删除,从而到达目标机器时源站选路选项已为空。如果该IP数据报的头部中目标IP地址是本机的某个IP地址,或是广播地址,则该数据报是发送给本机的,则IP模块就根据数据报头部中的协议字段决定将它派发给哪个上层应用(分用);如果IP模块发现这个数据报不是发送给本机的,则也调用数据报转发子模块来处理该数据报。

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

IP数据报应发送至哪个下一跳路由(或目标机器),以及经过哪个网卡来发送,就是IP路由过程,即上图中"计算下一跳路由"子模块。IP模块实现数据报路由的核心数据结构是路由表,这个表按照数据报的目标IP地址分类,同一类型的IP数据报将被发往相同的下一条路由器(或目标机器)。

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

上图中的虚线箭头显示了路由表更新的过程,此过程通过路由协议或route命令调整路由表,使其更适应最新的网络拓扑结构。

可用route命令查看路由表,在ernest-laptop上执行route命令:

每项包含8个字段,如下表所示:

代码清单2-2所示的路由表中,第一项的目标地址是default,即默认路由项,该项包含一个G标志,说明路由的下一跳是网关,网关地址为192.168.1.1(这是此网络中路由器的本地IP地址)。另一个路由项的目标地址是192.168.1.0,它指的是本地局域网(主机号全0的地址为网络地址,它不会分配给实际主机,因为此地址通常表示整个网络),该路由项的网关地址为*,说明数据报不需路由器中转,可直接发送到目标机器。

给定一个IP,在路由表中的匹配过程:

1.查找路由表中和给定IP完全匹配的主机IP地址,如果找到,就使用该路由项,否则转步骤2。

2.查找路由表中和数据报的目标IP地址具有相同网络ID的IP地址,如果找到,就使用该路由项,否则转步骤3。

3.选择默认路由项,通常意味着数据报的下一跳路由是网关。

因此,对于ernest-laptop机器而言,所有发送到IP地址为192.168.1.*的IP数据报都可直接发送到目标机器(匹配路由表第二项),而所有访问因特网的请求都将通过网关来转发(匹配默认路由项)。

路由表必须能更新,以反映网络连接的变化,这样IP模块才能准确、高效地转发数据报。route命令可修改路由表,以下操作在ernest-laptop上执行:

第一行表示添加主机192.168.1.109(Kongming20机器)对应的路由项,这样设置后,所有从ernest-laptop发送到Kongming20的IP数据报将通过网卡eth0直接发送至目标机器的接收网卡。第二行表示删除网络192.168.1.0对应的路由项,这样,除了Kongming20外,ernest-laptop将无法访问该局域网上的任何其他机器(能访问到Kongming20是由于执行了第一行命令)。第三行表示删除默认路由项,这样做的后果是无法访问因特网。第四行表示重新设置默认路由项,但这次其网关是Kongming20,而非能直接访问因特网的路由器。经过以上操作后的路由表:

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

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

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

允许IP数据报转发的系统(主机或路由器)的数据报转发子模块会对要转发的数据报做以下操作:

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

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

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

4.将TTL值减1。

5.处理IP头部选项。

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

ICMP重定向报文也能用于更新路由表,其报文格式为:

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

ICMP重定向报文数据部分给接收方提供以下信息:

1.引起重定向的IP数据报的源端和目的端IP地址。

2.应使用的路由器的IP地址。

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

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

上文中,我们把ernest-laptop机器的网关设置成了机器Kongming20,然后又打开了Kongming20的数据报转发功能,因此机器ernest-laptop将通过Kongming20访问因特网,如果我们在ernest-laptop上执行ping命令:

从ping命令的输出看,Kongming20给ernest-laptop发送了一个ICMP重定向报文,告诉ernest-laptop使其通过192.1681.1(路由器)来访问目标机器,因为这对ernest-laptop来说是更合理的路由方式。当主机ernest-laptop收到此ICMP重定向报文后,它将更新其路由表缓冲(可用命令route -Cn查看,-C选项表示操作的是路由表缓冲),并使用新的路由方式发送后续数据报,重定向过程为:

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

IPv6由40字节的固定头部和可变长的扩展头部组成,以下是IPv6的固定头部:

4位版本号指定IP协议版本,对IPv6来说,其值为6。

8位通信类型指示数据流通信类型或优先级,与IPv4中的TOS类似。

20位流标签用于某些对连接的服务质量有特殊要求的通信,如音频或视频等实时数据传输,可通过流标签为这些流量提供差异化的服务质量(如低延迟、高带宽)。

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

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

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

IPv6用128位(16字节)表示IP地址,使得IP地址的总量达到了2 128 ^{128} 128个,因此有人说,"IPv6使得地球上的每粒沙子都有一个IP地址"。

32位IPv4地址一般用点分十进制表示,而IPv6地址用十六进制字符串表示,如FE80:0000:0000:0000:1234:5678:0000:0012,可见IPv6地址被:分割为8组,每组2字节,IPv6还可使用零压缩法将其简写,即省略连续的、全0的组,本例用零压缩法可表示为FE80::1234:5678:0000:0012,但零压缩法对一个IPv6地址只能用一次,如本例中字节组5678后面的全零组不能再省略,否则我们将无法计算每个::之间省略了多少个全零组。

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

IPv6协议不是IPv4协议的简单扩展,而是完全独立的协议,用以太网帧封装的IPv6数据报和IPv4数据报具有不同类型字段值,IPv4的是0x800,而IPv6是0x86dd。

相关推荐
七夜zippoe1 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
盟接之桥1 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
会员源码网2 小时前
理财源码开发:单语言深耕还是多语言融合?看完这篇不踩坑
网络·个人开发
米羊1212 小时前
已有安全措施确认(上)
大数据·网络
Fcy6482 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满2 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
主机哥哥3 小时前
阿里云OpenClaw部署全攻略,五种方案助你快速部署!
服务器·阿里云·负载均衡
ManThink Technology3 小时前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
珠海西格电力科技4 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
QT.qtqtqtqtqt4 小时前
未授权访问漏洞
网络·安全·web安全