TCP/IP详解(四) 

范堡 发布于 2009/05/05 15:00
阅读 799
收藏 3

TCP/IP详解(四)  

习题 
9.1 为什么你认为存在两类ICMP改变路由报文——网络和主机? 
9.2 在9 .4节开头列出的svr4主机上的路由表中,到主机slip(140.252.13.65)的特定路由是必需的吗?如果把这一项从路由表中删除会有什么变化? 
9.3 考虑有一电缆连接4.2BSD主机和4.3BSD主机。假定网络号是140.1。4.2BSD主机把主机号为全0的地址识别的广播地址(140.1.0.0)。另外,4.2BSD主机在默认条件下要尽力转发接收到的数据报,尽管它们只有一个接口。 
请描述当4.2BSD主机收到一份目的地址为140.1.255.255的IP数据报时会发生什么事。 
9.4 继续前一个习题,假定有人在子网140.1上的某个系统ARP高速缓存中增加了一项(用arp命令)内容,指定IP地址140.1.255.255对应的以太网地址为全1(以太网广播地址)。请描述此时发生的情况。 
9.5 检查你所使用的系统上的路由表,并解释每一项内容。 



9-6 
10 动态选路协议 

10.1 引言 
在前面各章中,我们讨论了静态选路。在配置接口时,以默认方式生成路由表项(对于直接连接的接口),并通过route命令增加表项(通常从系统自引导程序文件),或是通过ICMP改变路由生成表项(通常是在默认方式出错的情况下)。 
在网络很小时,与其它网络只有单个连接点且没有多余路由时(若主路由失败时,可以使用备用路由),采用这种方法是可行的。如果上述三种情况不能全部满足的话,通常使用动态选路。 
本章讨论动态选路协议,它用于路由器间的通信。我们主要讨论RIP,即选路信息协议(Routing Infromation Protocol),大多数TCP/IP实现都提供的这个应用广泛的协议。然后我们讨论两种新的选路协议,OSPF和BGP。本章的最后研究了一种名叫无分类域间选路的新的选路技术,现在Internet上正在开始采用该协议以保持B类网络的数量。 

10.2 动态选路 
当相邻路由器之间进行通信,以告知对方每个路由器当前所连接的网络,这时就出现了动态选路。路由器之间必须采用选路协议进行通信,这种选路协议有很多种。路由器上有一个进程称为路由守护程序(routing daemon),它运行选路协议,并与其相邻的一些路由器进行通信。正如图9.1所示,路由守护程序根据它从相邻路由器接收到的信息,更新内核中的路由表。 
动态选路并不改变我们在9.2节中所描述的内核在IP层的选路方式。我们这种选路方式称为选路机制(routing mechanism)。内核搜索路由表,查找主机路由、网络路由以及默认路由的方式并没有改变。仅仅是放置到路由表中的信息改变了——当路由随时间变化时,路由是由路由守护程序动态地增加或删除,而不是来自于自引导程序文件中的route命令。 
正如我们前面所描述的那样,路由守护程序将选路策略(routing policy)加入到系统中,选择路由并加入到内核的路由表中。如果守护程序发现前往同一信宿存在多条路由,那么它(以某种方法)将选择最佳路由并加入内核路由表中。如果路由守护程序发现一条链路已经断开(可能是路由器崩溃或电话线路不好),它可以删除受影响的路由或增加另一条路由以绕过该问题。 
在像Internet这样的系统中,目前采用了许多不同的选路协议。Internet是以一组自治系统AS(Autonomous sys tem)的方式组织的,每个自治系统通常由单个实体管理。常常将一个公司或大学校园定义为一个自治系统。NSFNET的Internet骨干网形成一个自治系统,这是因为骨干网中的所有路由器都在单个的管理控制之下。 
每个自治系统可以选择该自治系统中各个路由器之间的选路协议。这种协议我们称之为内部网关协议IGP(Interior Gateway Protocol)或域内选路协议(intradomain routing protocol)。最常用的IGP是选路信息协议RIP。一种新的IGP是开放最短路径优先OSPF(Open Shortest Path First)协议。它意在取代RIP。另一种1986年在原来NSFNET骨干网上使用的IGP协议——HELLO,现在已经不用了。 

(下面是原书p.128①的译文) 
新的RFC [Almquist 1993]规定,实现任何动态选路协议的路由器必须同时支持OSPF和RIP,还可以支持其它IGP协议。 

外部网关协议EGP(Exterier Gateway Protocol)或域内选路协议的分隔选路协议用于不同自治系统之间的路由器。在历史上,(令人容易混淆)改进的EGP有着一个与它名称相同的协议:EGP。新EGP是当前在NSFNET骨干网和一些连接到骨干网的区域性网络上使用的是边界网关协议BGP(Border Gateway Protocol)。BGP意在取代EGP。 

10.3 Unix选路守护程序 
Unix系统上常常运行名为routed 路由守护程序。几乎在所有的TCP/IP实现中都提供该进程。该程序只使用RIP进行通信,我们将在下一节中讨论该协议。这是一种用于小型到中型网络中的协议。 
另一个程序是gated。IGP和EGP都支持它。[Fedor 1998]描述了早期开发的gated。图10.1对routed和两种不同版本的gated所支持的不同选路协议进行了比较。大多数运行路由守护程序的系统都可以运行routed,除非它们需要支持gated所支持的其它协议。 

图10.1 routed和gated所支持的选路协议 

我们在下一节中描述RIP 版本1,在10.5中描述它与RIP版本2的不同点,10.6节描述OSPF,在10.7节描述BGP。 

10.4 RIP:选路信息协议 
本节对RIP进行了描述,这是因为它是最广为使用(也是最受攻击)的选路协议。对于RIP的正式描述文件是RFC 1058 [Hedrick 1988a],但是该RFC是在该协议实现数年后才出现的。 

报文格式 
RIP报文包含中在UDP数据报中,如图10.2所示。(我们在第11章中对UDP进行更为详细的描述。) 

图10.2 封装在UDP数据报中的RIP报文 

图10.3给出了使用IP地址时的RIP报文格式。 
命令字段为1表示请求,2表示回答。还有两个舍弃不用的命令(3和4),两个非正式的命令:轮询(5)和轮询表项(6)。请求表示要求其它系统发送其全部或部分路由表。回答则包含发送者全部或部分路由表。 
版本字段通常为1,而第2版RIP(10.5节)将此字段设置为2。 
紧跟在后面的20字节指定地址系列(address family)(对于IP地址来说,其值是2),IP地址以及相应的度量。我们在本节的后面可以看出,RIP的度量是以跳计数的。 
采用这种20字节格式的RIP报文可以通告多达25条路由。上限25是用来保证RIP报文的总长度为20×25 + 4 = 504,小于512字节。由于每个报文最多携带25个路由,因此为了发送整个路由表,经常需要多个报文。 

正常运行 
让我们来看一下采用RIP协议的routed程序正常运行的结果。RIP常用的UDP端口号是520。 
•初始化:在启动一个路由守护程序时,它先判断启动了哪些接口,并在每个接口上发送一个请求报文,要求其它路由器发送完整路由表。在点对点链路中,该请求是发送给其它终点的。如果网络支持广播的话,这种请求是以广播形式发送的。目的UDP端口号是520(这是其它路由器的路由守护程序端口号)。 
这种请求报文的命令字段为1,但地址系列字段设置为0,而度量字段设置为16。这是一种要求另一端完整路由表的特殊请求报文。 
•接收到请求。如果这个请求是我们刚才提到的特殊请求,那么路由器就将完整的路由表发送给请求者。否则的话,就处理请求中的每一个表项:如果我们有连接到指明地址的路由,则将度量(metric)设置成我们的值,否则将度量置为16。(度量为16是一种称为“无穷大”的特殊值,它意味着我们没有到达目的的路由。)然后发回响应。 
•接收到响应。使响应生效,可能会更新路由表。可能会增加新表项,对已有的表项进行修改,或是将已有表项删除。 
•定期选路更新。每过30秒,所有或部分路由器会将其完整路由表发送给相邻路由器。发送路由表可以是广播形式的(如在以太网上),或是发送给点对点链路的其它终点的。 
•触发更新。每当一条路由的度量发生变化时,就对它进行更新。不需要发送完整路由表,而只需要发送那些发生变化的表项。 
每条路由都有与之相关的定时器。如果运行RIP的系统发现一条路由在3分钟内未更新,就将该路由的度量设置成无穷大(16),并标注为删除。这意味着我们已经在6个30秒更新时间里没收到通告该路由的路由器的更新了。再过60秒,将从本地路由表中删除该路由,以保证该路由的失效已被传播开。 

度量 
RIP所使用的度量是以跳(hop)计算的。所有直接连接接口的跳数为1。考虑图10.4所示的路由器和网络。我们画出的4条虚线是广播RIP报文。 

图10.4 路由器和网络示例 

路由器R1通过发送广播到N1通告它与N2之间的跳数是1。(发送给N1的广播中通告它与N1之间的路由是无用的。)它同时也通过发送广播给N2通告它与N1之间的跳数为1。同样,R2通告它与N2的度量为1,与N3的度量为1。 
如果相邻路由器通告它与其它网络路由的跳数为1,那么我们与那个网络的度量就是2,这是因为为了发送报文到该网络,我们必须经过那个路由器。在我们的例子中,R2到N1的度量是2,与R1到N3的度量一样。 
由于每个路由器都发送其路由表给邻站,因此,可以判断在同一个自治系统AS内到每个网络的路由。如果在该AS内从一个路由器到一个网络有多条路由,那么路由器将选择跳数最小的路由,而忽略其它路由。 
跳数的最大值是15,这意味着RIP只能用在主机间最大跳数值为15的AS内。度量为16表示到无路由到达该IP地址。 

问题 
这种方法看起来很简单,但它有一些缺陷。首先,RIP没有子网地址的概念。例如,如果标准的B类地址中16 bit的主机号不为0,那么RIP无法区分非零部分是一个子网号,或者是一个主机地址。有一些实现中通过接收到的RIP信息,来使用接口的网络掩码,而这有可能出错。 
其次,在路由器或链路发生故障后,需要很长的一段时间才能稳定下来。这段时间通常需要几分钟。在这段建立时间里,可能会发生路由环路。在实现RIP时,必须采用很多微妙的措施来防止路由环路的出现,并使其尽快建立。RFC 1058 [Hedrick 1988a]中指出了很多实现RIP的细节。 
采用跳数作为路由度量忽略了其它一些应该考虑的因素。同时,度量最大值为15则限制了可以使用RIP的网络的大小。 

例子 
我们将使用ripquery程序来查询一些路由器中的路由表,该程序可以从gated中得到。ripquery程序通过发送一个非正式请求(图10.3中命令字段为5的“poll”)给路由器,要求得到其完整的路由表。如果在5秒内未收到响应,则发送标准的RIP请求(command字段为1)。(我们前面提到过的,将地址系列字段置为0,度量字段置为16的请求,要求其它路由器发送其完整路由表。) 
图10.5给出了我们将从sun主机上查询其路由表的两个路由器。如果我们在主机 sun上执行ripquery程序,以得到其下一站路由器netb的选路信息,那么我们可以得到下面的结果: 

