概说《TCP/IP详解 卷2》第8章 IP:网际协议
Posted 大白爱爬山
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了概说《TCP/IP详解 卷2》第8章 IP:网际协议相关的知识,希望对你有一定的参考价值。
本文要点
引言
IP分组
输入处理:ipintr函数
ipintr概述
验证
转发或不转发
重装和分用
转发:ip_forward函数
转出处理:ip_output函数
首部初始化
路由选择
Internet检验和:in_cksum函数
setsockopt和getsockopt系统调用
ip_sysctl函数
引言
本文主要介绍IP分组的结构和基本的IP处理过程,包括输入、转发和输出。图1显示了IP层常见的组织形式。
图1 IP层的处理
在第4章中,我们已经介绍网络接口如何把到达的IP分组放到IP输入队列ipintrq中,并如何调用一个软件中断。因为硬件中断的优先级比软件中断的要高,所以在发生一次软件中断之前,有的分组可能会被放到队列中。在软件中断处理中,ipintr函数不断从ipintrq中移走和处理分组,直到队列为空。在最终的目的地,IP把分组重装为数据报,并通过函数调用把该数据报传给适当的运输层协议。如果分组没有到达最后的目的地,并且如果主机被配置成一个路由器,则IP把分组传给ip_forward。传输协议和ip_forward把要输出的分组传给ip_output,由ip_output完成IP首部、选择输出接口以及在必要时对分组分片。最终的分组被传给合适的网络接口输出函数。
当产生差错时,IP丢弃分组,并在某些条件下向分组的源站发出一个差错报文。这些报文是ICMP的一部分。Net/3通过调用icmp_error发出ICMP差错报文,icmp_error接收一个mbuf,其中包括差错分组、发现的差错类型以及一个选项码,提供依赖于差错类型的附加信息。
IP分组
为了更准确的讨论Internet协议处理,首先必须理解一些名词定义,图2显示了在不同Internet层之间传递数据时用来描述数据的名词。
图2 帧、分组、分片、数据报和报文
我们把传输层协议交给IP网络层的数据称为报文。典型的报文包含一个运输层首部和应用程序数据。图2所示的传输协议是UDP。IP在报文的首部前加上它自己的首部形成一个数据报。如果在选定的网络中,数据报的长度太大,IP就把数据报分裂成几个分片,每个分片中含有它自己的IP首部和一段原来数据报的数据。图2显示了一个数据报被分成三个分片。
当提交给数据链路层进行传送时,一个IP分片或者一个很小的无需分片的IP数据报称为分组。数据链路层在分组前面加上它自己的首部,并发送得到的帧。
IP只考虑它自己加上IP首部,对报文本身既不检查也不修改(除非进行分片)。图3显示了IP首部的结构。
图3 IP数据报
图3包括ip结构(图4)中各成员的名字,Net/3通过该结构访问IP首部。
图4 ip结构
47~67 因为在存储器中,比特字段的物理顺序根据机器和编译器的不同而不同,所以由#ifs保证编译器按照IP标准排列结构成员。从而,当Net/3把一个ip结构覆盖到存储器中的一个IP分组上时,结构成员能够访问到分组中正确的比特。
IP分组的格式由版本ip_v指定,通常为4;首部长度ip_hl,通常以4字节单元度量;分组长度ip_len以字节为单位度量;传输协议ip_p生成分组内数据;ip_sum是检验和,检测在发送中首部的变化。
标准的IP首部长度是20字节,所以ip_hl必须大于或者等于5。大于5表示IP选项紧跟在标准首部后。ip_hl的最大值为15(2的4次方减1),允许最多40个字节的选项(20+40=60)。IP数据报的最大长度为65535(2的16次方减1),因为ip_len是一个16bit的字段。图5显示了一个完整的IP分组构成。
图5 有选项的IP分组构成
因为ip_hl是以4字节为单元计算的,所以IP选项必须常常被填充成4字节的倍数。
输入处理:ipintr函数
当接口把分组放到ipintrq上排队后,通过schednetisr调用一个软中断。当该软中断发生时,如果IP处理过程已经由schednetisr调度,则内核调用ipintr。在调用ipintr之前,CPU的优先级被改变成splnet。
1. ipintr概述
ipintr是一个比较大的函数,我们将分4个部分讨论:a. 对到达分组验证;b. 选项处理及转发;c. 分组重装;d. 分用。在ipintr中发生分组的重装,比较复杂,将在后续章节讨论。图6显示了ipintr的整体结构。
图6 ipintr函数整体结构
100~117 标号next标识主要的分组处理循环开始。ipintr从ipintrq中移走分组,并对之加以处理直到整个队列为空。如果到函数最后控制失败,goto把控制权传回给next中最上面的函数。ipintr把分组阻塞在splimp内,避免当它访问队列时,运行网络的中断程序(例如slinput和ether_input)。
332~336 标号bad标识由于释放相关mbuf并且返回到next中处理循环开始而自动丢弃分组。在整个ipintr中,都是跳到bad来处理差错。
2. 验证
从图7开始,把分组从ipintrq中取出,验证它们的内容,损坏和有差错的分组自动被丢弃。
图7 ipintr函数:验证
a. IP版本
在ipintr访问任何IP首部之前,它必须证实ip_v是4。当m的数据长度小于结构ip的长度时,调用m_pullup函数,将ip首部放在同一个mbuf中,即一段连续的存储区。
135~146 下面的步骤保证IP首部(包括选项)位于一段连续的存储器缓存区上:
如果在第一个mbuf中的数据小于一个标准的IP首部(20字节),m_pullup会重新把标准商务部放到一个连续的存储器缓存区上去。链路层不太可能把最大的(60字节)IP首部分在两个mbuf中从而使用上面的m_pullup。
ip_hl通过乘以4得到首部字节长度,并将其保存在hlen中。
如果IP分组首部的字节数长度hlen小于标准首部(20字节),将是无效分组并被丢弃。
如果整个首部仍不在第一个mbuf中(也就是说,分组包含了IP选项),则由m_pullup完成其任务。同样,这不一定是必须的。
检验和计算是所有Internet协议的重要组成。所有的协议均使用相同的算法(由函数in_cksum完成),但应用于分组的不同部分。对于IP来说,检验和只保证IP首部(以及选项)。对于传输协议,如UDP或TCP,检验和覆盖了分组的数据部分和运输层首部。
b. IP检验和
147~150 ipintr把由in_cksum计算出来的检验和保存首部的ip_sum字段中。一个未被破坏的首部应该具有0检验和。如果结构非0,则该分组被丢弃。
c. 字节顺序
151~160 Internet标准在指定协议首部中多字节整数值的字节顺序时非常小心。NTOHS把IP首部中所有16bit的值从网络字节序列转换主机字节序列:分组长度(ip_len),数据报标识(ip_id)和分片偏移(ip_off)。如果两种格式相同,则NTOHS是一个空的宏。在这里就转换成主机字节序列,以避免Net/3每次检验该字段时必须进行一次转换。
d. 分组长度
161~177 如果分组的逻辑长度(ip_len)比储存在mbuf中的数据量(m_pkthdr.len)大,说明有些字节被丢失了,此时丢弃该分组。如果mbuf比分组大,则去掉多余的字节。
现在,有了完整的IP首部,分组的逻辑长度和物理长度相同,检验和表明分组的首部无损地到达。
3. 转发或不转发
图8显示了ipintr的下一部分,调用ip_dooptions来处理IP选项,然后决定分组是否到达它最后的目的地。如果分组没有到达最后的目的地,Net/3会尝试转发该分组(如果系统被配置成路由器)。如果分组到达最后目的地,就被交付给合适的运输层协议。
图8 ipintr函数:是否转发
a. 选项处理
178~186 通过对ip_nhops清零,丢掉前一个分组的原路由。如果分组首部大于默认首部长度,则它必然包含由ip_dooptions处理的选项。如果ip_dooptions返回0,ipintr将继续处理该分组;否则,ip_dooptions通过转发或丢弃分组完成对该分组的处理,ipintr可以处理输入队列中的下一个分组。我们将在第9章进一步讨论选项处理。
b. 最终目的地
与某个接口相关的多播组之一的匹配
图9 为判断分组是否到达最终目的地进行的比较
c. 转发
4. 重装和分用
最后,ipintr的最后一部分代码如图10所示,在这里进行重装和分用。我们略去了重装代码,推迟到第10章讨论。当无法重装完全的数据报时,略去的代码将指针ip设为空;否则ip指向一个已经到达目的地的完整数据报。
图10 ipintr函数
325~332 数据报中指定的协议被ip_p用ip_protox数组映射到inetsw数组的下标。ipintr调用选定的protosw结构中的pr_input函数来处理数据报包含的运输报文。当pr_input返回时,ipintr继续处理ipintrq中的下一个分组。
注意,运输层对分组的处理发生在ipintr处理循环内部。在IP和传输协议之间没有到达分组的排队。
转发:ip_forward函数
到达非最终目的地的系统的分组需要被转发。只有当ipforwarding非零或者当分组中包含源路由(第9章介绍)时,ipintr才调用实现转发算法的ip_forward函数。当分组中包含源路由时,ip_dooptions调用ip_forward,并且第2个参数srcrt设为1.
ip_forward通过图11显示了route结构与路由表接口。
图11 route结构
46~49 route结构有两个成员:ro_rt,指向rtentry结构的指针;ro_dst,一个socketaddr结构,指定与ro_rt所指的路由项相关的目的地。目的地是在内核的路由表中用来查找路由信息的关键字。第18章对rtentry结构和路由表进行详细讨论。
我们分两个部分讨论ip_forward。第一部分确定允许系统转发分组,修改IP首部,并为分组选择路由;第二部分处理ICMP重定向报文,并把分组交给ip_output进行发送。代码如图12所示。
图12 ip_forward函数:路由选择
a. 分组适合转发吗
867~871 ip_forward的第1个参数是指向一个mbuf链的指针,该mubf中包含了要被转发的分组。如果第2个参数非0,则分组由于源路由选项正在被转发。
879~884 if语句识别并丢弃以下分组
链路层广播
环回分组
对寻址到环回网络的分组,in_canforward返回0。这些分组将被ipintr提交给ip_forward,因为没有正确配置反馈接口。
处理分组的所有系统都必须把生存时间(TTL)字段至少减去1,即使TTL是以秒计算的。由于这个要求,TTL通常被认为是对IP分组在被丢掉之前能经过的跳的个数。
b. 减小TTL
885~890 由于转发时不再需要分组的标识符,所以标识符又被转换回网络字节序列。但是当ip_forward发送包含无效IP首部的ICMP差错报文时,分组的标识符又应该是正确的顺序。
如果ip_ttl达到1(IPTTLDEC),则向发送发返回一个ICMP超时报文,并丢掉该分组。否则,ip_forward把ip_ttl减去IPTTLDEC(先判断后减1)。
c. 定位下一跳
908~914 由于在产生差错时,ip_output要丢掉分组,所以m_copy复制分组的前64个字节,以便ip_forward发送ICMP差错报文。如果调用m_copy失败,ip_forward并不终止。在这个情况下不发送差错报文。ip_ifmatrix记录在接口之间进行路由选择的分组的个数。具有接收和发送接口索引的计数器是递增的。
当主机错误地选择某个路由器作为分组的第一跳路由器时,该路由器向源主机返回一个ICMP重定向报文。IP网络互连模型假定主机相对地并不知道整个互联网的拓扑结构,把维护正确路由选择的责任交给路由器。路由器发出重定向报文是向主机表明它为分组选择了一个不正确的路由。图13说明重定向报文。
图13 路由器R1重定向主机HS使用路由器R2到达HD
通常,管理员对主机的配置是:把远程网络的分组发送到某个默认路由器上。在图13中,主机HS上R1被配置成它的默认路由器。当HS首次向HD发送分组时,它不知道R2是合适的选择,所以把分组转发给R1。R1识别出差错,就把分组转发给R2,并向HS发回一个重定向报文。接收到重定向报文后,HS更新它的路由表,下一次发往HD的分组就直接发给R2。
在图14中,ip_forward决定是否发送重定向报文。
图14 ipforward函数:是否发送重定向报文
a. 在接收接口上离开吗
915~929 路由器识别重定向情况的规则很复杂。首先,只有在同一接口(rt_ifp和rcvif)上接收或重发分组时,才能应用重定向。其次,被选择的路由本身必须没有被ICMP重定向报文创建或者修改过(RTF_DYNAMIC|RTF_MODIFIED),而且该路由也不能是默认目的地(0.0.0.0)。
全局整数ipsendredirects指定系统是否被授权发送重定向,它的默认值为1。当传给ip_forward的参数srcrt指明系统是对分组路由选择的源时,禁止系统重定向,因为假定源主机要覆盖中间路由器的选择。
b. 发送重定向吗
c. 选择合适的路由器
图15 ip_forward
d. 转发分组
941~954 现在,ip_forward有一个路由,并决定是否需要ICMP重定向报文。ip_output把分组发送到路由ipforward_rt所指定的下一跳。IP_ALLOWBROADCAST标志位允许被转发分组是到某个局域网的广播。如果ip_output成功,并且不需要发送任何重定向报文,则丢掉分组的前64字节,if_forward返回。
e. 发送ICMP差错报文?
955~983 ip_forward可能会由于ip_output失败或者重定向而发送ICMP报文。如果没有原始分组的复制(可能当时复制时,缓存不足而复制失败),则无法发送重定向报文,ip_forward返回。如果有重定向,type和code以前被置位,但是如果ip_output失败,switch语句基于从ip_output返回的值重新设置新的ICMP类型和错误码。icmp_error发送该报文。来自失败的ip_outputICMP报文将覆盖任何重定向报文。
处理来自ip_output的差错的switch语句非常重要。它把本地差错翻译成适当的ICMP差错报文,并返回给分组的源站,如果是ICMP重定向报文,则dest则是告诉源站对于当前报文,下次应该跳转的目的地。对于图16对差错作了总结。
图16 来自ip_output的差错
输出处理:ip_output函数
IP输出代码从两处接收分组:ip_forward和运输协议(图1)。让inetsw[0].pr_output能访问到IP输出操作似乎很有道理,但事情并非如此。标准Internet传输协议(ICMP、IGMP、UDP和TCP)直接调用ip_output,而不查询inetsw表。对标准Internet传输协议而言,protosw结构不必具有一般性,因为调用函数并不是在与协议无关的情况下接入IP的。对于协议无关的路由选择插口可以调用pr_output接入IP。
下面分三个部分描述ip_output:
首部初始化
路由选择
1. 首部初始化
图17显示了ip_output的第一部分,把选项与外出的分组合并,完成传输协议提交(不是ip_forward提交的)的分组首部。
图17 函数ip_output
44~59 传给ip_output的参数包括:m0,要发送的分组;opt,包含的IP选项;ro,缓存的到目的地的路由;flags,见图18;imo,指向多播选项的指针。
图18 ip_output:flag值
IP_FORWARDING被 ip_forward和ip_mforward(多播分组转发)设置,并禁止ip_output重新设置任何IP首部字段。
a. 构造IP首部
60~73 如果调用程序提供任何IP选项,它们将被ip_insertoptions与分组合并,并把返回新的首部长度。
进程可以设置IP_OPTIONS插口选项来为一个插口指定IP选项。插口的运输层(TCP或UDP)总是把这些选项提交给ip_output。
被转发的分组(IP_FORWARDING)或有预构首部(IP_RAWOUTPUT)分组的IP首部不能被ip_output修改。任何其它分组需要有几个IP首部字段被初始化。ip_output把ip_v设置成4,把DF位需要的ip_off清零,并设置成调用程序提供的值,给来自全局整数的ip->ip_id赋一个唯一的标识符,把ip_id加1。ip_id是在协议初始化时由系统时钟设置。ip_hl被设置成用32bit字节度量的首部长度。
b. 分组已经包括首部
74~76 对一个已转发的分组(或一个有首部的原始IP分组),首部长度被保存在hlen中,留给将来分片算法使用。
2. 路由选择
在完成IP首部后,ip_output的下一个任务就是确定一条到目的地的路由。如图19所示。
图19 ip_output:选择路由
a. 验证调整缓存中的路由
77~99 ip_output可能把一条高速缓存中的路由作为ro参数来提供。如果没有路由,则ip_output把ro设置成临时route结构iproute。
b. 旁路路由选择
100~114 调用方可以通过设置IP_ROUTETOI标志禁止对分组进行路由选择。ip_output必须找到一个与分组中指定的目的地网络直接相连的接口。ifa_ifwithdstaddr搜索点到点接口,而in_ifwithnet搜索其它接口。如果任一函数均未找到与目的网络相连的接口,就返回ENETUNREACH;否则,ifp指向选定接口。
c. 本地路由
如果这些条件都不满足,就扔掉该分组,把相应错误码返回给调用方。否则,设置输出分组的M_BCAST,告诉接口输出函数把该分组作为链路级广播发送。
b. 发送分组
240~252 如果分组对所选择的接口足够小,ip_len和ip_off被转换成网络字节序列,IP检验和与in_cksum一起计算,把分组提交给所选接口的if_output函数。
c. 分片分组
253~338 大分组在被发送之前必须分片。这里暂时不予讨论,将在第10章讨论。
d. 清零
339~346 对第一路由入口都有一个引用计数。如果参数ro为空,ip_output可能会使用一个临时的route结构(iproute)。如果需要,RTFREE发布iproute内的路由入口,并把引用计数减1。Bad处的代码在返回前扔掉当前分组。
Internet检验和:in_cksum函数
检验和的目的是检验数据是否正确,没有被修改。对于检验和函数的设计和实现如下所示:
把被检验的相邻字节成对配成16bit整数,然后进行二进制反码求和。
为生成检验和,把检验和字段本身清零,把这个16bit的和的二进制反码放到检验和字段。
为了检验检验和,依然按16bit的方式进行二进制反码求和。如果结果为全1(在二进制反码运算中为0),则检验成功。
二进制反码运算:当对用二进制反码表示的整数进行加法运算时,把两个整数相加后再加上最高位的进位从而得到加法的结果。在二进制反码运算中,只要把每一位求补就得到一个数的反;所以在二进制反码运算中,0的表示方法有两种:全0和全1。
检验和算法在发送分组之前计算出要放在IP首部检验和字段的值。为了计算这个值,先把首部的检验和字段设为0,然后把首部作为一个16bit的整数数组来处理,计算整个首部(包括选项)的二进制反码的和。暂且把这个计算结果称为a,因为检验和字段被明确设为0,所以a是除了检验和字段外所有IP首部字段的各。a的二进制反码,用-a表示,被放在检验和字段中,发送该分组。
如果在传输过程中没有比特位被改变,则在目的地计算的检验和应该等于(a+-a)的二进制反码。在二进制反码运算中(a+-a)的和是-0(全1),而它的二进制反码应该等于0(全0)。所以在目的地,一个没有损坏分组计算出来的检验和应该总是为0,正如图7中看到的检验和判断部分。
图21是这个算法的一种原始的实现:
图21 IP检验和计算的一种原始的实现
1~16 这里唯一提高性能之处在于累计sum高16bit的进位。当循环结束时,累计的进位被加在低16bit上,直到没有其它进位发生。
图22显示的是Net/3的可移植C版本。它使用了延迟进位技术,作用于存储在一个mbuf链中的分组。
图22 IP检验和计算的一个优化
42~140 我们的新检验和实现假定所有的被检验字节存储在一个连续缓存而不是mbuf中。这个版本的检验和计算采用相同的底层算法来正确地处理mbuf:用32bit整数的延迟进位对16bit字节作加法。对奇数个字节的mbuf,多出来的一个字节保存起来,并与下一个mbuf的第一个字节配对。因为大多数体系结构中,对16bit字的不对齐访问是无效的,甚至会产生严重差错,所以不对齐字节被保存,in_cksum继续加上下一个对齐的字。当这种情况发生时,in_cksum总是很小心地交换字节,保证位于奇数和偶数位置的字节被放在单独的和字节中,以满足检验和算法的要求。
93~115 函数中的三个while循环在每次迭代中分别在和中加上16个字、4个字和1个字。展开的循环减小了循环的耗费,在某些体系结构中可能比一个直接循环要快很多,但代价是代码长度和复杂性增加。
setsockopt和getsockopt系统调用
Net/3提供setsockopt和getsockopt两个系统调用来访问一些网络互连的性质。这两个系统调用支持一个动态接口,进程可用该动态接口访问某种网络互连协议的一些性质,而标准的系统调用通常不支持该协议。这两个调用的原型是:
int setsockopt(int s, int level, int optname,
void *optval, int opt len);
int getsockopt(int s, int level, int optname,
const void * optval, int optlen)
大多数插口选项只影响它们在其上发布的插口。与sysctl参数相比,后者影响整个系统。
setsockopt和getsockopt设置和获取通信栈所有层上的选项。Net/3按照与s相关的协议和由level指定的标识处理选项。图23列出了在我们讨论的协议中level可能取得的值。
图23 sosetopt和sogetopt参数
图24显示了所有插口选项的总结。该图显示了IPPROTO_IP级选项。选项出现在第1列,optval指向变量的数据类型出现在第2列,第3列显示的是处理该选项的函数。
图24 IPPROTO_IP级的插口选项
图25显示了用于处理大部分IPPROT_IP选项的ip_ctloutput函数的整个结构。
图25 ip_ctloutput函数整体结构
431~447 ip_ctloutput的第一个参数op,可以是PRCO_SETOPT或者PRCO_GETOPT。第二个参数so,指向向其发布请求的插口。levlel必须是IPROTO_IP。optname是要改变或者检索的选项,mp间接指向一个含有与该选项相关数据的mbuf,m被初始化为指向由*mp引用的mbuf。
448~500 如果在调用setsockopt时指定了一个无法识别的选项,ip_ctloutput释放掉所有调用方传来的缓存,并返回EINVAL。
501~553 getsockopt传来的无法识别的选项导致ip_ctloutput返回ENOPROTOOPT,调用方释放mbuf。
1. PRCO_SETOPT的处理
对于PRCO_SETOPT的处理如图26所示。
图26 ip_ctloutput函数:处理PRCO_SETOPT
450~451 IP_OPTIONS是由ip_pcbopts处理的(第9章讨论)。
452~484 IP_TOS、IP_TTL、IP_RECVOPTS、IP_RECVERTOPTS以及IP_RECVDSTADDR选项都需要在由m指向的mbuf中有一个整数。该整数存储在optval中,用来改变与插口有关的ip_tos和ip_ttl的值,或者用来设置或复位与插口相关的INP_RECVOPTS、INT_RECVERTOPTS和INP_RECVDSTADDR标志位。如果optval是非0,则宏OPTSET设置或复位指定的比特。
2. PRCO_GETOPT的处理
图27显示了对PRCO_SETOPT的处理。
图27 ip_ctloutput函数:处理PRCO_GETOPT
503~538 对IP_OPTIONS,ip_ctloutput返回一个缓存,该缓存中包含了与该插口相关的选项的备份。对其他选项,ip_ctloutput返回ip_tos和ip_ttl的值,或与该选项相关的标志的状态。返回的值放在由m指向的mbuf中。如果在inp_flags中的bit是打开的,则宏OPTBIT返回1,否则返回0。
ip_sysctl函数
图24中显示,在调用sysctl中,当协议和协议族的标识符是0时,就调用ip_sysctl函数。图28显示了ip_sysctl支持的三个参数。
图28 sysctl参数
图29显示了ip_sysctl函数。
图29 ip_sysctl函数
因为ip_sysctl并不把sysctl请求转发给其他函数,所以在name中只能有一个成员,否则返回ENOTDIR。
switch语句选择的调用sysctl_int,它访问或修改ipforwarding、ipsendredirects或者ip_defttl。对无法识别的选项返回EOPNOTSUPP。
以上是关于概说《TCP/IP详解 卷2》第8章 IP:网际协议的主要内容,如果未能解决你的问题,请参考以下文章
TCP/IP详解 卷1:协议 第18章TCP连接的建立与终止
第2章 Internet地址结构 [TCP/IP详解 卷1:协议]