原文链接:https://mp.weixin.qq.com/s/dD88rS1PD4xoSXLgvY5wSA
本文要点
-
引言
-
选项格式
-
ip_dooptions函数
-
记录路由选项
-
源站和记录路由选项
-
save_rte函数
-
ip_srcroute函数
-
-
时间戳选项
-
ip_insertoptions函数
-
ip_pcbopts函数
-
其它
引言
在概说《TCP/IP详解 卷2》第8章 IP:网际协议中,IP输入函数(ipintr)在验证分组格式(检验和、长度等)之后,确定分组是否到达目的地之前,对选项进行处理。这表明,分组所遇到的每个路由器以及最终的目的主机都要对分组的选项进行处理。
本文将讨论大多数IP选项的格式和处理;同时介绍运输协议如何指定IP数据报内的IP选项。
IP分组内可以包含某些在分组被转发或者被接收之前处理的可选字段。IP实现可以按任意顺序处理选项;图1显示了标准IP首部之后最多可跟40字节的选项。
图1 一个IP首部可以有0~40字节的IP选项
选项格式
IP选项字段可能包含0个或者多个单独选项。选项有两种类型,单字节和多字节,如图2所示。
图2 单字节和多字节IP选项的结构
所有选项都以1字节类型(type)字段开始。在多字节选项中,类型字段后面紧接着一个长度(len)字段,其它的字节是数据(data)。许多选项数据字段的第一个字节是1字节的位移(offset)字段,指向数据字段内的某个字节。长度字节的计算覆盖了类型、长度和数据字段。类型被继续分成三个子字段:1bit备份(copied)标志、2bit类(class)字段和5bit数字(number)字段。图3列出了目前定义的IP选项。前两个选项是单字段选项,其它的是多字节选项。
图3 常用IP选项
第1列显示了Net/3的选项常量,第2列和第3列是该类型的十进制和二进制值,第4列是选项的长度。Net/3列显示的是在Net/3中由ip_dooptions实现的选项。IP必须自动忽略所有它不识别的选项。
当Net/3对一个有选项的分组进行分片时,它将检查copied标志位。该标志位指出是否把所有选项都备份到每个分片的IP首部。class字段把相关的选项按图4所示进行分组。在图3中,除时间戳选项具有class为2外,所有选项的class为0。
图4 class字段
ip_dooptions函数
在概说《TCP/IP详解 卷2》第8章 IP:网际协议图8中,我们看到ipintr函数在检测分组的目的地址之前调用ip_dooptions。ip_dooptions被传给一个指针m,该指针指向某个分组,ip_dooptions处理分组中它所知道的选项。如果ip_dooptions转发该分组,如在处理LSRR和SSRR选项时,或由于某个差错而丢弃该分组时,它返回1。如果它不转发分组,ip_dooptions返回0,由ipintr继续处理该分组。
ip_dooptions是一个长函数,我们将分几个部分来介绍。第一部分初始化一个for循环,处理首部中的各选项。
当处理每个选项时,cp指向选项的第一个字节。如图5所示,当可用时,如何从cp的常量位移访问type、length和offset字段。
图5 用常量位移访问IP选项字段
位移(offset)的值是某个字节在该选项内的序号(从type开始,序号为1),所以位移的最小值是4(IPOPT_MINOFF),它指向的是多字节选项中数据字段的第一个字节。
图6显示了ip_dooptions函数的整体结构。
图6 ip_dooptions函数
a. EOL和NOP过程
555~582 for循环按照每个选项在分组中出现的顺序分别对它们进行处理。EOL选项以及一个无效的选项长度都将终止该循环。当出现NOP选项时,忽略它。switch语句的default情况要求系统忽略未知的选项。
下面的内容描述了switch语句处理的每个选项。如果ip_dooptions在处理分组选项时没有出错,就把控制交给switch下面的代码。
b. 源路由转发
719~724 如果分组需要被转发,例如SSRR或LSRR选项处理代码就把forward置位。分组被传给ip_forward,并且第2个参数为1,表明分组是按源路由选择的。
如果转发了分组,则ip_dooptions返回1。如果分组中没有源路由,则返回0给ipintr,表明需要对该数据报进一步处理。注意,只有当系统被配置成路由器时(ipforwarding为1),才发生源路由转发。
c. 差错处理
725~730 如果switch语句里出现错误,ip_dooptions就跳到bad。从分组长度中把IP首部长度减去,因为icmp_error假设首部长度不包含在分组长度里。icmp_error发出适当差错报文,ip_dooptions返回1,避免ipintr再次处理该分组。
记录路由选项
1. 路由选项
记录路由选项使得分组在穿过互联网时所经过的路由被记录在分组内部。项的大小是源主机在构造时确定的,必须足够保存所有预期的地址。我们记得在IP分组的首部,选项最多只有40字节。记录路由选项可以有3个字节的开销,后面紧跟地址列表,每个地址4字节。如果该选项是唯一的选项,则最多可以有9个(3+4x9)地址出现。一旦分配给该选项的空间被填满,就按通常的情况对分组进行转发,中间的系统就不再记录地址。
图7说明了一个记录路由选项的格式,图8是其源程序。
图7 记录路由选项,其中n必须<=9
图8 函数ip_dooptions:记录路由选项的处理
647~657 如果位移选项太小,则ip_dooptions就发送一个ICMP参数问题差错。如果变量code被设置成分组内无效选项的字节位移量,并且bad标号语句的执行产生错误,则发出的ICMP参数问题差错报文中就具有该code值。如果选项中没有附加地址空间,则忽略该选项,并继续处理下一个选项。
658~673 如果ip_dst是某个系统地址(分组已到达目的地),则把接收接口的地址记录在选项中,否则把ip_rtaddr提供的外出接口地址记录下来。把位移更新为选项中下一个可用地址位置。如果ip_rtaddr无法找到目的地的路由,就发送一个ICMP主机不可达差错报文。
2. ip_rtaddr函数
函数ip_rtaddr查询路由缓存,必要时查询完整的路由表,来找到到给定IP地址的路由。它返回一个指向in_ifaddr结构的指针,该指针与该路由的外出接口有关。图9显示了该函数。
图9 函数ip_rtaddr:寻找外出的接口
a. 检查IP转发缓存
735~741 如果路由缓存为空,或者如果ip_rtaddr的唯一参数dest与路由缓存中的目的地址不匹配,则必须查询路由表选项一个外出接口。
b. 确定路由
742~750 旧的路由(如果有的话)被丢弃,并把新的路由存储在*sin(这是转发缓存的ro_dst成员)。rtalloc搜索路由表,寻找到目的地的路由。
c. 返回路由信息
751~754 如果没有路由可用,就返回一个空指针;否则,就返回一个指针,它指向与所选路由相关联的接口地址结构。
源站和记录路由选项
通常情况,路由转发是由中间路由器所选择的路径所决定。源站和记录路由选项允许源站明确指定一条到目的地的路由,覆盖掉中间路由器的路由选择决定。而且,在分组到达目的地的过程中,把该路由记录下来。
严格路由包含了源站和目的站之间的每个中间路由器的地址;宽松路由只指定某些中间路由器的地址。在宽松路由中,路由器可以*选择两个系统之间的任何路径;而在严格路由中,则不允许路由器这样做。我们用图10说明源路由处理。
图10 源路由举例
A、B和C是路由器,HS和HD是源和目的主机。因为每个接口都有自己的IP地址,所以我们看到路由器A有三个地址:A1、A2和A3。同样,路由器B和C也有多个地址。图11显示了源站和记录路由选项的格式。
图11 严格和宽松源路由选项
IP首部的源和目的地址以及在选项中列出的位移和地址表,指定了路由以及分组目前在该路由中所处的位置。图12显示,当分组按照这个宽松源路由从HS经过A、B、C到HD时,信息是如何改变的。每行代表当分组被第1列显示的系统发送时的状态。最后一行显示分组被HD接收。图13显示了相关代码。
图12 当分组通过该路由器时,源跌幅选项被修改
符号“.”表示位移与路由中地址的相对位置。注意,每个系统都把出口地址放到选项去。特别的,原来的路由指定A3为第一跳目的地,但是外出接口A2被记录在路由中。通过这种方法,分组所采用的路由被记录在选项中。被记录的路由将被目的地系统倒转过来放到所有应答分组上,让它们沿着原始的路由的逆方向发送。
图13 函数ip_dooptions:LSRR和SSRR选项处理
583~612 如果选项位移小于4,即IPOPT_MINOFF,则Net/3发送一个ICMP参数差错,并带上相应的code值。如果分组的目的地址与本地地址没有一个匹配,且选项是严格源路由(IPOPT_SSRR),则发送一个源路由失败差错。如果本地地址不在路由中,则上一个系统把分组发送到错误的主机上了。对宽松路由(IPOPT_LSRR)来说,这不是错误;仅意味着IP必须把分组转发到目的地。
a. 源路由的结束
613~620 减少off,把它转换成从选项开始的字节位移。如果IP首部的ip_dst是某个本地地址,并且off所指向的走过了源路由的末尾,源路由中没有地址了,则分组已经到达目的地。save_rte复制在静态结构ip_srcrt中的路由,并保存在全局ip_nhops(图16)里路由中的地址个数。
b. 为下一跳更新分组
621~637 如果ip_dst是一个本地地址,并且offset指向选项内的一个地址,则该系统是源路由中指定的一个中间系统,分组也没有到达目的地。在严格路由中,下一个系统必须位于某个直接相连的网络上。ifa_ifwithdst和ifa_ifwithnetefpd配置的接口中搜索匹配的目的地址(一个点到点接口)或者匹配的网络地址(广播接口)来寻找一条到下一个系统的路由。而在宽松路由中,ip_rtaddr(图9)通过查询路由表寻找到下一个系统的路由。如果没找到到下一系统的接口或者路由,就发送一个ICMP源路由失败差错报文。
638~644 如果找到一个接口或者一条路由,则ip_dooptions把ip_dst设置成off指向的IP地址。在源路由选项内,用外出接口地址代替中间系统的地址,把位移增加,指向路由中的下一个地址。
c. 多播目的地
645~646 如果新的目的地址不是多播地址,就将forward设置成1,表明在处理完所有选项后,应该把分组转发而不是返回给ipintr。
1. save_rte函数
在最终目的地,运输协议必须能够使用分组中被记录下来的路由。运输协议必须把该路由倒过来并附在所有应答的分组上。图16显示的save_rte函数,把源路由保存在图14所示的ip_srcrt结构中。
图14 结构ip_srcrt
57~63 该代码定义了ip_srcrt结构,并声明了静态变量ip_srcrt。只有两个函数访问ip_srcrt:save_rte,把到达分组的源路由复制到ip_srcrt中;ip_srcroute,创建一个与源路方向相逆的路由。图15说明了源路由处理过程。
图15 对求逆后的源路由的处理
图16 函数save_rte
759~771 当一个源路由选择的分组到达目的地时,ip_dooptions调用save_rte。option是一个指向分组源路由选项的指针,dst是从分组首部来的ip_src(也就是,返回路由的目的地)。如果选项长度超过ip_srcrt结构,save_rte立即返回。实际上,这种情况永远不会发生,因为ip_srcrt结构比最大选项长度(40字节)要大。
save_rte把该选项复制到ip_srcrt,计算保存ip_nhops中源路由的跳数,把返回路由的目的地保存在dst中。
2. ip_srcroute函数
当响应某个分组时,ICMP和标准的运输层协议必须把分组带的任意源路由逆转。逆转源路由是通过ip_srcroute构造的,如图17所示。
图17 ip_srcroute函数
777~783 ip_srcroute把保存在ip_srcrt结构中的源路由逆转后,返回与ipoption结构(图24)格式类似的结果。如果ip_nhops是0,则没有保存的路由,所以ip_srcroute返回一个空指针。
784~786 如果ip_nhops非0,ip_srcroute就分配一个mbuf,并把m_len设置成足够大,以便包含第一跳目的地、选项首部信息以及逆转后的路由。如果分配失败,则返回一个空指针,跟没有源路由一样。
p被初始化为指向到达路由的末尾,ip_srcroute把最后记录的地址复制到mbuf前面,在这里这为外出的第一跳目的地开始逆转后的路由。然后该函数把一份NOP选项源路由信息复制到mbuf中。
805~818 while循环把其余IP地址从源路由以相反的顺序复制到mbuf中。路由的最后一个地址被设置成到达分组中被save_rte放在ip_srcrt.dst中的源站地址。返回一个指向mbuf的指针。图18说明了对图10的路由如何构造逆转的路由。
图18 ip_srcroute逆转ip_srcrt中的路由
时间戳选项
当分组穿过一个互联网时,时间戳选项使各个系统把它当前的时间表示记录在分组的选项内。时间是以从UTC的午夜开始计算的毫秒计,被记录在一个32bit的字段里。
有三种时间戳类型,Net/3通过如图20所示的ip_timestamp结构访问。
114~133 如同ip结构一样,#ifs保证比特字段访问选项中正确的比特位。图19列出了由ipt_flg指定的三种时间戳选项类型。
图19 ipt_flg可能的值
图20 ip_timestamp结构和常量
初始主机必须构造一个具有足够大的数据区存放可能的时候戳和地址的时间戳选项。对于ipt_flg为3的时间戳选项,初始主机在构造该选项时,填写要记录时间戳的系统的地址。图21显示了有三种时间戳选项的结构。
图21 三种时间戳选项
因为IP选项最多只能有40个字节,所以时间戳选项只能有9个时间戳或者4个地址和时间戳对。图22显示了对三种不同时间戳选项类型的处理。
图22 函数ip_dooptions:时间戳选项处理
674~684 如果选项长度小于5个字节(时间戳选项最小长度),则 ip_dooptions发出一个ICMP参数问题差错报文。oflw字段统计由于选项数据区满而无法登记时间戳的系统个数。如果数据区满,则oflw加1;当它本身超过16(4bit)而溢出时,发出一个ICMP参数问题差错报文。
a. 只有时间戳
685~687 对于ipt_flg为0的时间戳选项(IPOPT_TS_TSONLY),所有的工作都在switch语句之后再做。
b. 时间戳和地址
688~700 对于ipt_flg为1的时间戳选项(IPOPT_TS_TSANDADDR),如果数据区还有空间的话,接收接口的地址被记录下来,选项的指针前进一步。因为Net/3支持一个接收接口上的多个IP地址,所以ip_dooptions调用ifaof_ifpforaddr选择与分组的初始目的地址(也就是在任何源路由选择发生之前的目的地)最匹配的地址。如果没有匹配,则跳过时间戳选项。
c. 预定地址上的时间戳
701~710 如果ipt_flg的值为3(IPOPT_TR_PRESPEC),ifa_ifwithaddr确定选项中的指定的下一个地址是否与系统的某个地址匹配。如果不匹配,该选项要求在这个系统上不处理;continue使ip_dooptions继续处理下一下选项。如果下一个地址与系统的某个地址匹配,则选项的指针前进到下一下位置,控制交给switch的后面。
d. 插入时间戳
711~713 default截获无效的ipt_flg值,并把控制传递给bad。
714~719 时间戳用switch语句后面的代码写入选项中。iptime返回自UTC午夜起到现在的毫秒数,ip_dooptions记录此时间戳,并增加此选项相对于下一个位置的偏移。
图23显示了iptime的实现。
图23 函数iptime
458~466 microtime返回从UTC1970年1月1号午夜以来的时间,放在timeval结构中。从午夜以来的毫秒用atv计算,并以网络字节返回。
ip_insertoptions函数
在概说《TCP/IP详解 卷2》第8章 IP:网际协议中,函数ip_output接收一个分组和选项。当ip_forward调用该函数时,选项已经是分组的一部分,所以ip_forward总是把一个空选项指针传给ip_output。但是,运输层协议可能会把由ip_insertoptions合并到分组中的选项传递给ip_forward。
ip_insertoption希望选项在ipoption结构中被格式化,如图24所示。
图24 结构ipoption
92~95 该结构只有两个成员:ipopt_dst,如果选项表中有源路由,则其中有第一跳目的地,ipopt_list,是一个最多拥有40(MAX_IPOPTLEN)字节的选项矩阵,其格式我们在本章中已做了描述。如果选项表中没有源路由,则ipopt_dst全为0。
注意,ip_srcrt结构(图14)和由ip_srcroute(图17)返回的mbuf都符合由ipoption结构所指定的格式。图25把结构ip_srcrt和ipoption作了比较。
图25 结构ip_srcrt和ipoption
结构ip_srcrt比ipoption多4个字节。路由矩阵的最后一个入口(route[9])永远都不会填上,因为这样的话,源路由选项将会有44字节,比IP首部所能容纳的要大。
函数ip_insertoptions如图26所示。
图26 函数ip_insertoptions
352~364 ip_insertoptions有三个参数:m,外出的分组;opt,在结构中格式化的选项;phlen,一个指向整数的指针,在这里返回新首部的长度(在插入之后)。如果插入选项分组长度超过最大分组长度65535字节,则自动将选项丢弃。ip_dooptions认为ip_insertoptions永远都不会失败,所以无法报告差错。幸好很少有应用程序会试图发送最大长度的数据报。
365~366 如果ipopt_dst.s_addr指定了一个非零地址,则选项中包括了源路由,并且分组首部的ip_dst被源路由的第一跳目的地代替。
TCP调用MGETHDR为IP和TCP首部分配一个单独的mbuf。图27显示了在执行367~378行代码之前,一个TCP报文段的mbuf结构。
图27 函数ip_insertoptions:TCP报文段
如果被插入的选项占据了多于16字节,则第367行的测试为真,并调用MGETHDR分配另一个mbuf。图28显示了选项被复制到新的mbuf后,该缓存的结构。
图28 函数ip_insertoptions:在选项被复制后的TCP报文段
367~378 如果分组首部被存放一簇,或者第一个缓存中没有多余选项的空间,则ip_insertoptions分配一个新的分组首部mbuf,初始化它的长度,从旧的缓存中把该IP首部截取下来,并把该首部从旧缓存中移动到新缓存中。
对于UDP,它使用M_PREPEND把UDP和IP首部放置到缓存的最后,与数据分离,如图29所示。因为首部是放在缓存的最后,所以在缓存中总是有空间存放选项,对UDP来说,第367行的条件总为假。
图29 函数ip_insertoptions:UDP报文段
379~384 如果分组在缓存数据区的开始部分有存放选项的空间,则修改m_data和m_len,以包含optlen更多的字节。并且当前的IP首部被ovbcopy移走,为选项腾出位置。
385~390 ip_insertoptions现在可以把ipoption结构的成员ipopt_list直接复制到紧接在IP首部后面的缓存中。把新的首部长度存放在*phlen中,修改数据报长度(ip_len),并返回一个指向分组首部缓存的指针。
ip_pcbopts函数
函数ip_pcbopts把IP选项表及IP_OPTIONS插口选项转换成ip_output希望的格式:ipoption结构。如图30所示。
图30 函数ip_pcbopts
559~562 第一个参数,pcbopt引用指向当前选项表的指针。然后该函数用一个指向新选项表的指针来代替该指针,这个新选项表是由第二个参数m指向的缓存链所指定的选项构造而来。该过程所准备的选项表,将被包含在IP_OPTIONS插口选项中,除了LSRR和SSRR选项的格式外,看起来像一个标准的IP选项表。对这些选项,第一跳目的地址作为路由的第一个地址出现的。图12中,在外出的分组中,第一跳目的地址是作为目的地址出现的,而不是路由的第一个地址。
a. 扔掉以前的选项
563~580 所有被m_free和*pcbopt扔掉的选项都被清除。如果该过程传过来一个空缓存或者根本不传递任何新的选项,并立即返回。
如果新选项表没有填充到4bit的边界,则ip_pcbopts跳到bad,扔掉该表,并返回EINVAL。
该函数的其余部分重新安排该表,使其看起来像一个ipoption结构。图31显示了这个过程。
图31 ip_pcbopts选项表处理
b. 为第一跳目的地腾出位置
581~592 如果缓存中没有位置,则把所有的数据都向后缓存的末尾移动4个字节(一个in_addr结构的大小)。ovbcopy完成复制,bzero清除缓存开始的4个字节。
c. 扫描选项表
593~606 for循环扫描选项表,寻找LSRR和SSRR选项。对于多字节选项,该循环也验证选项的长度是否合理。
d. 重新安排LSRR和SSRR选项
607~638 当该循环找到一个LSRR或者SSRR选项时,它把缓存的大小、循环变量和选项长度减去4,因为选项的第一个地址将被移走,并被移到缓存的前面。
bcopy把第一个地址移走,ovbcopy把选项的其它部分移动4个字节,来填补第一个地址留下的空隙。
e. 清除
639~646 循环结束后,选项表的大小(包括第一跳地址)必须不能超过44字节(MAX_IPOPTLEN+4)。更长的选项表无法放入IP分组的首部。该表被保存在*pcbopt中,函数返回。
更多最新文章尽在公众号:大白爱爬山,欢迎关注!