sun % ripquery -n netb 
504 bytes from netb (140.252.1.183): 第一份报文包含504字节 
这里删除了许多行 
140.252.1.0, metric 1 图10.5上面的以太网 
140.252.13.0, metric 1 图10.5下面的以太网 
244 bytes from netb (140.252.1.183): 第二份报文包含剩下的244字节 
下面删除了许多行 

正如我们所猜想的那样,netb告诉我们子网的度量为1。另外,与netb相连的位于机端的以太网(140.252.1.0)的metric也是1。(-n参数表示直接打印IP地址而不需要去查看其域名。)在本例中,将netb配置成认为所有位于140.252.13子网的主机都与其直接相连——即,netb并不知道哪些主机真正与140.252.13子网相连。由于与140.252.13子网只有一个连接点,因此,通告每个主机的度量实际上没有太大意义。 

图10.5 我们将要查询其路由表内容的两个路由器netb和gateway 

图10.6给出了使用tcpdump交换的报文。我们采用-i s10参数指定SLIP接口。 

图10.6 运行ripquery程序的tcpdump输出结果 

第一个请求发出一个RIP轮询命令(第1行)。这个请求在5秒后超时,发出一个常规的RIP请求(第2行)。第1行和第2行最后的24表示请求报文的长度:4个字节的RIP首部(包括命令和版本),然后是单个20字节的地址和度量。 
第3行是第一个回答报文。该行最后的25表示包含了25个地址和度量对,我们在前面已经计算过,其字节数为504。这是上面的ripquery程序所打印出来的结果。我们为tcpdump程序指定-s600选项,以让它从网络中读取600个字节。这样,它可以接收整个UDP数据报(而不是报文的前半部),然后打印出RIP响应的内容。我们将该输出结果省略了。 
第4行是来自路由器的第二个响应报文,它包含后面的12个地址和度量对。我们可以计算出,该报文的长度为12×20 + 4=244,这正是ripquery程序前面所打印出来的结果。 
如果我们越过netb路由器,到gateway,那么可以预测到我们子网(140.252.13.0)的度量为2。我们可以运行下面的命令来进行验证: 

sun % ripquery -n gateway 
504 bytes from gateway (140.252.1.4): 
这里删除了许多行 
140.252.1.0, metric 1 图10.5上面的以太网 
140.252.13.0, metric 1 图10.5下面的以太网 

这里,位于图10.5上面的以太网(140.252.1.0)的度量依然是1,这是因为该以太网直接与gateway和netb相连。而我们的子网140.252.13.0正如预想的一样,其度量为2。 

另一个例子 
我们现在察看以太网上所有非主动请求的RIP更新,以看一看RIP定期给其邻站发送的信息。图10.7是noao.edu网络的多种排列情况。为了简化,我们不用本文其它地方所采用的路由器表示方式,而以Rn来代表路由器,其中n是子网号。我们以虚线表示点对点链路,并给出了这些链路对端的IP地址。 

图10.7 noao.edu 140.252的多个网络 

我们在主机solaris上运行Solaris 2.x的snoop程序,它与tcpdump相类似。我们可以在不需要超用户权限的条件下运行该程序,但它只捕获广播报文、多播报文,以及发送给主机的报文。图10.8给出了在60秒内所捕获的报文。在这里,我们将大部分正式的主机名以Rn来表示。 

图10.8 solaris在60秒内所捕获到的RIP广播报文 

-P 标志以非混杂模式捕获报文,-tr 打印出相应的时戳,而udp port 520 只捕获信源或信宿端口号为520的UDP数据报。 
来自R6,R4,R2,R7,R8和R3的前6个报文,每个报文只通告一个网络。查看这些报文,我们可以发现R2通告前往140.252.6.0的跳数为1的一条路由,R4通告前往140.252.4.0的跳数为1的一条路由,等等。 
但是,gateway路由器却通告了15条路由。我们可以通过运行 snoop程序时加上-v参数来查看RIP报文的全部内容。(这个标志输出全部报文的全部内容:以太网首部,IP首部,UDP首部,以及RIP报文。我们只保留了RIP信息而删除了其它信息。)图10.9给出了输出结果。 
把这些子网140.252.1上通告报文经过的路由与图10.7中的拓扑结构进行比较。 
使人迷惑不解的一个问题是为什么图10.8输出结果中,R10通告其有4个网络而在图10.7中显示的只有3个。如果我们查看带snoop的RIP报文,就会得到以下通告路由: 

RIP: Address Metric 
RIP: 140.251.0.0 16 (not reachable) 
RIP: 140.252.9.0 1 
RIP: 140.252.10.0 1 
RIP: 140.252.11.0 1 

前往B类网络140.251的路由是假的,不应该通告它。(它属于其它机构而不是noao.edu。) 

图10.9 来自gateway的RIP响应 

图10.8中,对于 R10发送的RIP报文,snoop输出“BROADCAST”符号,它表示目的IP地址是有限的广播地址255.255.255.255(12.2节),而不是其它路由器用来指向子网的广播地址(140.252.1.255)。 

10.5 RIP协议版本2 
RFC 1388 [Malkin 1993a]中对RIP定义进行了扩充,通常称其结果RIP-2。这些扩充并不改变协议本身,而是利用图10.3中的一些标注为“必须为0”的字段来传递一些额外的信息。如果RIP忽略这些必须为0的字段,那么,RIP和RIP-2可以互操作。 
图10.10重新给出了由RIP-2定义的图。对于RIP-2来说,其版本字段为2。 
选路域(routing domain)是一个选路守护程序的标识符,它指出了这个数据报的所有者。在一个Unix实现中,它可以是选路守护程序的进程号。该域允许管理者在单个路由器上运行多个RIP实例,每个实例在一个选路域内运行。 
选路标记(routing tag)是为了支持外部网关协议而存在的。它携带着一个EGP和BGP的自治系统号。 
每个表项的子网掩码应用于相应的IP地址上。下一站IP地址指明发往目的IP地址的报文该发往哪里。该字段为0意味着发往目的地址的报文应该发给发送RIP报文的系统。 

图10.10 RIP-2报文格式 

RIP-2提供了一种简单的鉴别机制。可以指定RIP报文的前20字节表项地址系列为0xffff,路由标记为2。表项中的其余16字节包含一个明文口令。 
最后,RIP-2除了广播(第12章)外,还支持多播。这可以减少不收听RIP-2报文的主机的负载。 

10.6 OSPF:开放最短路径优先 
OSPF是除RIP外的另一个内部网关协议。它克服了RIP的所有限制。RFC 1247 [Moy 1991]中对第2版OSPF进行了描述。 
与采用距离向量的RIP协议不同的是,OSPF是一个链路状态协议。距离向量的意思是,RIP发送的报文包含一个距离向量(跳数)。每个路由器都根据它所接收到邻站的这些距离向量来更新自己的路由表。 
在一个链路状态协议中,路由器并不与其邻站交换距离信息。它采用的是每个路由器主动地测试与其邻站相连链路的状态,将这些信息发送给它的其它邻站,而邻站将这些信息在自治系统中传播出去。每个路由器接收这些链路状态信息,并建立起完整的路由表。 
从实际角度来看,二者的不同点是链路状态协议总是比距离向量协议收敛更快。收敛的意思是在路由发生变化后,例如在路由器关闭或链路出故障后,可以稳定下来。[Perlman 1992]的9.3节对这两种类型的选路协议的其它方面进行了比较。 
OSPF与RIP(以及其它选路协议)的不同点在于,OSPF直接使用IP。也就是说,它并不使用UDP或TCP。对于IP首部的protocol字段,OSPF有其自己的值(图3.1)。 
另外,作为一种链路状态协议而不是距离向量协议,OSPF还有着一些优于RIP的特点。 
1. OSPF可以对每个IP服务类型计算(图3.2)计算各自的路由集。这意味着对于任何目的,可以有多个路由表表项,每个表项对应着一个IP服务类型。 
2. 给每个接口指派一个无维数的费用。可以通过吞吐率、往返时间、可靠性或其它性能来进行指派。可以给每个IP服务类型指派一个单独的费用。 
3. 当对同一个目的地址存在着多个相同费用的路由时,OSPF在这些路由上平均分配流量。我们称之为流量平衡。 
4. OSPF支持子网:子网掩码与每个通告路由相送连。这样就允许将一个任何类型的IP地址分割成多个不同大小的子网。(我们在3.7节中给出了这样的一个例子,称之为变长度子网。)到一个主机的路由是通过全1子网掩码进行通告的。默认路由是以IP地址为0.0.0.0,网络掩码为全0进行通告的。 
5. 路由器之间的点对点链路不需要每端都有一个IP地址。我们称之为无编号网络。这样可以节省IP地址——现在非常紧缺的一种资源。 
6. 采用了一种简单鉴别机制。可以采用类似于RIP-2机制(10.5节)的方法指定一个明文口令。 
7. OSPF采用多播(第12章),而不是广播形式,以减少不参与OSPF的系统负载。 
随着大部分厂商支持OSPF,在很多网络中OSPF将逐步取代RIP。 

10.7 BGP:边界网关协议 
BGP是一种不同自治系统之间网关进行通信的外部网关协议。BGP是ARPANET所使用的老EGP的取代品。RFC1267 [Lougheed and Rekhter 1991] 对第3版的BGP进行了描述。 
RFC 1268 [Rekhter and Gross 1991] 描述了如何在Internet中使用BGP。下面对于BGP的大部分描述都来自于这两个RFC文档。同时,1993年正在开发第4版的BGP(见RFC 1467 [Topolcic 1993]),以支持我们将在10.8节中所描述的CIDR。 
BGP系统与其它BGP系统之间交换网络可到达信息。这些信息包括数据到达这些网络所必须经过的自治系统AS中的所有路径。这些信息足以构造一幅自治系统连接图。然后,可以根据连接图删除选路环,制订选路策略。 
首先,我们将一个自治系统中的IP数据报分成本地流量和通过流量。在自治系统中,本地流量是起始或终止于该自治系统的流量。也就是说,其信源IP地址或信宿IP地址所指确定的主机位于该自治系统中。其它的流量则称为通过流量。在Internet中使用BGP的一个目的就是减少通过流量。 
可以将自治系统分为以下几种类型: 
1. 残桩自治系统(stub AS),它与其它自治系统只有单个连接。stub AS只有本地流量。 
2. 多接口自治系统(multihomed AS),它与其它自治系统有多个连接,但拒绝传送通过流量。 
3. 转送自治系统(transit AS),它与其它自治系统有多个连接,在一些策略准则之下,它可以传送本地流量和通过流量。 
这样,可以将Internet的总拓扑结构看成是由一些残桩自治系统、多接口自治系统以及转送自治系统的任意互连。残桩自治系统和多接口自治系统不需要使用BGP——它们通过运行EGP在自治系统之间交换可到达信息。 
BGP允许使用基于策略的选路。由自治系统管理员制订策略,并通过配置文件将策略指定给BGP。制订策略并不是协议的一部分,但指定策略允许BGP实现在存在多个可选路径时选择路径,并控制信息的重发送。选路策略与政治、安全或经济因素有关。 
BGP与RIP和OSPF的不同之处在于BGP使用TCP作为其传输层协议。两个运行BGP的系统之间建立一条TCP连接,然后交换整个BGP路由表。从这个时候开始,在路由表发生变化时,再发送更新信号。 
BGP是一个距离向量协议,但是与(通告到目的地址跳数的)RIP不同的是,BGP列举了到每个目的地址的路由(自治系统到达目的地址的序号)。这样就排除了一些距离向量协议的问题。采用16 bit数字表示自治系统标识。 
BGP通过定期发送keepalive报文给其邻站来检测TCP连接对端的链路或主机失败。两个报文之间的时间间隔建议值为30秒。应用层的keepalive报文与TCP的keepalive选项(第23章)是独立的。 

10.8 CIDR:无类型域间选路(Classless Interdomain Routing) 
在第3章中,我们指出了B类地址的缺乏,因此现在的多个网络站点只能采用多个C类网络号,而不采用单个B类网络号。尽管分配这些C类地址解决了一个问题(B类地址的缺乏),但它却带来了另一个问题:每个C类网络都需要一个路由表表项。无类型域间选路(CIDR)是一个防止Internet路由表膨胀的方法。它也称为超网(supernetting),在RFC 1518 [Rekher and Li 1993] 和RFC 1519 [Fuller et al. 1993]中对它进行了描述,而[Ford, Rekhter, and Braun 1993]是它的综述。CIDR有一个InternetArchitecture Board's blessing [Huitema 1993]。RFC 1467 [Topolcic 1993] 对Internet中CIDR开发状况进行了小结。 
CIDR的基本观点是采用一种分配多个IP地址的方式,使其能够将路由表中的许多表项总和(summarization)成更少的数目。例如,如果给单个站点分配16个C类地址,以一种可以用总和的方式来分配这16个地址,这样,所有这16个地址可以参照Internet上的单个路由表表项。同时,如果有8个不同的站点是通过同一个Internet服务提供商的同一个连接点接入Internet的,且这8个站点分配的8个不同IP地址可以进行总和,那么,对于这8个站点,在Internet上,只需要单个路由表表项。 
要使用这种总和,必须满足以下三种特性。 
1. 为进行选路要对多个IP地址进行总和时,这些IP地址必须具有相同的高位地址比特。 
2. 路由表和选路算法必须扩展成根据32 bitIP地址和32 bit掩码做出选路决策的。 
3. 必须扩展选路协议使其除了32 bit地址外,还要有32 bit掩码。OSPF(10.6节)和RIP-2(10.5节)都能够携带第4版BGP所提出的32 bit掩码。 
例如,RFC 1466 [Gerich 1993] 建议欧洲新的C类地址的范围是194.0.0.0到195.255.255.255。以16进制表示,这些地址的范围是0xc2000000到0xc3ffffff。它代表了65536个不同的C类网络号,但他们地址的高7 bit是相同的。在欧洲以外的国家里,可以采用IP地址为0xc2000000和32 bit0xfe000000 (254.0.0.0) 为掩码的单个路由表表项来对所有这些65536个C类网络号选路到单个点上。C类地址的后面各比特位(即在194或195后面各比特)也可以进行层次分配,例如以国家或服务提供商分配,以允许在欧洲路由器之间,使用除了这32 bit掩码的高7 bit外的其它比特进行概括。 
CIDR同时还使用一种技术,使最佳匹配总是最长的匹配:即在32 bit掩码中,它具有最大值。我们继续采用上一段中所用的例子,欧洲的一个服务提供商可能会采用一个与其它欧洲服务提供商不同的接入点。如果给该提供商分配的地址组是从194.0.16.0到194.0.31.255 (16个C类网络号),那么可能只有这些网络的路由表项的IP地址是194.0.16.0,掩码为255.255.240.0 (0xfffff000)。发往194.0.22.1地址的数据报将同时与这个路由表表项与其它欧洲C类地址的表项进行匹配。但是由于掩码255.255.240比254.0.0.0更“长”,因此将采用具有更长掩码的路由表表项。 
“无类型”的意思是现在的选路决策是基于整个32 bitIP地址的掩码操作。而不管其IP地址是A类、B类或是C类,都没有什么区别。 
CIDR的最初是针对新的C类地址提出的。这种变化将使Internet路由表增长的速度缓慢下来,但对于现存的选路则没有任何帮助。这是一个短期解决方案。作为一个长期解决方案,如果将CIDR应用于所有IP地址,并根据各洲边界和服务提供商对已经存在的IP地址进行重新分配(且所有现有主机重新进行编址!),那么[Ford, Rekhter, and Braun 1993] 是宣称,目前包含10,000网络表项的路由表将会减少成只有200个表项。 

10.9 小结 
有两种基本的选路协议,即用于同一自治系统各路由器之间的内部网关协议(IGP)和用于不同自治系统内路由器通信的外部网关协议(EGP)。 
最常用的IGP是路由信息协议(RIP),而OSPF是一个正在得到广泛使用的新IGP。一种新近浒的EGP是边界网关协议(BGP)。在本章中,我们考虑了RIP及其交换的报文类型。第2版是RIP是其最近的一个改进版,它支持子网,还有一些其它改进技术。我闪同时也对OSPF,BGP和无类型域间选路(CIDR)进行了描述,CIDR是一种新技术,采用它可以减小Internet路由表的大小。 
你可能还会遇到一些其它的OSI选路协议。域间选路协议(IDRP)最开始时,是一个为了使用OSI地址而不是IP地址,而进行修改的BGP版本。Intermediate sys tem to Intermediate sys tem 协议(IS-IS)是OSI的标准IGP。可以用它来选路CLNP(无连接网络协议),这是一种与IP类似的OSI协议。IS-IS和OSPF相似。 
动态选路仍然是一个网间互连的研究热点。对使用的选路协议和运行的路由守护程序进行选择,是一项复杂的工作。[Perlman 1992]提供了许多细节。 

习题: 
10.1 在图10.9中哪些路由是从路由器kpno进入gateway的? 
10.2 假设一个路由器要使用RIP通告30个路由,这需要一个包含25条路由和另一个包含5条路由的数据报。如果每过一个小时,第一个包含25条路由的数据报丢失一次,那么其结果如何? 
10.3 OSPF报文格式中有一个校验和字段,而RIP报文则没有此项,这是为什么? 
10.4 像OSPF这样的负载平衡,对于传输层的影响是什么? 
10.5 查阅RFC 1058 关于实现RIP的其它资料。在图10.8中,140.252.1网络的每个路由器只通告它所提供的路由,而它并不能通过其它路由器的广播中知道任何其它路由。这种技术的名称是什么? 
10.6 在3.4节中,我们说过除了图10.7中所示的8个路由器外,140.252.1子网上还有超过100个主机。那么这100个主机是如何处理每30秒到达它们的8个广播信息呢(图10.8)? 

10-1 
11 UDP:用户数据报协议 

11.1 引言 
UDP是一个简单的面向数据报的运输层协议:进程的每个输出操作都正好产生一个UDP数据报,并组装成一份待发送的IP数据报。这与面向流字符的协议不同,如TCP,应用程序产生的全体数据与真正发送的单个IP数据报可能没有什么联系。 
UDP数据报封装成一份IP数据报的格式如图11.1所示。 

图11.1 UDP封装 

RFC 768 [Postel 1980] 是UDP的正式规约。 
UDP不提供可靠性:它把应用程序传给IP层的数据发送出去,但是并不保证它们能到达目的地。由于缺乏可靠性,我们似乎觉得要避免使用UDP而使用一种可靠协议如TCP。我们在第17章讨论完TCP后将再回到这个话题,看看什么样的应用程序可以使用UDP。 
应用程序必须关心IP数据报的长度。如果它超过网络的MTU(2.8节),那么就要对IP数据报进行分片。如果需要,源端到目的端之间的每个网络都要进行分片,并不只是发送端主机连接第一个网络才这样做。(我们在2.9节中已定义了路径MTU的概念。)在11.5节中,我们将讨论IP分片机制。 

11.2 UDP首部 
UDP首部的各字段如图11.2所示。 

图11.2 UDP首部 

端口号表示发送进程和接收进程。在图1.8中,我们画出了TCP和UDP用目的端口号来分用来自IP层的数据的过程。由于IP层已经把IP数据报分配给TCP或UDP(根据IP首部中协议字段值),因此TCP端口号由TCP来查看,而UDP端口号由UDP来查看。TCP端口号与UDP端口号是相互独立的。 

(下面是原书p.144①的译文) 
尽管相互独立,如果TCP和UDP同时提供某种知名服务,两个协议通常选择相同的端口号。这纯粹是为了使用方便,而不是协议本身的要求。 

UDP长度字段指的是UDP首部和UDP数据的字节长度。该字段的最小值为8字节。(发送一份0字节的UDP数据报是OK。)这个UDP长度是有冗余的。IP数据报长度指的是数据报全长(图3.1),因此UDP数据报长度是全长减去IP首部的长度(该值在首部长度字段中指定,如图3.1)。 

11.3 UDP检验和 
UDP检验和覆盖UDP首部和UDP数据。回想IP首部的检验和,它只覆盖IP的首部----并不覆盖IP数据报中的任何数据。 
UDP和TCP在首部中都有覆盖它们首部和数据的检验和。UDP的检验和是可选的,而TCP的检验和是必需的。 
尽管UDP检验和的基本计算方法与我们在3.2节中描述的IP首部检验和计算方法相类似(16 bit字的二进制反码和),但是它们之间存在不同的地方。首先,UDP数据报的长度可以为奇数字节,但是检验和算法是把若干个16 bit字相加。解决方法是必要时在最后增加填充字节0,这只是为了检验和的计算。(也就是说,可能增加的填充字节不被传送。) 
其次,UDP数据报和TCP段都包含一个12字节长的伪首部,它是为了计算检验和而设置的。伪首部包含IP首部一些字段。其目的是让UDP两次检查数据是否已经正确到达目的地(例如,IP没有接受地址不是本主机的数据报,以及IP没有把应传给另一高层的数据报传给UDP)。UDP数据报中的伪首部格式如图11.3所示。 

图11.3 UDP检验和计算过程中使用的各个字段 

在该图中,我们特地举了一个奇数长度的数据报例子,因而在计算检验和时需要加上填充字节。注意,UDP数据报的长度在检验和计算过程中出现两次。 
如果检验和的计算结果为0,它存入的值为全1(65535),相当于它的算术二进制反码。如果传送的检验和为0,说明发送端没有计算检验和。 
如果发送端没有计算检验和而接收端检测到检验和有差错,那么UDP数据报就要被悄悄地丢弃。不产生任何差错报文。(当IP层检测到IP首部检验和有差错时也这样做。) 
UDP检验和是一个端到端的检验和。它由发送端计算,然后由接收端验证。其目的是为了发现UDP首部和数据在发送端到接收端之间发生的任何改动。 
尽管UDP检验和是可选的,但是它们应该始终利用它。在80年代,一些计算机产商在默认条件下关闭UDP检验和的功能,以提高使用UDP协议的NFS(Network File sys tem)的速度。在单个局域网中这可能是可以接受到,但是在数据报通过路由器时,通过对链路层数据帧进行循环冗余检验(如以太网或令牌环数据帧)可以检测到大多数的差错,导致传输失败。不管相信与否,路由器中也存在软件和硬件差错,以致于修改数据报中的数据。如果关闭端到端的UDP检验和功能,那么这些差错在UDP数据报中就不能被检测出来。另外,一些数据链路层协议(如SLIP)没有任何形式的数据链路检验和。 

(下面是原书p.146①的译文) 
Host Requirements RFC声明,UDP检验和选项在默认条件下是打开的。它还声明,如果发送端已经计算了检验和,那么接收端必须检验接收到的检验和(如接收到检验和不为0)。但是,许多系统没有遵守这一点,只是在出口检验和选项被打开时才验证接收到的检验和。 

tcpdump输出 
很难知道某个特定系统是否打开了UDP检验和选项。应用程序通常不可能得到接收到的UDP首部中的检验和。为了得到这一点,作者在tcpdump程序中增加了一个选项,以打印出接收到的UDP检验和。如果打印出的值为0,说明发送端没有计算检验和。 
我们测试网络上三个不同系统的输出如图11.4所示(参见封面二)。运行我们自编的sock程序(附录C),发送一份包含9个字节数据的UDP数据报给标准回显服务器。 

图11.4 tcpdump输出,观察其他主机是否打开UDP检验和选项 

我们从这里可以看出,三个系统中有两个打开了UDP检验和选项。 
还要注意的是,在这个简单例子中,送出的数据报与收到的数据报具有相同的检验和值(第3和第4行,第5和第6行)。从图11.3我们可以看出,两个IP地址进行了交换,正如两个端口号一样。伪首部和UDP首部中的其他字段都是相同的,就像数据回显一样。这再次表明UDP检验和(事实上,TCP/IP协议簇中所有的检验和)是简单的16 bit和。它们检测不出交换两上16 bit的差错。 

(下面是原书p.147①的译文) 
作者在14.2节中在八个域名服务器中各进行了一次DNS查询。DNS主要使用UDP,结果只有两台服务器打开了UDP检验和选项。 

一些统计结果 
文献[Mogul 1992]提供了在一个繁忙的NFS(Network File sys tem)服务器上所发生的不同检验和差错的统计结果,时间持续了40天。统计数字结果如图11.5所示。 

图11.5 检测到不同检验和差错的分组统计结果 

最后一列是每一行的大概总数,因为以太网和IP层还使用有其他的协议。例如,不是所有的以太网数据帧都是IP数据报,至少以太网还要使用ARP协议。不是所有的IP数据报都是UDP或TCP数据,因为ICMP也用IP传送数据。 
注意,TCP发生检验和差错的比例与UDP相比要高得多。这很可能是因为在该系统中的TCP连接经常是“远程”连接(经过许多路由器,网桥等中间设备),而UDP一般为本地通信。 
从最后一行可以看出,不要完全相信数据链路(如以太网,令牌环等)的CRC检验。你应该始终打开端到端的检验和功能。而且,如果你的数据很有价值,也不要完全相信UDP或TCP的检验和,因为这些都只是简单的检验和,不能检测出所有可能发生的差错。 

11.4 一个简单的例子 
用我们自己编写的sock程序生成一些可以通过tcpdump观察的UDP数据报: 

bsdi % sock -v -u -i -n4 svr4 discard 
connected on 140.252.13.35.1108 to 140.252.13.34.9 

bsdi % sock -v -u -i -n4 -w0 svr4 discard 
connected on 140.252.13.35.1110 to 140.252.13.34.9 

第一次执行这个程序时我们指定verbose模式(-v)来观察ephemeral端口号,指定UDP(-u)而不是默认的TCP,并且指定源模式(-i)来发送数据,而不是读写标准的输入和输出。-n4选项指明输出4份数据报(默认条件下为1024),目的主机为svr4。我们在1.12节描述了丢弃服务。每次写操作的输出长度取默认值1024。 
第二次运行该程序时我们指定-w0,意思是写长度为0数据报。两个命令的tcpdump输出结果如图11.6所示。 

图11.6 向一个方向发送UDP数据报时的tcpdump输出 

输出显示有四份1024字节的数据报,接着有四份长度为0的数据报。每份数据报间隔几毫秒。(输入第二个命令花了41秒的时间。) 
在发送第一份数据报之前,发送端和接收端之间没有任何通信。(在第17章,我们将看到TCP在发送数据的第一个字节之前必须与另一端建立连接。)另外,当收到数据时,接收端没有任何确认。在这个例子中,发送端并不知道另一端是否已经收到这些数据报。 
最后要指出的是,每次运行程序时,源端的UDP端口号都发生变化。第一次是1108,然后是110。在1 .9节我们已经提过,客户程序使用ephemeral端口号一般在1024到5000之间,正如我们现在看到的这样。 

11.5 IP分片 
正如我们在2.8节描述的那样,物理网络层一般要限制每次发送数据帧的最大长度。任何时候IP层接收到一份要发送的IP数据报时,它要判断向本地哪个接口发送数据(路由选择),并查询该接口获得其MTU。IP把MTU与数据报长度进行比较,如果需要则进行分片。分片可以发生在原始发送端主机上,也可以发生在中间路由器上。 
当把一份IP数据报分片以后,只有到达目的地才进行重新组装。(这里的重新组装与其他网络协议不同,它们要求在下一站就进行进行重新组装,而不是在最终的目的地。)重新组装由目的端的IP层来完成,其目的是使分片和重新组装过程对运输层(TCP和UDP)是透明的,除了某些可能的越级操作外。已经分片过的数据报有可能会再次进行分片(可能不止一次)。IP首部中包含的数据为分片和重新组装提供了足够的信息。 
回忆IP首部(图3.1),下面这些字段用于分片过程。对于发送端发送的每份IP数据报来说,其标识字段都包含一个唯一值。该值在数据报片时被复制到每个片中。(我们现在已经看到这个字段的用途。)标志字段用其中一个比特来表示“更多的片”。除了最后一片外,其他每个组成数据报的片都要把该比特置1。片偏移字段指的是该片偏移原始数据报开始处的位置。另外,当数据报被分片后,每个片的总长度值要改为该片的长度值。 
最后,标志字段中有一个比特称作“不分片”位。如果将这一比特置1,IP将不对数据报进行分片。相反把数据报丢弃并发送一个ICMP差错报文(“需要进行分片但设置了不分片比特”,图6 .3)给发起端。在下一节我们将看到出现这个差错的例子。 
当IP数据报被分片后,每一片都成为一个分组,具有自己的IP首部,并在选择路由时与其他分组独立。这样,当数据报的这些片到达目的端时有可能会失序,但是在IP首部中有足够的信息让接收端能正确组装这些数据报片。 
尽管IP分片过程看起来是透明的,但有一点让人不想使用它:即使只丢失一片数据也要重传整个数据报。为什么会发生这种情况呢?因为IP层本身没有超时重传的机制——由更高层来负责超时和重传。(TCP有超时和重传机制,但UDP没有。一些UDP应用程序本身也执行超时和重传。)当来自TCP报文段的某一片丢失后,TCP在超时后会重发整个TCP报文段,该报文段对应于一份IP数据报。没有办法只重传数据报中的一个数据报片。事实上,如果对数据报分片的是中间路由器,而不是发起的端系统,那么发起的端系统就无法知道数据报是如何被分片的。就这个原因,经常要避免分片。文献[Kent and Mogul 1987]对避免分片进行了论述。 
使用UDP很容易导致IP分片。(在后面我们将看到,TCP试图避免分片,但对于应用程序来说几乎不可能强迫TCP发送一个需要进行分片的长报文段。)我们可以用sock程序来增加数据报的长度,直到分片发生。在一个以太网上,数据帧的最大长度是1500字节(图2.1),其中1472字节留给数据,假定IP首部为20字节,UDP首部为8字节。我们分别以数据长度为1471, 1472, 1473和1474字节运行sock程序。最后两次应该发生分片: 

bsdi % sock -u -i -nl -w1471 svr4 discard 
bsdi % sock -u -i -nl -w1472 svr4 discard 
bsdi % sock -u -i -nl -w1473 svr4 discard 
bsdi % sock -u -i -nl -w1474 svr4 discard 

相应的tcpdump输出如图11.7所示。 

图11.7 观察UDP数据报分片 

前两份UDP数据报(第1行和第2行)能装入以太网数据帧,没有被分片。但是对应于写1473字节的IP数据报长度为1501,就必须进行分片(第3行和第4行)。同理,写1474字节产生的数据报长度为1502,它也需要进行分片(第5行和第6行)。 
当IP数据报被分片后,tcpdump打印出其他的信息。首先,frag 26304(第3行和第4行)和frag 26313(第5行和第6行)指的是IP首部中标识字段的值。 
分片信息中的下一个数字,即第三行中位于冒号和@号之间的1480,是除IP首部外的片长。两份数据报第一片的长度均为1480:UDP首部占8字节,用户数据占1472字节。(加上IP首部的20字节分组长度正好为1500字节。)第一份数据报的第二片(第4行)只包含1字节数据----剩下的用户数据。第二份数据报的第二片(第6行)包含剩下的2字节用户数据。 
在分片时,除最后一片外,其他每一片中的数据部分(除IP首部外的其余部分)必须是8字节的整数倍。在本例中,1480是8的整数倍。 
位于@符号后的数字是从数据报开始处计算的片偏移值。两份数据报第一片的偏移值均为0(第3行和第5行),第二片的偏移值为1480(第4行和第6行)。跟在偏移值后面的加号对应于IP首部中3 bit标志字段中的“更多片”比特。设置这一比特的目的是让接收端知道在什么时候完成所有的分片组装。 
最后,注意第4行和第6行(不是第一片)省略了协议名(UDP),源端口号和目的端口号。协议名是可以打印出来的,因为它在IP首部并被复制到各个片中。但是,端口号在UDP首部,只能在第一片中被发现。 
发送的第三份数据报(用户数据为1473字节)分片情况如图11.8所示。需要重申的是,任何运输层首部只出现在第一片数据中。 
另外需要解释几个术语:IP数据报是指IP层端到端的传输单元(在分片之前和重新组装之后),分组是指在IP层和链路层之间传送的数据单元。一个分组可以是一个完整的IP数据报,也可以是IP数据报的一个分片。 

图11.8 UDP分片例子 

11.6 ICMP不可到达差错(需要分片) 
发生ICMP不可到达差错的另一种情况是,当路由器收到一份需要分片的数据报,而在IP首部又设置了不分片(DF)的标志比特。如果某个程序需要判断到达目的端的路途中最小MTU是多少——称作路径MTU发现机制(2.9节),那么这个差错就可以被该程序使用。 
这种情况下的ICMP不可到达差错报报文格式如图11.9。这里的格式与图6.10不同,因为在第二个32 bit字中,16-31 bit可以提供下一站的MTU,而不再是0。 

图11.9 需要分片但又设置不分片标志比特时的ICMP不可到达差错报文格式 

如果路由器没有提供这种新的ICMP差错报文格式,那么下一站的MTU就设为0。 

(下面是原书p.151①的译文) 
新版的路由器需求RFC [Almquist 1993]声明,在发生这种ICMP不可到达差错时,路由器必须生成这种新格式的报文。 

例子 
关于分片作者曾经遇到一个问题,ICMP差错试图判断从路由器netb到主机sun之间的拔号SLIP链路的MTU。我们知道从sun到netb的链路的MTU:当SLIP被安装到主机sun时,这是SLIP配置过程中的一部分,加上我们在3.9节中已经通过netstat命令观察过。现在,我们想从另一个方向来判断它的MTU。(在第25章,我们将讨论如何用SNMP来判断。)在点到点的链路中,不要求两个方向的MTU为相同值。 
所采用的技术是在主机solaris上运行ping程序到主机bsdi,增加数据分组长度,直到看见进入的分组被分片为止。如图11.10所示。 

图11.10 用来判断从netb到sun的SLIP链路MTU的系统 

在主机sun上运行tcpdump观察SLIP链路,看什么时候发生分片。开始没有观察到分片,一切都很正常直到ping分组的数据长度从500增加到600字节。可以看到接收到的回显请求(仍然没有分片),但不见回显回答。 
为了跟踪下去,也在主机bsdi上运行tcpdump,观察它接收和发送的报文。输出如图11.11所示。 

图11.11 当IP数据报长度为600字节从solaris主机ping到bsdi主机时的tcpdump输出 

首先,每行中的标记(DF)说明在IP首部中设置了不分片比特。这意味着Solaris 2.2 一般把不分片比特置1,作为实现路径MTU发现机制的一部分。 
第1行显示的是回显请求通过路由器netb到达sun主机,没有进行分片,并设置了DF比特,因此我们知道还没有达到netb的SLIP MTU。 
接下来,在第2行注意DF标志被复制到回显回答报文中。这就带来了问题。回显回答与回显请求报文长度相同(超过600字节),但是sun外出的SLIP接口MTU为552。因此回显回答需要进行分片,但是DF标志比特又被设置了。这样,sun就产生一个ICMP不可到达差错报文返回给bsdi(报文在bsdi处被丢弃)。 
这就是我们在主机solaris上没有看到任何回显回答的原因。这些回答永远不能通过sun。分组的路径如图11.12所示。 

图11.12 例子中的分组交换 

最后,在图11.11中的第3行和第6行中,mtu=0表示主机sun没有在ICMP不可到达报文中返回出口MTU值,如图11.9所示。(在25.9节中,我们将重新回到这个问题,用SNMP判断netb上的SLIP接口MTU值为1500。) 

11.7 用Traceroute确定路径MTU 
尽管大多数的系统不支持路径MTU发现功能,我们可以很容易地修改traceroute程序(第8章),用它来确定路径MTU。我们要做的是发送分组,并设置“不分片”标志比特。我们发送的第一个分组的长度正好与出口MTU相等,每次收到ICMP“不能分片”差错时(我们在上一节讨论的)我们减小分组的长度。如果路由器发送的ICMP差错报文是新格式,包含出口的MTU,那么我们就用该MTU值来发送,否则就用下一个最小的MTU值来发送。正如RFC 1191 [Mogul and Deering 1990]声明的那样,MTU值的个数是有限的,因此在我们的程序中有一些由近似值构成的表,取下一个最小MTU值来发送。 
首先,我们尝试判断从主机sun到主机slip的路径MTU,知道SLIP链路的MTU为296。 

(见原书p.154的①) 

在这个例子中,路由器bsdi没有在ICMP差错报文中返回出口MTU,因此我们选择另一个MTU近似值。TTL为2的第1行输出打印的主机名为bsdi,但这是因为它是返回ICMP差错报文的路由器。TTL为2的最后一行正是我们所要找的。 
在bsdi上修改ICMP代码使它返回出口MTU值并不困难,如果我们那样做并再次运行该程序,得到如下输出结果: 

(见原书p.154的②) 

这时,在找到正确的MTU值之前我们不用逐个尝试8个不同的MTU值——路由器返回了正确的MTU值。 

全球互连网 
作为一个实验,我们多次运行修改以后的traceroute程序,目的端为世界各地的主机。可以到达十五个国家(包括南极洲),使用了多个跨大西洋和跨太平洋的链路。但是,在这样做之前,作者所在子网与路由器netb之间的拔号SLIP链路MTU(图11.12)增加到1500,与以太网相同。 
在18次运行当中,只有其中2次发现的路径MTU小于1500。其中一个跨大西洋的链路MTU值为572(其近似值甚至在RFC 1191也没有被列出),而路由器返回的是新格式的ICMP差错报文。另外一条链路,在日本的两个路由器之间,不能处理1500字节的数据帧,并且路由器没有返回新格式的ICMP差错报文。把MTU值设成1006则可以正常工作。 
从这个实验我们可以得出结论,现在许多但不是所有的广域网都可以处理大于512字节的分组。利用路径MTU发现机制,应用程序就可以充分利用更大的MTU来发送报文。 

11.8 采用UDP的路径MTU发现 
下面让我们对使用UDP的应用程序与路径MTU发现机制之间的交互作用进行研究。我们看一看如果应用程序写了一个对于一些中间链路来说太长的数据报时会发生什么情况。 

例子 
由于我们所使用的支持路径MTU发现机制的唯一系统就是Solaris 2.x,因此,我们将采用它作为源站发送一份650字节数据报经slip。由于我们的slip主机位于MTU为296的SLIP链路后,因此,任何长于268字节(296-20-8)且“不分片”比特置为1的UDP数据都会使bsdi路由器产生ICMP“不能分片”差错报文。图11.13给出了拓扑结构和MTU。 

图11.13 使用UDP进行路径MTU发现的系统 

可以用下面的命令行来产生650字节UDP数据报,每两个UDP数据报之间的间隔是5秒: 

solaris % sock -u -I -n10 -w650 -p5 slip discard 

图11.14是tcpdump的输出结果。在运行这个例子时,将bsdi设置成在ICMP“不能分片”差错中,不返回下一跳MTU信息。 
在发送的第一个数据报中将DF比特置1(第1行),其结果是从bsdi路由器发回的我们可以猜测的结果(第2行)。令不不解的是,发送一个DF比特置1的数据报(第3行),其结果是同样的ICMP差错(第4行)。我们预计这个数据报在发送时应该将DF比特置0。 
第5行结果显示,IP已经知道了发往该目的地址的数据报不能将DF比特置1,因此,IP进而将数据报在源站主机上进行分片。这与前面的例子中,IP发送经过UDP的数据报,允许具有较小MTU的路由器(在本例中是bsdi)对它进行分片的情况不一样。由于ICMP“不能分片”报文并没有指出下一跳的MTU,因此看来IP猜测MTU为576就行了。第一次分片(第5行)包含544字节的UDP数据,8字节UDP首部以及20字节IP首部,因此,总IP数据报长度是572字节。第2次分片(第6行)包含剩余的106字节UDP数据和20字节IP首部。 

图11.14 使用UDP路径MTU发现 

不幸的是,第7行的下一个数据报将其DF比特置1,因此bsdi将它丢弃并返回ICMP差错。这时发生了IP定时器超时,通知IP查看是不是因为路径MTU增大了而将DF比特再一次置1。我们可以从第19行和20行看出这个结果。将第7行与19行进行比较,看来IP每过30秒就将DF比特置1,以查看路径MTU是否增大了。 

(下面是原书p.156①的译文) 
这个30秒的定时器值看来太短。RFC 1191建议其值取10分钟。可以通过修改ip_ire_pathmtu_interval(E.4节)参数来改变该值。同时,Solaris 2.2无法对单个UDP应用或所有UDP应用关闭该路径MTU发现。只能通过修改ip_path_mtu_discovery参数,在系统一级上开放或关闭它。正如我们在这个例子里所能看到的那样,如果允许路径MTU发现,那么当UDP应用程序写入可能被分片数据报时,该数据报将被丢弃。 

solaris的IP层所假设的最大数据报长度(576字节)是不正确的。在图11.13中,我们看到,实际的MTU值是296字节。这意味着经solaris分片的数据报还将被bsdi分片。图11.15给出了在目的主机(slip)上所收集到的tcpdump对于第一个到达数据报的输出结果(图11.14的第5行和第6行)。 

图11.15 从solaris到达slip的第一个数据报 

在本例中,solaris不应该对外出数据报分片,它应该将DF比特置0,让具有最小MTU的路由器来完成分片工作。 
现在我们运行同一个例子,只是对路由器bsdi进行修改使其在ICMP“不能分片”差错中返回下一跳MTU。图11.16给出了tcpdump输出结果的前6行。 

图11.16 使用UDP的路径MTU发现 

与图11.14一样,前两个数据报同样是将DF比特置1后发送出去的。但是在知道了下一跳MTU后,只产生了3个数据报片,而图11.15中的bsdi路由器则产生了4个数据报片。 

11.9 UDP和ARP之间的交互作用 
使用UDP,我们可以看到UDP与ARP典型实现之间的有趣的(而常常未被人提及)交互作用。 
我们用sock程序来产生一个包含8192字节数据的UDP数据报。我们预测这将会在以太网上产生6个数据报片(见习题11.3)。我们同时也确保在运行该程序前,ARP缓存是清空的,这样,在发送第一个数据报片前必须交换ARP请求和回答。 

bsdi % arp -a验证ARP高速缓存是空的 
bsdi % sock -u -i -nl -w8192 svr4 discard 

我们预计在发送第一个数据报片前会先发送一个ARP请求。IP还会产生5个数据报片,这样就提出了我们必须用tcpdump来回答的两个问题:在接收到ARP回答前,其余数据报片是否已经做好了发送准备?如果是这样的话,那么在ARP等待回答时,它会如何处理发往给定目的的多个报文?图11.17给出了tcpdump的输出结果。 

图11.17 在以太网上发送8192字节UDP数据报时的报文交换 

在这个输出结果中有一些令人吃惊的结果。首先,在第一个ARP回答返回以胶,总共产生了6个ARP请求。我们认为其原因是IP很快地产生了6个数据报片,而每个数据报片都引发了一个ARP请求。 
第二,在接收到第一个ARP回答时(第7行),只发送最后一个数据报片(第9行)!看来似乎将前5个数据报片全都丢弃了。实际上,这是ARP的正常操作。在大多数的实现中,在等待一个ARP回答时,只将最后一个报文发送给特定目的主机。 

(下面是原书p.158①的译文) 
Host Requirements RFC要求实现中必须防止这种类型的ARP洪泛(ARP flooding,即以高速率重复发送到同一个IP地址的ARP请求)。建议最高速率是每秒一次。而这里却在4.3 ms内发出了6个ARP请求。 
Host Requirements RFC规定,ARP应该保留至少一个报文,而这个报文必须是最后一个报文。这正是我们在这里所看到的结果。 

另一个无法解释的不正常的现象是,svr4发回7个,而不是6个ARP回答。 
最后要指出的是,在最后一个ARP回答返回后,继续运行tcpdump程序5分钟,以看看svr4是否会返回ICMP“组装超时”差错。并没有发送ICMP差错。(我们在图8.2中给出了该消息的格式。code字段为1表示在重新组装数据报时发生了超时。) 
在第一个数据报片出现时,IP层必须启动一个定时器。这里“第一个”表示给定数据报的第一个到达数据报片,而不是第一个数据报片(数据报片偏移为0)。正常的定时器值为30或60秒。如果在定时器超时而该数据报的所有数据报片未能全部到达,那么将这些数据报片丢弃。如果不这么做的话,那些永远不会到达的数据报片(正如我们在本例中所看到的那样)迟早会引起接收端缓存满。 
这里我们没看到ICMP消息的原因有两个。首先,大多数从Berkeley衍生的实现从不产生该差错!这些实现会设置定时器,也会在定时器溢出时将数据报片丢弃,但是不生成ICMP差错。第二,并未接收到包含UDP首部的偏移量为0的第一个数据报片。(这是被ARP所丢弃的5个报文的第1个。)除非接收到第一个数据报片,否则并不要求任何实现产生ICMP差错。其原因是因为没有运输层首部的话,ICMP差错的接收者无法区分出是哪个进程所发送的数据报被丢弃。这里假设上层(TCP或使用UDP的应用程序)最终会超时并重传。 
在本节中,我们使用IP数据报片来查看UDP与ARP之间的交互作用。如果发送端迅速发送多个UDP数据报的话,也可以看到这个交互过程。我们选择采用分片的方法,是因为IP可以生成报文的速度,比一个用户进程生成多个数据报的速度更快。 
尽管本例看来不太可能,但它确实经常发生。NFS发送的UDP数据报长度超过8192字节。在以太网上,这些数据报以我们所指出的方式进行分片,如果适当的ARP缓存入口发生超时,那么你就可以看到我们这里所显示的现象。NFS将超时并重传,但是由于ARP的有限队列,第一个IP数据报仍可能被丢弃。 

11.10 最大UDP数据报长度 
理论上,IP数据报的最大长度是65535字节,这是由IP首部(图3.1节)16比特总长度字段所限制的。去除20字节的IP首部和8个字节的UDP首部,UDP数据报中用户数据的最长长度为65507字节。但是,大多数实现所提供的长度比这个最大值小。 
我们将遇到两个限制因素。第一,应用程序可能会受到其程序接口的限制。socket API提供了一个可供应用程序调用的函数,以设置接收和发送缓存的长度。对于UDP socket,这个长度与应用程序可以读写的最大UDP数据报的长度直接相关的。现在的大部分系统都默认提供了可读写大于8192字节的UDP数据报。(使用这个默认值是因为8192是NFS读写用户数据数的默认值。) 
第二个限制来自于TCP/IP的内核实现。可能存在一些实现特性(或差错),使IP数据报长度小于65535字节。 

(下面是原书p.159①的译文) 
作者使用sock程序对不同UDP数据报长度进行了试验。在SunOS 1.1.3下使用环回接口的最大IP数据报长度是32767字节。比它大的值都会发生差错。但是从BSD/386到SunOS 4.1.3的情况下,Sun所能接收到最大IP数据报长度为32786字节(即32758字节用户数据)。在Solaris 2.2下使用环回接口,最大可收发IP数据报长度为65535字节。从Solaris 2.2到AIX 3.2.2发送的最大IP数据报长度可以是65535字节。很显然,这个限制与源端和目的端的实现有关。 

我们在3.2节中提过,要求主机必须能够接收最短为576字节的IP数据报。在许多UDP应用程序的设计中,其应用程序数据被限制成512字节或更小,因此比这个限制值小。例如,我们在10.4节中看到,路径信息协议总是发送每份数据报小于512字节的数据。我们还会在其它UDP应用程序如DNS(第14章),TFTP(第15章),BOOTP(第16章)以及SNMP(第25章)遇到这个限制。 

数据报截断 
由于IP能够发送或接收特定长度的数据报并不意味着接收应用程序可以读取该长度的数据。因此,UDP编程接口允许应用程序指定每次返回的最大字节数。如果接收到的数据报长度大于应用程序所能处理的长度,那么会发生什么情况呢? 
不幸的是,该问题的答案取决于编程接口和实现。 

(下面是原书p.160①的译文) 
典型的Berkeley版socket API对数据报进行截断,并丢弃任何多余的数据。应用程序何时能够知道则与版本有关。(4.3BSD Reno及其后的版本可以通知应用程序数据报被截断。) 
SVR4下的socket API(包括Solaris 2.x) 并不截断数据报。超出部分数据在后面的读取中返回。它也不通知应用程序从单个UDP数据报中多次进行读取操作。 
TLI API不丢弃数据。相反地,它返回一个标志表明可以获得更多的数据,而应用程序后面的读操作将返回数据报的其余部分。 

在我们讨论TCP时,我们发现它为应用程序提供连续的字节流,而没有任何信息边界。TCP以应用程序读操作时所要求的长度来传送数据,因此,在这个接口下,不会发生数据丢失。 

11.11 ICMP源站抑制差错 
我们同样也可以使用UDP产生ICMP“源站抑制(source quench)”差错。当一个系统(路由器或主机)接收数据报的速度比其处理速度快时,可能产生这个差错。注意限定词“可能”。即使一个系统已经没有缓存并丢弃数据报,也不要求它一定要发送源站抑制报文。 
图11.18给出了ICMP源站抑制差错报文的格式。我们有一个很好的方案可以在我们的测试网络里产生该差错报文。我们可以从bsdi通过必须经过拨号SLIP链路的以太网,将数据报发送给路由器sun。由于SLIP链路的速度大约只有以太网的千分之一,因此,我们很容易就可以使其缓存用完。下面的命令行从主机bsdi通过路由器sun发送100个1024字节长数据报给solaris。我们将数据报发送给标准的丢弃服务,这样,这些数据报将被忽略: 

bsdi % sock -u -I -w1024 -n100 solaris discard 

图11.18 ICMP源站抑制差错报文格式 

图11.19给出了与此命令行相对应的tcpdump输出结果 

图11.19 来自路由器sun的ICMP源站抑制 

在这个输出结果中,我们删除了很多行,这只是一个模型。接收前26个数据报时未发生差错;我们只给出了第一个数据报的结果。然而,从我们的第27个数据报开始,我们每发送一份数据报,就会接收到一份源站抑制差错报文。总共有26 +(74×2)=174行输出结果。 
从我们2.10节的并行线吞吐率计算结果可以知道,以9600 b/s速率传送1024字节数据报只需要1秒时间。(由于从sun到netb的SLIP链路的MTU为552字节,因此在我们的例子中,20 + 8 + 1024字节数据报将进行分片,因此,其时间会稍长一些。)但是我们可以从图11.19的时间中看出,sun路由器在不到1秒时间内就处理完所有的100个数据报,而这时,第一份数据报还未通过SLIP链路。因此我们用完其缓存就不足不奇了。 

(下面是原书p.161①的译文) 
尽管RFC 1009 [Braden and Postel 1987] 要求路由器在没有缓存时产生源站抑制差错报文,但是新的Router Requirements RFC [Almquist 1993] 对此作了修改,提出路由器不应该产生源站抑制差错报文。由于源站抑制要消耗网络带宽且对于拥塞来说是一种无效而不公平的调整,因此现在人们对于源站抑制差错的态度是不支持的。 

在本例中,还需要指出的是,我们的sock程序要么没有接收到源站抑制差错报文,或者接收到却将它们忽略了。结果是如果采用UDP协议,那么BSD实现通常忽略其接收到的源站抑制报文。(正如我们在21.10节所讨论的那样,TCP接受源站抑制差错报文,并将放慢在该连接上的数据传输速度。)其部分原因在于,在接收到源站抑制差错报文时,导致源站抑制的进程可能已经中止了。实际上,如果我们使用Unix 的time程序来测定我们的sock程序所运行的时间,其结果是它只运行了大约0.5秒时间。但是从图11.19中我们可以看到,在发送第一份数据报过后0.71秒才接收到一些源站抑制,而此时该进程已经中止。其原因是我们的程序写入了100个数据报然后中止了。但是所有的100个数据报都已发送出去——有一些数据报在输出队列中。 
这个例子重申了UDP是一个非可靠的协议,它说明了端到端的流量控制。尽管我们的sock程序成功地将100个数据报写入其网络,只有26个数据报真正发送到了目的端。其它74个数据报可能被中间路由器所丢弃。除非我们在应用程序中建立一些回答机制,否则发送端并不知道接收端是否收到了这些数据。 

11.12 UDP服务器的设计 
使用UDP的一些蕴含对于设计和实现服务器产生影响。通常,客户端的设计和实现比服务器端的要容易一些,这就是我们为什么要讨论服务器的设计,而不是讨论客户端的设计的原因。典型的服务器与操作系统进行交互作用,而且大多数需要同时处理多个客户。 
通常一个客户启动后直接与单个服务器通信,然后就结束了。而对于服务器来说,它启动后处于休眠状态,等待客户请求的到来。对于UDP来说,当客户数据报到达时,服务器苏醒过来,数据报中可能包含来自客户的某种形式的请求消息。 
在这里我们所感兴趣的并不是客户和服务器的编程方面([Stevens 1990]对这些方面的细节进行了讨论),而是UDP那些影响使用该协议的服务器的设计和实现方面的协议特性。(我们在 18.11节中对TCP服务器的设计进行了描述。)尽管我们所描述的一些特性取决于所使用UDP的实现,但对于大多数实现来说,这些特性是公共的。 

客户IP地址及端口号 
来自客户的是UDP数据报。IP首部包含源端和目的端IP地址,UDP首部包含了源端和目的端的UDP端口号。当一个应用程序接收到UDP数据报时,操作系统必须告诉它是谁发送了这份消息,即源IP地址和端口号。 
这个特性允许一个交互UDP服务器对多个客户进行处理。给每个发送请求的客户发回回答。 

目的IP地址 
一些应用程序需要知道数据报是发送给谁的,即目的IP地址。例如,Host Requirements RFC规定,TFTP服务器必须忽略接收到的发往广播地址的数据报。(我们分别在第12章和第15章对广播和TFTP进行描述。) 
这要求操作系统从接收到的UDP数据报中将目的IP地址交给应用程序。不幸的是,并非所有的实现都提供这个功能。 

(下面是原书p.163①的译文) 
socket API以IP_RECVDSTADDR socket选项提供了这个功能。对于本文中使用的系统,只有BSD/386,4.4BSD和AIX 3.2.2支持该选项。SVR4,SunOS 4.x和Solaris 2.x都不支持该选项。 

UDP输入队列 
我们在1.8节中说过,大多数UDP服务器是交互服务器。这意味着,单个服务器进程对单个UDP端口上(服务器上的名知端口)的所有客户请求进行处理。 
通常程序所使用的每个UDP端口都与一个有限大小的输入队列相联系。这意味着,来自不同客户的差不多同时到达的请求将由UDP自动排队。接收到的UDP数据报以其接收顺序交给应用程序(在应用程序要求交送下一个数据报时)。 
然而,排队溢出造成内核中的UDP模块丢弃数据报的可能性是存在的。我们可以进行以下试验。我们在作为UDP服务器的bsdi主机上运行sock程序: 

bsdi % sock -s -u -v -E -R256 -P30 6666 
from 140.252.13.33, to 140.252.13.63: 1111111111 从sun发送到广播地址 
from 140.252.13.34, to 140.252.13.35: 4444444444444 从svr4发送到单目地址 

我们指明以下标志:-s表示作为服务器运行,-u表示UDP,-v表示打印客户的IP地址,-E表示打印目的IP地址(该系统支持这个功能)。另外,我们将这个端口的UDP接收缓存设置为256字节(-R),其每次应用程序读取的大小也是这个数(-r)。标志 -P30 表示创建UDP端口后,先暂停30秒后再读取第一个数据报。这样,我们就有时间在另两台主机上启动客户程序,发送一些数据报,以查看接收队列是如何工作的。 
服务器一开始工作,处于其30秒的暂停时间内,我们就在sun主机上启动一个客户,并发送三个数据报: 

sun % sock -u -v 140.252.13.63 6666 到以太网广播地址 
connected on 140.252.13.33.1252 to 140.252.13.63.6666 
1111111111 11字节的数据(新行) 
222222222 10字节的数据(新行) 
3333333333312字节的数据(新行) 

目的地址是广播地址(140.252.13.63)。我们同时也在主机svr4上启动第2个客户,并发送另外三个数据报: 

svr4 % sock -u -v bsdi 6666 
connected on 0.0.0.0.1042 to 140.252.13.35.6666 
4444444444444 14字节的数据(新行) 
555555555555555 16字节的数据(新行) 
66666666 9字节的数据(新行) 

首先,我们早些时候在bsdi上所看到的结果表明,应用程序只接收到2个数据报:来自sun的第一个全1报文,和来自svr4的第一个全4报文。其它4个数据报看来全被丢弃。 
图11.20给出的tcpdump输出结果表明,所有6个数据报都发送给了目的主机。两个客户的数据报以交替顺序键入:第一个来自sun,然后是来自svr4的,以此类推。我们同时也可以看出,全部6个数据报大约在12秒内发送完毕,也就是在服务器休眠的30秒内完成的。 

图11.20 两个客户发送UDP数据报的tcpdump输出结果 

我们还可以看到,服务器的-E选项使其可以知道每个数据报的目的IP地址。如果需要的话,它可以选择如何处理其接收到的第一个数据报,这个数据报的地址是广播地址。 
我们可以从本例中看到以下几个要点。首先,应用程序并不知道其输入队列何时溢出。只是由UDP对超出数据报进行丢弃处理。同时,从tcpdump输出结果,我们看到,没有发回任何信息告诉客户其数据报被丢弃。这里不存在象ICMP源站抑制这样发回发送端的消息。最后,看来UDP输出队列是FIFO(先进先出)的,而我们在11.9节中所看到的ARP输入却是LIFO(后进先出)的。 

限制本地IP地址 
大多数UDP服务器在创建UDP端点时都使其本地IP地址具有通配符(wildcard)的特点。这就表明进入的UDP数据报如果其目的地为服务器端口,那么在任何本地接口均可接收到它。例如,我们以端口号777启动一个UDP服务器: 

sun % sock -u -s 7777 

然后,我们用netstat命令观察端点的状态: 

sun % netstat -a -n -f inet 
Active Internet connections (including servers) 
Proto Recv-Q Send-Q Local Address Foreign Address (state) 
udp 0 0 *.7777 *.* 

这里,我们删除了许多行,只保留了其中我们感兴趣的东西。-a选项表示报告所有网络端点的状态。-n选项表示以点数格式打印IP地址而不用DNS把地址转换成名字,打印数字端口号而不是服务名称。-f inet选项表示只报告TCP和UDP端点。 
本地地址以*.7777格式打印,星号表示任何本地IP地址。 
当服务器创建端点时,它可以把其中一个主机本地IP地址包括广播地址指定为端点的本地IP地址。只有当目的IP地址与指定的地址相匹配时,进入的UDP数据报才能被送到这个端点。用我们的sock程序,如果我们在端口号之前指定一个IP地址,那么该IP地址就成为该端点的本地IP地址。例如: 

sun % sock -u -s 140.252.1.29 7777 

就限制服务器在SLIP接口(140.252.1.29)处接收数据报。netstat输出结果显示如下: 

Proto Recv-Q Send-Q Local Address Foreign Address (state) 
udp 0 0 140.252.1.29.7777 *.* 


如果我们试图在以太网上的主机bsdi以地址140.252.13.35向该服务器发送一份数据报,那么将返回一个ICMP端口不可到达差错。服务器永远看不到这份数据报。这种情形如图11.21所示。 

图11.21 服务器本地地址绑定导致拒绝接收UDP数据报 

有可能在相同的端口上启动不同的服务器,每个服务器具有不同的本地IP地址。但是,一般必须告诉系统应用程序重用相同的端口号没有问题。 

(下面是原书p.165①的译文) 
使用sockets API时,必须指定SO_REUSEADDR socket选项。在我们的sock程序中是通过-A选项来完成的。 

在我们的主机sun上,我们可以在同一个端口号(8888)上启动5个不同的服务器: 

(见原书p.165的②) 

除了第一个以外,其他的服务器都必须以-A选项启动,告诉系统可以重用同一个端口号。5个服务器的netstat输出结果如下所示: 

(见原书p.165的③) 

在这种情况下,到达服务器的数据报中,只有带星号的本地IP地址其目的地址为140.252.1.255,因为其他4个服务器占用了其他所有可能的IP地址。 
如果存在一个含星号的IP地址,那么就隐含了一种优先级关系。如果为端点指定了特定IP地址,那么在匹配目的地址时始终优先匹配该IP地址。只有在匹配不成功时才使用含星号的端点。 

限制外部IP地址 
在前面所有的netstat结果输出中,外部IP地址和外部端口号都显示为*.* ,其意思是该端点将接受来自任何IP地址和任何端口号的UDP数据报。大多数系统允许UDP端点对外部地址进行限制。 
这说明端点将只能接收特定IP地址和端口号的UDP数据报。我们的sock程序用-f 选项来指定外部IP地址和端口号: 

sun % sock -u -s -f 140.252.13.35.4444 5555 

这样就设置的外部IP地址140.252.13.35(即主机bsdi)和外部端口号4444 。服务器的有名端口号为5555。如果我们运行netstat命令,我们发现本地IP地址也被设置了,尽管我们没有指定。 

Proto Recv-Q Send-Q Local Address Foreign Address (state) 
udp 0 0 140.252.13.33.5555 140.252.13.35.4444 

这是在伯克利派生系统中指定外部IP地址和端口号带来的副作用:如果在指定外部地址时没有选择本地地址,那么将自动选择本地址址。它的值就成为选择到达外部IP地址路由时将选择的接口IP地址。事实上,在这个例子中,sun在以太网上的IP地址与外部地址140.252.13.33相连。 
图11.22总结了UDP服务器本身可以创建的三类地址绑定。 

(以下是图11.22中的部分译文) 
本地地址 
外部地址 
描述 
localIP.lport 
foreignIP.fport 
只限于一个客户 
localIP.lport 
*.* 
限于到达一个本地接口的数据报:localIP 
*.lport 
*.* 
接收发送到lport的所有数据报 
图11.22 为UDP服务器指定本地和外部IP地址及端口号 

在所有情况下,lport指的是服务器有名端口号,localIP必须是本地接口的IP地址。表中这三行的排序是UDP模块在判断用哪个端点接收数据报时所采用的顺序。最为确定的地址(第一行)首先被匹配,最不确定的地址(最后一行IP地址带有两个星号)最后进行匹配。 

每个端口有多个接收者 
尽管在RFC中没有指明,但大多数的系统在某一时刻只允许一个程序端点与某个本地IP地址及UDP端口号相关联。当目的地为该IP地址及端口号的UDP数据报到达主机时,就复制一份传给该端点。端点的IP地址可以含星号,正如我们前面讨论的那样。 
例如,在SunOS 4.1.3中,我们启动一个端口号为9999的服务器,本地IP地址含有星号: 

sun % sock -u -s 9999 

接着,如果我们启动另一个具有相同本地地址和端口号的服务器,那么它将不运行,尽管我们指定了-A选项: 

sun % sock -u -s 9999 我们预计它会失败 
can't bind local address: Address already in use 

sun % sock -u -s -A 9999 因此这次尝试-A参数 
can't bind local address: Address already in use 

在一个支持多播的系统上(第12章),这种情况将发生变化。多个端点可以使用同一个IP地址和UDP端口号,尽管应用程序通常必须告诉API是可行的(如,用我们的-A标志来指明SO_REUSEADDR socket选项)。 

(下面是原书p.167①的译文) 
4.4BSD支持多目传送,需要应用程序设置一个不同的socket选项(SO_REUSEPORT)以允许多个端点共享同一个端口。另外,每个端点必须指定这个选项,包括使用该端口的第一个端点。 

当UDP数据报到达的目的IP地址为广播地址或多播地址,而且在目的IP地址和端口号处有多个端点,那么就向每个端点传送一份数据报复制。(端点的本地IP地址可以含有星号,它可匹配任何目的IP地址。)但是,如果UDP数据报到达的是一个单目地址,那么只向其中一个端点传送一份数据报复制。选择哪个端点传送数据取决于各个不同的系统实现。 

11.13 小结 
UDP是一个简单协议。它的正式规约是RFC 768 [Postel 1980],只包含三页内容。它向用户进程提供的服务位于IP层之上,包括端口号和可选的检验和。我们用UDP来检查检验和,并观察分片是如何进行的。 
接着,我们的讨论了ICMP不可到达差错,它是新的路径MTU发现功能中的一部分(2.9节)。我们用Traceroute和UDP来观察路径MTU发现过程。我们还查看了UDP和ARP之间的接口,大多数的ARP实现在等待ARP回答时只保留最近传送给目的端的数据报。 
当系统接收IP数据报的速率超过这些数据报被处理的速率时,系统可能发送ICMP源站抑制差错报文。使用UDP时很容易产生这样的ICMP差错。 

习题 
11.1 在11.5节中,我们向UDP数据报中写入1473字节用户数据时导致以太网数据报片的发生。在采用以太网IEEE802封装格式时,导致分片的最小用户数据长度为多少? 
11.2 阅读RFC 791[Postel 1981a],理解为什么除最后一片外,其他片中的数据长度均要求为8字节整数倍? 
11.3 假定有一个以太网和一份8192字节的UDP数据报,那么需要分成多少个数据报片,每个数据报片的偏移和长度为多少? 
11.4 继续前一习题,假定这些数据报片要经过一条MTU为552的SLIP链路。必须记住每一个数据报片中的数据(除IP首部外)为8字节整数倍。那么又将分成多少个数据报片,每个数据报片的偏移和长度为多少? 
11.5 一个用UDP发送数据报的应用程序,它把数据报分成4个数据报片。假定第1片和第2片到达目的端,而第3片和第4片丢失了。应用程序在10秒钟后超时重发该UDP数据报,并且被分成相同的4片(相同的偏移和长度)。假定这一次接收主机重新组装的时间为60秒,那么当重发的第3片和第4片到达目的端时,原先收到的第1片和第2片还没有被丢弃。接收端能否把这4片数据重新组装成一份IP数据报? 
11.6 你是如何知道图11.15中的片实际上与图11.14中第5行和第6行相对应? 
11.7 主机gemini开机33天后,netstat程序显示48,000,000份IP数据报中由于首部检验和差错被丢弃129份,在30,000,000个TCP段中由于TCP检验和差错而被丢弃20个。但是,在大约18,000,000份UDP数据报中,因为UDP检验和差错而被丢弃的数据报一份也没有。请说明两个方面的原因。(提示:参见图11.4 。) 
11.8 我们在讨论分片时没有提及任何关于IP首部中的选项——它们是否也要被复制到每个数据报片中,或者只留在第一个数据报片中?我们已经讨论过下面这些IP选项:记录路由(7.3节),时间戳(7.4节),严格和宽松的源站选路(8.5节)。你希望分片如何处理这些选项?对照RFC 791检查你的答案。 
11.9 在图1.8中,我们说UDP数据报是根据目的UDP端口号进行分配的。这正确的吗? 

11-1 
12 

广播和多播 

12.1 引言 

在第1章中我们提到有三种IP地址:单播地址、广播地址和多播地址。本章将更详细地介绍广播和多播。 
广播和多播仅应用于UDP,它们对需将报文同时传往多个接收者的应用来说十分重要。TCP是一个面向连接的协议,它意味着分别运行于两主机(由IP地址确定)内的两进程(由端口号确定)间存在一条连接。 
考虑包含多个主机的共享信道网络如以太网。每个以太网帧包含源主机和目的主机的以太网地址(48 bit)。通常每个以太网帧仅发往单个目的主机,目的地址指明单个接收接口,因而称为单播(unicast)。在这种方式下,任意两个主机的通信不会干扰网内其他主机(可能引起争夺共享信道的情况除外)。 
然而,有时一个主机要向网上的其他主机发送帧,这就是广播。通过ARP和RARP可以看到这一过程。多播(multicast) 处于单播和广播之间:帧仅传送给属于多播组的多个主机。 
为了弄清广播和多播,需要了解主机对由信道传送过来帧的过滤过程。图12.1说明了这一过程。 
首先,网卡查看由信道传送过来的帧,确定是否接收该帧,若接收后就将它传往设备驱动程序。通常网卡仅接收那些目的地址为网卡物理地址或广播地址的帧。另外,多数接口均被设置为混合模式,这种模式能接收每个帧的一个复制。作为一个例子,tcpdump使用这种模式。 

图12.1 协议栈各层对收到帧的过滤过程 

目前,大多数的网卡经过配置都能接收目的地址为多播地址或某些子网多播地址的帧。对于以太网,当地址中最高字节的最低位设置为1时表示该地址是一个多播地址,用十六进制可表示为01:00:00:00:00:00。(以太网广播地址ff:ff:ff:ff:ff:ff可看作是以太网多播地址的特例。) 
如果网卡收到一个帧,这个帧将被传送给设备驱动程序。(如果帧检验和错,网卡将丢弃该帧。)设备驱动程序将进行另外的帧过滤。首先,帧类型中必须指定要使用的协议(IP,ARP等等)。其次,进行多播过滤来检测该主机是否属于多播地址说明的多播组。 
设备驱动程序随后将数据帧传送给下一层,比如,当帧类型指定为IP数据报时,就传往IP层。IP根据IP地址中的源地址和目的地址进行更多的过虑检测,如果正常,将数据报传送给下一层(如TCP或UDP)。 
每次UDP收到由IP传送来的数据报,就根据目的端口号,有时还有源端口号进行数据报过滤。如果当前没有进程使用该目的端口号,就丢弃该数据报并产生一个ICMP不可达报文。(TCP根据它的端口号作相似的过虑。)如果UDP数据报存在检验和错,将被丢弃。 
使用广播的问题在于它增加了对广播数据不感兴趣主机的处理负荷。拿一个使用UDP广播应用作为例子,如果网内有50个主机,但仅有20个参与该应用,每次这20个主机中的一个发送UDP广播数据时,其余30个主机不得不处理这些广播数据报,而直到UDP层,收到的UDP广播数据报才会被丢弃。这30个主机丢弃UDP广播数据报是因为这些主机没有使用这个目的端口。 
多播的出现减少了对应用不感兴趣主机的处理负荷。使用多播,主机可加入一个或多个多播组。这样,网卡将获悉该主机属于哪个多播组,然后仅接收主机所在多播组的那些多播帧。 

12.2 广播 

在图3.9中,我们知道了四种IP广播地址,下面对它们进行更详细的介绍。 

受限的广播 
受限的广播地址是255.255.255.255。该地址用于主机配置过程中IP数据报中目的地址,此时,主机可能还不知道它所在网络的网络掩码,甚至连它的IP地址也不知道。 
在任何情况下,路由器都不转发目的地址为受限的广播地址的数据报,这样的数据报仅出现在本地网络中。 
一个未解的问题是:如果一个主机是多接口的,当一个进程向本网广播地址发送数据报时,为实现广播,是否应该将数据报发送到每个相连的接口上?如果不是这样,想对主机所有接口广播的应用必须确定主机中支持广播的所有接口,然后向每个接口发送一个数据报复制。 
大多数BSD系统将255.255.255.255看作是配置后第一个接口的广播地址,并且不提供向所属具备广播能力接口传送数据报的功能。不过,routed(见10.3)和rwhod(BSD rwho客户的服务器)是向每个接口发送UDP数据报的两个应用程序。这两个应用程序均有相似的启动过程来确定主机中的所有接口,并了解哪些接口具备广播能力。同时,将对应于那种接口的指向网络的广播地址作为向该接口发送的数据报的目的地址。 

Host Requirements RFC没有进一步涉及多接口主机是否应当向其所有的接口发送受限的广播。 

指向网络的广播 
指向网络的广播地址是主机号字段均为1的地址。A类网络广播地址为netid.255.255.255,其中netid为A类网络的网络号。 
一个路由器必须转发指向网络的广播,但它也必须有一个不进行转发的选择。 

指向子网的广播 
指向子网的广播地址为主机号码字段均为1且有特定子网号的地址。作为子网直接广播地址的IP地址需要了解子网的掩码。例如,如果路由器收到发往128.1.2.255的数据报,当B类网络128.1的子网掩码为255.255.255.0时,该地址就是指向子网的广播地址;但如果该子网的掩码为255.255.254.0,该地址就不是指向子网的广播地址。 

指向所有子网的广播 
指向所有子网的广播也需要了解目的网络的子网掩码,以便与指向网络的广播地址区分开。指向所有子网的广播地址的子网号字段及主机号字段均为1。例如,如果目的子网掩码为255.255.255.0,那么IP地址128.1.255.255是一个指向所有子网的广播地址。然而,如果网络没有划分子网,这就是一个指向网络的广播。 
当前的看法[Almquist 1993]是这种广播是陈旧过时的,更好的方式是使用多播而不是对所有子网的广播。 

[Almquist 1993] 解释了RFC 922需要一个向所有子网的广播来传送给所有子网,但当前的路由器没有这么做。这很幸运,因为一个已经错误配置的主机没有它的主机掩码会把它的本地广播传送到所有子网。例如,如果IP地址为128.1.2.3的主机没有设置子网掩码,它的广播地址在正常情况下的默认值是128.1.255.255。但如果子网掩码被设置为255.255.255.0,那么由错误配置的主机发出的广播将指向所有的子网。 

1983年问世的4.2BSD是第一个影响广泛的TCP/IP的实现,它使用主机号全0作为广播地址。一个最早有关广播IP地址参考是IEN 212 [Gurwitz and Hinden 1982],它提出用主机号中的1比特来表示IP广播地址。(IENs 是互联网试验注释,基本上是RFC的前身。)RFC 894 [Hornig 1984]认为4.2BSD使用不标准的广播地址,但RFC 906 [Finlayson 1984] 注意到对广播地址还没有Internet标准。他还在RFC 906加了一个脚注承认缺少标准的广播地址,并强烈推荐将主机号全1作为广播地址。尽管1986年的4.3BSD采用主机号全1表示广播地址,但直到90年代早期操作系统(突出的是SunOS 4.x)还继续使用非标准的广播地址。 

12.3 广播的例子 

广播是怎样传送的?路由器及主机又如何处理广播?很遗憾,这是难于回答的问题,因为它依赖于广播的类型、应用的类型、TCP/IP实现方法以及有关路由器的配置。 
首先,应用程序必须支持广播。如果执行 

sun % ping 255.255.255.255 
/usr/etc/ping: unknown host 255.255.255.255 

打算进行在本地电缆上的广播,但它无法进行,原因在于该应用程序(ping)中存在一个程序设计上的问题。大多数应用程序收到点分十进制的IP地址或主机名后,会调用函数inet_addr(3)来把它们转化为32 bit的二进制IP地址。假定要转化的是一个主机名,如果转化失败,该库函数将返回-1来表明存在某种差错(例如是字符而不是数字或串中有小数点),但本网广播地址(255.255.255.255)也被当作存在差错而返回-1。大多数程序均假定接收到的字符串是主机名,然后查找域名系统DNS(第14章),失败后输出差错信息如“未知主机”。 
如果我们修复ping程序中这个欠缺,结果也并不总是令人满意的。在6个不同系统的测试中,仅有一个像预期的那样产生了一个本网广播数据报。大多数则在路由表中查找IP地址255.255.255.255,而该地址被用作默认路由器地址,因此向默认路由器单播一个数据报。最终该数据报被丢弃。 
指向子网的广播是我们应该使用的。在6.3节中,我们向测试网络(见封2)中IP地址为 140.252.13.63 的以太网发送数据报,并接收以太网中所有主机的回答。与子网广播地址关联的每个接口是用于命令ifconfig(见3.8节)的值。如果我们ping那个地址,预期的结果是: 

(见原书p.173①) 

IP通过目的地址(140.252.13.63)来确定这是指向子网的广播地址,然后向链路层的广播地址发送该数据报。 
我们在6.3提及的这种类型广播的接收对象为包括发送主机在内的局域网中的所有主机,因此可以看到除了收到网内其他主机的答复外,还收到了来自发送主机(sun)的答复。 
在这个例子中,我们也显示了






原文链接:http://bbs.chinaunix.net/viewthread.php?tid=16063

转载请注明作者名及原文出处



加载中
返回顶部
顶部