2007年6月4日星期一

TCP/IP详解

1 概述

1.1 引言
很多不同的厂家生产各种型号的计算机,它们运行完全不同的操作系统,但TCP/IP协议组件允许它们互相进行通信。这一点很让人感到吃惊,因为它的作用已远远超出了起初的设想。TCP/IP起源于60年代末美国政府资助的一个分组交换网络研究项目,到现在90年代已发展成为计算机之间最常应用的组网形式。它是一个真正的开放系统,因为协议组件的定义及其多种实现可以不用花钱或花很少的钱就可以公开地得到。它成为被称作“全球互联网”或“因特网”(Internet)的基础,该广域网(WAN)已包含超过100万台遍布世界各地的计算机。
本章主要对TCP/IP协议组件进行概述,其目的是为本书其余章节提供充分的背景知识。如果读者要从历史的角度了解有关TCP/IP的早期发展情况,请参考文献[Lynch 1993]。

1.2 分层
网络协议通常分不同层次进行开发,每一层分别负责不同的通信功能。一个协议组件,比如TCP/IP,是一组不同层次上的多个协议的组合。TCP/IP通常被认为是一个四层协议系统,如图1.1所示。

图1.1 TCP/IP协议组件的四个层次

每一层负责不同的功能:
1. 链路层,有时也称作数据链路层或网络接口层,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆(或其他任何传输媒介)的物理接口细节。
2. 网络层,有时也称作互连网层,处理分组在网络中的活动,例如分组的路由选择。在TCP/IP协议组件中,网络层协议包括IP协议(网际协议),ICMP协议(Internet互连网控制报文协议),以及IGMP协议(Internet组管理协议)。
3. 运输层主要为两台主机上的应用程序提供端到端的通信。在TCP/IP协议组件中,有两个互不相同的传输协议:TCP(传输控制协议)和UDP(用户数据报协议)。
TCP为两台主机提供高可靠性的数据通信。它所做的工作包括把应用程序交给它的数据分成合适的小块交给下面的网络层,确认接收到的分组,设置发送最后确认分组的超时时钟等。由于运输层提供了高可靠性的端到端的通信,因此应用层可以忽略所有这些细节。
而另一方面,UDP则为应用层提供一种非常简单的服务。它只是把称作数据报的分组从一台主机发送到另一台主机,但并不保证该数据报能到达另一端。任何必需的可靠性必须由应用层来提供。
这两种运输层协议分别在不同的应用程序中有不同的用途,这一点我们将在后面看到。
4. 应用层负责处理特定的应用程序细节。几乎各种不同的TCP/IP实现都会提供下面这些通用的应用程序:
•Telnet 远程登录
•FTP 文件传输协议
•SMTP 用于电子邮件的简单邮件传输协议
•SNMP 简单网络管理协议
另外还有许多其他应用,我们在后面章节中将介绍其中的一部分。

假设我们在一个局域网(LAN)如以太网中有两台主机,二者都运行FTP协议,图1.2列出了该过程所涉及到的所有协议。

图1.2 局域网上运行FTP的两台主机

这里,我们列举了一个FTP客户程序和另一个FTP服务器程序。大多数的网络应用程序都被设计成客户-服务器模式。服务器为客户提供某种服务,在本例中就是访问服务器所在主机上的文件。在远程登录应用程序Telnet中,为客户提供的服务是登录到服务器主机上。
在同一层上,双方都有对应的一个或多个协议进行通信。例如,某个协议允许TCP层进行通信,而另一个协议则允许两个IP层进行通信。
在图1.2的右边,我们注意到应用程序通常是一个用户进程,而下三层则一般在(操作系统)内核中执行。尽管这不是必需的,但通常都是这样处理的,例如UNIX操作系统。
在图1.2中,顶层与下三层之间还有另一个关键的不同之处。应用层关心的是应用程序的细节,而不是数据在网络中的传输活动。下三层对应用程序一无所知,但它们要处理所有的通信细节。
我们在图1.2中例举了四种不同层次上的协议。FTP是一种应用层协议,TCP是一种运输层协议,IP是一种网络层协议,而以太网协议则应用于链路层上。TCP/IP协议组件是一组不同的协议组合在一起构成的协议族。尽管通常称该协议组件为TCP/IP,但TCP和IP只是其中的两种协议而已。(该协议组件的另一个名字是Internet协议族(Internet Protocol Suite)。
网络接口层和应用层的目的是很显然的――前者处理有关通信媒介的细节(以太网,令牌环网等),而后者处理某个特定的用户应用程序(FTP,Telnet等)。但是,从表面上看,网络层和运输层之间的区别不那么明显。为什么要把它们划分成两个不同的层次呢?为了理解这一点,我们必须把视野从单个网络扩展到一组网络。
在80年代,网络不断增长的原因之一是大家都意识到只有一台孤立的计算机构成的“孤岛”没有太大意义,于是就把这些孤立的系统组在一起形成网络。随着这样的发展,到了90年代,我们又逐渐认识到这种由单个网络构成的新的更大的“岛屿”同样没有太大的意义。于是,人们又把多个网络连在一起形成一个网络的网络,或称作互连网(internet)。一个互连网就是一组通过相同协议族互连在一起的网络。
构造互连网最简单的方法是把两个或多个网络通过路由器进行连接。它是一种特殊的用于网络互连的硬件盒。路由器的好处是为不同类型的物理网络提供连接:以太网,令牌环网,点对点的链接,FDDI(光纤分布式数据接口)等等。

(下面是原书p.4①的译文)
这些盒子也称作IP路由器(IP Routers),但我们这里使用路由器(Router)这个术语。
从历史上说,这些盒子称作网关(gateways),在很多TCP/IP文献中都使用这个术语。现在网关这个术语只用来表示应用层网关:一个连接两种不同协议组件的进程(例如,TCP/IP和IBM的SNA),它为某个特定的应用程序服务(常常是电子邮件或文件传输)。

图1.3是一个包含两个网络的互连网:一个以太网和一个令牌环网,通过一个路由器互相连接。尽管这里是两台主机通过路由器进行通信,实际上以太网中的任何主机都可以与令牌环网中的任何主机进行通信。
在图1.3中,我们可以划分出端系统(end system)(两边的两台主机)和中间系统(intermediate system)(中间的路由器)。应用层和运输层使用端到端(end-to-end)协议。在我们的图中,只有端系统需要这两层协议。但是,网络层提供的却是逐跳(hop-by-hop)协议,两个端系统和每个中间系统都要使用它。

图1.3 通过路由器连接的两个网络

在TCP/IP协议组件中,网络层IP提供的是一种不可靠的服务。也就是说,它只是尽可能快地把分组从源结点送到目的结点,但是并不提供任何可靠性保证。而另一方面,TCP在不可靠的IP层上提供了一个可靠的运输层。为了提供这种可靠的服务,TCP采用了超时重传,发送和接收端到端的确认分组等机制。由此可见,运输层和网络层分别负责不同的功能。
从定义上看,一个路由器具有两个或多个网络接口层(因为它连接了两个或多个网络)。任何具有多个接口的系统英文都称作是多接口的multihomed。一个主机也可以有多个接口,但一般不称作路由器, 除非它的功能只是单纯地把分组从一个接口传送到另一个接口。同样,路由器并不一定指那种在互连网中用来转发分组的特殊硬件盒。大多数的TCP/IP实现也允许一个多接口主机来担当路由器的功能,但是主机为此必须进行特殊的配置。在这种情况下,我们既可以称该系统为主机(当它运行某一应用程序时,如FTP或Telnet),也可以称之为路由器(当它把分组从一个网络转发到另一个网络时)。我们在不同的场合下使用不同的术语。
互连网的目标之一是在应用程序中隐藏所有的物理细节。虽然这一点在图1.3由两个网络组成的互连网中并不很明显,但是应用层不能关心(也不关心)一台主机是在以太网上,而另一台主机是在令牌环网上,它们通过路由器进行互连。随着增加不同类型的物理网络,可能会有20个路由器,但应用层仍然是一样的。物理细节的隐藏使得互连网功能非常强大,也非常有用。
连接网络的另一个途径是使用网桥。网桥是在链路层上对网络进行互连,而路由器则是在网络层上对网络进行互连。网桥使得多个局域网(LAN)组合在一起,这样对上层来说就好像是一个局域网。
TCP /IP倾向于使用路由器而不是网桥来连接网络,因此我们将着重介绍路由器。文献[Perlman 1992]的第12章对路由器和网桥进行了比较。

1.3 TCP/IP的分层
在TCP/IP协议组件中,有很多种协议。图1.4给出了本书将要讨论的其他协议。

图1.4 TCP/IP协议组件中不同层次的协议

TCP和UDP是两种最为著名的运输层协议,二者都使用IP作为网络层协议。
虽然TCP使用不可靠的IP服务,但它却提供一种可靠的运输层服务。本书第17章到第22章将详细讨论TCP的内部操作细节。然后,我们将介绍一些TCP的应用,如第26章中的Telnet和Rlogin,第27章中的FTP,以及第28章中的SMTP等。这些应用通常都是用户进程。
UDP为应用程序发送和接收数据报。一个数据报是指从发送方传输到接收方的一个信息单元(例如,发送方指定的一定字节数的信息)。但是与TCP不同的是,UDP是不可靠的,它不能保证数据报能安全无误地到达最终目的。本书第11章将讨论UDP,然后在第14章(域名系统:Domain Name System),第15章(简单文件传输协议Trivial File Transfer Protocol),以及第16章(引导程序协议Bootstrap Protocol)介绍使用UDP的应用程序。SNMP(简单网络管理协议)也使用了UDP协议,但是由于它还要处理许多其他的协议,因此本书把它留到第25章再进行讨论。
IP是网络层上的主要协议,同时被TCP和UDP使用。TCP和UDP的每组数据都通过端系统和每个中间路由器中的IP层在互连网中进行传输。在图1.4中,我们给出了一个直接访问IP的应用程序。这是很少见的,但也是可能的。(一些较老的路由选择协议就是以这种方式来实现的。当然新的运输层协议也有可能试用这种方式。)第3章主要讨论IP协议,但是为了使内容更加有针对性,一些细节将留在后面的章节中进行讨论。第9章和第10章讨论IP如何进行路由选择。
ICMP是IP协议的附属协议。IP层用它来与其他主机或路由器交换错误报文和其他重要信息。第6章对ICMP的有关细节进行讨论。尽管ICMP主要被IP使用,但应用程序也有可能访问它。我们将分析两个流行的诊断工具,Ping和Traceroute(第7章和第8章),它们都使用了ICMP。
IGMP是Internet组管理协议。它用来把一个UDP数据报多播到多个主机。我们在第12章中描述广播(把一个UDP数据报发送到某个指定网络上的所有主机)和多点传送的一般特性,然后在第13章中对IGMP协议本身进行描述。
ARP(地址解析协议)和RARP(逆地址解析协议)是某些网络接口(如以太网和令牌环网)使用的特殊协议,用来转换IP层和网络接口层使用的地址。我们分别在第4章和第5章对这两种协议进行分析和介绍。

1.4 互连网的地址
互连网上的每个接口必须有一个唯一的Internet地址(也称作IP地址)。IP地址长32 bit。Internet地址并不采用平面形式的地址空间,如1,2,3等。IP地址具有一定的结构,五类不同的互连网地址格式如图1.5所示。
这些32位的地址通常写成四个十进制的数,其中每个整数对应一个字节。这种表示方法称作“点分十进制表示法”(dotted decimal notation)。例如,作者的系统就是一个B类地址,它表示为:140.252.13.33。
区分各类地址的最简单方法是看它的第一个十进制整数。图1.6列出了各类地址的起止范围,其中第一个十进制整数用加黑字体表示。

图1.5五类互连网地址

图1.6 各类IP地址的范围

需要再次指出的是,多接口主机具有多个IP地址,其中每个接口都对应一个IP地址。
由于互连网上的每个接口必须有一个唯一的IP地址,因此必须要有一个管理机构为接入互连网的网络分配IP地址。这个管理机构就是互连网络信息中心(Internet Network Information Centre)称作InterNIC。InterNIC只分配网络号。主机号的分配由系统管理员来负责。

(下面是原书p.8①的译文)
Internet注册服务(IP地址和DNS域名)过去由NIC来负责,其网络地址是nic.ddn.mil。1993年4月1日,InterNIC成立。现在,NIC只负责处理国防数据网的注册请求,所有其他的Internet用户注册请求均由InterNIC负责处理,其网址是:rs.internic.net。
事实上InterNIC有三部分组成:注册服务(rs.internic.net),目录和数据库服务(ds.internic.net),以及信息服务(is.internic.net)。有关InterNIC的其他信息参见习题1.8。

有三类IP地址:单目传送地址(目标为单个主机),广播传送地址(目的端为给定网络上的所有主机),以及多目传送地址(目的端为同一组内的所有主机)。第12章和第13章将分别讨论广播传送和多目传送的更多细节。
在3.4节中,我们在介绍IP路由选择以后将进一步介绍子网的概念。图3.9给出了几个特殊的IP地址:主机号和网络号为全0或全1。

1.5 域名系统
尽管通过IP地址可以识别主机上的网络接口,进而访问主机,但是人们最喜欢使用的还是主机名。在TCP/IP领域中,域名系统(DNS)是一个分布的数据库,由它来提供IP地址和主机名之间的映射信息。我们在第14章将详细讨论DNS。
现在,我们必须理解,任何应用程序都可以调用一个标准的库函数来查看给定名字的主机的IP地址。类似地,系统还提供一个逆函数――给定主机的IP地址,查看它所对应的主机名。
大多数使用主机名作为参数的应用程序也可以把IP地址作为参数。例如,在第4章中当我们用Telnet进行远程登录时,我们既可以指定一个主机名,也可以指定一个IP地址。

1.6 封装
当应用程序用TCP传送数据时,数据被送入协议栈中,然后逐个通过每一层直到被当作一串比特流送入网络。其中每一层对收到的数据都要增加一些首部信息(有时还要增加尾部信息),该过程如图1.7所示。TCP传给IP的数据单元称作TCP报文段或简称为TCP段(TCP segment)。IP传给网络接口层的数据单元称作IP数据报(IP datagram)。通过以太网传输的比特流称作帧(frame)。
图1.7中帧头和帧尾下面所标注的数字是典型以太网帧首部的字节长度。在后面的章节中我们将详细讨论这些帧头的具体含义。
以太网数据帧的物理特性是其长度必须在46-1500字节之间。我们将在4.5节遇到最小长度的数据帧,在2.8节中遇到最大长度的数据帧。

(下面是原书p.9①的译文)
所有的Internet标准和大多数有关TCP/IP的书都使用octet这个术语来表示字节。使用这个过分雕琢的术语是有历史原因的,因为TCP/IP的很多工作都是在DEC-10系统上进行的,但是它并不使用8 bit的字节。由于现在几乎所有的计算机系统都采用8 bit的字节,因此我们在本书中使用字节(byte)这个术语。
更准确地说,图1.7中IP和网络接口层之间传送的数据单元应该是分组(packet)。分组既可以是一个IP数据报,也可以是IP数据报的一个片(fragment)。我们将在11.5节讨论IP数据报分片的详细情况。

UDP数据与TCP数据基本一致。唯一的不同是UDP传给IP的信息单元称作UDP数据报(UDP datagram),而且UDP的首部长为8字节。

图1.7 数据进入协议栈时的封装过程

回想第6页中的图1.4,由于TCP,UDP,ICMP和IGMP都要向IP传送数据,因此IP必须在生成的IP首部中加入某种标识,以表明数据属于哪一层。为此,IP在首部中存入一个长度为8比特的数值,称作协议域。1表示为ICMP协议,2表示为IGMP协议,6表示为TCP协议,17表示为UDP协议。
类似地,许多应用程序都可以使用TCP或UDP来传送数据。运输层协议在生成报文首部时要存入一个应用程序的标识符。TCP和UDP都用一个16 bit的端口号来表示不同的应用程序。TCP和UDP把源端口号和目的端口号分别存入报文首部中。
网络接口分别要发送和接收IP,ARP和RARP数据,因此也必须在以太网的帧首部中加入某种形式的标识,以指明生成数据的网络层协议。为此,以太网的帧首部也有一个16 bit的帧类型域。

1.7 分用(Demultiplexing)
当目的主机收到一个以太网数据帧时,数据就开始从协议栈中由底向上升,同时去掉各层协议加上的报文首部。每层协议盒都要去检查报文首部中的协议标识,以确定接收数据的上层协议。这个过程称作分用,图1.8显示了该过程是如何发生的。

图1.8 以太网数据帧的分用过程

(下面是原书p.11①的译文)
为协议ICMP和IGMP定位一直是一件很棘手的事情。在图1.4中,我们把它们与IP放在同一层上,那是因为事实上它们是IP的附属协议。但是在这里,我们又把它们放在IP层的上面,这是因为ICMP和IGMP报文都被封装在IP数据报中。
对于ARP和RARP我们也遇到类似的难题。在这里我们把它们放在以太网设备驱动程序的上方,这是因为它们和IP数据报一样,都有各自的以太网数据帧类型。但在图2.4中,我们又把ARP作为以太网设备驱动程序的一部分,放在IP层的下面,其原因在逻辑上是合理的。

当进一步描述TCP的细节时,我们将看到协议确实是通过目的端口号,源IP地址和源端口号进行解包的。

1.8 客户服务器模型
大部分网络应用程序在编写时都假设一端是客户,另一端是服务器,其目的是为了让服务器为客户提供一些特定的服务。
我们可以将这种服务分为两种类型:重复型或并发型。重复型服务器通过以下步骤进行交互:
I1. 等待一个客户请求的到来。
I2. 处理客户请求。
I3. 发送响应给发送请求的客户。
I4. 返回I1步骤。
重复型服务器主要的问题发生在I2状态。在这个时候,它不能为其他客户机提供服务。
相应地,并发型服务器采用以下步骤:
C1. 等待一个客户请求的到来
C2. 启动一个新的服务器来处理这个客户的请求。在这期间可能生成一个新的进程、任务或线程,并依赖底层操作系统的支持。这个步骤如何进行取决于操作系统。生成的新服务器对客户的全部请求进行处理。处理结束后,终止这个新服务器。
C3.返回C1步骤。
并发服务器的优点在于它是利用生成其他服务器的方法来处理客户的请求。也就是说,每个客户都有它自己对应的服务器。如果操作系统允许多任务,那么就可以同时为多个客户同时服务。
我们对服务器,而不是对客户进行分类的原因是因为对于一个客户来说,它通常并不能够辨别自己是与一个重复型服务器或并发型服务器进行对话。
一般来说,TCP服务器是并发的,而UDP服务器是重复的,但也存在一些例外。我们将在11.12节对UDP对其服务器产生的影响进行详细讨论,并在18.11节对TCP对其服务器的影响进行讨论。

1.9 端口号
我们前面已经指出过,TCP和UDP采用16比特的端口号来识别应用程序。那么这些端口号是如何选择的呢?
服务器一般都是通过人们所熟知的端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传输协议)服务器的UDP端口号都是69。任何TCP/IP实现所提供的服务都用众所周知的1-1023之间的端口号。这些人们所熟知的端口号由Internet端口号分配机构(Internet Assigned Numbers Authority, IANA)来管理。

(下面是原书p.13①的译文)
到1992年为止,人们所熟知的端口号介于1-255之间。256-1023之间的端口号通常都是由Unix系统占用,以提供一些特定的Unix服务――也就是说,提供一些只有Unix系统才有的,而其他操作系统可能不提供的服务。现在IANA管理1-1023之间所有的端口号。
Internet扩展服务与Unix特定服务之间的一个差别就是Telnet和Rlogin。它们二者都允许我们通过计算机网络登录到其他主机上。Telnet是采用端口号为23的TCP/IP标准且几乎可以在所有操作系统上进行实现。相反,Rlogin最开始时只是为Unix系统设计的(尽管许多非Unix系统现在也提供该服务),因此在80年代初,它的有名端口号为513。

客户端通常对它所使用的端口号并不关心,只需保证该端口号在本机上是唯一的就可以了。客户端口号又称作临时端口号(即存在时间很短暂)。这是因为它通常只是在用户运行该客户程序时才存在,而服务器则只要主机开着的,其服务就运行。
大多数TCP/IP实现给临时端口分配1024-5000之间的端口号。大于5000的端口号是为其他服务器预留的(Internet上并不常用的服务)。我们可以在后面看见许多这样的给临时端口分配端口号的例子。

(下面是原书p.13②的译文)
Solaris 2.2是一个很有名的例外。通常TCP和UDP的缺省临时端口号从32768开始。在E.4节中,我们将详细描述系统管理员如何对配置选项进行修改以改变这些缺省项。
大多数Unix系统的file/etc/services都包含了人们熟知的端口号。为了找到Telnet服务器和域名系统的端口号,我们可以运行以下语句:

(见原书p.13的③)

保留端口号
Unix系统有保留端口号的概念。只有具有超级用户特权的进程才允许给它自己分配一个保留端口号。
这些端口号介于1到1023之间,一些应用程序(如有名的Rlogin,26.2节)将它作为客户与服务器之间身份认证的一部分。

1.10 标准化过程
究竟是谁控制着TCP/IP协议组件,又是谁在定义新的标准以及其他类似的事情?事实上,有四个小组在负责Internet技术。
1. Internet协会(ISOC: Internet Society)是一个推动、支持和促进Internet不断增长和发展的专业组织,它把Internet作为全球研究通信的基础设施。
2. Internet体系结构委员会(IAB:Internet Architecture Board)是一个技术监督和协调的机构。它由国际上来自不同专业的15个志愿者组成,其职能是负责Internet标准的最后编辑和技术审核。IAB隶属于ISOC。
3. Internet工程专门小组(IETF:Internet Engineering Task Force)是一个面向近期标准的组织,它分为9个领域(应用,寻径和寻址,安全等等)。IETF开发成为Internet标准的规约。为帮助IETF主席,又成立了Internet工程指导小组(IESG:Internet Engineering Steering Group)。
4. Internet研究专门小组主要对长远的项目进行研究。
IRTF和IETF都隶属于IAB。文献[Crocker 1993]提供了关于Internet内部标准化进程更为详细的信息,同时还介绍了它的早期历史。

1.11 RFC
所有关于Internet的正式标准都以RFC(Request for Comment)文档出版。另外,大量的RFC并不是正式的标准,出版的目的只是为了提供信息。RFC的篇幅从1页到200页不等。每一项都用一个数字来标识,如RFC 1122,数字越大说是RFC的内容越新。
所有的RFC都可以通过电子邮件或用FTP从Internet上免费获取。如果发送下面这份电子邮件,你就会收到一份获取RFC的方法清单:

To: rfc-info@ISI.EDU
Subject: getting rfcs
help: ways_to_get_rfcs

最新的RFC索引总是搜索信息的起点。这个索引列出了RFC被替换或局部更新的时间。
下面是一些重要的RFC文档:
1. 赋值RFC(Assigned Numbers RFC)列出了所有Internet协议中使用的数字和常数。至本书出版时为止,最新RFC的编号是1340 [Reynolds and Postel 1992]。所有著名的Internet端口号都列在这里。
当这个RFC被更新时(通常每年至少更新一次),索引清单会列出RFC 1340被替换的时间。
2. Internet正式协议标准,目前是RFC 1600[Postel 1994]。这个RFC描述了各种Internet协议的标准化现状。每种协议都处于下面几种标准化状态之一:标准,草案标准,提议标准,实验标准,信息标准,和历史标准。另外,对每种协议都有一个要求的层次:必需的,建议的,可选择的,限制使用的,或者不推荐的。
与赋值RFC一样,这个RFC也定期更新。请一定随时查看最新版本。
3. 主机需求RFC,1122和1123[Braden 1989a, 1989b]。RFC 1122针对链路层,网络层和运输层,RFC 1123针对应用层。这两个RFC对早期重要的RFC文档作了大量的纠正和解释。如果要查看有关协议更详细的细节内容,它们通常是一个入口点。它们列出了协议中关于“必须”,“应该”,“可以”,“不应该”或者“不能”等特性及其实现细节。
文献[Borman 1993b]提供了有关这两个RFC的实用内容。RFC 1127[Braden 1989c]对工作小组开发主机需求RFC过程中的讨论内容和结论进行了非正式的总结。
4.路由器需求RFC,目前正式版是RFC 1009[Braden and Postel 1987],但一个新版已接近完成[Aknqyust 1993]。它与主机需求RFC类似,但是只单独描述了路由器的需求。

1.12 标准的简单服务
有一些标准的简单服务几乎每种实现都要提供。在本书中我们将使用其中的一些服务程序,而客户程序通常选择Telnet。图1.9描述了这些服务。从该图我们可以看出,当使用TCP和UDP提供相同的服务时,一般选择相同的端口号.

(下面是原书p.15①的译文)
如果仔细检查这些标准的简单服务以及其他标准的TCP/IP服务(如Telnet, FTP, SMTP等)的端口号时,我们发现它们都是奇数。这是有历史原因的,因为这些端口号都是从NCP端口号派生出来的。(NCP,即网络控制协议,是ARPANET的运输层协议,是TCP的前身。NCP是单工的,不是全双工的,因此每个应用程序需要两个连接,需预留一对奇数和偶数端口号。当TCP和UDP成为标准的运输层协议时,每个应用程序只需要一个端口号,因此就使用了NCP中的奇数。

(以下是原书p.16图1.9的译文)
名字
TCP端口号
UDP端口号
RFC
描述
echo
7
7
862
服务器返回客户发送的所有内容。
discard
9
9
863
服务器丢弃客户发送的所有内容。
daytime
13
13
867
服务器以可读形式返回时间和日期。
chargen
19
19
864
当客户发送一个数据报时,TCP服务器发送一串连续的字符流,直到客户中断连接。UDP服务器发送一个随机长度的数据报。
time
37
37
868
服务器返回一个二进制形式的32 bit数,表示从UTC时间1900年1月1日午夜至今的秒数。

图1.9 大多数实现都提供的标准的简单服务

1.13 互连网
在图1.3中,我们列举了一个由两个网络组成的互连网-一个以太网和一个令牌环网。在1.4节和1.9节中,我们讨论了世界范围内的互连网-Internet,以及集中分配IP地址的需要(InterNIC),还讨论了著名的端口号(IANA)。internet这个词第一个字母是否大写决定了它具有不同的含义。
internet意思是用一个共同的协议族把多个网络连接在一起。而Internet指的是世界范围内通过TCP/IP互相通信的所有主机集合(超过100万台)。Internet是一个internet(互连网),但internet不等于Internet。

1.14 实现
既成事实标准的TCP/IP软件实现来自于位于伯克利的加利福尼亚大学的计算机系统研究小组。从历史上看,软件是随同4.x BSD系统(Berkeley Software Distribution)的网络版一起发布的。它的源代码是许多其他实现的基础。
图1.10列举了各种BSD版本发布的时间,并标注了重要的TCP/IP特性。列在左边的BSD网络版,其所有的网络源代码可以公开得到:包括协议本身以及许多应用程序和工具(如Telnet和FTP)。
在本书中,我们将使用“伯克利派生系统”来指SunOS 4.x, SVR4, 以及AIX 3.2等那些基于伯克利源代码开发的系统。这些系统有很多共同之处,经常包含相同的错误。

(以下是原书p.17图1.10的译文)
4.2BSD (1983) 第一个广泛可用的TCP/IP发布
4.3BSD (1986) TCP性能得到改善
4.3BSD Tahoe (1988) 启动慢,拥塞避免措施
BSD网络软件1.0版(1989):Net/1
4.3BSD Reno(1990) TCP首部预测,SLIP首部压缩
路由表修改
BSD网络软件2.0版(1991):Net/2
4.4BSD(1993) 多播,长胖管道修改
4.4BSD-Lite (1994)
又称为Net/3
图1.10 不同的BSD版及其重要的TCP/IP特性

起初关于Internet的很多研究现在仍然在伯克利系统中应用-新的拥塞控制算法(21.7节),多目传送(12.4节),“又长又胖的管道”修改(24.3节),以及其他类似的研究。

1.15 应用编程接口
使用TCP/IP协议的应用程序通常采用两种应用编程接口(API):socket和TLI(运输层接口:Transport Layer Interface)。前者有时称作“Berkeley socket”,表明它是从伯克利版发展而来的。后者起初是由AT&T开发的,有时称作XTI(X/Open传输接口),以承认X/Open这个自己定义标准的国际计算机生产产商所做的工作。XTI实际上是TLI的一个超集。
本书不是一本编程方面的书,但是偶尔会引用一些内容来说明TCP/IP的特性,不管大多数的API(socket)是否提供它们。所有关于socket和TLI的编程细节请参阅文献[Stevens 1990]。

1.16 测试网络
图1.11是本书中所有的例子运行的测试网络。为阅读时参考方便,该图还复制在本书的封面内侧。

图1.11 本书例子运行的测试网络,所有的IP地址均从140.252开始编址。

在这个图中(作者的子网),大多数的例子都运行在下面四个系统中。图中所有的IP地址属于B类地址,网络号为140.252。所有的主机名属于.tuc.noao.edu这个域。(noao代表National Optical Astronomy Observatories,tuc代表Tucson)。例如,右下方的系统有一个完整的名字: svr4.tuc.noao.edu,其IP地址是:140.252.13.34。每个方框上方的名称是该主机运行的操作系统。这一组系统和网络上的主机及路由器运行于不同的TCP/IP实现。
需要指出的是,noao.edu这个域中的网络和主机要比图1.11中的多得多。这里列出来的只是本书中将要用到的系统。
在3.4节中,我们将描述这个网络所用到的子网形式,在4.6中我们将更介绍sun与netb之间的拔号SLIP的有关细节。2.4节将详细讨论SLIP。

1.17 小结
本章快速地浏览了TCP/IP协议族,介绍了我们在后面的章节中将要详细讨论的许多术语和协议。
TCP/IP协议族分为四层:链路层,网络层,运输层和应用层,每一层各有不同的责任。在TCP/IP中,网络层和运输层之间的区别是最为关键的:网络层(IP)提供点到点的服务,而运输层(TCP和UDP)提供端到端的服务。
一个互连网是网络的网络。构造互连网的共同基石是路由器,它们在IP层把网络连在一起。第一个字母大写的Internet是指分布在世界各地的大型互连网,其中包括1万多个网络和超过100万台主机。
在一个互连网上,每个接口都用IP地址来标识,尽管用户习惯使用主机名而不是IP地址。域名系统为主机名和IP地址之间提供动态的映射。端口号用来标识互相通信的应用程序。服务器使用众所周知的端口号,而客户使用临时设定的端口号。


习题
1.1 请计算最多有多少个A类、B类和C类网络号。
1.2 用匿名FTP(见27.3节)从主机nic.merit.edu 上获取文件nsfnet/statistics/history.netcount。该文件包含在NSFNET网络上登记的国内和国外的网络数。画一坐标系,横坐标代表年,纵坐标代表网络总数的对数值。纵坐标的最大值是习题1.1的结果。如果数据显示一个明显的趋势,请估计按照当前的编址体制推算,何时会用完所有的网络地址。(3.10节讨论解决该难题的建议。)
1.3 获取一份主机需求RFC拷贝[Braden 1989a],阅读有关应用于TCP/IP协议族每一层的稳健性原则。这个原则的参考对象是什么?
1.4 获取一份最新的赋值RFC拷贝。“quote of the day"协议的有名端口号是什么?哪个RFC对该协议进行了定义?
1.5 如果你有一个接入TCP/IP互连网的主机帐号,它的主IP地址是多少?这台主机是否接入了Internet?它是多接口主机吗?
1.6 获取一份RFC 1000的拷贝,了解RFC这个术语从何而来。
1.7 与Internet协会联系,isoc@isoc.org或者+1 703 648 9888,了解有关加入的情况。
1.8 用匿名FTP从主机is.internic.net处获取文件about-internic/information-about-the-internic。
1-1
2 链路层

2.1 引言
从图1.4我们可以看出,在TCP/IP协议族中,链路层主要有三个目的:(1)为IP模块发送和接收IP数据报;(2)为ARP模块发送ARP请求和接收ARP应答;(3)为RARP发送RARP请求和接收RARP应答。TCP/IP支持多种不同的链路层协议,这取决于网络所使用的硬件,如以太网,令牌环网,FDDI(光纤分布式数据接口),RS-232串行线路等。
在本章中,我们将详细讨论以太网链路层协议,两个串行接口链路层协议(SLIP和PPP),以及大多数实现都包含的环回(loopback)驱动程序。以太网和SLIP是本书中大多数例子使用的链路层。我们对MTU(最大传输单元)进行了介绍,这个概念在本书的后面章节中将多次遇到。我们还讨论了如何为串行线路选择MTU。

2.2 以太网和IEEE 802封装
以太网这个术语一般是指数字设备公司(Digital Equipment Corp.)、 英特尔公司(Intel Corp.)、和Xerox公司联合在1982年公布的一个标准。它是当今TCP/IP采用的主要的局域网技术。它采用一种称作CSMA/CD的媒体接入方法,其意思是载波侦听多路接入/冲突检测(Carrier Sense, Multiple Access with Collision Detection)。它的速率为10 Mb/s,地址为48 bit。
几年后,IEEE(电子电气工程师协会)802委员会公布了一个稍有不同的标准集,其中802.3针对了整个CSMA/CD网络,802.4针对令牌总线网络,802.5针对令牌环网络。这三者的共同特性由802.2标准来定义,那就是802网络共有的逻辑链路控制(LLC)。不幸的是,802.2和802.3定义了一个与以太网不同的帧格式。文献[Stallings 1987]对所有的IEEE 802标准进行了详细的介绍。
在TCP/IP世界中,以太网IP数据报的封装是在RFC 894[Hornig 1984]中定义的,IEEE 802网络的IP数据报封装是在RFC 1042[Postel and Reynolds 1988]中定义的。主机需求RFC要求每台Internet主机都与一个10Mbit/s的以太网电缆相连接:
1. 必须能发送和接收采用RFC 894(以太网)封装格式的分组。
2. 应该能接收与RFC 894混合的RFC 1042(IEEE 802)封装格式的分组。
3. 也许能够发送采用RFC 1042格式封装的分组。如果主机能同时发送两种类型的分组数据,那么发送的分组必须是可以设置的,而且默认条件下必须是RFC 894分组。
最常使用的封装格式是RFC 894定义的格式。图2.1显示了两种不同形式的封装格式。图中每个方框下面的数字是它们的字节长度。
两种帧格式都采用48 bit(6字节)的目标地址和源地址。(802.3允许使用16 bit的地址,但一般是48 bit地址。)这就是我们在本书中所称的硬件地址。ARP和RARP协议(第4章和第5章)对32 bit的IP地址和48 bit的硬件地址进行映射。
接下来的2个字节在两种帧格式中互不相同。在802标准定义的帧格式中,长度字段是指它后续数据的字节长度,但不包括CRC检验码。以太网的类型字段定义了后续数据的类型。在802标准定义的帧格式中,类型字段则由后续的子网接入协议(Sub-network Access Protocol,SNAP)的首部给出。幸运的是,802定义的有效长度值与以太网的有效类型值无一相同,这样,就可以对两种帧格式进行区分。
在以太网帧格式中,类型字段之后就是数据,而在802帧格式中,跟随在后面的是3字节的802.2 LLC和5字节的802.2 SNAP。目的服务访问点(Destination Service Access Point, DSAP)和源服务访问点(Source Service Access Point, SSAP)的值都设为0xaa。ctrl字段的值设为3。随后的3个字节org code都置为0。再接下来的2个字节类型字段和以太网帧格式一样。(其他类型字段值可以参见RFC 1340 [Reynolds and Postel 1992])。
CRC字段用于帧内后续字节差错的循环冗余码检验(检验和)。(它也被称为FCS或帧检验序列)
802.3标准定义的帧和以太网的帧都有最小长度要求。802.3规定数据部分必须至少为38字节,而对于以太网,则要求最少要有46字节。为了保证这一点,必须在不足的空间插入填充(pad)字节。我们在开始观察线路上的分组时将遇到这种最小长度的情况。
在本书中,我们在需要的时候将给出以太网的封装格式,因为这是最为常见的封装格式。

图2.1 IEEE 802.2/802.3(RFC 1042)和以太网的封装格式(RFC 894)

2.3 尾部封装
RFC 893[Leffler and Karels 1984]描述了另一种用于以太网的封装格式,称作尾部封装(trailer encapsulation)。这是一个早期BSD系统在DEC VAX机上运行时的试验格式,它通过调整IP数据报中字段的次序来提高性能。在以太网数据帧中,开始的那部分是变长的字段(IP首部和TCP首部)。把它们移到尾部(在CRC之前),这样当把数据复制到内核时,就可以把数据帧中的数据部分映射到一个硬件页面,节省内存到内存的复制过程。TCP数据报的长度是512字节的整数倍,正好可以用内核中的页表来处理。两台主机通过协商使用ARP扩展协议对数据帧进行尾部封装。这些数据帧需定义不同的以太网帧类型值。
现在,尾部封装已遭到反对,因此我们不对它举任何例子。有兴趣的读者请参阅RFC 893以及文献[Leffler et al. 1989]的11.8节。

2.4 SLIP:串行线路IP
SLIP的全称是Serial Line IP。它是一种在串行线路上对IP数据报进行封装的简单形式,在RFC 1055[Romkey 1988]中有详细描述。SLIP适用于家庭中每台计算机几乎都有的RS-232串行端口和高速调制解调器接入Internet。
下面的规则描述了SLIP协议定义的帧格式:
1.IP数据报以一个称作END(0xc0)的特殊字符结束。同时,为了防止数据报到来之前的线路噪声被当成数据报内容,大多数实现在数据报的开始处也传一个END字符。(如果有线路噪声,那么END字符将结束这份错误的报文。这样当前的报文得以正确的传输,而前一个错误报文交给上层后,会被发现其内容毫无意义而被丢弃。)
2.如果IP报文中某个字符为END,那么就要连续传输两个字节0xdb, 0xdc来取代它。0xdb这个特殊字符被称作SLIP的ESC字符,但是它的值与ASCII码的ESC字符(0x1b)不同。
3. 如果IP报文中某个字符为SLIP的ESC字符,那么就要连续传输两个字节0xdb,0xdd来取代它。
图2.2中的例子就是含有一个END字符和一个ESC字符的IP报文。在这个例子中,在串行线路上传输的总字节数是原IP报文长度再加4个字节。
SLIP是一种简单的帧封装方法,还有一些值得一提的缺陷:
1. 每一端必须知道对方的IP地址。没有办法把本端的IP地址通知给另一端。
2. 数据帧中没有类型字段(类似于以太网中的类型字段)。如果一条串行线路用于SLIP,那么它不能同时使用其他协议。
3. SLIP没有在数据帧中加上检验和(类似于以太网中的CRC字段)。如果SLIP传输的报文被线路噪声影响而发生错误,只能通过上层协议来发现。(另一种方法是,新型的调制解调器可以检测并纠正错误报文。)这样,上层协议提供某种形式的CRC就显得很重要。在第3章和第17章中,我们将看到IP首部和TCP首部及其数据始终都有检验和。在第11章中,我们将看到UDP首部及其数据的检验和却是可选的。

图2.2 SLIP报文的封装

尽管存在这些缺点,SLIP仍然是一种广泛使用的协议。

(下面是原书p.25①的译文)
SLIP的历史要追溯到1984年,Rick Adams第一次在4.2BSD系统中实现。尽管它本身的描述是一种非标准的协议,但是随着调制解调器的速率和可靠性的提高,SLIP越来越流行。现在,它的许多产品可以公开获得,而且很多产家都支持这种协议。

2.5 压缩的SLIP
由于串行线路的速率通常较低(19200 b/s或更低),而且通信经常是交互式的(如Telnet和Rlogin,二者都使用TCP),因此在SLIP线路上有许多小的TCP分组进行交换。为了传送1个字节的数据需要20个字节的IP首部和20个字节的TCP首部,总数超过40个字节。(19.2节描述了Rlogin会话过程中,当敲入一个简单命令时这些小报文传输的详细情况。)
既然承认这些性能上的缺陷,于是人们提出一个被称作CSLIP(即压缩SLIP)的新协议,它在RFC 1144[Jacobson 1990a]中被详细描述。CSLIP一般能把上面的40个字节压缩到3或5个字节。它能在CSLIP的每一端维持多达16个TCP连接,并且知道其中每个连接的首部中的某些字段一般不会发生变化。对于那些发生变化的字段,大多数只是一些小的数字和的改变。这些被压缩的首部大大地缩短了交互响应时间。

(下面是原书p.25②的译文)
现在大多数的SLIP产品都支持CSLIP。作者所在的子网(参见封面内页)中有两条SLIP链路,它们均是CSLIP链路。

2.6 PPP:点对点协议
PPP,点对点协议修改了SLIP协议中的所有缺陷。PPP包括以下三个部分:
1.在串行链路上封装IP数据报的方法。PPP既支持数据为8位和无奇偶检验的异步模式(如大多数计算机上都普遍存在的串行接口),还支持面向比特的同步链接。
2.建立、配置及测试数据链路的链路控制协议(LCP:Link Control Protocol)。它允许通信双方进行协商,以确定不同的选项。
3.针对不同网络层协议的网络控制协议(NCP:Network Control Protocol)体系。当前RFC定义的网络层有IP,OSI网络层,DECnet,以及AppleTalk。例如,IP NCP允许双方商定是否对报文首部进行压缩,类似于CSLIP。(缩写词NCP也可用在TCP的前面)。
RFC 1548[Simpson 1993]描述了报文封装的方法和链路控制协议。RFC 1332[McGregor 1992]描述了针对IP的网络控制协议。
PPP数据帧的格式看上去很像ISO的HDLC(高层数据链路控制)标准。图2.3是PPP数据帧的格式。

图2.3 PPP数据帧的格式

每一帧都以标志字符0x7e开始和结束。紧接着是一个地址字节,值始终是0xff,然后是一个值为0x03的控制字节。
接下来是协议字段,类似于以太网中类型字段的功能。当它的值为0x0021时表示信息字段是一个IP数据报,值为0xc021时表示信息字段是链路控制数据,值为0x8021时表示信息字段是网络控制数据。
CRC字段(或FCS,帧校验序列)是一个循环冗余检验码,以检测数据帧中的错误。
由于标志字符的值是0x7e,因此当该字符出现在信息字段中时,PPP需要对它进行转义。在同步链路中,该过程是通过一种称作比特填充(bit stuffing)的硬件技术来完成的[Tanenbaum 1989]。在异步链路中,特殊字符0x7d用作转义字符。当它出现在PPP数据帧中时,那么紧接着的字符的第六个比特要取其补码,具体实现过程如下:
1. 当遇到字符0x7e时,需连续传送两个字符:0x7d和0x5e,以实现标志字符的转义。
2. 当遇到转义字符0x7d时,需连续传送两个字符:0x7d和0x5d,以实现转义字符的转义。
3. 默认情况下,如果字符的值小于0x20(比如,一个ASCII控制字符),一般都要进行转义。例如,遇到字符0x01时需连续传送0x7d和0x21两个字符。(这时,第六个比特取补码后变为1,而前面两种情况均把它变为0。)
这样做的原因是防止它们出现在双方主机的串行接口驱动程序或调制解调器中,因为有时它们会把这些控制字符解释成特殊的含义。另一种可能是用链路控制协议来指定是否需要对这32个字符中的某一些值进行转义。默认情况下是对所有的32个字符都进行转义。
与SLIP类似,由于PPP经常用于低速的串行链路,因此减少每一帧的字节数可以降低应用程序的交互时延。利用链路控制协议,大多数的产品通过协商可以省略标志符和地址字段,并且把协议字段由2个字节减少到1个字节。如果我们把PPP的帧格式与前面的SLIP的帧格式(图2.2)进行比较会发现,PPP只增加了3个额外的字节:1个字节留给协议字段,另2个给CRC字段使用。另外,使用IP网络控制协议,大多数的产品可以通过协商采用Van Jacobson报文首部压缩方法(对应于CSLIP压缩),减小IP和TCP首部长度。
总的来说,PPP比SLIP具有下面这些优点:(1)PPP支持在单根串行线路上运行多种协议,不只是IP协议;(2)每一帧都有循环冗余检验;(3)通信双方可以进行IP地址的动态协商(使用IP网络控制协议);(4)与CSLIP类似,对TCP和IP报文首部进行压缩;(5)链路控制协议可以对多个数据链路选项进行设置。为这些优点我们付出的代价是在每一帧的首部增加3个字节,当建立链路时要发送几帧协商数据,以及更为复杂的实现。

(下面是原书p.27①的译文)
尽管PPP比SLIP有更多的优点,但是现在的SLIP用户仍然比PPP用户多。随着产品越来越多,产家也开始逐渐支持PPP,因此最终PPP应该取代SLIP。

2.7 环回接口
大多数的产品都支持环回接口(Loopback Interface),以允许运行在同一台主机上的客户程序和服务器程序通过TCP/IP进行通信。A类网络号127就是为环回接口预留的。根据惯例,大多数系统把IP地址127.0.0.1分配给这个接口,并命名为localhost。一个传给环回接口的IP数据报不能在任何网络上出现。
我们想象,一旦传输层检测到目的端地址是环回地址时,应该可以省略部分传输层和所有网络层的逻辑操作。但是大多数的产品还是照样完成传输层和网络层的所有过程,只是当IP数据报离开网络层时把它返回给自己。
图2.4是环回接口处理IP数据报的简单过程。

图2.4 环回接口处理IP数据报的过程

需要指出图中的关键点是:
1. 传给环回地址(一般是127.0.0.1 )的任何数据均作为IP输入。
2. 传给广播地址或多播地址的数据报复制一份传给环回接口,然后送到以太网上。这是因为广播传送和多播传送的定义(第12章)包含主机本身。
3. 任何传给该主机IP地址的数据均送到环回接口。
看上去用传输层和IP层的方法来处理环回数据似乎效率不高,但它简化了设计,因为环回接口可以被看作是网络层下面的另一个链路层。网络层把一份数据报传送给环回接口,就像传给其他链路层一样,只不过环回接口把它返回到IP的输入队列中。
在图2.4中,另一个隐含的意思是送给主机本身IP地址的IP数据报一般不出现在相应的网络上。例如,在一个以太网上,分组一般不被传出去然后读回来。某些BSD以太网的设备驱动程序的注释说明,许多以太网接口卡不能读回它们自己发送出去的数据。由于一台主机必须处理发送给自己的IP数据报,因此图2.4所示的过程是最为简单的处理办法。

(下面是原书p.29①的译文)
4.4BSD系统定义了变量useloopback,并初始化为1。但是,如果这个变量置为0,以太网驱动程序就会把本地分组送到网络,而不是送到环回接口上。它也许不能工作,这取决于你所使用的以太网接口卡和设备驱动程序。

2.8 最大传输单元MTU
正如我们在图2.1看到的那样,以太网和802.3对数据帧的长度都有一个限制,其最大值分别是1500和1492字节。链路层的这个特性称作MTU,最大传输单元。不同类型的网络大多数都有一个上限。
如果IP层有一个数据报要传,而且数据的长度比链路层的MTU还大,那么IP层就需要进行分片(fragmentation),把数据报分布若干片,这样每一片都小于MTU。我们将在11.5节讨论IP分片的过程。
图2.5列出了一些典型的MTU值,它们摘自RFC 1191[Mogul and Deering 1990]。点到点的链路层(如SLIP和PPP)的MTU并非指的是网络媒体的物理特性。相反,它是一个逻辑限制,目的是为交互使用提供足够快的响应时间。在2.10节中,我们将看到这个限制值是如何计算出来的。
在3.9节中,我们将用netstat命令打印出网络接口的MTU。

图2.5 几种常见的最大传输单元(MTU)

2.9 路径MTU
当在同一个网络上的两台主机互相进行通信时,该网络的MTU是非常重要的。但是如果两台主机之间的通信要通过多个网络,那么每个网络的链路层就可能有不同的MTU。重要的不是两台主机所在网络的MTU的值,重要的是两台通信主机路径中的最小MTU。它被称作路径MTU。
两台主机之间的路径MTU不一定是个常数。它取决于当时所选择的路由。而路由选择不一定是对称的(从A到B的路由可能与从B到A的路由不同),因此路径MTU在两个方向上不一定是一致的。
RFC 1191[Mogul and Deering 1990]描述了路径MTU的发现机制,即在任何时候确定路径MTU的方法。我们在介绍了ICMP和IP分片方法以后再来看它是如何操作的。在11.6节中,我们将看到ICMP的不可到达错误就采用这种发现方法。在11.7节中,我们还会看到,traceroute程序也是用这个方法来确定到达目标节点的路径MTU。在 11.8节和24.2节,我们将介绍当产品支持路径MTU的发现方法时,UDP和TCP是如何进行操作的。

2.10 串行线路吞吐量计算
如果线路速率是9600 b/s,而一个字节有8 bit,加上一个起始比特和一个停止比特,那么线路的速率就是960 B/s(字节/秒)。以这个速率传输一个1024字节的分组需要1066 ms。如果我们用SLIP链接运行一个交互式应用程序,同时还运行另一个应用程序如FTP发送或接收1024字节的数据,那么一般来说我们就必须等待一半的时间(533 ms)才能把交互式应用程序的分组数据发送出去。
假定我们的交互分组数据可以在其它“大块”分组数据发送之前被发送出去。大多数的SLIP实现确实提供这类服务排队方法,把交互数据放在大块的数据前面。交互通信一般有Telnet,Rlogin,以及FTP的控制部分(用户的命令,而不是数据)。

(下面是原书p.31①的译文)
这种服务排队方法是不完善的。它不能影响已经进入下游(如串行驱动程序)队列的非交互数据。同时,新型的调制解调器具有很大的缓冲区,因此非交互数据可能已经进入该缓冲区了。

对于交互应用来说,等待533 ms是不能接受的。关于人的有关研究表明,交互响应时间超过100-200 ms就被认为是不好的[Jacobson 1990a]。这是发送一份交互报文出去后,直到接收到响应信息(通常是出现一个回显字符)为止的往返时间。
把SLIP的MTU缩短到256就意味着链路传输一帧最长需要266 ms,它的一半是133 ms(这是我们一般需要等待的时间)。这样情况会好一些,但仍然不完美。我们选择它的原因(与64或128相比)是因为大块数据提供良好的线路利用率(如大文件传输)。假设CSLIP的报文首部是5个字节,数据帧总长为261个字节,256个字节的数据使线路的利用率为98.1%,帧头占了1.9%,这样的利用率是很不错。如果把MTU降到256以下,那么将降低传输大块数据的最大吞吐量。
在图2.5列出的MTU值中,点对点链路的MTU是296个字节。假设数据为256字节,TCP和IP首部占40个字节。由于MTU是IP向链路层查询的结果,因此该值必须包括通常的TCP和IP首部。这样就会导致IP如何进行分片的决策。IP对于CSLIP的压缩情况一无所知。
我们对平均等待时间的计算(传输最大数据帧所需时间的一半)只适用于SLIP链路(或PPP链路)在交互通信和大块数据传输这两种情况下。当只有交互通信时,如果线路速率是9600 b/s,那么任何方向上的1字节数据(假设有5个字节的压缩帧头)往返一次都大约需要12.5 ms。它比前面提到的100-200 ms足够小。需要注意的是,由于帧头从40个字节压缩到5个字节,使得1字节数据往返时间从85 ms减到12.5 ms。
不幸的是,当使用新型的纠错和压缩调制解调器时,这样的计算就更难了。这些调制解调器所采用的压缩方法使得在线路上传输的字节数大大减少,但纠错机制又会增加传输的时间。不过,这些计算是我们进行合理决策的入口点。
在后面的章节中,我们将用这些串行线路吞吐量的计算来验证数据从串行线路止通过的时间。

2.11 小结
本章讨论了Internet协议族中的最底层协议,链路层协议。我们比较了以太网和IEEE 802.2/802.3的封装格式,以及SLIP和PPP的封装格式。由于SLIP和PPP经常用于低速的链路,二者都提供了压缩不常变化的公共字段的方法。这使交互性能得到提高。
大多数的实现都提供环回接口。访问这个接口可以通过特殊的环回地址,一般为127.0.0.1,也可以通过发送IP数据报给主机所拥有的任一IP地址。当环回数据回到上层的协议栈中时,它已经过传输层和IP层完整的处理过程。
我们描述了很多链路都具有一个重要特性,MTU,相关的一个概念是路径MTU。根据典型的串行线路MTU,我们对SLIP和CSLIP链路的传输时延进行了计算。
本章内容只覆盖了当今TCP/IP所采用部分数据链路公共技术。TCP/IP成功的原因之一是它几乎能在任何数据链路技术上运行。

习题
2.1 如果你的系统支持netstat(1)命令(参见3.9小节),那么请用它确定系统上的接口及其MTU。








2-1
3 IP:网际协议

3.1 引言
IP是TCP/IP协议族中最为核心的协议。所有的TCP,UDP,ICMP,及IGMP数据都以IP数据报格式传输(图1.4)。许多刚开始接触TCP/IP的人对IP提供不可靠、无连接的数据报传送服务感到很奇怪,特别是那些具有X.25或SNA背景知识的人。
不可靠(unreliable)的意思是它不能保证IP数据报能成功地到达目的地。IP仅提供最好的传输服务。如果发生某种错误时,如某个路由器暂时用完了缓冲区,IP有一个简单的错误处理算法:丢弃该数据报,然后发送ICMP消息报给信源端。任何要求的可靠性必须由上层来提供(如TCP)。
无连接(connectionless)这个术语的意思是IP并不维护任何关于后续数据报的状态信息。每个数据报的处理是相互独立的。这也说明,IP数据报可以不按发送顺序接收。如果一信源向相同的信宿发送两个连续的数据报(先是A,然后是B),每个数据报都是独立地进行路由选择,可能选择不同的路线,因此B可能在A到达之前先到达。
在本章,我们将简要介绍IP首部中的各个字段,讨论IP路由选择和子网的有关内容。我们还要介绍两个有用的命令:ifconfig和netstat。关于IP首部中一些字段的细节,我们将留在以后使用这些字段的时候再进行讨论。RFC 791[Postel 1981a]是IP的正式规约文件。

3.2 IP首部
IP数据报的格式如图3.1所示。普通的IP首部长为20个字节,除非含有选项字段。

图3.1 IP数据报格式及首部中的各字段

我们来分析图3.1中的首部。最高位在左边,记为0 bit,最低位在右边,记为31 bit。
4个字节的32 bit值以下面的次序传输:首先是0-7 bit,其次8-15 bit,然后16-23 bit,最后是24-31 bit。这种传输次序称作big endian字节次序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节次序。以其他形式存储二进制整数的机器,如little endian格式,则必须在传输数据之前把首部转换成网络字节次序。
目前的协议版本号是4,因此IP有时也称作IPv4。3.10节将对一种新版的IP协议进行讨论。
首部长度指的是首部占32 bit字的数目,包括任何先期选项。由于它是一个4比特字段,因此首部最长为60个字节。在第8章中,我们将看到这种限制使某些选项如路由记录选项在当今已没有什么用处。普通IP数据报(没有任何选择项)该字段的值是5。
服务类型(TOS)字段包括一个3 bit的优先权子字段(现在已被忽略),4 bit的TOS子字段,和1 bit未用位但必须置0。4 bit的TOS分别代表:最小时延,最大吞吐量,最高可靠性,最小费用。4 bit中只能置其中1 bit。如果所有4 bit均为0,那么就意味着是普遍服务。RFC 1340 [Reynolds and Postel 1992]描述了所有的标准应用如何设置这些服务类型。RFC 1349 [Almquist 1992]对该RFC进行了修正,更为详细地描述了TOS的特性。
图3.2列出了对不同应用建议的TOS值。在最后一列中,我们给出的是十六进制值,因为这就是在后面我们将要看到的tcpdump命令输出。

图3.2 服务类型字段推荐值

Telnet和Rlogin这两个交互应用要求最小的传输时延,因为人们主要用它们来传输少量的交互数据。另一方面,FTP文件传输则要求有最大的吞吐量。最高可靠性被指明给网络管理(SNMP)和路由选择协议。用户网络新闻(Usenet news, NNTP)是唯一要求最小费用的应用。
现在大多数的TCP/IP实现都不支持TOS特性,但是自4.3BSD Reno以后的新版系统都对它进行了设置。另外,新的路由协议如OSPF和IS-IS都能根据这些字段的值进行路由决策。

(下面是原书p.35①的译文)
在2.10节中,我们提到SLIP一般提供基于服务类型的排队方法,允许对交互通信数据在处理大块数据之前进行处理。由于大多数的实现都不使用TOS字段,因此这种排队机制由SLIP自己来判断和处理,驱动程序先查看协议字段(确定是否是一个TCP段),然后检查TCP信源和信宿的端口号,以判断是否是一个交互服务。一个驱动程序的注释这样认为,这种“令人厌恶的处理方法”是必需的,因为大多数实现都不允许应用程序设置TOS字段。
总长度字段是指整个IP数据报的长度,以字节为单位。利用首部长度字段和总长度字段,我们就可以知道IP数据报中数据内容的起始位置和长度。由于该字段长16比特,所以IP数据报最长可达65535字节。(回忆图2.5,超级通道的MTU为65535。它的意思其实不是一个真正的MTU-—它使用了最长的IP数据报。)当数据报被分片时,该字段的值也随着变化,这一点我们将在11.5节中进一步描述。
尽管可以传送一个长达65535字节的IP数据报,但是大多数的链路层都会对它进行分片。而且,主机也要求不能接收超过576字节的数据报。由于TCP把用户数据分成若干片,因此一般来说这个限制不会影响TCP。我们在后面的章节中将遇到大量使用UDP的应用(RIP,TFTP,BOOTP,DNS,以及SNMP),它们都限制用户数据报长度为512字节,小于576字节。但是,事实上现在大多数的实现(特别是那些支持网络文件系统,NFS的实现)允许超过8192字节的IP数据报。
总长度字段是IP首部中必要的内容,因为一些数据链路(如以太网)需要填充一些数据以达到最小长度。尽管以太网的最小帧长为46字节(图2.1),但是IP数据可能会更短。如果没有总长度字段,那么IP层就不知道46字节中有多少是IP数据报的内容。
标识字段唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1。我们在11.5节介绍分片和重组时再详细讨论它。同样,在讨论分片时我们再来分析标志字段和片偏移字段。

(下面是原书p.36①的译文)
RFC 791 [Postel 1981a]认为标识字段应该由让IP发送数据报的上层来选择。假设有两个连续的IP数据报,其中一个是由TCP生成的,而另一个是由UDP生成的,那么它们可能具有相同的标识字段。尽管这也可以照常工作(由重组算法来处理),但是在大多数从伯克利派生出来的系统中,每发送一个IP数据报时,IP层都要把一个内核变量的值加1,不管交给IP的数据来自哪一层。内核变量的初始值根据系统引导时的时间来设置。

生存时间字段(time-to-live)TTL设置了数据报可以经过的最多路由器数。它指定了数据报的生存时间。TTL的初始值由源主机设置(通常为32或64),一旦经过一个处理它的路由器,它的值就减去1。当该字段的值为0时,数据报就被丢弃,并发送ICMP报文通知源主机。第8章我们讨论Traceroute程序时将再回来讨论该字段。
我们已经在第1章讨论了协议字段,并在图1.8中示出了它如何被IP用来对数据报进行分用。根据它可以识别是哪个协议向IP传送数据。
首部检验和字段是根据IP首部计算的检验和码。它不对首部后面的数据进行计算。ICMP,IGMP,UDP和TCP在它们各自的首部中均含有同时覆盖首部和数据检验和码。
为了计算一份数据报的IP检验和,首先把检验和字段置为0。然后,对首部中每个16 bit的二进制反码进行求和(整个首部看成是由一串16 bit的字组成),结果存在检验和字段中。当收到一份IP数据报后,同样对首部中每个16 bit的二进制反码进行求和。由于收方在计算过程中包含了发方存在首部中的检验和,因此首部在传输过程中没有发生任何差错时,收方计算的结果应该为全1。如果结果不是全1(即检验和错误),那么IP就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。
ICMP,IGMP,UDP和TCP都采用相同的检验和算法,尽管TCP和UDP除了本身的首部和数据外,在IP首部中还包含不同的字段。在RFC 1071[Braden,Borman and Patridge 1988]中有关于如何计算Internet检验和的实现技术。由于路由器经常只修改TTL字段(减1),因此当路由器转发一份报文时可以增加它的检验和,而不需要对IP整个首部进行重新计算。RFC 1141[Mallory and Kullberg 1990]为此给出了一个很有效的方法。

(下面是原书p.37①的译文)
但是,标准的BSD实现在转发数据报时并不是采用这种增加的办法。

每一份IP数据报都包含源IP地址和目的IP地址。我们在1.4节中说过,它们都是32 bit的值。
最后一个字段是任选项,是数据报中的一个可变长的可选信息。目前,这些任选项定义如下:
•安全和处理限制(用于军事领域,详细内容参见RFC 1108[Kent 1991])
•记录路径(让每个路由器都记下它的IP地址,见7.3节)
•时间戳(让每个路由器都记下它的IP地址和时间,见7.4节)
•宽松的源站选路(为数据报指定一系列必须经过的IP地址,见8.5节)
•严格的源站选路(与宽松的源站选路类似,但是它要求只能经过指定的这些地址,不能经过其它的地址)。
这些选项很少被使用,并非所有的主机和路由器都支持这些选项。
选项字段一直都是以32 bit作为界限,在必要的时候插入值为0的填充字节。这样就保证IP首部始终是32 bit的整数倍(这是首部长度字段所要求的)。

3.3 IP路由选择
从概念上说,IP路由选择是简单的,特别对于主机来说。如果目的主机与源主机直接相连(如点对点链路)或都在一个共享网络上(以太网或令牌环网),那么IP数据报就直接送到目的主机上。否则,主机把数据报发往一默认的路由器上,由路由器来转发该数据报。大多数的主机都是采用这种简单机制。
在本节和第9章中,我们将讨论更一般的情况,即IP层既可以配置成路由器的功能,也可以配置成主机的功能。当今的大多数多用户系统,包括几乎所有的Unix系统,都可以配置成一个路由器。我们可以为它指定主机和路由器都可以使用的简单路由算法。根本上的区别在于主机从不把数据报从一个接口转发到另一个接口,而路由器则要转发数据报。内含路由器功能的主机应该从不转发数据报,除非它被设置成那样。在9.4小节中,我们将进一步讨论配置的有关问题。
在一般的体制中,IP可以从TCP,UDP,ICMP,IGMP接收数据报(即在本地生成的数据报)并进行发送,或者从一个网络接口接收数据报(待转发的数据报)进行发送。IP层在内层中有一个路由表。当收到一份数据报进行发送时,它都要对该表搜索一次。当数据报来自某个网络接口时,IP首先检查目的IP地址是否为本机的IP地址之一或者IP广播地址。如果确实是这样,数据报就被送到由IP首部协议字段所指定的协议模块进行处理。如果数据报的目的不是这些地址,那么(1)如果IP层被设置为路由器的功能,那么就对数据报进行转发(也就是说,像下面对待发出的数据报一样处理),否则(2)数据报被丢弃。
路由表中的每一项都包含下面这些信息:
•目的IP地址。它既可以是一个完整的主机地址,也可以是一个网络地址,由该表目中的标志字段来指定(如下所述)。主机地址有一个非0的主机号(图1.5),以指定某一特定的主机,而网络地址中的主机号为0,以指定网络中的所有主机(如以太网,令牌环网)。
•下一站(或下一跳)路由器(next-hop router)的IP地址,或者有直接连接的网络IP地址。下一站路由器是指一个在直接相连网络上的路由器,通过它可以转发数据报。下一站路由器不是最终的目的,但是它可以把我们传送给它的数据报转发到最终目的。
•标志。其中一个标志指明目的IP地址是网络地址还是主机地址,另一个标志指明下一站路由器是否为真正的下一站路由器,还是一个直接相连的接口。(我们将在9.2节中详细介绍这些标志。)
•为数据报的传输指定一个网络接口。

IP路由选择是逐跳地(hop-by-hop)进行的。从这个路由表信息可以看出,IP并不知道到达任何目的的完整路径(当然,除了那些与主机直接相连的目的)。所有的IP路由选择只为数据报传输提供下一站路由器的IP地址。它假定下一站路由器比发送数据报的主机更接近目的,而且下一站路由器与该主机是直接相连的。
IP路由选择主要完成以下这些功能:
1. 搜索路由表,寻找能与目的IP地址完全匹配的表目(网络号和主机号都要匹配)。如果找到,则把报文发送给该表目指定的下一站路由器或直接连接的网络接口(取决于标志字段的值)。
2. 搜索路由表,寻找能与目的网络号相匹配的表目。如果找到,则把报文发送给该表目指定的下一站路由器或直接连接的网络接口(取决于标志字段的值)。目的网络上的所有主机都可以能过这个表目来处置。例如,一个以太网上的所有主机都是通过这种表目进行寻径的。
这种搜索网络的匹配方法必须考虑可能的子网掩码。关于这一点我们在下一节中进行讨论。
3.搜索路由表,寻找标为“默认”(default)的表目。如果找到,则把报文发送给该表目指定的下一站路由器。
如果上面这些步骤都没有成功,那么该数据报就不能被传送。如果不能传送的数据报来自本机,那么一般会向生成数据报的应用程序返回一个“主机不可达”或“网络不可达”的错误。
完整主机地址匹配在网络号匹配之前执行。只有当它们都失败后才选择默认路由。默认路由,以及下一站路由器发送的ICMP间接报文(如果我们为数据报选择了错误的默认路由),是IP路由选择机制中功能强大的特性。我们在第9章对它们进行讨论。
为一个网络指定一个路由器,而不必为每个主机指定一个路由器,这是IP路由选择机制的另一个基本特性。这样做可以极大地缩小路由表的规模,比如Internet上的路由器有只有几千个表目,而不会是超过100万个表目。

例子
首先考虑一个简单的例子:我们的主机bsdi有一个IP数据报要发送给主机sun。双方都在同一个以太网上(参见封面内侧的图)。数据报的传输过程如图3.3所示。
当IP从某个上层收到这份数据报后,它搜索路由表,发现目的IP地址(140.252.13.133)在一个直接相连的网络上(以太网140.252.13.0)。于是,在表中找到匹配网络地址。(在下一节中,我们将看到,由于以太网的子网掩码的存在,实际的网络地址是140.252.13.32,但是这并不影响这里所讨论的路由选择。)
数据报被送到以太网驱动程序,然后作为一个以太网数据帧被送到sun主机上(图2.1)。IP数据报中的目的地址是sun的IP地址(140.252.13.33),而在链路层首部中的目的地址是48 bit的sun主机的以太网接口地址。这个48 bit的以太网地址是用ARP协议获得的,我们将在下一章对此进行描述。

图3.3 数据报从主机bsdi到sun的传送过程

现在让我们来看另一个例子:主机bsdi有一份IP数据报要传到ftp.uu.net主机上,它的IP地址是192.48.96.9。经过的前三个路由器如图3.4所示。首先,主机bsdi搜索路由表,但是没有找到与主机地址或网络地址相匹配的表目,因此只能用默认的表目,把数据报传给下一站路由器,即主机sun。当数据报从bsdi被传到sun主机上以后,目的IP地址是最终的信宿机地址(192.48.96.9),但是链路层地址却是sun主机的以太网接口地址。这与图3.3不同,在那里数据报中的目的IP地址和目的链路层地址都指的是相同的主机(sun)。
当sun收到数据报后,它发现数据报的目的IP地址并不是本机的任一地址,而sun已被设置成具有路由器的功能,因此它把数据报进行转发。经过搜索路由表,选用了默认表目。根据sun 的默认表目,它把数据报转发到下一站路由器netb,该路由器的地址是140.252.1.183。数据报是经过点对点SLIP链路被传送的,采用了图2.2所示的最小封装格式。这里,我们没有给出像以太网链路层数据帧那样的首部,因为在SLIP链路中没有那样的首部。
当netb收到数据报后,它执行与sun主机相同的步骤:数据报的目的地址不是本机地址,而netb也被设置成具有路由器的功能,于是它也对该数据报进行转发。采用的也是默认路由表目,把数据报送到下一站路由器gateway(140.252.1.4)。位于以太网140.252.1上的主机netb用ARP获得对应于140.252.1.4的48 bit以太网地址。这个以太网地址就是链路层数据帧头上的目的地址。
路由器gateway也执行与前面两个路由器相同的步骤。它的默认路由表目所指定的下一站路由器IP地址是140.252.104.2。(我们将在图8.4中证实,使用Traceroute程序时,它就是gateway使用的下一站路由器。)
对于这个例子我们需要指出一些关键点:
1. 该例子中的所有主机和路由器都使用了默认路由。事实上,大多数主机和一些路由器可以用默认路由来处理任何目的,除非它在本地局域网上。

图3.4 从bsdi到ftp.uu.net (192.48.96.9)的初始路径

2. 数据报中的目的IP地址始终不发生任何变化。(在8.5节中,我们将看到,只有使用源路由选项时目的IP地址才有可能被修改,但这种情况很少出现。)所有的路由选择决策都是基于这个目的IP地址。
3. 每个链路层可能具有不同的数据帧首部,而且链路层的目的地址(如果有的话)始终指的是下一站的链路层地址。在我们的例子中,两个以太网封装了含有下一站以太网地址的链路层首部,但是SLIP链路没有这样做。以太网地址一般通过ARP获得。
在第9章,我们在描述了ICMP之后将再次讨论IP路由选择问题。我们将看到一些路由表的例子,以及如何用它们来进行路由决策的。

3.4 子网寻址
现在所有的主机都要求支持子网编址(RFC 950 [Mogul and Postel 1985])。不是把IP地址看成由单纯的一个网络号和一个主机号组成,而是把主机号再分成一个子网号和一个主机号。
这样做的原因是因为A类和B类地址为主机号分配了太多的空间,可分别容纳主机数224-2和216-2。事实上,在一个网络中人们并不安排这么多的主机。(各类IP地址的格式如图1.5所示。)由于全0或全1的主机号都是无效的,因此我们把总数减去2。
在InterNIC获得某类IP网络号后,就由当地的系统管理员来进行分配,由他(或她)来决定是否建立子网,以及分配多少比特给子网号和主机号。例如,这里有一个B类网络地址(140 .252),在剩下的16 bit中,8 bit用于子网号,8 bit用于主机号,格式如图3.5所示。这样就允许有254个子网,每个子网可以有254台主机。

图3.5 B类地址的一种子网编址

许多管理员采用自然的划分方法,即把B类地址中留给主机的16 bit中的前8 bit作为子网地址,后8 bit作为主机号。这样用点分十进制方法表示的IP地址就可以比较容易确定子网号。但是,并不要求A类或B类地址的子网划分都要以字节为划分界限。
大多数的子网例子都是B类地址。其实,子网还可用于C类地址,只是它可用的比特数较少而已。很少出现A类地址的子网例子是因为A类地址本身就很少。(但是,大多数A类地址都是进行子网划分的。)
子网对外部路由器来说隐藏了内部网络组织(一个校园或公司内部)的细节。在我们的网络例子中,所有的IP地址都有一个B类网络号140.252。但是其中有超过30个子网,多于400台主机分布在这些子网中。由一台路由器提供了Internet的接入,如图3.6所示。
在这个图中,我们把大多数的路由器编号为Rn,n是子网号。我们给出了连接这些子网的路由器,同时还包括了封二图中的九个系统。在图中,以太网用粗线表示,点对点链路用虚线表示。我们没有画出不同子网中的所有主机。例如,在子网140.252.3上,就超过50台主机,而在子网140.252.1上则超过100台主机。
与30个C类地址相比,用一个包含30个子网的B类地址的好处是,它可以缩小Internet路由表的规模。B类地址140.252被划分为若干子网的事实对于所有子网以外的Internet路由器都是透明的。为了到达IP地址开始部分为140.252的主机,外部路由器只需要知道通往IP地址140.252.104.1的路径。这就是说,对于网络140.252只需一个路由表目,而如果采用30个C类地址,则需要30个路由表目。因此,子网划分缩减了路由表的规模。(在10.8小节中,我们将介绍一种新技术,即使用C类地址也可以缩减路由表的规模。)

图3.6网络noao.edu(140.252)中的大多数子网安排

子网对于子网内部的路由器是不透明的。如图3.6所示,一份来自Internet的数据报到达gateway,它的目的地址是140.252.57.1。路由器gateway需要知道子网号是57,然后把它送到kpno。同样,kpno必须把数据报送到R55,最后由R55把它送到R57。

3.5 子网掩码
任何主机在引导时进行的部分配置是指定主机IP地址。大多数系统把IP地址存在一个磁盘文件里供引导时读用。在第5章我们将讨论一个无盘系统如何在引导时获得IP地址。
除了IP地址以外,主机还需要知道有多少比特用于子网号及多少比特用于主机号。这也是在引导过程中通过子网掩码来确定的。这个掩码是一个32 bit的值,其中值为1的比特留给网络号和子网号,为0的比特留给主机号。图3.7是一个B类地址的两种不同的子网掩网格式。第一个例子是noao.edu网络采用的子网划分方法,如图3.5所示,子网号和主机号都是8 bit宽。第二个例子是一个B类地址划分成10 bit的子网号和6 bit的主机号。

图3.7 两种不同的B类地址子网掩码例子

尽管IP地址一般以点分十进制方法表示,但是子网掩码却经常用十六进制来表示,特别是当界限不是一个字节时,因为子网掩码是一个比特掩码。
给定IP地址和子网掩码以后,主机就可以确定IP数据报的目的是:(1)本子网上的主机;(2)本网络中其它子网中的主机;(3)其它网络上的主机。如果知道本机的IP地址,那么就知道它是否为A类,B类或C类地址(从IP地址的高位可以得知),也就知道网络号和子网号之间的分界限。而根据子网掩码则知道子网号与主机号之间的分界限。

例子
假设我们的主机地址是140.252.1.1(一个B类地址),而子网掩网为255.255.255.0(其中8 bit为子网号,8 bit为主机号)
. 如果目的IP地址是140.252.4.5,那么我们知道B类网络号是相同的(140.252),但是子网号是不同的(1和4)。用子网掩码在两个IP地址之间的比较如图3.8所示。
. 如果目的IP地址是140.252.1.22,那么B类网络号还是一样的(140.252),而且子网号也是一样的(1),但是主机号是不同的。
. 如果目的IP地址是192.43.235.6(一个C类地址),那么网络号是不同的,因而进一步的比较就不用再进行了。

图3.8 使用子网掩码的两个B类地址之间的比较

给定两个IP地址和子网掩码后,IP路由选择功能一直进行这样的比较。

3.6 特殊情况的IP地址
经过子网划分的描述,我们现在介绍7个特殊的IP地址,如图3.9所示。在这个图中,0表示所有的比特位全为0,-1表示所有的比特位全为1,netid, subnetid, 和hostid分别表示不为全0或全1的对应字段。子网号栏为空表示该地址没有进行子网划分。

(以下是图3.9的译文)
IP地址
可以为
描述
网络号
子网号
主机号
源端?
目的端?

0

0
OK
不可能
网络上的主机(参见下面的限制)
0

hostid
OK
不可能
网络上的特定主机(参见下面的限制)
127

任何值
OK
OK
环回地址(2.7节)
-1

-1
不可能
OK
受限的广播(永远不被转发)
netid

-1
不可能
OK
以网络为目标向netid广播
netid
subnetid
-1
不可能
OK
以子网为目标向netid, subnetid广播
netid
-1
-1
不可能
OK
以所有子网为目标向netid广播
图3.9 特殊情况的IP地址

我们把这个表分成三个部分。表的头两项是特殊的源地址,中间项是特殊的环回地址,最后四项是广播地址。
表中的头两项,网络号为0,只能作为初始化过程中的源地址出现,如主机使用BOOTP协议确定本机IP地址时。
在12.2节中,我们将进一步分析四类广播地址。

3.7 一个子网的例子
这个例子是本文中采用的子网,以及如何使用两个不同的子网掩码。具体安排如图3.10所示。

图3.10 作者所在子网中的主机和网络安排

如果把该图与封二中的图相比,你会发现我们在图3.10中省略了从路由器sun到上面的以太网之间的连接细节,实际上它们之间的连接是拔号SLIP。这个细节不影响我们本节中讨论的子网划分问题。我们在4.6节讨论ARP代理时将再回头讨论到这个细节。
问题是我们在子网13中有两个分离的网络:一个以太网和一个点对点链路(硬件连接的SLIP链路)。(点对点链接始终会带来问题,因为它一般在两端都需要IP地址。)将来或许会有更多的主机和网络,但是为了不让主机跨越不同的网络就得使用不同的子网号。我们的解决方法是把子网号从8 bit扩充到11 bit,把主机号从8 bit减为5 bit。这就叫作变长子网,因为140.252网络中的大多数子网都采用8 bit子网掩码,而我们的子网却采用11 bit的子网掩码。

(下面是原书p.46①的译文)
RFC 1009[Braden and Postel 1987]允许一个含有子网的网络使用多个子网掩码。新的路由器需求RFC[Almquist 1993]则要求支持这一功能。
但是,问题在于并不是所有的路由选择协议在交换目的网络时也交换子网掩码。在第10章中,我们将看到RIP不支持变长子网,RIP 2版和OSPF则支持变长子网。在我们的例子中不存在这种问题,因为在我的子网中不要求使用RIP协议。

作者子网中的IP地址结构如图3.11所示,11位子网号中的前8 bit始终是13。在剩下的3 bit中,我们用二进制001表示以太网,010表示点对点SLIP链路。这个变长子网掩码在140.252网络中不会给其它主机和路由器带来问题――只要目的是子网140.252.13的所有数据报都传给路由器sun(IP地址是140.252.1.29),如图3.11所示,而如果sun知道子网13中的主机有11 bit子网号,那么一切都好办了。

图3.11 变长子网

140.252.13子网中的所有接口的子网掩码是255.255.255.224,或0xffffffe0。这表明最右边的5 bit留给主机号,左边的27 bit留给网络号和子网号。
图3.10中所有接口的IP地址和子网掩码的分配情况如图3.12所示。

图3.12 作者子网的IP地址

第一栏标为是“主机”,但是sun和bsdi也具有路由器的功能,因为它们是多接口的,可以把分组数据从一个接口转发到另一个接口。
这个表中的最后一行是图3.10中的广播地址140.252.13.63:它是根据以太网子网号(140.252.13.32)和图3.11中的低5位置1(16+8+4+2+1=31)得来的。(我们在第12章中将看到,这个地址被称作以子网为目标的广播地址(subnet-directed broadcast address)。)

3.8 ifconfig命令
到目前为止,我们已经讨论了链路层和IP层,现在可以介绍TCP/IP对网络接口进行配置和查询的命令了。ifconfig(8)命令一般在引导时运行,以配置主机上的每个接口。
由于拔号接口可能会经常接通和挂断(如SLIP链路),每次线路接通和挂断时ifconfig都必须(以某种方法)运行。这个过程如何完成取决于使用的SLIP软件。
下面是作者子网接口的有关参数。请把它们与图3.12的值进行比较。

(见原书p.48的①)

环回接口(2.7节)被认为是一个网络接口。它是一个A类地址,没有进行子网划分。
需要注意的是以太网没有采用尾部封装(2.3节),而且可以进行广播,而SLIP链路是一个点对点的链接。
SLIP接口的标志LINK0是一个允许压缩slip的数据(CSLIP,参见2.5节)的配置选项。其它的选项有LINK1(如果从另一端收到一份压缩报文,就允许采用CSLIP)和LINK2(所有外出的ICMP报文都被丢弃)。我们在4.6节中将讨论SLIP链接的目的地址。

(下面是原书p.48②的译文)
安装指南中的注释对最后这个选项进行了解释:“一般它不应设置,但是由于一些不当的ping操作,你可能会导致吞吐量降到0。”

bsdi是另一台路由器。由于-a参数是SunOS操作系统具有的功能,因此我们必须多次执行ifconfig,并指定接口名字参数:

(见原书p.48的③)

这里,我们看到以太网接口(we0)的一个新选项:SIMPLEX。这个4.4BSD标志表明接口不能收到本机传送的数据。在BSD/386中所有的以太网都这样设置。一旦这样设置后,如果接口发送一帧数据到广播地址,那么就会为本机拷贝一份数据送到环回地址。(在6.3小节我们将举例子说明这一点。)
在主机slip中,SLIP接口的设置基本上与上面的bsdi一致,只是两端的IP地址进行了互换:

slip % /sbin/ifconfig sl0
sl0: flags=1011
inet 140.252.13.65 --> 140.252.13.66 netmask ffffffe0

最后一个接口是主机svr4上的以太网接口。它与前面的以太网接口类似,只是SVR4版的ifconfig没有打印RUNNING标志:

svr4 % /usr/sbin/ifconfig emd0
emd0: flags=23
inet 140.252.13.34 netmask ffffffe0 broadcast 140.252.13.63

ifconfig命令一般支持TCP/IP以外的其它协议族,而且有很多参数。关于这些细节你可以查看系统说明书。

3.9 netstat命令
netstat(1)命令也提供系统上的接口信息。-i参数将打印出接口信息,-n参数则打印出IP地址,而不是主机名字。

(见原书p.49的①)

这个命令打印出每个接口的MTU,输入分组数,输入错误,输出分组数,输出错误,冲突,以及当前的输出队列长度。
我们在第9章将用netstat命令检查路由表,那时再回头讨论该命令。另外,在第13章我们将用它的一个改进版本来查看活动的广播组。

3.10 IP的未来
IP主要存在三个方面的问题。这是Internet在过去几年快速增长所造成的结果。(参见习题1.2。)
1. 超过半数的B类地址已被分配。根据当前的估计,如果B类地址继续以当前的速度分配,它们将大约在1995年耗尽。
2. 32 bit的IP地址从长期的Internet增长角度来看一般是不够用的。
3. 当前的路由结构没有层次结构,属于平面型(flat)结构,每个网络都需要一个路由表目。随着网络数目的增长,一个具有多个网络的网站就必须分配多个C类地址,而不是一个B类地址,因此路由表的规模会不断增长。
无类别的域间路由选择CIDR(Classless Interdomain Routing)提出了一个可以解决第三个问题的建议,对当前版本的IP(IP版本4)进行扩充,以适应下个世纪Internet的发展。对此我们将在10.8节进一步详细介绍。
对新版的IP,即下一代IP,经常称作IPng,主要有四个方面的建议。1993年5月发行的IEEE Network (vol.7, no.3)对前三个建议进行了综述,同时有一篇关于CIDR的论文。RFC 1454 [Dixon 1993]对前三个建议进行了比较。
1. SIP,简单Internet协议。它针对当前的IP提出了一个最小幅度的修改建议,采用64位地址和一个不同的首部格式。(首部的前4比特仍然包含协议的版本号,其值不再是4。)
2. PIP。这个建议也采用了更大的,可变长度的,有层次结构的地址,而且首部格式也不相同。
3. TUBA,代表“TCP and UDP with Bigger Address”,它基于OSI 的CLNP(Connectionless Network Protocol,无连接网络协议),一个与IP类似的OSI协议。它提供大得多的地址空间:可变长度,可达20个字节。由于CLNP是一个现有的协议,而SIP和PIP只是建议,因此关于CLNP的文档已经出现。RFC 1347[Callon 1992]提供了TUBA的有关细节。文献[Perlman 1992]的第7章对IPv4和CLNP进行了比较。许多路由器已经支持CLNP,但是很少有主机也提供支持。
4. TP/IX,由RFC 1475 [Ullmann 1993]对它进行了描述。虽然SIP采用了64 bit的址址,但是它还改变了TCP和UDP的格式:二个协议均为32 bit的端口号,64 bit的序列号,64 bit的确认号,以及TCP的32 bit窗口。
前三个建议基本上采用了相同版本的TCP和UDP作为传输层协议。
由于四个建议只能有一个被选为IPv4的替换者,而且在你读到此书时可能已经做出选择,因此我们对它们不进行过多评论。虽然CIDR即将实现以解决目前的缺期问题,但是IPv4后继者的实现则需要经过许多年。

3.11 小结
本章开始描述了IP首部的格式,并简要讨论了首部中的各个字段。我们还介绍了IP路由选择,并指出主机的路由选择可以非常简单:如果目的主机在直接相连的网络上,那么就把数据报直接传给目的主机,否则传给默认路由器。
在进行路由选择决策时,主机和路由器都使用路由表。在表中有三种类型的路由:特定主机型,特定网络型,默认路由型。路由表中的表目具有一定的优先级。在选择路由时,主机路由优先于网络路由,最后在没有其它可选路由存在时才选择默认路由。
IP路由选择是通过逐跳(hop-by-hop)来实现的。数据报在各站的传输过程中目的IP地址始终不变,但是封装和目的链路层地址在每一站都可以改变。大多数的主机和许多路由器对于非本地网络的数据报都使用默认的下一站路由器。
A类和B类地址一般都要进行子网划分。用于子网号的比特数能过子网掩码来指定。我们为此举了一个实例详细说明,即作者所在的子网,并介绍了变长子网的概念。子网的划分缩小了Internet路由表的规模,因为许多网络经常可以能过单个表目就可以访问了。接口和网络的有关信息通过ifconfig和netstat命令可以获得,包括接口的IP地址、子网的掩码、广播地址以及MTU等。
在本章的最后,我们对Internet协议族潜在的改进建议――下一代IP进行了讨论。

习题
3.1 环回地址必须是127.0.0.1吗?
3.2 在图3.6中指出有两个网络接口的路由器。
3.3 子网号为16 bit的A类地址与子网号为8 bit的B类地址的子网掩码有什么不同?
3.4 阅读RFC 1219 [Tsuchiya 1991],学习分配子网号和主机号的有关推荐技术。
3.5 子网掩码255.255.0.255是否对A类地址有效?
3.6 为什么你认为3.9小节中打印出来的环回接口的MTU要设置为1536?
3.7 TCP/IP协议族是基于一种数据报网络技术,即IP层,其它的协议族则基于面向连接的网络技术。阅读文献[Clark 1988],找出数据报网络层提供的三个优点。
3-1
4 ARP:地址解析协议

4.1 引言
本章我们要讨论的问题是只对TCP/IP协议簇有意义的IP地址。数据链路如以太网或令牌环网都有自己的寻址机制(常常为48 bit地址),这是使用数据链路的任何网络层都必须遵从的。一个网络如以太网可以同时被不同的网络层使用。例如,一组使用TCP/IP协议的主机和另一组使用某种PC网络软件的主机可以共享相同的电缆。
当一台主机把以太网数据帧发送到位于同一局域网上的另一台主机时,是根据48 bit的以太网地址来确定目的接口的。设备驱动程序从不检查IP数据报中的目的IP地址。
地址解析为这两种不同的地址形式提供映射:32 bit的IP地址和数据链路层使用的任何类型的地址。RFC 826 [Plummer 1982]是ARP规约描述文档。
本章及下一章我们要讨论的两种协议如图4.1所示:ARP(地址解析协议)和RARP(逆地址解析协议)。

图4.1 地址解析协议:ARP和RARP

ARP为IP地址到对应的硬件地址之间提供动态映射。我们之所以用动态这个词是因为这个过程是自动完成的,一般应用程序用户或系统管理员不必关心。
RARP是被那些没有磁盘驱动器的系统使用(一般是无盘工作站或X终端),它需要系统管理员进行手工设置。我们在第5章对它进行讨论。

4.2 一个例子
任何时候我们敲入下面这个形式的命令:
% ftp bsdi
都会进行以下这些步骤。这些步骤的序号如图4.2所示。
1. 应用程序FTP客户端调用函数gethostbyname(3)把主机名(bsdi)转换成32 bit的IP地址。这个函数在DNS(域名系统)中称作解析器,我们将在第14章对它进行介绍。这个转换过程或者使用DNS,或者在较小网络中使用一个静态的主机文件(/etc/hosts)。
2. FTP客户端请求TCP用得到的IP地址建立连接。
3. TCP发送一个连接请求段到远端的主机,即用上述IP地址发送一份IP数据报。(在第18章我们将讨论完成这个过程的细节。)
4. 如果目的主机在本地网络上(如以太网,令牌环网,或点对点链接的另一端),那么IP数据报可以直接送到目的主机上。如果目的主机在一个远程网络上,那么就通过IP路由选择函数来确定位于本地网络上的下一站路由器地址,并让它转发IP数据报。在这两种情况下,IP数据报都是被送到位于本地网络上的一台主机或路由器。
5. 假定是一个以太网,那么发送端主机必须把32 bit的IP地址变换成48 bit的以太网地址。从逻辑Internet地址到对应的物理硬件地址需要进行翻译。这个过程就是ARP的功能完成。
ARP本来是用于广播网络的,有许多主机或路由器连在同一个网络上。
6. ARP发送一份称作ARP请求的以太网数据帧给以太网上的每个主机。这个过程称作广播,如图4.2中的虚线所示。ARP请求数据帧中包含目的主机的IP地址(主机名为bsdi),其意思是“如果你是这个IP地址的拥有者,请回答你的硬件地址。”

图4.2 当用户输入命令“ftp 主机名"时ARP的操作

7. 目的主机的ARP层收到这份广播报文后,识别出这是发送端在寻问它的IP地址,于是发送一个ARP回答。这个ARP回答包含IP地址及对应的硬件地址。
8. 收到ARP回答后,使ARP进行请求-回答交换的IP数据报现在就可以传送了。
9. 发送IP数据报到目的主机。
在ARP背后有一个基本概念,那就是网络接口有一个硬件地址(一个48 bit的值,标识不同的以太网或令牌环网络接口)。在硬件层次上进行的数据帧交换必须有正确的接口地址。但是,TCP/IP有自己的地址:32 bit的IP地址。知道主机的IP地址并不能让内核发送一帧数据给主机。内核(如以太网驱动程序)必须知道目的端的硬件地址才能发送数据。ARP的功能是在32 bitIP地址和采用不同网络技术的硬件地址之间提供动态映射。
点对点链路不使用ARP。当设置这些链路时(一般在引导过程进行),必须告知内核链路每一端的IP地址。像以太网地址这样的硬件地址并不涉及。

4.3 ARP高速缓存
ARP高效运行的关键是由于每个主机上都有一个ARP高速缓存。这个高速缓存存放了最近Internet地址到硬件地址之间的映射记录。高速缓存中每一项的生存时间一般为20分钟,起始时间从被创建时开始算起。
我们可以用arp(8)命令来检查ARP高速缓存。参数-a的意思是显示高速缓存中所有的内容。

bsdi % arp -a
sun (140.252.13.33) at 8:0:20:3:f6:42
svr4 (140.252.13.34) at 0:0:c0:c2:9b:26

48 bit的以太网地址用6个十六进制的数来表示,中间以冒号隔开。在4.8小节我们将讨论arp命令的其它功能。

4.4 ARP的分组格式
在以太网上解析IP地址时,ARP请求和回答分组的格式如图4.3所示。(ARP可以用于其它类型的网络,可以解析IP地址以外的地址。紧跟着帧类型字段的前四个字段指定了最后四个字段的类型和长度。)

图4.3 用于以太网的ARP请求或回答分组格式

以太网报头中的前两个字段是以太网的源地址和目的地址。目的地址为全1的特殊地址是广播地址。电缆上的所有以太网接口都要接收广播的数据帧。
2个字节长的以太网帧类型表示后面数据的类型。对于ARP请求或回答来说,该字段的值为0x0806。
形容词hardware(硬件)和protocol(协议)用来描述ARP分组中的各个字段。例如,一个ARP请求分组询问协议地址(这里是IP地址)对应的硬件地址(这里是以太网地址)。
硬件类型字段表示硬件地址的类型。它的值为1即表示以太网地址。协议类型字段表示要映射的协议地址类型。它的值为0x0800即表示IP地址。它的值与包含IP数据报的以太网数据帧中的类型字段的值相同,这是有意设计的。(参见图2.1)
接下来的两个1字节的字段,硬件地址长度和协议地址长度分别指出硬件地址和协议地址的长度,以字节为单位。对于以太网上IP地址的ARP请求或回答来说,它们的值分别为6和4。
操作字段指出四种操作类型,它们是ARP请求(值为1),ARP回答(值为2),RARP请求(值为3),RARP回答(值为4)。(我们在第5章讨论RARP。)这个字段必需的,因为ARP请求和ARP回答的帧类型字段值是相同的。
接下来的四个字段是发送端的硬件地址(在本例中是以太网地址),发送端的协议地址(IP地址),目的端的硬件地址,目的端的协议地址。注意,这里有一些重复信息:在以太网的数据帧报头中和ARP请求数据帧中都有发送端的硬件地址。
对于一个ARP请求来说,除目的端硬件地址外的所有其他的字段都有填充值。当系统收到一份目的端为本机的ARP请求报文后,它就把硬件地址填进去,然后用两个目的端地址分别替换两个发送端地址,并把操作字段置为2,最后把它发送回去。

4.5 ARP举例
在本小节中,我们用tcpdump命令来看一看运行像Telnet这样的普通TCP工具软件时ARP会做些什么。附录A包含tcpdump命令的其它细节。

普通例子
为了看清楚ARP的运作过程,我们执行telnet命令与无效的服务器连接。

(见原书p.57的①)

当我们在另一个系统上(sun)运行带有-e参数的tcpdump命令时,显示的是硬件地址(在我们的例子中是48 bit的以太网地址。)

图4.4 TCP连接请求产生的ARP请求和回答

图4.4中的tcpdump的原始输出如图附录A中的A.3所示。由于这是本书第一个tcpdump输出例子,你应该去查看附录中的原始输出,看看我们作了哪些修改。
我们删除了tcpdump命令输出的最后四行,因为它们是结束连接的信息(我们将在第18章进行讨论),与这里讨论的内容不相关。
在第1行中,源端主机(bsdi)的硬件地址是0:0:c0:6f:2d:40。目的端主机的硬件地址是ff:ff:ff:ff:ff:ff,这是一个以太网广播地址。电缆上的每个以太网接口都要接收这个数据帧并对它进行处理,如图4.2所示。
第1行中紧接着的一个输出字段是arp,表明帧类型字段的值是0x0806,说明此数据帧是一个ARP请求或回答。
在每行中,单词arp或ip后面的值60指的是以太网数据帧的长度。由于ARP请求或回答的数据帧长都是42字节(28字节的ARP数据,14字节的以太网帧头),因此每一帧都必须加入填充字符以达到以太网的最小长度要求:60字节。
请参见图1.7,这个最小长度60字节包含14字节的以太网帧头,但是不包括4个字节的以太网帧尾。有一些书把最小长度定为64字节,它包括以太网的帧尾。我们在图1.7中把最小长度定为46字节,是有意不包括14字节的帧首部,因为对应的最大长度(1500字节)指的是MTU――最大传输单元(图2.5)。我们使用MTU经常是因为它对IP数据报的长度进行限制,但一般与最小长度无关。大多数的设备驱动程序或接口卡自动地用填充字符把以太网数据帧充满到最小长度。第3,4和5行中的IP数据报(包含TCP段)的长度都比最小长度小,因此都必须进行填充到60字节。
第1行中的下一个输出字段arp who-has表示作为ARP请求的这个数据帧中,目的IP地址是svr4的地址,发送端的IP地址是bsdi的地址。tcpdump打印出主机名对应的默认IP地址。(在4.7节中,我们将用-n参数来查看ARP请求中真正的IP地址。)
从第2行中我们可看到,尽管ARP请求是广播的,但是ARP回答的目的地址却是bsdi(0:0:c0:6f:2d:40)。ARP回答是直接送到请求端主机的,而是广播的。
tcpdump打印出arp reply的字样,同时打印出响应者的主机名和硬件地址。
第3行是第一个请求建立连接的TCP段。它的目的硬件地址是目的主机(svr4)。我们将在第18章讨论这个段的细节内容。
在每一行中,行号后面的数字表示tcpdump收到分组的时间(以秒为单位)。除第1行外,其它每行在括号中还包含了与上一行的时间差异(以秒为单位)。我们从这个图可以看出,发送ARP请求与收到ARP回答之间的时延是2.2 ms。而在0.7 ms之后发出第一段TCP报文。在本例中,用ARP进行动态地址解析的时间小于3 ms。
最后需要指出的一点,在tcpdump命令输出中,我们没有看到svr4在发出第一段TCP报文(第4行)之前发出的ARP请求。这是因为可能在svr4的ARP高速缓存中已经有bsdi的表项。一般情况下,当系统收到ARP请求或发送ARP回答时,都要把请求端的硬件地址和IP地址存入ARP高速缓存。在逻辑上可以假设,如果请求端要发送IP数据报,那么数据报的接收端将很可能会发送一个回答。

对不存在主机的ARP请求
如果查询的主机已关机或不存在会发生什么情况呢?为此我们指定一个并不存在的Internet地址――根据网络号和子网号所对应的网络确实存在,但是并不存在所指定的主机号。从图3.10我们可以看出,主机号从36到62的主机并不存在(主机号为63是广播地址)。这里,我们用主机号36来举例子。

(见原书p.59的①)

tcpdump命令的输出如图4.5所示。

图4.5 对不存在主机的ARP请求

这一次,我们没有用-e选项,因为我们已经知道ARP请求是在网上广播的。
令人感兴趣的是看到多次进行ARP请求:第一次请求发生后5.5秒进行第二次请求,在24秒之后又进行第三次请求。(在第21章我们将看到TCP的超时和重发算法的细节。)tcpdump命令输出的超时限制为29.5秒。但是,在telnet命令使用前后分别用date命令检查时间,可以发现Telnet客户端的连接请求似乎在大约75秒后才放弃。事实上,我们在后面将看到,大多数的BSD实现把完成TCP连接请求的时间限制设置为75秒。
在第18章中,当我们看到建立连接的TCP报文段序列时,会发现ARP请求对应于TCP试图发送的初始TCP SYN(同步)段。
注意,在线路上我们始终看不到TCP的报文段。我们能看到的是ARP请求。直到ARP回答返回时,TCP报文段才可以被发送,因为硬件地址到这时才可能知道。如果我们用过滤模式运行tcpdump命令,只查看TCP数据,那么将没有任何输出。

ARP 高速缓存超时设置
在ARP高速缓存中的表项一般都要设置超时值。(在4.8小节中,我们将看到管理员可以用arp命令把地址放入高速缓存中而不设置超时值。)从伯克利系统演变而来的系统一般对完整的表项设置超时值为20分钟,而对不完整的表项设置超时值为3分钟。(在前面的例子中我们已见过一个不完整的表项,即在以太网上对一个不存在的主机发出ARP请求。)当这些表项再次使用时,这些实现一般都把超时值重新设为20分钟。

(下面是原书p.60①的译文)
在RFC中说,在表项正在使用时,超时值就应该启动,但是大多数的从伯克利系统演变而来的系统没有这样做――它们每次都是在访问表项进重设超时值。

4.6 ARP代理
如果ARP请求是从一个网络的主机发往另一个网络上的主机,那么连接这两个网络的路由器就可以回答该请求,这个过程称作委托ARP或ARP代理(Proxy ARP)。这样可以欺骗发起ARP请求的发送端,使它误以为路由器就是目的主机,而事实上目的主机是在路由器的“另一边”。路由器的功能相当于目的主机的代理,把分组从其它主机转发给它。
举例是说明ARP代理的最好方法。如图3.10所示,系统sun与两个以太网相连。但是,我们也指出过,事实上并不是这样,请把它与封二中的图进行比较。在sun和子网140.252.1之间实际存在一个路由器,就是这个具有ARP代理功能的路由器使得sun就好像在子网140.252.1上一样。具体安置如图4.6所示,路由器Telebit NetBlazer,取名为netb,在子网和主机sun之间。

图4.6 ARP代理的例子

当子网140.252.1(称作gemini)上的其它主机有一份IP数据报要传给地址为140.252.1.29的sun,gemini比较网络号(140.252)和子网号(1),因为它们都是相同的,因而在图4.6上面的以太网中发送IP地址140.252.1.29的ARP请求。路由器netb识别出该IP地址属于它的一个拔号主机,于是把它的以太网接口地址140.252.1作为硬件地址来回答。主机gemini通过以太网发送IP数据报到netb,netb通过拔号SLIP链路把数据报转发到sun。这个过程对于所有140.252.1子网上的主机来说都是透明的,主机sun实际上是在路由器netb后面进行配置的。
如果我们在主机gemini上执行arp命令,经过与主机sun通信以后,我们发现在同一个子网140.252.1上的netb和sun的IP地址映射的硬件地址是相同的。这通常是使用委托ARP的线索。

gemini % arp -a
这里是子网140.252.1上其他主机的输出行
netb (140.252.1.183) at 0:80:ad:3:6a:80
sun (140.252.1.29) at 0:80:ad:3:6a:80

图4.6中的另一个需要解释的细节是在路由器netb的下方(SLIP链路)显然缺少一个IP地址。为什么在拔号SLIP链路的两端只拥有一个IP地址,而在bsdi和slip之间的两端却分别有一个IP地址?在3.8小节我们已经指出,用ifconfig命令可以显示拔号SLIP链路的目的地址,它是140.252.1.183。NetBlazer不需要知道拔号SLIP链路每一端的IP地址。(这样做会用更多的IP地址。)相反,它通过分组到达的串行线路接口来确定发送分组的拔号主机,因此对于连接到路由器的每个拔号主机不需要用唯一的IP地址。所有的拔号主机使用同一个IP地址140.252.1.183作为SLIP链路的目的地址。
ARP代理可以把数据报传送到路由器sun上,但是子网140.252.13上的其它主机是如何处理的呢?路由选择必须使数据报能到达其它主机。这里需要特殊处理,路由选择表中的表项必须在网络140.252的某个地方制定,使所有数据报的目的端要么是子网140.252.13,要么是子网上的某个主机,这样都指向路由器netb。而路由器netb知道如何把数据报传到最终的目的端,即通过路由器sun。
ARP代理也称作混合ARP(promiscuous ARP)或ARP 出租(ARP hack)。这些名字来自于ARP代理的其它用途:通过两个物理网络之间的路由器可以互相隐藏物理网络。在这种情况下,两个物理网络可以使用相同的网络号,只要把中间的路由器设置成一个ARP代理,以响应一个网络到另一个网络主机的ARP请求。这种技术在过去用来隐藏一组在不同物理电缆上运行旧版TCP/IP的主机。分开这些旧主机有两个共同的理由,其一是它们不能处理子网划分,其二是它们使用旧的广播地址(所有比特值为0的主机号,而不是目前使用的所有比特值为1的主机号)。

4.7 免费ARP
我们可以看到的另一个ARP特性称作免费ARP (gratuitous ARP)。它是指主机发送ARP查找自己的IP地址。通常,它发生在系统引导期间进行接口配置的时候。
在我们的互联网中,如果我们引导主机bsdi并在主机sun上运行tcpdump命令,我们可以看到如图4.7所示的分组。

图4.7 免费ARP的例子

(我们用-n选项运行tcpdump命令,打印出点分十进制的地址,而不是主机名。)对于ARP请求中的各字段来说,发送端的协议地址和目的端的协议地址是一致的:即主机bsdi的地址140.252.13.35。另外,以太网报头中的源地址0:0:c0:6f:2d:40,正如tcpdump命令显示的那样,等于发送端的硬件地址(见图4.4)。
免费ARP可以有两个方面的作用。
1. 一个主机可以通过它来确定另一个主机是否设置了相同的IP地址。主机bsdi并不希望对此请求有一个回答。但是,如果收到一个回答,那么就会在终端日志上产生一个错误消息“以太网地址:a:b:c:d:e:f发送来重复的IP地址”。这样就可以警告系统管理员,某个系统有不正确的设置。
2. 如果发送免费ARP的主机正好改变了硬件地址(很可能是主机关机了,并换了一块接口卡,然后重新启动),那么这个分组就可以使其它主机高速缓存中旧的硬件地址进行相应的更新。一个比较著名的ARP协议事实[Plummer 1982]是,如果主机收到某个IP地址的ARP请求,而且它已经在接收者的高速缓存中,那么就要用ARP请求中的发送端硬件地址(如以太网地址)对高速缓存中相应的内容进行更新。主机接收到任何ARP请求都要完成这个操作。(ARP请求是在网上广播的,因此每次发送ARP请求时网络上的所有主机都要这样做。)
文献[Bhide, Elnozahy, and Morgan 1991]中有一个应用例子,通过发送含有备份硬件地址和故障服务器的IP地址的免费ARP请求,使得备份文件服务器可以顺利地接替故障服务器进行工作。这使得所有目的地为故障服务器的报文都被送到备份服务器那里,客户程序不用关心原来的服务器是否出了故障。

(以下是原书p.63①的译文)
不幸的是,作者却反对这个做法,因为这取决于所有不同类型的客户端都要有正确的ARP协议实现。它们显然碰到过客户端的ARP协议实现与规范不一致的情况。
通过检查作者所在子网上的所有系统可以发现,SunOS 4.1.3和4.4BSD在引导时都发送免费ARP,但是SVR4却没有这样做。

4.8 arp命令
我们已经用这个命令及参数-a来显示ARP高速缓存中的所有内容。这里介绍其它参数的功能。
超级用户可以用参数-d来删除ARP高速缓存中的某一项内容。(这个命令格式可以在运行一些例子之前使用,以让我们看清楚ARP的交换过程。)
另外,可以通过参数-s来增加高速缓存中的内容。这个参数需要主机名和以太网地址:对应于主机名的IP地址和以太网地址被增加到高速缓存中。新增加的内容是永久性的(比如,它没有超时值),除非在命令行的末尾附上关键字temp。
位于命令行末尾的关键字pub和-s参数一起,可以使系统起着主机ARP代理的作用。系统将回答与主机名对应的IP地址的ARP请求,并以指定的以太网地址作为回答。如果广播的地址是系统本身,那么系统就为指定的主机名起着委托ARP代理的作用。

4.9 小结
在大多数的TCP/IP实现中,ARP是一个基础协议,但是它的运行对于应用程序或系统管理员来说一般是透明的。ARP高速缓存在它的运行过程中非常关键,我们可以用arp命令对高速缓存进行检查和操作。高速缓存中的每一项内容都有一个定时器,根据它来删除不完整和完整的表项。arp命令可以显示和修改ARP高速缓存中的内容。
我们介绍了ARP的一般操作,同时也介绍了一些特殊的功能:委托ARP(当路由器对来自于另一个路由器接口的ARP请求进行回答时)和免费ARP(发送自己IP地址的ARP请求,一般发生在引导过程中)。

习题
4.1 当我们输入命令以生成类似图4.4那样的输出时,发现本地ARP快速缓存为空以后,输入命令
bsdi % rsh svr4 arp -a
如果发现目的主机上的ARP快速缓存也是空的,那将发生什么情况?(该命令将在svr4主机上运行arp -a命令。)
4.2 请描述如何判断一个给定主机是否能正确处理接收到的非必要的ARP请求的方法。
4.3 由于发送一个数据包后ARP将等待响应,因此4.2节所描述的步骤7可能会持续一段时间。你认为ARP将如何处理在这期间收到相同目的IP地址发来的多个数据包?
4.4 在4.5节的最后,我们指出Host Requirements RFC和伯克利派生系统在处理活动ARP表目的超时时存在差异。那么如果我们在一个由伯克利派生系统的客户端上,试图与一个正在更换以太网卡而处于关机状态的服务器主机联系,这时会发生什么情况?如果服务器在引导过程中广播一份免费(gratuitous)ARP,这种情况是否会发生变化?











4-1
5 RARP:逆地址解析协议

5.1 引言
具有本地磁盘的系统引导时,一般是从磁盘上的配置文件中读取IP地址。但是无盘机,如X终端或无盘工作站,则需要采用其他方法来获得IP地址。
网络上的每个系统都具有唯一的硬件地址,它是由网络接口生产厂家配置的。无盘系统的RARP实现过程是从接口卡上读取唯一的硬件地址,然后发送一份RARP请求(一帧在网络上广播的数据),请求某个主机响应该无盘系统的IP地址(在RARP回答中)。
在概念上这个过程是很简单的,但是实现起来常常比ARP要困难,其原因在本章后面介绍。RARP的正式规范是RFC 903 [Finlayson et al. 1984]。

5.2 RARP的分组格式
RARP分组的格式与ARP分组基本一致(图4.3)。它们之间主要的差别是RARP请求或回答的帧类型代码为0x8035,而且RARP请求的操作代码为3,回答操作代码为4。
对应于ARP,RARP请求以广播方式传送,而RARP回答一般是单播(unicast)传送的。

5.3 RARP举例
在我们的互连网中,我们可以强制sun主机从网络上引导,而不是从本地磁盘引导。如果我们在主机bsdi上运行RARP服务程序和tcpdump命令,那么可以得到如图5.1那样的输出。我们用-e参数使得tcpdump命令打印出硬件地址:

图5.1 RARP请求和回答

RARP请求是广播方式(第1行),而第2行的RARP回答是单播方式。第2行的输出中at sun表示RARP回答包含主机sun的IP地址(140.252.13.33)。
在第3行中,我们可以看到,一旦sun收到RARP回答,它就发送一个TFTP读请求(RRQ)给文件8CFC0D21.SUN4C。(TFTP表示简单文件传输协议。我们将在第15章详细介绍它。)文件名中的8个十六进制数字表求主机sun的IP地址140.252.13.33。这个IP地址在RARP回答中返回。文件名的后缀SUN4C表示被引导系统的类型。
tcpdump在第3行中指出IP数据报的长度是65个字节,而不是一个UDP数据报(实际上是一个UDP数据报),因为我们运行tcpdump命令时带有-e参数,以查看硬件层的地址。在图5.1中需要指出的另一点是,第2行中的以太网数据帧长度比最小长度还要小(在4.5节中我们说过应该是60字节。)其原因是我们在发送该以太网数据帧的系统(bisdi)上运行tcpdump命令的。应用程序rarpd写42字节到BSD分组过滤设备上(其中14字节为以太网数据帧的报头,剩下的28字节是RARP回答),这就是tcpdump收到的副本。但是以太网设备驱动程序要把这一短帧填充空白字符以达到最小传输长度(60)。如果我们在另一个系统上运行tcpdump命令,其长度将会是60。
我们从这个例子可以看出,当无盘系统从RARP回答中收到它的IP地址后,它将发送TFTP请求来读取引导映象。在这一点上我们将不再进一步详细讨论无盘系统是如何引导的。(第16章将描述无盘X终端利用RARP,BOOTP以及TFTP进行引导的过程。)
当网络上没有RARP服务器时,其结果如图5.2所示。每个分组的目的地址都是以太网广播地址。在who-后面的以太网地址是目的硬件地址,跟在tell后面的以太网地址是发送端的硬件地址。
请注意重发的频度。第一次重发是在6.55秒以后,然后增加到42.80秒,然后又减到5.34秒和6.55秒,然后又回到42.79秒。这种不确定的情况一直继续下去。如果计算一下两次重发之间的时间间隔,我们发现存在一种双倍的关系:从5.34到6.55是1.21秒,从 6.55到8.97是2.42秒,从8.97到13.80是4.83秒,一直这样继续下去。当时间间隔达到某个阈值时(大于42.80秒),它又重新置为5.34秒。

图5.2 网络中没有RARP服务器的RARP请求

超时间隔采用这样的递增方法比每次都采用相同值的方法要好。在图6.8中,我们将看到一种错误的超时重发方法,以及在第21章中将看到TCP的超时重发机制。

5.4 RARP服务器的设计
虽然RARP在概念上很简单,但是设计一个RARP服务器与系统相关而且比较复杂。相反,提供一个ARP服务器很简单,通常是TCP/IP在内核中实现的一部分。由于内核知道IP地址和硬件地址,因此当它收到一个询问IP地址的ARP请求时,只需用相应的硬件地址来提供回答就可以了。

作为用户进程的RARP服务器
RARP服务器的复杂性在于,服务器一般要为多个主机(网络上所有的无盘系统)提供硬件地址到IP地址的映射。该映射包含在一个磁盘文件中(在Unix系统中一般位于/etc/ethers目录中)。由于内核一般不读取和分析磁盘文件,因此RARP服务器的功能就由用户进程来提供,而不是作为内核的TCP/IP实现的一部分。
更为复杂的是,RARP请求是作为一个特殊类型的以太网数据帧来传送的(帧类型字段值为0x8035,如图2.1所示)。这说明RARP服务器必须能够发送和接收这种类型的以太网数据帧。在附录A中,我们描述了BSD分组过滤器,Sun的网络接口栓,以及SVR4数据链路提供者接口都可用来接收这些数据帧。由于发送和接收这些数据帧与系统有关,因此RARP服务器的实现与系统是捆绑在一起的。

每个网络有多个RARP服务器
RARP服务器实现的一个复杂因素是RARP请求是在硬件层上进行广播的,如图5.2所示。这意味着它们不经过路由器进行转发。为了让无盘系统在RARP服务器关机的状态下也能引导,通常在一个网络上(例如一根电缆)要提供多个RARP服务器。
当服务器的数目增加时(以提供冗余备份),网络流量也随之增加,因为每个服务器对每个RARP请求都要发送RARP回答。发送RARP请求的无盘系统一般采用最先收到的RARP回答。(对于ARP我们从来没有遇到这种情况,因为只有一台主机发送ARP回答。)另外,还有一种可能发生的情况是每个RARP服务器同时回答,这样会增加以太网发生冲突的概率。

5.5 小结
RARP协议是许多无盘系统在引导时用来获取IP地址。RARP分组格式基本上与ARP分组一致。一个RARP请求在网络上进行广播,它在分组中标明发送端的硬件地址,以请求相应IP地址的响应。回答通常是单播传送的。
RARP带来的问题包括使用链路层广播,这样就阻止大多数路由器转发RARP请求,只返回很少信息:只是系统的IP地址。在第16章中,我们将看到BOOTP在无盘系统引导时会返回更多的信息:IP地址,引导主机的名字等等。
虽然RARP在概念上很简单,但是RARP服务器的实现却与系统相关。因此,并不是所有的TCP/IP实现都提供RARP服务器。

习题
5.1 RARP需要不同的帧类型字段吗?ARP和RARP都使用相同的值0x0806吗?
5.2 在一个有多个RARP服务器的网络上,如何防止它们的响应发生冲突?
5-3
6 ICMP:Internet控制报文协议

6.1 引言
ICMP经常被认为是IP层的一个组成部分。它传递差错信息以及其它需要注意的信息。ICMP报文通常被IP层或更高层协议(TCP或UDP)使用。一些ICMP报文把差错信息返回给用户进程。
ICMP信息是在IP数据报内部被传输的,如6.1所示。

图6.1 ICMP封装在IP数据报内部

ICMP 的正式规范参见RFC 792 [Posterl 1981b]。
ICMP报文的格式如图6.2所示。所有报文的前4个字节都是一样的,但是剩下的其它字节则互不相同。下面我们将逐个介绍各种报文格式。
类型字段可以有15个不同的值,以描述特定类型的ICMP报文。某些ICMP报文还使用代码字段的值来进一步描述不同的条件。
检验和字段覆盖整个ICMP报文。使用的算法与我们在3.2节中介绍的IP首部检验和算法相同。ICMP的检验和是必需的。

图6.2 ICMP报文

在本章中,我们将粗浅地讨论ICMP报文,并对其中一部分作详细介绍:地址掩码请求和回答,时间戳请求和回答,以及不可答端口。我们将详细介绍第27章Ping程序所使用的回应请求和回答报文和第9章处理IP路由的ICMP报文。

6.2 ICMP报文的类型
各种类型的ICMP报文如图6.3所示,不同类型由报文中的类型字段和代码字段来共同决定。
图中的最后两列表明ICMP报文是一份查询报文还是一份差错报文。因为对ICMP差错报文有时需要作特殊处理,因此我们需要对它们进行区分。例如,在对ICMP差错报文进行响应时,永远不会生成另一份ICMP差错报文。(如果没有这个限制规则,我们可能会遇到一个差错产生另一个差错的情况,而差错再产生差错,这样无休止地循环下去。)
当发送一份ICMP差错报文时,报文始终包含IP的首部和产生ICMP差错报文的IP数据报的前8个字节。这样,接收ICMP差错报文的模块就会把它与某个特定的协议(根据IP数据报首部中的协议字段来判断)和用户进程(根据包含在IP数据报前8个字节中的TCP或UDP报文首部中的TCP或UDP端口号来判断)联系起来。在6.5节我们将举例来说明一点。
下面各种情况都不会导致产生ICMP差错报文:
1.ICMP差错报文。(但是,ICMP查询报文可能会产生ICMP差错报文。)
2.目的地址是广播地址(图3.9)或多播地址(D类地址,图1.5)的IP数据报。
3.作为链路层广播的数据报。
4.不是IP分片的第一片。(我们将在11.5节介绍分片。)
5.源地址不是单个主机的数据报。这就是说,源地址不能为零地址、环回地址、广播地址或多播地址。

(下面是图6.3的译文)
类型
代码
描述
查询
差错
0
0
回答回显(Ping回答,第7章)


3

目的不可到达:



0
网络不可到达(9.3节)



1
主机不可到达(9.3节)



2
协议不可到达



3
端口不可到达(6.5节)



4
需要进行分片但设置了不分片比特(11.6节)



5
源站路由选择失败(8.5节)



6
目的网络不认识



7
目的主机不认识



8
源主机被隔离(作废不用)



9
目的网络被强制禁止



10
目的主机被强制禁止



11
由于服务类型TOS网络不可到达(9.3节)



12
由于服务类型TOS主机不可到达(9.3节)



13
由于过滤通信被强制禁止



14
主机越权



15
优先权中止生效


4
0
源端被关闭(基本流控制,11.11节)


5

改变路由(9.5节):



0
对网络改变路由



1
对主机改变路由



2
对服务类型和网络改变路由



3
对服务类型和主机改变路由


8
0
请求回显(Ping请求,第7章)


9
0
路由器通告(9.6节)


10
0
路由器请求(9.6节)


11

超时:



0
传输期间生存时间为0(Traceroute, 第8章)



1
在数据报组装期间生存时间为0(11.5节)


12

参数问题:



0
坏的IP首部(包括各种差错)



1
缺少必需的选项


13
0
时间戳请求(6.4节)


14
0
时间戳回答(6.4节)


15
0
信息回答(作废不用)


16
0
信息回答(作废不用)


17
0
地址掩码请求(6.3节)


18
0
地址掩码回答(6.3节)


图6.3 ICMP报文类型

这些规则是为了防止过去允许ICMP差错报文对广播分组响应所带来的广播风暴。

6.3 ICMP地址掩码请求与回答
ICMP地址掩码请求用于无盘系统在引导过程中获取自己的子网掩码(3.5节)。系统广播它的ICMP请求报文。(这一过程与无盘系统在引导过程中用RARP获取IP地址是类似的。)无盘系统获取子网掩码的另一个方法是BOOTP协议,我们将在第16章中介绍。ICMP地址掩码请求和回答报文的格式如图6.4所示。

图6.4 ICMP地址掩码请求和回答报文

ICMP报文中的标识符和序列号字段由发送端任意选择设定,这些值在回答中将被返回。这样,发送端就可以把回答与请求进行匹配。
我们可以写一个简单的程序(取名为icmpaddrmask),它发送一份ICMP地址掩码请求报文,然后打印出所有的回答。由于一般是把请求报文发往广播地址,因此这里我们也这样做。目的地址(140.252.13.63)是子网140.252.13.32的广播地址(图3.12)。

sun % icmpaddrmask 140.252.13.33
received mask = ffffffe0, from 140.252.13.33 来自本机
received mask = ffffffe0, from 140.252.13.35 来自bsdi
received mask = ffff0000, from 140.252.13.34 来自svr4

在输出中我们首先注意到的是,从svr4返回的子网掩码是差错的。显然,尽管svr4接口已经设置了正确的子网掩码,但是SVR4还是返回了一个普通的B类地址掩码,就好像子网并不存在一样。

svr4 % ifconfig emd0
emd0: flags=23
inet 140.252.13.34 netmask ffffffe0 broadcast 140.252.13.63

SVR4处理ICMP地址掩码请求过程存在差错。
我们用tcpdump命令来查看主机bsdi上的情况,输出如图6.5所示。我们用-e参数来查看硬件地址。

图6.5 发到广播地址的ICMP地址掩码请求

注意,尽管在线路上什么也看不见,但是发送主机sun也能接收到ICMP回答(带有from ourself的输出行)。这是广播的一般特性:发送主机也能通过某种内部环回机制收到一份广播报文拷贝。由于术语“广播”的定义是指局域网上的所有主机,因此它必须包括发送主机在内。(参见图2.4,当以太网驱动程序识别出目的地址是广播地址后,它就把分组送到网络上,同时传一份拷贝到环回接口。)
接下来,bsdi广播回答,而svr4却只把回答传给请求主机。通常,回答地址必须是单播地址,除非请求端的源IP地址是0.0.0.0,本例不属于这种情况。因此,把回答发送到广播地址是BSD/386的一个内部差错。

(下面是原书p.73①的译文)
RFC规定,除非系统是地址掩码的授权代理,否则它不能发送地址掩码回答。(为了成为授权代理,它必须进行特殊配置,以发送这些回答。参见附录E。)但是,正如我们从本例中看到的那样,大多数主机在收到请求时都发送一个回答,甚至有一些主机还发送差错的回答。

最后一点可以通过下面的例子来说明。我们向本机IP地址和环回地址分别发送地址掩码请求:

sun % icmpaddrmask sun
received mask= ff000000, from 140.252.13.33

sun % icmpaddrmask localhost
received mask= ff000000, from 127.0.0.1


上述两种情况下返回的地址掩码对应的都是环回地址,即A类地址127.0.0.1。还有,我们从图2.4可以看到,发送给本机IP地址的数据报(140.252.12.33)实际上是送到环回接口。ICMP地址掩码回答必须是收到请求接口的子网掩码(这是因为多接口主机每个接口有不同的子网掩码),因此两种情况下地址掩码接求都来自于环回接口。

6.4 ICMP时间戳请求与回答
ICMP时间戳请求允许系统向另一个系统查询当前的时间。返回的建议值是自午夜开始计算的毫秒数,协调的统一时间(Coordinated Universal Time, UTC)。(早期的参考手册认为UTC是格林尼治时间。)这种ICMP报文的好处是它提供了毫秒级的分辨率,而利用其它方法从别的主机获取的时间(如某些Unix系统提供的rdate命令)只能提供秒级的分辨率。由于返回的时间是从午夜开始计算的,因此调用者必须通过其它方法获知当时的日期,这是它的一个缺陷。
ICMP时间戳请求和回答报文格式如图6.6所示。

图6.6 ICMP时间戳请求和回答报文

请求端填写发起时间戳,然后发送报文。回答系统收到请求报文时填写接收时间戳,在发送回答时填写发送时间戳。但是,实际上,大多数的实现把后面两个字段都设成相同的值。(提供三个字段的原因是可以让发送方分别计算发送请求的时间和发送回答的时间。)

例子
我们可以写一个简单程序(取名为icmptime),给某个主机发送ICMP时间戳请求,并打印出返回的回答。它在我们的小互连网上运行结果如下:

(见原书p.74的①)

程序打印出ICMP报文中的三个时间戳:发起时间戳(orig),接收时间戳(recv),以及发送时间戳(xmit)。正如我们在这个例子以及下面的例子中所看到的那样,所有的主机把接收时间戳和发送时间戳都设成相同的值。
我们还能计算出往返时间(rtt),它的值是收到回答时的时间值减去发送请求时的时间值。difference的值是接收时间戳值减去发起时间戳值。这些值之间的关系如图6 7所示。
如果我们相信RTT的值,并且相信RTT的一半用于请求报文的传输,另一半用于回答报文的传输,那么为了使本机时钟与查询主机的时钟一致,本机时钟需要进行调整,调整值是difference减去RTT的一半。在前面的例子中,bsdi的时钟比sun的时钟要慢7 ms和8 ms。
由于时间戳的值是自午夜开始计算的毫秒数,即UTC,因此它们的值始终小于86,400,000 (24×60×60×1000)。这些例子都是在下午4:00以前运行的,并且在一个比UTC慢7个小时的时区,因此它们的值比82,800,000(2300小时)要大是有道理的。
如果我们对主机bsdi重复运行该程序数次,我们发现接收时间戳和发送时间戳的最后一位数总是0。这是因为该版本的软件(0.9.4版)只能提供10毫秒的时间分辨率。(说明参见附录B。)
如果我们对主机svr4运行该程序两次,我们发现SVR4时间戳的最后三位数始终为0:

(见原书p.75的①)

由于某种原因,SVR4在ICMP时间戳中不提供毫秒级的分辨率。这样,对秒以下的时间差调整将不起任何作用。
如果我们对子网140.252.1上的其它主机运行该程序,结果表明其中一台主机的时钟与sun相差3.7秒,而另一个主机时钟相差近75秒:

(见原书p.75的②)

另一个令人感兴趣的例子是路由器gateway(一个Cisco路由器)。这表明,当系统返回一个非标准时间戳值时(不是自午夜开始计算的毫秒数,UTC),它就用32 bit时间戳中的高位来表示。我们的程序证明了一点,在尖括号中打印出了接收和发送的时间戳值(在关闭高位之后)。另外,我们不能计算发起时间戳和接收时间戳之间的时间差,因为它们的单位不一致。

(见原书p.76的①)

如果我们在这台主机上运行该程序数次,会发现时间戳值显然具有毫秒级的分辨率,而且是从某个起始点开始计算的毫秒数,但是起始点并不是午夜UTC。(例如,可能是从路由器引导时开始计数的毫秒数。)
作为最后一个例子,我们来比较sun主机和另一个已知是准确的系统时钟----一个NTP stratum 1服务器。(下面我们会更多地讨论NTP,网络时间协议。)

(见原书p.76的②)

如果我们把difference的值减去RTT的一半,结果表明sun主机上的时钟要快38.5到51.5 ms。

另一种方法
还可以用另一种方法来获得时间和日期。
1. 我们在1.12节中描述了日期时间服务程序和时间服务程序。前者是以人们可读的格式返回当前的时间和日期,是一行ASCII字符。我们可以用telnet命令来验证这个服务:

(见原书p.76的③)

另一方面,时间服务程序返回的是一个32 bit的二制进数值,表示自UTC,1900年1月1日午夜起算的秒数。这个程序是以秒为单位提供的日期和时间。(前面我们提过的rdate命令使用的是TCP时间服务程序。)
2. 严格的计时器使用网络时间协议(NTP),该协议在RFC 1305中给出了描述[Mills 1992]。这个协议采用先进的技术来保证LAN或WAN上的一组系统的时钟误差在毫秒级以内。对计算机精确时间感兴趣的读者应该阅读这份RFC文档。
3. 开放软件基金会(OSF)的分布式计算环境(DCE)定义了分布式时间服务(DTS),它也提供计算机之间的时钟同步。文献[Rosenberg, Kenney and Fisher 1992]提供了该服务的其它细节描述。
4. 伯克利大学的Unix系统提供守护程序timed(8),来同步局域网上的系统时钟。不像NTP和DTS,timed不在广域网范围内工作。

6.5 ICMP端口不可达差错
最后两小节我们来讨论ICMP查询报文----地址掩码和时间戳查询及回答。我们现在来分析一种ICMP差错报文,即端口不可到达报文,它是ICMP目的不可到达报文中的一种,以此来看一看ICMP差错报文中所附加的信息。我们使用UDP(见第11章)来查看它。
UDP的规则之一是,如果收到一份UDP数据报而目的端口与某个正在使用的进程不相符,那么UDP返回一个ICMP不可到达报文。我们可以用TFTP来强制生成一个端口不可到达报文。(TFTP将在第15章描述。)
对于TFTP服务器来说,UDP的公共端口号是69。但是大多数的TFTP客户程序允许我们用connect命令来指定一个不同的端口号。这里,我们就用为它指定为8888:

(见原书p.77的①)

connect命令首先指定要连接的主机名及其端口号,接着用get命令来取文件。敲入get命令后,一份UDP数据报就发送到主机svr4上的8888端口。tcpdump命令引起的报文交换结果如图6.8所示。
在UDP数据报送到svr4之前,要先发送一份ARP请求来确定它的硬件地址(第1行)。接着返回ARP回答(第2行),然后才发送UDP数据报(第3行)。(我们在tcpdump的输出中保留ARP请求和回答是为了提醒我们,这些报文交换可能在第一个IP数据报从一个主机发送到的另一个主机之前是必需的。在本书以后的章节中,如果这些报文与讨论的题目不相关,那么我们将省略它们。)

图6.8 由TFTP产生的ICMP端口不可到达差错

一个ICMP端口不可到达差错是立刻返回的(第4行)。但是,TFTP客户程序看上去似乎忽略了这个ICMP报文,而在5秒钟之后又发送了另一份UDP数据报(第5行)。在客户程序放弃之前重发了三次。
注意,ICMP报文是在主机之间交换的,而不用目的端口号,而每个20字节的UDP数据报则是从一个特定端口(2924)发送到另一个特定端口(8888)。
跟在每个UDP后面的数字20指的是UDP数据报中的数据长度。在这个例子中,20字节包括TFTP的2个字节的操作代码,9个字节以空字符结束的文件名temp.foo,以及9个字节以空字符结束的字符串netascii。(TFTP报文的详细格式参见图15.1。)
如果用-e参数运行同样的例子,我们可以看到每个返回的ICMP端口不可到达报文的完整长度。这里的长度为70字节,各字段分配如图6.9所示。

图6.9 “UDP端口不可到达”例子中返回的ICMP报文

ICMP的一个规则是,ICMP差错报文(参见图6.3的最后一列)必须包括生成该差错报文的数据报IP首部(包含任何选项),还必须至少包括跟在该IP首部后面的前8个字节。在我们的例子中,跟在IP首部后面的前8个字节包含UDP的首部(图11.2)。
一个重要的事实是包含在UDP首部中内容是源端口号和目的端口号。就是由于目的端口号(8888)才导致产生了ICMP端口不可到达的差错报文。接收ICMP的系统可以根据源端口号(2924)来把差错报文与某个特定的用户进程相关联(在本例中是TFTP客户程序)。
导致差错的数据报中的IP首部要被送回的原因是因为IP首部中包含了协议字段,使得ICMP可以知道如何解释后面的8个字节(在本例中是UDP首部)。如果我们来查看TCP首部(图17.2),可以发现源端口和目的端口被包含在TCP首部的前8个字节中。
ICMP不可到达报文的一般格式如图6.10所示。

图6.10 ICMP不可到达报文

在图6.3中,我们注意到有16种不同类型的ICMP不可到达报文,代码分别从0到15。ICMP端口不可到达差错代码是3。另外,尽管图6.10指出了在ICMP报文中的第二个32 bit字必须为0,但是当代码为4时(“需要分片但设置了不分片比特”),路径MTU发现机制(2.9节)却允许路由器把外出接口的MTU填在这个32 bit字的低16 bit中。我们在11.6节中给出了一个这种差错的例子。

(下面是原书p.79①的译文)
尽管ICMP规则允许系统返回多于8个字节的产生差错的IP数据报中的数据,但是大多数从伯克利派生出来的系统只返回8个字节。Solaris 2.2的ip_icmp_return_data_bytes选项默认条件下返回前64个字节(E.4节)。

tcpdump时间系列
在本书的后面章节中,我们还要以时间系列的格式给出tcpdump命令的输出,如图6.11所示。

图6.11 发送到无效端口的TFTP请求的时间系列

时间随着向下而递增,在图左边的时间标记与tcpdump命令的输出是相同的(图6.8).位于图顶部的标记是通信双方的主机名和端口号。需要指出的是,随着页面向下的y坐标轴与真正的时间值不是成比例的。当出现一个有意义的时间段时,在本例中是每5秒之间的重发,我们就在时间系列的两侧作上标记。当UDP或TCP数据正在被传送时,我们用粗线的行来表示。
当ICMP报文返回时,为什么TFTP客户程序还要继续重发请求呢?这是由于网络编程中的一个因素,即BSD系统不把从插口(socket)接收到的ICMP报文中的UDP数据通知用户进程,除非该进程已经发送了一个connect命令给该插口。标准的BSD TFTP客户程序并不发送connect命令,因此它永远也不会收到ICMP差错报文的通知。
这里需要注意的另一点是TFTP客户程序所采用的不太好的超时重发算法。它只是假定5秒是足够的,因此每隔5少就重传一次,总共需要25秒钟的时间。在后面我们将看到TCP有一个较好的超时重发算法。

(下面是原书p.81的①的译文)
TFTP客户程序所采用的超时重传算法已被RFC所禁用。不过,在作者所在子网上的三个系统以及Solaris 2.2仍然在使用它。AIX 3.2.2采用一种指数退避方法来设置超时值,分别在0,5,15和35秒时重发报文,这正是所推荐的方法。我们将在第21章更详细地讨论超时问题。
最后需要指出的是,ICMP报文是在发送UDP数据报3.5 ms后返回的,这与第7章我们所看到的Ping回答的往返时间差不多。

6.6 ICMP报文的4.4BSD处理
由于ICMP覆盖的范围很广泛,从致命差错到信息差错,因此即使在一个给定的系统实现中,对每个ICMP报文的处理都是不相同的。图6.12的内容与图6.3相同,它显示的是4.4BSD系统对每个可能的ICMP报文的处理方法。
如果最后一列标明是“内核”,那么ICMP就由内核来处理。如果最后一列指明是“用户进程”,那么报文就被传送到所有在内核中登记的用户进程,以读取收到的ICMP报文。如果不存在任何这样的用户进程,那么报文就悄悄地被丢弃。(这些用户进程还会收到所有其他类型的ICMP报文的拷贝,虽然它们应该由内核来处理,当然用户进程只有在内核处理以后才能收到这些报文。)有一些报文完全被忽略。最后,如果最后一列标明的是引号内的一串字符,那么它就是对应的Unix差错。其中一些差错,如TCP对发送端关闭的处理等,我们将在以后的章节中对它们进行讨论。

(下面是图6.12的译文)
类型
代码
描述
处理方法
0
0
回显回答
用户进程
3

目的不可到达:


0
网络不可到达
“无路由到达主机”

1
主机不可到达
“无路由到达主机”

2
协议不可到达
“连接被拒绝”

3
端口不可到达
“连接被拒绝”

4
需要进行分片但设置了不分片比特DF
“报文太长”

5
源站路由选择失败
“无路由到达主机”

6
目的网络不认识
“无路由到达主机”

7
目的主机不认识
“无路由到达主机”

8
源主机被隔离(作废不用)
“无路由到达主机”

9
目的网络被强制禁止
“无路由到达主机”

10
目的主机被强制禁止
“无路由到达主机”

11
由于服务类型TOS网络不可到达
“无路由到达主机”

12
由于服务类型TOS主机不可到达
“无路由到达主机”

13
由于过滤通信被强制禁止
(忽略)

14
主机越权
(忽略)

15
优先权中止生效
(忽略)
4
0
源站被抑制(quench)
TCP由内核处理,UDP则忽略
5

改变路由


0
对网络改变路由
内核更新路由表

1
对主机改变路由
内核更新路由表

2
对服务类型和网络改变路由
内核更新路由表

3
对服务类型和主机改变路由
内核更新路由表
8
0
回显请求

9
0
路由器通告
用户进程
10
0
路由器请求
用户进程
11

超时:


0
传输期间生存时间为0
用户进程

1
在数据报组装期间生存时间为0
用户进程
12

参数问题:


0
坏的IP首部(包括各种差错)
“协议不可用”

1
缺少必需的选项
“协议不可用”
13
0
时间戳请求
内核产生回答
14
0
时间戳回答
用户进程
15
0
信息请求(作废不用)
(忽略)
16
0
信息回答(作废不用)
用户进程
17
0
地址掩码请求
内核产生回答
18
0
地址掩码回答
用户进程
图6.12 4.4BSD系统对ICMP报文的处理

6.7 小结
本章对每个系统都必须包括的Internet控制报文协议进行了讨论。图6.3列出了所有的ICMP报文类型,其中大多数我们都将在以后的章节中加以讨论。
我们详细讨论了ICMP地址掩码请求和回答以及时间戳请求和回答。这些是典型的请求-回答报文。二者在ICMP报文中都标识符和序号。发送端应用程序在标识字段内存入一个唯一的数值,以区别于其它进程的回答。序号字段使得客户程序可以在回答和请求之间进行匹配。
我们还讨论了ICMP端口不可到达差错,一种常见的ICMP差错。我们对返回的ICMP差错信息进行了分析:导致差错的IP数据报的首部及后序8个字节。这个信息对于ICMP差错的接收方来说是必要的,可以更多地了解导致差错的原因。这是因为TCP和UDP都在它们的首部前8个字节中存入源端口号和目的端口号。
最后,我们第一次给出了按时间先后的tcpdump输出,这种表现方式的输出在本书后面的章节中会经常用到。

习题
6.1 在6.2节的末尾我们列出了5种不发送ICMP差错报文的特殊条件。如果这些条件不满足而我们又在局域网上向一个似乎不存在的端口号发送一份广播UDP数据报,这时会发生什么样的情况?
6.2 阅读RFC [Braden 1989a],注意生成一个ICMP端口不可到达差错是否为“必须”,“应该”或者“可能”。这些信息所在的页码和章节是多少?
6.3 阅读RFC 1349 [Almquist 1992],看看IP的服务类型字段(图3.2)是如何被ICMP设置的?
6.4 如果你的系统提供netstat命令,请用它来查看接收和发送的ICMP报文类型。

6-9
7 Ping程序

7.1 引言
“ping”这个名字来自于声纳定位操作。Ping程序由Mike Muuss编写,目的是为了测试另一台主机是否可达。该程序发送一份ICMP回显请求报文给主机,并等待返回ICMP回显回答。(图6.3列出了所有的ICMP报文类型。)
一般来说,如果你不能Ping到某台主机,那么你就不能Telnet或者FTP到那台主机。反过来,如果你不能Telnet到某台主机,那么通常可以用Ping程序来确定问题出在哪里。Ping程序还能测出到这台主机的往返时间,以表明该主机离我们有“多远”。
在本章中,我们将使用Ping程序作为诊断工具来深入剖析ICMP。Ping还给我们提供了检测IP记录路由和时间戳选项的机会。文献[Stevens 1990]的第11章提供了Ping程序的源代码。

(下面是原书p.85①的译文)
几年前我们还可以作出这样没有限定的断言,如果我们不能Ping到某台主机,那么就不能Telnet或FTP那台主机。随着Internet安全意识的增强,出现了提供访问控制清单的路由器和防火墙,那么像这样没有限定的断言不再成立了。一台主机的可达性可能不只取决于IP层是否可达,还取决于使用何种协议以及端口号。Ping程序的运行结果可能显示某台主机不可达,但我们可以用Telnet远程登录到该台主机的25号端口(邮件服务器)。

7.2 Ping程序
我们称发送回显请求的ping程序为客户,而称被ping的主机为服务器。大多数的TCP/IP实现都在内核中直接支持Ping服务器——这种服务器不是一个用户进程。(我们在第6章中描述的两种ICMP查询服务,地址掩码和时间戳请求,也都是直接在内核中进行处理的。)
ICMP回显请求和回显回答报文如图7.1所示。

图7.1 ICMP回显请求和回显回答报文格式

对于其他类型的ICMP查询报文,服务器必须响应标识符和序号字段。另外,客户发送的选项数据必须回显,假设客户对这些信息都会感兴趣。
Unix系统在实现ping程序时是把ICMP报文中的标识符字段置成发送进程的ID号。这样即使在同一台主机上同时运行了多个ping程序实例,ping程序也可以识别出返回的信息。
序号从0开始,每发送一次新的回显请求就加1。ping程序打印出返回的每个分组的序号,允许我们查看是否有分组丢失,失序或重复。IP是一种最好的数据报传递服务,因此这三个条件都有可能发生。
旧版本的ping程序曾经以这种模式运行,即每秒发送一个回显请求,并打印出返回的每个回显回答。但是,新版本的实现需要加上-s参数才能以这种模式运行。默认情况下,新版本的ping程序只发送一个回显请求,如果收到回显回答则输出“host is alive”,否则在20秒内没有收到回答就输出“no answer”(没有回答)。

LAN输出
在局域网LAN上运行ping程序的结果输出一般有如下格式:

(见原书p.86的①)

当返回ICMP回显回答时,要打印出序号和TTL,并计算往返时间。(TTL位于IP首部中的生存时间字段。当前的BSD系统中的ping程序每次收到回显回答时都打印出收到的TTL----有些系统并不这样做。我们将在第8章中通过traceroute程序来介绍TTL的用法。)
我们从上面的输出中可以看出,回显回答是以发送的次序返回的(0,1,2等等)。
ping程序通过在ICMP报文数据中存放发送请求的时间值来计算往返时间。当回答返回时,用当前时间减去存放在ICMP报文中的时间值,即是往返时间。注意,在发送端bsdi上,往返时间的计算结果都为0 ms。这是因为程序使用的计时器分辨率低的原因。BSD/386版本0.9.4系统只能提供10 ms级的计时器。(我们在附录B中有更详细的介绍。)在后面的章节中,当我们在具有较高分辨率计时器的系统上(Sun)查看tcpdump输出时会发现,ICMP回显请求和回显回答的时间差在4 ms以下。
输出的第一行包括目的主机的IP地址,尽管我们指定的是它的名字(svr4)。这说明名字已经经过解析器被转换成IP地址了。我们将在第14章介绍解析器和DNS。现在,我们发现,如果敲入ping命令,几秒钟过后会在第一行打印出IP地址,DNS就是利用这段时间来确定主机名所对应的IP地址。
本例中的tcpdump输出如图7.2所示。

图7.2 在LAN上运行ping程序的结果

从发送回显请求到收到回显回答,时间间隔始终为3.7 ms。我们还可以看到,回显请求大约每隔1秒钟发送一次。
通常,第一个往返时间值要比其他的大。这是由于目的端的硬件地址不在ARP高速缓存中的缘故。正如我们在第4章中看到的那样,在发送第一个回显请求之前要发送一个ARP请求并接收ARP回答,这需要花费几毫秒的时间。下面的例子说明了这一点:

(见原书p.88的①)

第一个RTT中多的3 ms很可能就是因为发送ARP请求和接收ARP回答所花费的时间。
这个例子运行在sun主机上,它提供的是具有微秒级分辨率的计时器,但是ping程序只能打印出毫秒级的往返时间。在前面运行于BSD/386 0.9.4版上的例子中,打印出来的往返时间值为0 ms,这是因为计时器只能提供10 ms的误差。下面的例子是BSD/386 1.0版的输出,它提供的计时器也具有微秒级的分辨率,因此ping程序的输出结果也具有较高的分辨率。

(见原书p.88的②)

WAN输出
在一个广域网WAN上,结果会有很大的不同。下面的例子是在某个工作日的下午即Internet具有正常通信量时的运行结果:

(见原书p.88的③)

这里,序号为1,2,3,4,6,10,11,12和13的回显请求或回显回答在某个地方丢失了。另外,我们注意到往返时间发生了很大的变化。(像52%这样高的分组丢失率是不正常的。即使是在工作日的下午,对于Internet来说也是不正常的,。)
通过广域网还有可能看到重复的分组(即相同序号的分组被打印两次或更多次),失序的分组(序号为N + 1的分组在序号为N的分组之前被打印。)

线路SLIP链接
让我们再来看看SLIP链路上的往返时间,因为它们经常运行于低速的异步方式,如9600 b/s或更低。回想我们在2.10节计算的串行线路吞吐量。针对这个例子,我们把主机bsdi和slip之间的SLIP链路传输速率设置为1200 b/s。
下面我们可以来估计往返时间。首先,我们从前面的Ping程序输出例子中可以注意到,默认情况下发送的ICMP报文有56个字节。再加上20个字节的IP首部和8个字节的ICMP首部,IP数据报的总长度为84字节。(我们可以运行tcpdump -e命令查看以太网数据帧来验证这一点。)另外,从2.4节我们可以知道,至少要增加两个额外的字节:在数据报的开始和结尾加上END字符。此外,SLIP帧还有可能再增加一些字节,但这取决于数据报中每个字节的值。对于1200 b/s这个速率来说,由于每个字节含有8 bit数据,1 bit起始位和1 bit结束位,因此传输速率是每秒120个字节,或者说每个字节8.33 ms。所以我们可以估计需要1433(86×8.33×2) ms 。(乘2是因为我们计算的是往返时间。)
下面的输出证实了我们的计算:

(见原书p.89的①)

(对于SVR4来说,如果每秒钟发送一次请求则必须带-s参数。)往返时间大约是1.5秒,但是程序仍然每间隔1秒钟发送一次ICMP回显请求。这说明在第一个回显回答返回之前(1.480秒时刻)就已经发送了两次回显请求(分别在0秒和1秒时刻)。这就是为什么总结行指出丢失了一个分组。实际上分组并未丢失,很可能仍然在返回的途中。
我们在第8章讨论traceroute程序时将回头再讨论这种低速的SLIP链路。

拔号SLIP链路
对于拔号SLIP链路来说,情况有些变化,因为在链路的两端增加了调制解调器。用在sun和netb系统之间的调制解调器提供的是V.32调制方式(9600 b/s),V.42错误控制方式(也称作LAP-M),以及V.42bis数据压缩方式。这表明我们针对线路链路参数进行的简单计算不再准确了。
很多因素都有可能影响。调制解调器带来了时延。随着数据的压缩,分组长度可能会减小,但是由于使用了错误控制协议,分组长度又可能会增加。另外,接收端的调制解调器只能在验证了循环检验字符(检验和)后才能释放收到的数据。最后,我们还要处理每一端的计算机异步串行接口,许多操作系统只能在固定的时间间隔内,或者收到若干字符后者才去读这些接口。
作为一个例子,我们在sun主机上ping主机gemini,输出结果如下:

(见原书p.90的①)

注意,第一个RTT不是10 ms的整数倍,但是其它行都是10 ms的整数倍。如果我们运行该程序若干次,发现每次结果都是这样。(这并不是由sun主机上的时钟分辨率造成的结果,因为根据附录B中的测试结果可以知道它的时钟能提供毫秒级的分辨率。)
另外还要注意,第一个RTT要比其他的大,而且依次递减,然后徘徊在280至300 ms之间。我们让它运行一分钟到两分钟,RTT一直处于这个范围,不会低于260 ms。如果我们以9600 b/s的速率计算RTT(习题7.2),那么我们观察到的值应该大约是估计值的1.5倍。
如果运行ping程序60秒钟并计算观察到的RTT的平均值,我们发现在V.42和V.42bis模式下平均值为277 ms。(这要比上个例子打印出来的平均值要好,因为运行时间较长,这样就把开始较长的时间平摊了。)如果我们关闭V.42bis数据压缩方式,平均值为330 ms。如果我们关闭V.42错误控制方式(它同时也关闭了V.42bis数据压缩方式),平均值为300 ms。这些调制解调器的参数对RTT的影响很大,使用错误控制和数据压缩方式似乎效果最好。

7.3 IP记录路由选项
ping程序为我们提供了查看IP记录路由(RR)选项的机会。大多数不同版本的ping程序都提供-R参数,以提供记录路由的功能。它使得ping程序在发送出去的IP数据报中设置IP RR选项(该IP数据报包含ICMP回显请求报文)。这样,每个处理该数据报的路由器都把它的IP地址放入选项字段中。当数据报到达目的端时,IP地址清单应该复制到ICMP回显回答中,这样返回途中所经过的路由器地址也被加入清单中。当ping程序收到回显回答时,它就打印出这份IP地址清单。
这个过程听起来简单,但存在一些缺陷。源端主机生成RR选项,中间路由器对RR选项的处理,以及把ICMP回显请求中的RR清单复制到ICMP回显回答中,所有这些都是选项功能。幸运的是,现在的大多数系统都支持这些选项功能,只是有一些系统不把ICMP请求中的IP清单复制到ICMP回答中。
但是,最大的问题是IP首部中只有有限的空间来存放IP地址。我们从图3.1可以看到,IP首部中的首部长度字段只有4 bit,因此整个IP首部最长只能包括15个32 bit长的字(即60个字节)。由于IP首部固定长度为20字节,RR选项用去3个字节(下面我们再讨论),这样只剩下37个字节(60 - 20 - 3)来存放IP地址清单,也就是说只能存放9个IP地址。对于早期的ARPANET来说,9个IP地址似乎是很多了,但是现在看来是非常有限的。(在第8章中,我们将用Traceroute工具来确定数据报的路由。)除了这些缺点,记录路由选项工作得很好,为详细查看如何处理IP选项提供了一个机会。
IP数据报中的RR选项的一般格式如图7.3所示。

图7.3 IP首部中的记录路由选项的一般格式

code是一个字节,指明IP选项的类型。对于RR选项来说,它的值为7。len是RR选项总字节长度,在这种情况下为39。(尽管可以为RR选项设置比最大长度小的长度,但是ping程序总是提供39字节的选项字段,最多可以记录9个IP地址。由于IP首部中留给选项的空间有限,它一般情况都设置成最大长度。)
ptr称作指针字段。它是一个基于1的指针,指向存放下一个IP地址的位置。它的最小值为4,指向存放第一个IP地址的位置。随着每个IP地址存入清单,ptr的值分别为8,12,16,最大到36。当记录下9个IP地址后,ptr的值为40,表示清单已满。
当路由器(根据定义应该是多穴的)在清单中记录IP地址时,它应该记录哪个地址呢?是入口地址还是出口地址?为此,RFC 791 [Postel 1981a]指定路由器记录出口IP地址。我们在后面将看到,当原始主机(运行ping程序的主机)收到带有RR选项的ICMP回显回答时,它也要把它的入口IP地址放入清单中。

正常的例子
我们举一个用RR选项运行ping程序的例子。我们在主机svr4上运行ping程序到主机slip。一个中间路由器(bsdi)将处理这个数据报。下面是svr4的输出结果:

(见原书p.92的①)

分组所经过的四站如图7.4所示(每个方向各有两站),每一站都把自己的IP地址加入RR清单。

图7.4 带有记录路由选项的ping程序

路由器bsdi在不同方向上分别加入了不同的IP地址。它始终是把出口的IP地址加入清单。我们还可以看到,当ICMP回显回答到达原始系统(svr4)时,它把自己的入口IP地址也加入清单中。
我们还可以通过运行带有-v参数的tcpdump命令来查看主机sun上进行的分组交换(参见IP选项)。输出如图7.5所示。

图7.5 记录路由选项的tcpdump输出

输出中optlen=40表示在IP首部中有40个字节的选项空间。(IP首部长度必须为4字节的整数倍。)RR{39}的意思是记录路由选项已被设置,它的长度字段是39。然后是9个IP地址,符号“#”用来标记RR选项中的ptr字段所指向的IP地址。由于我们是在主机sun上观察这些分组(参见图7.4),因此我们所能看到ICMP回显请求中的IP地址清单是空的,而ICMP回显回答中有3个IP地址。我们省略了tcpdump输出中的其它行,因为它们与图7.5基本一致。
位于路由信息末尾的标记EOL表示IP选项“end of list”(清单结束)的值。EOL选项的值可以为0。这时表示39个字节的RR数据位于IP首部中的40字节空间中。由于在数据报发送之前空间选项被设置为0,因此跟在39个字节的RR数据之后的0字符就被解释为EOL。这正是我们所希望的结果。如果在IP首部中的选项字段中有多个选项,在开始下个选项之前必须填入空白字符,另外还可以用另一个值为1的特殊字符NOP(“no operation”)。

(下面是原书p.93①的译文)
在图7.5中,SVR4把回显请求中的TTL字段设为32,BSD/386设为255。(它打印出的值为254是因为路由器bsdi已经将其减去1。)新的系统都把ICMP报文中的TTL设为最大值(255)。
在作者使用的三个TCP/IP系统中,BSD/386和SVR4都支持记录路由选项。这就是说,当转发数据报时,它们都能正确地更新RR清单,而且能正确地把接收到的ICMP回显请求中的RR清单复制到出口ICMP回显回答中。虽然SunOS 4.1.3在转发一个数据报时能正确更新RR清单,但是不能复制RR清单。Solaris 2.x对这个问题已作了修改。

异常的输出
下面的例子是作者观察到的,我们把它作为第9章讨论ICMP间接报文的起点。我们在子网140.252.1子网上ping主机aix(在主机sun上通过拔号SLIP连接可以访问),并带有记录路由选项。在slip主机上运行有如下输出结果:

(见原书p.94的①)

我们已经在主机bsdi上运行过这个例子。现在我们选择slip来运行它,观察RR清单中所有的9个IP地址。
在输出中令人感到疑惑的是,为什么传出的数据报(ICMP回显请求)直接从netb传到aix,而返回的数据报(ICMP回显回答)却从aix开始经路由器gateway再到netb?这里看到的正是下面我们将要描述的IP路由选择的一个特点。数据报经过的路由如图7.6所示。

图7.6 运行带有记录路由选项的ping程序,显示IP路由选择的特点

问题是aix不知道要把目的地为子网140.252.13的IP数据报发到主机netb上。相反,aix在它的路由表中有一个默认项,它指明当没有明确某个目的主机的路由时,就把所有的数据报发往默认项指定的路由器gateway。路由器gateway比子网140.252.1上的任何主机都具备更强的路由选择能力。(在这个以太网上有超过150台主机,每台主机的路由表中都有一个默认项指向路由器gateway,这样就不用在每台主机上都运行一个路由选择守护程序。)
这里没有回答的一个问题是为什么gateway不直接发送ICMP报文改变路由到aix(9.5节),以更新它的路由表?由于某种原因(很可能是由于数据报产生的改变路由是一份ICMP回显请求报文),改变路由并没有产生。但是如果我们用Telnet登录到aix上的daytime服务器,ICMP就会产生改变路由,因而它在aix上的路由表也随之更新。如果我们接着执行ping程序并带有记录路由选项,其路由显示表明数据报从netb到aix,然后返回netb,而不再经过路由器gateway。在9.5节中我们将更详细地讨论ICMP改变路由的问题。

7.4 IP时间戳选项
IP时间戳选项与记录路由选项类似。IP时间戳选项的格式如图7.7所示(请与图7.3进行比较)。

图7.7 IP首部中时间戳选项的一般格式

时间戳选项的代码为0x44。其它两个字段len和ptr与记录路由选项相同:选项的总长度(一般为36或40)和指向下一可用空间的指针(5,9,13,等)。
接下来的两个字段是4 bit的值:OF表示溢出字段,FL表示标志字段。时间戳选项的操作根据标志字段来进行,如图7.8所示。

(下面是图7.8的译文)
标志
描述
0
只记录时间戳,正如我们在图7.7看到的那样。
1
每台路由器都记录它的IP地址和时间戳。在选项列表中只有存放四对地址和时间戳的空间。
3
发送端对选项列表进行初始化,存放了4个IP地址和四个取值为0的时间戳值。只有当列表中的下一个IP地址与当前路由器地址相匹配时,才记录它的时间戳。
图7.8 时间戳选项不同标志字段值的意义

如果路由器由于没有空间而不能增加时间戳选项,那么它将增加溢出字段的值。
时间戳的取值一般为自午夜开始计的毫秒数,UTC,与ICMP时间戳请求和回答相类似。如果路由器不使用这种格式,它就可以插入任何它使用的时间表示格式,但是必须打开时间戳中的高位以表明为非标准值。
与我们遇到的记录路由选项所受到的限制相比,时间戳选项遇到情况要更坏一些。如果我们要同时记录IP地址和时间戳(标志位为1),那么就可以同时存入其中的四对值。只记录时间戳是没有用处的,因为我们没有标明时间戳与路由器之间的对应关系(除非我们有一个永远不变的拓扑结构)。标志值取3会更好一些,因为我们可以插入时间戳的路由器。一个更为基本的问题是,你很可能无法控制任何给定路由器上时间戳的正确性。这使得试图用IP选项来计算路由器之间的跳站数是徒劳的。我们将看到(第8章)traceroute程序可以提供一种更好的方法来计算路由器之间的跳数。

7.5 小结
ping程序是对两个TCP/IP系统连通性进行测试的基本工具。它只利用ICMP回显请求和回显回答报文,而不用经过传输层(TCP/UDP)。Ping服务器一般在内核中实现ICMP的功能。
我们分析了在LAN,WAN以及SLIP链路(拔号和线路)上运行ping程序的输出结果,并对串行线路上的SLIP链路吞吐量进行了计算。我们还讨论并使用了ping程序的IP记录路由选项。利用该IP选项,我们可以看到它是如何经常使用默认路由的。在第9章我们将再次回到这个讨论主题。另外,我们还讨论了IP 时间戳选项,但它在实际使用时有所限制。

习题
7.1 请画出7.2节中ping输出的时间线。
7.2 若把bsdi和slip主机之间的SLIP链路设置为9600 b/s,请计算这时的RTT。假定默认的数据是56字节。
7.3 当前BSD版中的ping程序允许我们为ICMP报文的数据部分指定一种模式。(数据部分的前8个字节不用来存放模式,因为它要存放发送报文的时间。)如果我们指定的模式为0xc0,请重新计算上一题中的答案。(提示:阅读2.4节。)
7.4 使用压缩SLIP(CSLIP,见2.5节)是否会影响我们在7.2节中看到的ping输出中的时间值?
7.5 在图2.4中,ping环回地址与ping主机以太网地址会出现什么不同?




7-7
8 Traceroute程序

8.1 引言
由Van Jacobson编写的Traceroute程序是一个能更深入探索TCP/IP协议的方便可用的工具。尽管不能保证从源端发往目的端的两份连续的IP数据报具有相同的路由,但是大多数情况下是这样的。Traceroute程序可以让我们看到IP数据报从一台主机传到另一台主机所经过的路由。Traceroute程序还可以让我们使用IP源路由选项。

(下面是原书p.97①的译文)
使用手册上说:“程序由Steve Deering提议,由Van Jacobson实现,并由许多其他人根据C. Philip Wood, Tim Seaver 及Ken Adelman等人提出的令人信服的建议或补充意见进行调试。”

8.2 Traceroute程序的操作
在7.3节中,我们描述了IP记录路由选项(RR)。为什么不使用这个选项而另外开发一个新的应用程序?有三个方面的原因。首先,原先并不是所有的路由器都支持记录路由选项,因此该选项在某些路径上不能使用。(Traceroute程序不需要中间路由器具备任何特殊的或可选的功能。)
其次,记录路由一般是单向的选项。发送端设置了该选项,那么接收端不得不从收到的IP首部中提取出所有的信息然后全部返回给发送端。在7.3节中,我们看到大多数Ping服务器的实现(内核中的ICMP回显回答功能)把接收到的RR清单返回,但是这样使得记录下来的IP地址翻了一番(一来一回),这样会受到一些限制,这一点我们在下一段讨论。(Traceroute程序只需要目的端运行一个UDP模块----其他不需要任何特殊的服务器应用程序。)
最后一个原因也是最主要的原因是,IP首部中留给选项的空间有限,不能存放当前大多数的路径。在IP首部选项字段中最多只能存放9个IP地址。在原先的ARPANET中这是足够的,但是对现在来说是远远不够的。
Traceroute程序使用ICMP报文和IP首部中的TTL字段。TTL字段(生存周期)是由发送端初始设置一个8 bit字段。推荐的初始值由分配数字RFC指定,当前值为64。较老版本的系统经常初始化为15或32。我们从第7章中的一些ping程序例子中可以看出,发送ICMP回显回答时经常把TTL设为最大值255。
每个处理数据报的路由器都需要把TTL的值减1或减去数据报在路由器中停留的秒数。由于大多数的路由器转发数据报的时延都小于1秒钟,因此TTL最终成为一个跳站的计数器,所经过的每个路由器都将其值减1。

(下面是原书p.98①的译文)
RFC 1009 [Braden and Postel 1987]指出,如果路由器转发数据报的时延超过1秒,那么它将把TTL值减去所消耗的时间(秒数)。但很少有路由器这么实现。新的路由器需求文档RFC [Almquist 1993]为此指定它为可选择功能,允许把TTL看成一个跳站计数器。

TTL字段的目的是防止数据报在路由选择时无休止地在网络中流动。例如,当路由器瘫痪或者两个路由器之间的连接丢失时,路由选择协议有时会去检测丢失的路由并一直进行下去。在这段时间内,数据报可能在循环回路被终止。TTL字段就是在这些循环传递的数据报上加上一个生存上限。
当路由器收到一份IP数据报,如果其TTL字段是0或1,则路由器不转发该数据报。(接收到这种数据报的目的主机可以将它交给应用程序,这是因为不需要转发该数据报。但是在通常情况下,系统不应该接收TTL字段为0的数据报。)相反地,路由器将该数据报丢弃并给信源机发一份ICMP“超时”信息。Traceroute程序的关键在于包含这份ICMP信息的IP报文的信源地址是该路由器的IP地址。
我们现在可以猜想一下Traceroute程序的操作过程。它发送一份TTL字段为1的IP数据报给目的主机。处理这份数据报的第一个路由器将TTL值减1,丢弃该数据报,并发回一份超时ICMP报文。这样就得到了该路径中的第一个路由器的地址。然后Traceroute程序发送一份TTL值为2的数据报,这样我们就可以得到第二个路由器的地址。继续这个过程直至该数据报到达目的主机。但是目的主机哪怕接收到TTL值为1的IP数据报,也不会丢弃该数据报并产生一份超时ICMP报文,这是因为数据报已经到达其最终目的地。那么我们该如何判断已经到达目的主机了呢?
Traceroute程序发送一份UDP数据报给目的主机,但它选择一个不可能的值作为UDP端口号(大于30,000),使目的主机的任何一个应用程序都不可能使用该端口。因为,当该数据报到达时,将使目的主机的UDP模块产生一份“端口不可到达”错误(见6.5节)的ICMP报文。这样,Traceroute程序所要做的就是区分接收到的ICMP信息是超时还是端口不可到达,以判断什么时候结束。

(下面是原书p.99①的译文)
Traceroute程序必须可以为发送的数据报设置TTL字段。并非所有与TCP/IP接口的程序都支持这项功能,同时并非所有的实现都支持这项能力,但目前大部分系统都支持这项功能,并可以运行Traceroute程序。这个程序界面通常要求用户具有超级用户权限,这意味着它可能需要特殊的权限以在你的主机上运行该程序。

8.3 局域网输出
我们现在已经做好运行Traceroute程序并观察其输出的准备了。我们将使用从svr4到slip,经路由器bsdi的简单互连网(见内封面)。 bsdi和slip之间是9600 b/s的SLIP链路。

(见原书p.99的②)

输出的第一个无标号行给出了目的主机名和其IP地址,指出traceroute程序最大的TTL字段值为30。40字节的数据报包含20字节IP首部,8字节的UDP首部和12字节的用户数据。(12字节的用户数据包含每发一个数据报就加1的序号,送出TTL的副本以及发送数据报的时间。)
输出的后面两行以TTL开始,接下来是主机或路由器名,以及其IP地址。对于每个TTL值,发送3份数据报。每接收到一份ICMP报文,就计算并打印出往返时间。如果在5秒种内仍未收到3份数据报的任意一份的响应,则打印一个星号,并发送下一份数据报。在上述输出结果中,TTL字段为1的前三份数据报的ICMP报文分别在20,10和10 ms收到。TTL字段为2的3份数据报的ICMP报文则在120 ms后收到。由于TTL字段为2到达最终目的主机,因此程序就此停止。
往返时间是由发送主机的traceroute程序计算的。它是指从traceroute程序到该路由器的总往返时间。如果我们对每段路径的时间感兴趣,可以用TTL字段为N+1所打印出来的时间减去TTL字段为N的时间。
图8.1给出了tcpdump的运行输出结果。正如我们所预想的那样,第一个发往bsdi的探测数据报的往返时间是20 ms而后面两个数据报往返时间是10 ms的原因是发生了一次ARP交换。tcpdump结果证实了确实是这种情况。
目的主机UDP端口号最开始设置为33435,且每发送一个数据报加1。可以通过命令行选项来改变开始的端口号。UDP数据报包含12个字节的用户数据,我们在前面traceroute程序输出的40字节数据报中已经对其进行了描述。
后面tcpdump打印出了TTL字段为1的IP数据报的注释[ttl 1]。当TTL值为0或1时,tcpdump打印出这条信息,以提示我们数据报中有些不太寻常之处。在这里我们可以预见到TTL值为1,而在其它一些应用程序中,它可以警告我们数据报可能无法到达其最终目的主机。我们不可能看到路由器传送一个TTL值为0的数据报,除非发出该数据报的该路由器已经崩溃。

图8.1 从svr4到slip的traceroute程序示例的tcpdump输出结果

因为bsdi路由器将TTL值减到0,因此我们预计它将发回“传送超时”的ICMP报文。即使这份被丢弃的IP报文发送往slip,路由器也会发回ICMP报文。
有两种不同的ICMP“超时”报文(见p.71页的图6.3),它们的ICMP报文中code字段不同。图8.2给出了这种ICMP错误报文的格式。

图8.2 ICMP超时报文

我们所讨论的ICMP报文是在TTL值等于0时产生的,其code字段为0。
主机在组装分片时可能发生超时,这时,它将发送一份“组装报文超时”的ICMP报文。(我们将在11.5节讨论分片和组装。)这种错误报文将code字段置1。
图8.1的第9-14行对应于TTL为2的3份数据报。这3份报文到达最终目的主机,并产生一份ICMP端口不可到达报文。
计算出SLIP链路的往返时间是很有意义的,就象我们在7.2节中所举的Ping例子,将链路值设置为1200 b/s一样。发送出动的UDP数据报共42个字节,包括12字节的数据,8字节UDP首部,20字节的IP首部以及(至少)2字节的SLIP帧(2.4节)。但是与Ping不一样的是,返回的数据报大小是变化的。从图6.9可以看出,返回的ICMP报文包含发生差错的数据报的IP首部以及紧随该IP首部的8字节数据(在traceroute程序中,即UDP首部)。这样,总共就是20 + 8 + 20 + 8 + 2,即58字节。在数据速率为960 B/s的情况下,预计的RTT就是(42 + 58/960),即104 ms。这个值与svr4上所估算出来的110 ms是吻合的。
图8.1中的源端口号(42804)看起来有些大。traceroute程序将其发送的UDP数据报的源端口号设置为Unix进程号与32768之间的逻辑或值。对于在同一台主机上多次运行traceroute程序的情况,每个进程都查看ICMP返回的UDP首部的源端口号,并且只处理那些对自己发送回答的报文。
关于traceroute程序还有一些必须指出的事项。首先,并不能保证现在的路由也是将来所要采用的路由,甚至两份连续的IP数据报都可能采用不同的路由。如果在运行程序时,路由发生改变,你就会观察到这种变化,这是因为对于一个给定的TTL,如果其路由发生变化,traceroute程序将打印出新的IP地址。
第二,不能保证ICMP报文的路由与traceroute程序发送的UDP数据报采用同一路由。这表明所打印出来的往返时间可能并不能真正体现数据报发出和返回的时间差。(如果UDP数据报从信源到路由器的时间是1秒,而ICMP报文用另一条路由返回信源用了3秒时间,则打印出来的往返时间是4秒。)
第三,返回的ICMP报文中的信源IP地址是UDP数据报到达的路由器接口的IP地址。这与IP记录路由选项(7.3节)不同,记录的IP地址指的是发送接口地址。由于每个定义的路由器都有2个或更多的接口,因此,从A主机到B主机上运行traceroute程序和从B主机到A主机上运行traceroute程序所得到的结果可能是不同的。事实上,如果我们从slip主机到svr4上运行traceroute程序,其输出结果变成了:

(见原书p.101的①)

这次打印出来的bsdi主机的IP地址是140.252.13.66,对应于SLIP接口,而上次的地址是140.252.13.35,是以太网接口地址。由于traceroute程序同时也打印出与IP地址相关的主机名,因而主机名也可能变化。(在我们的例子中,bsdi上的两个接口都采用相同的名字。)
考虑图8.3的情况。它给出了两个局域网通过一个路由器相连的情况。两个路由器通过一个点对点的链路相连。如果我们在左边LAN的一个主机上运行traceroute程序,那么它将发现路由器的IP地址为if1和if3。但在另一种情况下,就会发现打印出来的IP地址为if4和if2。if2和if3有着同样的网络号,而另两个接口则有着不同的网络号。

图8.3 traceroute程序打印出的接口标识

最后,在广域网情况下,如果traceroute程序的输出是可读的域名形式,而不是IP地址形式,那么会更好理解一些。但是由于traceroute程序接收到ICMP报文时,它所获得的唯一信息就是IP地址,因此,在给定IP地址的情况下,它做一个“反向域名查看”工作来获得域名。这就需要路由器或主机的管理员正确配置其反向域名查看功能(并非所有的情况下都是如此)。我们将在14.5节描述如何使用DNS将一个IP地址转换成域名。

8.4 广域网输出
我们前面所给出的小互连网的输出例子对于查看协议运行过程来说是足够了,但对于像全球互连网这样的大互连网来说,应用traceroute程序就需要一些更为实际的东西。
图8.4是从sun主机到NIC (Network Information Center)的情况。

图8.4 从sun主机到nic.ddn.mil的traceroute程序

由于运行的这个例子包含文本,非DDN站点(如,非军方站点)的NIC已经从nic.ddn.mil转移到rs.internic.net,即新的“InterNIC"。
一旦数据报离开tuc.noao.edu网,它们就进入了telcom.arizona.edu网络。然后这些数据报进入NASA Science Internet,nsn.nasa.gov。TTL字段为6和7的路由器位于JPL (Jet Propulsion Laboratory)上。TTL字段为11所输出的sura.net网络位于Southeastern Universities Research Association Network上。TTL字段为12的域名GSI是Government Systems, Inc., NIC的运营者。
TTL字段为6的第二个RTT(590)几乎是其它两个RTT值(234和262)的两倍 。它表明IP路由的动态变化。在发送主机和这个路由器之间发生了使该数据报速度变慢的事件。同样,我们不能区分是发出的数据报还是返回的ICMP差错报文被拦截。
TTL字段为3的第一个RTT探测值(204)比TTL字段为2的第一个探测值(233)值还小。由于每个打印出来的RTT值是从发送主机到路由器的总时间,因此这种情况是可能发生的。
图8.5的例子是从sun主机到作者出版商之间的运行例子。

图8.5 从sun.tuc.noao.edu主机到aw.com的traceroute程序

在这个例子中,数据报离开telcom.arizona.edu网络后就进行了地区性的网络westnet.net (TTL字段值为6和7)。然后进行了由Advanced Network & Services运营的NSFNET主干网,t3.ans.net,(T3是对于主干网采用的45 Mb/s电话线的一般缩写。)最后的网络是alter.net,即aw.com与互连网的连接点。

8.5 IP源站选路选项
通常IP路由是动态的,即每个路由器都要判断数据报下面该转发到哪个路由器。应用程序对此不进行控制,而且通常也并不关心路由。它采用类似traceroute程序的工具来发现实际的路由。
源站选路(source routing)的思想是由发送者指定路由。它可以采用以下两种形式:
•严格的源路由选择。发送端指定IP数据报所必须采用的确切路由。如果一个路由器发现源路由所指定的下一路由器不在其直接连接的网络上,那么它就返回一个“源站路由失败”的ICMP差错报文。
宽松的源站选路。发送端指定一个数据报经过的IP地址清单,但是数据报在清单上指定的任意两个地址之间可以通过其它路由器。
Traceroute程序为我们提供了一个查看源站选路的方法,我们可以在选项中指明源站路由,然后检查其运行情况。

(原书p.104①的译文)
一些公开的Traceroute程序源代码包中包含指明宽松的源站选路的补丁。但是在标准版中通常并不包含此项。这些补丁的解释是“Van Jacobson的原始Traceroute程序(1988年春)支持该特性,但他后来因为有人提出会使网关崩溃而将此功能去除。”对于本章中所给出的例子,作者将这些补丁安装上,并将它们设置成允许宽松的源站选路和严格的源站选路。

图8.6给出了源站路由选项的格式。

图8.6 IP首部源站路由选项的通用格式

这个格式与我们在图7.3中所示的记录路由选项格式基本一致。但不同之处是,对于源站选路,我们必须在发送IP数据报前填充IP地址清单,而对于记录路由选项,我们需要为IP地址清单分配并清空一些空间,并让路由器填充该清单中的各项。同时,对于源站选路,我们只要为所需要的IP地址数分配空间并进行初始化,通常其数量小于9。而对于记录路由选项来说,我们必须尽可能地分配空间,以达到9个地址。
对于宽松的源站选路来说,code字段的值是0x83,而对于严格的源站选路,其值为0x89。len和ptr字段与我们在7.3节中所描述的一样。
源站路由选项的实际称呼为“源站及记录路由”(对于宽松的源站选路和严格的源站选路,分别用LSRR和SSRR表示),这是因为在数据报沿路由发送过程中,对IP地址清单进行更新。下面是其运行过程:
•发送主机从应用程序接收源站路由清单,将第一个项去掉(它是数据报的最终目的地址),将剩余的项移到一个项中(如图8.6所示),并将原来的目的地址作为清单的最后一项。指针仍然指向清单的第一项(即,指针的值为4)。
•每个处理数据报的路由器检查其是否为数据报的最终地址。如果不是的话,则正常转发数据报。(在这种情况下,必须指明宽松源站选路,否则我们就不能接收到该数据报。)
•如果该路由器是最终目的,且指针不大于路径的长度,那么(1)由ptr所指定的清单中的下一个地址就是数据报的最终目的地址,(2)由出接口(outgoing interface)相对应的IP地址取代刚才使用的源地址,然后,(3)指针加4。
可以用下面这个例子很好地解释上述过程。在图8.7中,我们假设主机S上的发送应用程序发送一份数据报给D,指定源路由为R1,R2和R3。

图8.7 IP源路由示例

在上图中,#表示指针字段,其值分别是4,8,12和16。长度字段恒为15(三个IP地址加上三个字节首部)。可以看出,每一跳IP数据报中的目的地址都发生改变。
当一个应用程序接收到由信源指定路由的数据时,在发送回答时,应该读出接收路由值,并提供反向路由。

(下面是原书p.105①的译文)
Host Requirements RFC指明,TCP客户必须能指明源站路由选择,同时,TCP服务器必须能够接收源站路由选择,并且对于该TCP连接的所有报文段都能采用反向路由。如果TCP服务器下面接收到一个不同的源站路由选择,那么新的源站路由将取代旧的源站路由。

宽松的源站选路的traceroute程序示例
使用traceroute程序的 -g 选项,我们可以为宽松的源站选路指明一些中间结点。采用该选项可以最多指定8个中间路由。(其个数是8而不是9的原因是,所使用的编程接口要求最后的表目是目的主机。)
在图8.4中,去往NIC,nic.ddn.mil的路由经过NASA Science Internet。在图8.8中,我们通过指定路由器 enss142.UT.westnet.net (192.31.39.21) 作为中间路由器来强制数据报通过NSFNET:

图8.8 采用宽松源站选路通过NSFNET到达nic.ddn.mil的traceroute程序

在这种情况下,看起来路径中共有16跳,其平均RTT大约是350 ms,而图8.4的通常路由选择则只有13跳,其平均RTT约为322 ms。默认路径看起来更好一些。(在建立路径时,还需要考虑其它的一些因素。其中一些必须考虑的因素是所包含网络的组织及政治因素。)
前面我们说看起来有16跳,这是因为将其输出结果与我们前面的通过NSFNET(图8.5)的示例比较,发现在本例采用宽松源路由,选择了3个路由器。(这可能是因为路由器对源站选路数据报产生ICMP超时差错报文上存在一些差错。)在netb和butch路由器之间的gateway.tuc.noao.edu路由器丢失了,同时,位于Gabby和enss142.UT.west.net之间的Westgate.Telcom.Arizona.edu和uu-ua.AZ.westnet.net两个路由器也丢失了。在这些丢失的路由器上可能发生了与接收到宽松的源站选路选项数据报有关的程序问题。实际上,当采用NSFNET时,信源和NIC之间的路径有19跳。本章习题8.5继续对这些丢失路由器进行讨论。
本例同时也指出了另一个问题。在命令行,我们必须指定路由器enss142.UT.westnet.net的点分十进制IP地址,而不能以其域名代替。这是因为,反向域名解析(14.5节中描述的通过IP地址返回域名)将域名与IP地址相关联,但是前向解析(即给出域名返回IP地址)则无法做到。在DNS(Domain Name System,域名系统)中,前向映射和反向映射是两个独立的文件,而并非所有的管理者都同时拥有这两个文件。因此,在一个方向是工作正常而另一个方向失败的情况并不少见。
还有一种我们以前没有碰到过的情况是在TTL字段为8的情况下,对于第一个RTT,打印一个星号(*)。这表明,发生超时,在5秒内未收到本次探查的回答信号。
将本图与图8.4相比较,我们还可以得出一个结论,即路由器ns-FIX-pe.sura.net同时与NSFNET和NASA Science Internet相连。

严格的源站选路的traceroute程序示例
在作者的traceroute程序版本中,-G参数与前面所描述的-g参数是完全一样的,不过此时是严格的源站选路而不是宽松的源站选路。我们可以采用这个参数来观察在指明无效的严格的源站选路时其结果会是什么样的。从图8.5可以看出来,从作者的子网发往NSFNET的数据报的正常路由器顺序是netb,gateway,butch和gabby。(为了便于查看,我们后面所有的输出结果中,省略了域名后缀 .tuc.noao.edu和 .telcom.arizona.edu。)我们指定了一个严格源路由,使其试图将数据报从gateway直接发送到gabby,而省略了butch。我们可以猜测到其结果会是失败的,正如图8.9所给出的结果。

图8.9 采用严格源路由失败的traceroute程序

这里的关键是在于TTL字段为3的输出行中,RTT后面的!S。这表明traceroute程序接收到ICMP“源站路由失败”的差错报文:即图6.3中type字段为3,而code字段为5。TTL字段为3的第二个RTT位置的星号表示未收到这次探查的回答信号。这与我们所猜想的一样, gateway不可能直接发送数据报给gabby,这是因为它们之间没有直接的连接。
TTL字段为2和3的结果都来自于gateway,对于TTL字段为2的回答来自gateway是因为gateway接收到TTL字段为1的数据报。在它查看到(无效的)严格的源站选路之前,就发现TTL已过期,因此发送回ICMP超时报文。TTL字段等于3的行,在进入gateway时其TTL字段为2,因此,它查看严格的源站选路,发现它是无效的,因此发送回ICMP源站选路失败的差错报文。
图8.10给出了与本例相对应的tcpdump输出结果。该输出结果是在sun和netb之间的SLIP链路上惧到的。我们必须在tcpdump中指定-v选项以显示出源站路由信息。这样,会输出一些像数据报ID这样的我们不需要的结果,我们在给出结果中将这些不需要的结果删除掉。同样,我们用SSRR表示“严格的源站及记录路由”。

图8.10 失败的严格的源站选路traceroute程序的tcpdump输出结果

首先注意到,sun所发送的每个UDP数据报的目的地址都是netb,而不是目的主机(westgate)。这一点可以用图8.7的例子来解释。类似的,-G参数所指定的另外两个路由器(gateway和gabby)以及最终目的(westgate)成为第一跳的SSRR选项。
从这个输出结果中,我们还可以看出,traceroute程序所采用的定时时间(第15行和16行之间的时间差)是5秒。

宽松的源站选路traceroute程序的往返路由
我们在前面已经说过,从A到B的路径并不一定与从B到A的路径完全一样。除非同时在两个系统中登录并在每个终端上运行traceroute程序,否则很难发现两条路径是否不同。但是,采用宽松的源站选路,我们就可以决定两个方向上的路径。
这里的窍门就在于指定一个宽松的源站路由,该路由的目的端和宽松路径一样,但发送端为目的主机。例如,在sun主机上,我们可以查看到发往以及来自bruno.cs.colorado.edu的结果如图8.11所示。

图8.11 显示非对称路径的traceroute程序

发出路径(TTL字段为1-11)的结果与返回路径(TTL字段为11-21)不同,这很好地说明了在Internet 上,路由选择可能是不对称的。
该输出同时还说明了我们在图8.3中所讨论的问题。比较TTL字段为2和19的输出结果:它们都是路由器gateway.tuc.noao.edu,但两个IP地址却是不同的。由于traceroute程序以进入接口作为其标识,而我们从两条不同的方向经过该路由器,一条是发出路径(TTL字段为2),另一条是返回路径(TTL字段为19),因此我们可以猜想到这个结果。通过比较TTL字段为3和18,4和17的结果,我们可以看到同样的结果。

8.6 小结
在一个TCP/IP网络中,traceroute程序是不可缺少的工具。其操作很简单:开始时发送一个TTL字段为1的UDP数据报,然后将TTL字段每次加1,以确定路径中的每个路由器。每个路由器在丢弃UDP数据报时都返回一个ICMP超时报文2,而最终目的主机则产生一个ICMP端口不可到达的报文。
我们给出了在LAN和WAN上运行traceroute程序的例子,并用它来考察IP源站选路。我们用宽松的源站选路来检测发往目的主机的路由是否与从目的主机返回的路由一样。

习题:
8.1 当IP将接收到的TTL字段减1,发现它为0时,将会发生什么结果?
8.2 traceroute程序是如何计算RTT的?将这种计算RTT的方法与ping相比较。
8.3 (本习题与下一道习题是基于开发traceroute程序过程中遇到的实际问题,它们来自于traceroute程序源代码注释。)假设有源主机和目的主机之间有三个路由器(R1,R2和R3),而中间的路由器(R2)在进入TTL字段为1时,将TTL字段减1,但却错误地将该IP数据报发往下一个路由器。请描述会发生什么结果。在运行traceroute程序是你会看到什么样的现象?
8.4 同样,假设源主机和目的主机之间有三个路由器。由于目的主机上存在错误,因此,它总是将进入TTL值作为外出ICMP报文的TTL值。请描述这将发生什么结果,你会看到什么现象。
8.5 在图8.8运行例子中,我们可以在sun和netb之间的SLIP链路上运行tcpdump程序。如果我们指定-v参数,就可以看到返回ICMP报文的TTL值。这样,我们可以看到进入netb,butch,Gabby和enss142.UT.westnet.net的TTL值分别为255,253,252和249。这是否为我们判断是否存在丢失路由器提供了额外的信息?
8.6 SunOS和SVR4都提供了带-l选项的ping版本,以提供松源路由选择。手册上说明,该选项可以与 -R 选项(指定记录路由选项)一起用的。如果你已经进入到这些系统中,请尝试同时用这两个选项。其结果是什么:如果你采用tcpdump来观测数据报,请描述其过程。
8.7 比较ping和traceroute程序在处理同一台主机客户的多个实例上的不同点。
8.8 比较ping和traceroute程序在计算往返时间上的不同点。
8.9 我们已经说过,traceroute程序选取开始UDP目的主机端口号为33453,每发送一个数据报将此数加1。在1.9节中,我们说过暂时端口号通常是1024到5000之间的值,因此traceroute程序的目的主机端口号不可能是目的主机上所使用的端口号。在Solaris2.2系统中的情况也是如此吗?(提示:查看E.4节)
8.10 RFC 1393 [Malkin 1993b]提出了另一种判断到目的主机路径的方法。请问其优缺点是什么?
8-8
9 IP路由选择

9.1 引言
路由选择是IP最重要的功能之一。图9.1是IP层处理过程的简单流程。需要进行路由选择的数据报可以由本地主机产生,也可以由其他主机产生。在后一种情况下,主机必须配置成一个路由器,否则通过网络接口接收到的数据报,如果目的地址不是本机就要被丢弃(例如,悄无声息地被丢弃)。
在图9.1中,我们还描述了一个路由守护程序(daemon),一般来说是一个用户进程。在Unix系统中,大多数普通的守护程序都是路由程序和网关程序。(术语daemon指的是运行在后台的进程,它代表整个系统执行某些操作。daemon一般在系统引导时启动,在系统运行期间一直存在。)在某个给定主机上运行何种路由协议,如何在相邻路由器上交换选路信息,以及选路协议是如何工作的,所有这些问题都是非常复杂的,其本身就可以用整本书来加以讨论。(有兴趣的读者可以参考文献[Perlman 1992]以获得更详细的信息。)在第10章中,我们将简单讨论动态路由选择和选路信息协议RIP(Routing Information Protocol)。在本章我们主要的目的是了解单个IP层如何作出路由决策的。
图9.1所示的路由表经常被IP访问(在一个繁忙的主机上,一秒钟内可能要访问几百次),但是它被路由守户程序更新的频度却要低得多(可能大约30秒种一次)。当接收到ICMP“改变路由(redirect)”报文时路由表也要被更新,这一点我们将在9.5节讨论route命令时加以介绍。在本章中,我们还将用netstat命令来显示路由表。

图9.1 IP层工作流程

9.2 路由选择的原理
开始讨论IP路由选择之前,首先要理解内核是如何维护路由表的。路由表中包含的信息决定了IP层所做的所有决策。
在3.3节中,我们列出了IP搜索路由表的几个步骤:
1. 搜索匹配的主机地址;
2. 搜索匹配的网络地址;
3. 搜索默认表项。(默认表项一般在路由表中被指定为一个网络表项,其网络号为0。)

匹配主机地址步骤始终发生在匹配网络地址步骤之前。
IP层进行的路由选择实际上是一种路由选择机制,它搜索路由表并决定向哪个网络接口发送分组。这区别于路由选择策略,它只是一组决定把哪些路由放入路由表的规则。IP执行路由选择机制,而路由守护程序则一般提供路由选择策略。

简单路由表
首先让我们来看一看一些典型的主机路由表。在主机svr4上,我们先执行带-r参数的netstat命令列出路由表,然后以-n参数再次执行该命令,以数字格式打印出IP地址。(我们这样做是因为路由表中的一些表项是网络地址,而不是主机地址。如果没有-n参数,netstat命令将搜索文件/etc/networks并列出其中的网络名。这样会与另一种形式的名字——网络名加主机名相混淆。

(见原书p.113的①)

第一行说明,如果目的地是140.252.13.65(slip主机),那么网关(路由器)将把分组转发给140.252.13.35(bsdi)。这正是我们所期望的,因为主机slip通过SLIP链路与bsdi相连接,而bsdi与该主机在同一个以太网上。
对于一个给定的路由器,可以打印出五种不同的标志(flag):
U 该路由可以使用。
G 该路由是到一个网关(路由器)。如果没有设置该标志,说明目的地是直接相连的。
H 该路由是到一个主机,也就是说,目的地址是一个完整的主机地址。如果没有设置该标志,说明该路由是到一个网络,而目的地址是一个网络地址:一个网络号,或者网络号与子网号的组合。
D 该路由是由改变路由(redirect)报文创建的(9.5节)。
M 该路由已被改变路由报文修改(9.5节)。

标志G是非常重要的,因为由它区分了间接路由和直接路由。(对于直接路由来说是不设置标志G的。)其区别在于,发往直接路由的分组中不但具有指明目的端的IP地址,还具有其链路层地址(图3.3)。当分组被发往一个间接路由时,IP地址指明的是最终的目的地,但是链路层地址指明的是网关(即下一站路由器)。我们在图3.4已看到这样的例子。在个路由表例子中,我们有一个间接路由(设置了标志G),因此采用这一项路由的分组其IP地址是最终的目的地(140.252.13.65),但是其链路层地址必须对应于路由器140.252.13.35。
理解G和H标志之间的区别是很重要的。G标志区分了直接路由和间接路由,如上所述。但是H标志表明,目的地址(netstat命令输出第一行)是一个完整的主机地址。没有设置H标志说明目的地址是一个网络地址(主机号部分为0)。当为某个目的IP地址搜索路由表时,主机地址项必须与目的地址完全匹配,而网络地址项只需要匹配目的地址的网络号和子网号就可以了。另外,大多数版本的netstat命令首先打印出所有的主机路由表项,然后才是网络路由表项。
参考记数Refcnt(“Reference count ”)列给出的是正在使用路由的活动进程个数。面向连接的协议如TCP在建立连接时要固定路由。如果在主机svr4和slip之间建立Telnet连接,我们可以看到参考记数值变为1。建立另一个Telnet连接时,它的值将增加为2,以此下去。
下一列(“use")显示的是通过该路由发送的分组数。如果我们是这个路由的唯一分组,那么我们运行ping程序发送5个分组后,它的值将变为5。最后一列列出的(interface)是本地接口的名字。
输出的第2行是环回接口(2.7节),它的名字始终为lo0。没有设置G标志,因为该路由不是一个网关。H标志说明目的地址(127.0.0.1)是一个主机地址,而不是一个网络地址。由于没有设置G标志,说明这是一个直接路由,网关列给出的是出口IP地址。
输出的第3行是默认路由。每个主机都有一个或多个默认路由。这一项表明,如果在表中没有找到特定的路由,就把分组发送到路由器140.252.13.33(sun主机)。这说明当前主机(svr4)利用这一个路由表项就可以通过Internet经路由器sun(及其SLIP链路)访问其他的系统。建立默认路由是一个功能很强的概念。该路由标志(UG)表明它是一个网关,这是我们所期望的。

(下面是原书p.114①的译文)
这里,我们有目的地称sun为路由器而不是主机,因为它被当作默认路由器来使用,它发挥的是IP转发功能,而是主机功能。
Host Requirements RFC文档特别说明,IP层必须支持多个默认路由。但是,许多实现系统并不支持这一点。当存在多个默认路由时,一般的技术就成为它们周围的知更鸟了,例如,Solaris 2.2就是这样做的。

输出中的最后一行是所在的以太网。H标志没有设置,说明目的地址(140.252.13.32)是一个网络地址,其主机地址部分设为0。事实上,是它的低5位设为0(图3.11)。由于这是一个直接路由(G标志没有被设置),网关列指出的IP地址是出口地址。
netstat命令输出的最后一项还隐含了另一个信息,那就是目的地址(140.252.13.32)的子网掩码。如果要把该目的地址与140.252.13.33进行比较,那么在比较之前首先要把它与目的地址掩码(0xffffffe0,3.7节)进行逻辑与。由于内核知道每个路由表项对应的接口,而且每个接口都有一个对应的子网掩码,因此每个路由表项都有一个隐含的子网掩码。
主机路由表的复杂性取决于主机所在网络的拓扑结构。
1. 最简单的(也是最不令人感兴趣的)情况是主机根本没有与任何网络相连。TCP/IP协议仍然能用于这样的主机,但是只能与自己本身通信!这种情况下的路由表只包含环回接口一项。
2. 接下来的情况是主机连在一个局域网上,只能访问局域网上的主机。这时路由表包含两项:一项是环回接口,另一项是局域网(如以太网)。
3. 如果主机能够通过单个路由器访问其他网络(如Internet)时,那么就要进行下一步。一般情况下增加一个默认表项指向该路由器。
4. 如果要新增其他的特定主机或网络路由,那么就要进行最后一步。在我们的例子中,到主机slip的路由要通过路由器bsdi就是这样的例子。
我们根据上述IP操作的步骤使用这个路由表为主机svr4上的一些例子分组选择路由。
1. 假定目的地址是主机sun,140.252.13.33。首先进行主机地址的匹配。路由表中的两个主机地址表项(slip和localhost)均不匹配,接着进行网络地址匹配。这一次匹配成功,找到表项140.252.13.32(网络号和子网号都相同),因此使用emd0接口。这是一个直接路由,因此链路层地址将是目的端的地址。
2. 假定目的地址是主机slip,140.252.13.65。首先在路由表搜索主机地址,并找到一个匹配地址。这是一个间接路由,因此目的端的IP地址仍然是140.252.13.65,但是链路层地址必须是网关140.252.13.65的链路层地址,其接口名为emd0。
3. 这一次我们通过Internet给主机aw.com(192.207.117.2)发送一份数据报。首先在路由表中搜索主机地址,失败后进行网络地址匹配。最后成功地找到默认表项。该路由是一个间接路由,通过网关140.252.13.33并使用接口名为emd0。
4. 在我们最后一个例子中,我们给本机发送一份数据报。有四种方法可以完成这件事,如用主机名,主机IP地址,环回名,或者环回IP地址:

ftp svr4
ftp 140.252.13.34

ftp localhost
ftp 127.0.0.1

在前两种情况下,对路由表的第二次搜索得到一个匹配的网络地址140.252.13.32,并把IP报文传送给以太网驱动程序。正如图2.4所示的那样,IP报文中的目的地址为本机IP地址,因此报文被送给环回驱动程序,然后由驱动程序把报文放入IP输出队列中。
在后两种情况下,由于指定了环回接口的名字或IP地址,第一次搜索就找到匹配的主机地址,因此报文直接被送给环回驱动程序,然后由驱动程序把报文放入IP输出队列中。
上述四种情况报文都要被送给环回驱动程序,但是采用的两种路由决策是不相同的。

初始化路由表
我们从来没有说过这些路由表是如何被创建的。每当初始化一个接口时(通常是用 ifconfig命令设置接口地址时),就为接口自动创建一个直接路由。对于点对点链路和环回接口来说,路由是到达主机(例如,设置H标志)。对于广播接口来说,如以太网,路由是到达网络。
到达主机或网络的路由如果不是直接相连的,那么就必须加入路由表。一个普通的方法在系统引导时显式地在初始化文件中运行route命令。在主机svr4上,我们运行下面两个命令来添加路由表中的表项:

route add default sun 1
route add slip bsdi 1

第三个参数(default和slip)代表目的端,第四个参数代表网关(路由器),最后一个参数代表路由的度量(metric)。route命令在度量值大于0时要为该路由设置G标志,否则当耗费值为0时就不设置G标志。

(下面是原书p.116①的译文)
不幸的是,几乎没有系统愿意在启动文件中包含route命令。在4.4 BSD和BSD/386系统中,启动文件是/etc/netstart, 在SVR4系统中,启动文件是/etc/inet/rc.inet, 在Solaris 2.x中,启动文件是/etc/rc2.d/S69inet, 在SunOS 4.1.x中,启动文件是/etc/rc.local, 而AIX 3.2.2则使用文件/etc/rc.net。
一些系统允许在某个文件中指定默认的路由器,如/etc/defaultrouter,于是在每次重新启动系统时都要在路由表中加入该默认项。
初始化路由表的其它方法是运行路由守护程序(第10章)或者用较新的路由器发现协议(9.6节)。

较复杂的路由表
在我们的子网上,主机sun是所有主机的默认路由器,因为它有拔号SLIP链路连接到Internet上(参见封二上的图)。

(见原书p.117的①)

前两项与主机svr4的前两项一致:通过路由器bsdi到达slip的特定主机路由,以及环回路由。
第三行是新加的。这是一个直接到达主机的路由(没有设置G标志,但设置了H标志),对应于点对点的链路,即SLIP接口。如果我们把它与ifconfig命令的输出进行比较:

sun % ifconfig sl0
sl0: flags=1051< UP,POINTOPOINT,RUNNING>
inet 140.252.1.29 --> 140.252.1.183 netmask ffffff00

我们发现路由表中的目的地址就是点对点链路的另一端(即路由器netb), 网关地址为本地出口IP地址(140.252.1.29)。(我们前面已经说过, netstat为直接路由打印出来的网关地址就是本地接口所用的IP地址。)
默认的路由表项是一个到达网络的间接路由(设置了G标志,但没有设置H标志),这正是我们所希望的。网关地址是路由器的地址(140.252.1.183,SLIP链路的另一端), 而不是SLIP链路的本地IP地址(140.252.1.29)。其原因还是因为是间接路由,不是直接路由。
我们还应该指出的是,netstat输出的第三和第四行(接口名为sl0)由SLIP软件在启动时创建,并在关闭时删除.

没有到达目的地的路由
我们所有的例子都假定对路由表的搜索都能找到匹配的表项,即使匹配的是默认项。如果路由表中没有默认项,而又没有找到匹配项,这时会发生什么情况呢?
结果取决于该IP数据报是由主机产生的还是被转发的(例如,我们就充当一个路由器)。如果数据报是由本地主机产生的,那么就给发送该数据报的应用程序返回一个差错,或者是“主机不可达差错”或者是“网络不可达差错”。如果是被转发的数据报,那么就给原始发送端发送一份ICMP主机不可达差错的报文。在下一节我们将讨论这种差错。

9.3 ICMP主机与网络不可达差错
当路由器收到一份IP数据报但又不能转发时,就要发送一份ICMP“主机不可到达”差错报文。(ICMP主机不可达报文的格式如图6.10所示)。我们可以很容易发现,在我们的网络上把接在路由器sun上的拔号SLIP链路断开,然后试图通过该SLIP链路发送分组给任何指定sun为默认路由器的主机。

(下面是原书p.118①的译文)
较老版本的BSD产生一个主机不可达或者网络不可达差错,这取决于目的端是否处于一个局域子网上。4.4 BSD只产生主机不可达差错。

我们在上一节通过在路由器sun上运行netstat命令可以看到,当接通SLIP链路启动时就要在路由表中增加一项使用SLIP链路的表项,而当断开SLIP链路时则删除该表项。这说明当SLIP链路断开时,sun的路由表中就没有默认项了。但是我们不想改变网络上其他主机的路由表,即同时删除它们的默认路由。相反,对于sun不能转发的分组我们对它产生的ICMP主机不可达差错报文进行计数。
在主机svr4上运行ping程序就可以看到这一点,它在拔号SLIP链路的另一端(拔号链路已被断开):

(见原书p.118的②)

在主机bsdi上运行tcpdump命令的输出如图9.2所示。

图9.2 响应ping命令ICMP主机不可达报文

当路由器sun发现找不到能到达主机gemini的路由时,它就响应一个主机不可达的回响请求报文。
如果我们把SLIP链路接到Internet上,然后试图ping一个与Internet没有连接的IP地址,那么应该会产生差错。但令人感兴趣的是,我们可以看到在返回差错报文之前,分组要在Internet上传送多远:

(见原书p.118的③)

从图8.5可以看出,在发现该IP地址是无效的之前,该分组已通过了6个路由器。只有当它到达NSFNET骨干网的边界时才检测到差错。这说明, 6个路由器之所以能转发分组是因为路由表中有默认项,只有当分组到达NSFNET骨干网时,路由器才能知道每个连接到Internet上的每个网络的信息。这说明许多路由器只能在局部范围内工作。
参考文献[Ford, Rekhter, and Braun 1993]定义了顶层选路域(top-level routing domain),由它来维护大多数Internet网站的路由信息,而不使用默认路由。他们指出,在Internet上存在5个这样的顶层选路域:NSFNET主干网,商业互连网交换(Commercial Internet Exchange: CIX),NASA科学互连网(NASA Science Internet: NSI),SprintLink,以及欧洲IP主干网(EBONE)。

9.4 转发或不转发
前面我们已经提过几次,一般都假定主机不转发IP数据报,除非对它们进行特殊配置而作为路由器使用。如何进行这样的配置呢?
大多数伯克利派生出来的系统都有一个内核变量ipforwarding,或其他类似的名字。(参见附录E。)一些系统(如BSD/386和SVR4)只有在该变量值不为0的情况下才转发数据报。SunOS 4.1.x允许该变量可以三个不同的值:-1表示始终不转发并且始终不改变它的值;0表示默认条件下不转发,但是当打开两个或更多个接口时就把该值设为1;1表示始终转发。Solaris 2.x把这三个值改为0(始终不转发),1(始终转发)和2(在打开两个或更多个接口时才转发)。
较旧版本的4.2BSD主机在默认条件下可以转发数据报,这给没有进行正确配置的系统带来了许多问题。这就是内核选项为什么要设成默认的“始终不转发”的原因,除非系统管理员进行特殊设置。

9.5 ICMP改变路由差错
当IP数据报应该被发送到另一个路由器时,收到数据报的路由器就要发送ICMP改变路由(redirect)差错报文给IP数据报的发送端。这在概念上是很简单的,正如我们在图9.3中所示的那样。只有当主机可以选择路由器发送分组的情况下我们才可能看到ICMP改变路由报文。(回忆我们在图7.6中看过的例子。)
1. 我们假定主机发送一份IP数据报给R1。这种路由选择决策经常发生,因为R1是该主机的默认路由。
2. R1收到数据报并且检查它的路由表,发现R2是发送该数据报的下一站。当它把数据报发送给R2时,R1检测到它正在发送的接口与数据报到达接口是相同的(即主机和两个路由器所在的LAN)。这样就给路由器发送改变路由报文给原始发送端提供了线索。
3. R1发送一份ICMP改变路由报文给主机,告诉它以后把数据报发送给R2而不是R1。

图9.3 ICMP改变路由例子

改变路由一般用来让具有很少路由选择信息的主机逐渐建立更完善的路由表。主机启动时路由表中可以只有一个默认表项(在图9.3的例子中,为R1或R2),一旦默认路由发生差错,默认路由器将通知它进行改变路由,允许主机对路由表作相应的改动。ICMP改变路由允许TCP/IP主机在进行路由选择时不需要具备智能特性,而把所有的智能特性放在路由器端。显然,在我们的例子中,R1和R2 必须知道有关相连网络的更多拓扑结构的信息,但是连在LAN上的所有主机在启动时只需一个默认路由,通过接收改变路由报文来逐步学习。
一个例子
我们可以在我们的网络上观察到ICMP改变路由的操作过程(见封二的图)。尽管我们在拓扑图中只画出了三台主机(aix, solaris和gemini)和两台路由器(gateway和netb),但是整个网络有超过150台主机和10台另外的路由器。大多数的主机都把gateway指定为默认路由器,因为它提供了Internet的入口。
子网140.252.1上的主机是如何访问作者所在子网(图中底下的四台主机)的呢?首先,如果在SLIP链路的一端只有一台主机,那么就要使用代理ARP(4.6节)。这意味着位于拓扑图顶部的子网(140.252.1)中的主机不需要其他特殊条件就可以访问主机sun(140.252.1.29)。位于netb上的代理ARP软件处理这些事情。
但是,当网络位于SLIP链路的另一端时,就要涉及到路由选择了。一个办法是让所有的主机和路由器都知道路由器netb是网络140.252.13的网关。这可以在每个主机的路由表中设置静态路由,或者在每个主机上运行守护程序来实现。另一个更简单的办法(也是实际采用的方法)是利用ICMP改变路由报文来实现。
让我们在位于网络顶部的主机solaris上运行ping程序到主机bsdi(140.252.13.35)。由于子网号不相同,代理ARP不能使用。假定没有安装静态路由,发送的第一个分组将采用到路由器gateway的默认路由。下面是我们运行ping程序之前的路由表:

(见原书p.121的①)

(224.0.0.0所在的表项是IP广播地址。我们将在第12章讨论。)如果为ping程序指定-v选项,我们可以看到主机接收到的任何ICMP报文。我们需要指定该选项以观察发送的改变路由报文。

(见原书p.121的②)

在我们收到ping程序的第一个响应之前,主机先收到一份来自默认路由器gateway发来的ICMP改变路由报文。如果我们这时查看路由表,会发现已经插入了一个到主机bsdi的新路由。(该表项如下黑体字所示。)

(见原书p.121的③)

这是我们第一次看到D标志,表示该路由是被ICMP改变路由报文创建的。G标志说明这是一份到达gateway(netb)的间接路由,H标志则说明这是一个主机路由(正如我们期望的那样),而不是一个网络路由。
由于这是一个被主机改变路由报文增加的主机路由,因此它只处理到达主机bsdi的报文。如果我们接着访问主机svr4,那么就要产生另一个ICMP改变路由报文,创建另一个主机路由。类似地,访问主机slip也创建另一个主机路由。位于子网上的三台主机(bsdi, svr4和slip)还可以由一个指向路由器sun的网络路由来进行处理。但是ICMP改变路由报文创建的是主机路由,而不是网络路由,这是因为在本例中,产生ICMP改变路由报文的路由器并不知道位于140.252.13网络上的子网信息。

更多的细节
ICMP改变路由报文的格式如图9.4所示。

图9.4 ICMP改变路由报文

有四种不同类型的改变路由报文,有不同的代码值,如图9.5所示。

图9.5 ICMP改变路由报文的不同代码值

ICMP改变路由报文的接收者必须查看三个IP地址:(1)导致改变路由的IP地址(即ICMP改变路由报文的数据位于IP数据报的首部);(2)发送改变路由报文的路由器的IP地址(包含改变路由信息的IP数据报中的源地址;(3)应该采用的路由器IP地址(在ICMP报文中的4-7字节)。
关于ICMP改变路由报文有很多规则。首先,改变路由报文只能由路由器生成,而不能由主机生成。另外,改变路由报文是为主机而不是为路由器使用的。假定路由器和其他一些路由器共同参与某一种路由选择协议,则该协议就能消除改变路由的需要。(这意味着在图9.1中的路由表应该消除或者能被路由选择守护程序修改,或者能被改变路由报文修改,但不能同时被二者修改。)
在4.4BSD系统中,当主机作为路由器使用时,要进行下列检查,在生成ICMP改变路由报文之前这些条件都要满足。
1. 出接口必须等于入接口。
2. 用于向外传送数据报的路由不能被ICMP改变路由报文创建或修改过,而且不能是路由器的默认路由。
3. 数据报不能用源站选路来转发。
4. 内核必须配置成可以发送改变路由报文。

(下面是原书p.123①的译文)
内核变量取名为ip_sendredirects或其他类似的名字。(参见附录E。)大多数当前的系统(例如BSD, SunOS 4.1.x, Solaris 2.x, 及AIX 3.2.2)在默认条件下都设置该变量使系统可以发送改变路由报文。其他系统如SVR4则关闭了该项功能。

另外,一台4.4BSD主机收到ICMP改变路由报文后,在修改路由表之前要作一些检查。这是为了防止路由器或主机的误操作,以及恶意用户的破坏,导致差错地修改系统路由表。
1. 新的路由器必须直接与网络相连接。
2. 改变路由报文必须来自当前到目的地所选择的路由器。
3. 改变路由报文不能让主机本身作为路由器。
4. 被修改的路由必须是一个间接路由。

关于改变路由最后要指出的是,路由器应该发送的只是对主机的改变路由(代码1或3,如图9.5所示),而不是对网络的改变路由。子网的存在使得难于准确指明何时应发送对网络的改变路由而不是对主机的改变路由。只当路由器发送了错误的类型时,一些主机才把收到的对网络的改变路由当作对主机的改变路由来处理。

9.6 ICMP路由器发现报文
我们在本章前面已提到过一种初始化路由表的方法,即在配置文件中指定静态路由。这种方法经常用来设置默认路由。另一种新的方法是利用ICMP路由器通告和请求报文。
一般认为,主机在引导以后要广播或多播传送一份路由器请求报文。一台或更多台路由器响应一份路由器通告报文。另外,路由器定期地广播或多播传送它们的路由器通告报文,允许每个正在监听的主机相应地更新它们的路由表。
RFC 1256 [Deering 1991]确定了这两种ICMP报文的格式。ICMP路由器请求报文的格式如图9.6所示。ICMP路由器通告报文的格式如图9.7所示。
路由器在一份报文中可以通告多个地址。地址数指的是报文中所含的地址数。地址项大小指的是每个路由器地址32-bit字的数目,始终为2。生命周期指的是通告地址有效的时间(秒数)。

图9.6 ICMP路由器请求报文格式

图9.7 ICMP路由器通告报文格式

接下来是一对或多对IP地址和优先级。IP地址必须是发送路由器的某个地址。优先级是一个有符号的32-bit整数,指出该IP地址作为默认路由器地址的优先等级,这是与子网上的其他路由器相比较而言的。值越大说明优先级越高。优先级为0x80000000说明对应的地址不能作为默认路由器地址使用,尽管它也包含中通告报文中。优先级的默认值一般为0。

路由器操作
当路由器启动时,它定期在所有广播或多播传送接口上发送通告报文。准确地说,这些通告报文不是定期发送的,而是随机传送的,以减小与子网上其他路由器发生冲突的概率。一般每两次通告间隔450和600秒。一份给定的通告报文默认生命周期是30分钟。
使用生命周期域的另一个时机是当路由器上的某个接口被关闭时。在这种情况下,路由器可以大该接口上发送最后一份通告报文,并把生命周期值设为0。
除了定期发送主动提供的通告报文以外,路由器还要监听来自主机的请求报文,并发送路由器通告报文以响应这些请求报文。
如果子网上有多台路由器,由系统管理员为每个路由器设置优先等级。例如,主默认路由器就要比备份路由器具有更高的优先级。

主机操作
主机在引导期间一般发送三份路由器请求报文,每三秒钟发送一次。一旦接收到一个有效的通告报文,就停止发送请求报文。
主机也监听来自相邻路由器的请求报文。这些通告报文可以改变主机的默认路由器。另外,如果没有接收到来自当前默认路由器的通告报文,那么默认路由器会超时。
只要有一般的默认路由器,该路由器就会每隔10分钟发送通告报文,报文的生命周期是30分钟。这说明主机的默认表项是不会超时的,即使错过一份或两份通告报文。

实现
路由器发现报文一般由用户进程(守护程序)创建和处理。这样,在图9.1中就有另一个修改路由表的程序,尽管它只增加或删除默认表项。守护程序必须把它配置成一台路由器或主机来使用。

(下面是原书 p.125①的译文)
这两种ICMP报文是新加的,不是所有的系统都支持它们。在我们的网络中,只有Solaris 2.x支持这两种报文( in.rdisc守护程序)。尽管RFC建议用尽可能用IP多播传送,但是路由器发现还可以利用广播报文来实现。

9 .7 小结
IP路由操作对于运行TCP/IP的系统来说是最基本,不管是主机还是路由器。路由表项的内容很简单,它包括:5 bit标志,目的IP地址(主机,网络,或默认),下一站路由器的IP地址(间接路由)或者本地接口的IP地址(直接路由),指向本地接口的指针。主机表项比网络表项具有更高的优先级,而网络表项比默认项具有更高的优先级。
系统产生的或转发的每份IP数据报都要搜索路由表,它可以被路由守护程序或ICMP改变路由报文修改。系统在默认情况下不转发数据报,除非进行特殊的配置。用route命令可以进入静态路由,可以利用新ICMP路由器发现报文来初始化默认表项,并进行动态修改。主机在启动时只有一个简单的路由表,它可以被来自默认路由器的ICMP改变路由报文动态修改。
在本章,我们集中讨论了单个系统是如何利用路由表的。在下一章,我们将讨论路由器之间是如何交换路由信息的。

习题
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 System)的方式组织的,每个自治系统通常由单个实体管理。常常将一个公司或大学校园定义为一个自治系统。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 System to Intermediate System 协议(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 System)的速度。在单个局域网中这可能是可以接受到,但是在数据报通过路由器时,通过对链路层数据帧进行循环冗余检验(如以太网或令牌环数据帧)可以检测到大多数的差错,导致传输失败。不管相信与否,路由器中也存在软件和硬件差错,以致于修改数据报中的数据。如果关闭端到端的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 System)服务器上所发生的不同检验和差错的统计结果,时间持续了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字节的数据(新行)
33333333333 12字节的数据(新行)

目的地址是广播地址(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)的答复。
在这个例子中,我们也显示了执行ping广播地址前后ARP缓存的内容。这可以显示广播与ARP之间的相互作用。执行ping命令前ARP缓存是空的,而执行后是满的。(也就是说,对网内其他每个响应回显请求的主机在ARP缓存中均有一个条目。)我们提到的该以太数据帧被传送到链路层的广播地址(0xffffffff)是如何发生的呢?由sun主机发送的数据帧不需要ARP。
如果使用tcpdump来观察ping的执行过程,可以看到广播数据帧的接收者在发送它的响应之前,首先产生一个对sun主机的ARP请求,因为它的回答是以单播形式发回的。在4.5节我们介绍了一个ARP请求的接收者(该例中是sun)通常在发送ARP应答外,还将请求主机的IP地址和物理地址加入到ARP缓存中去。这基于这样一个假定:如果请求者向我们发送一个数据报,我们也很可能想向它发回什么。
我们使用的ping程序有些特殊,原因在于它使用的编程接口(在大多数Unix实现中是“低级插口(raw socket)”)通常允许向一个广播地址发送数据报。如果我们使用不支持广播的应用如TFTP情况如何呢?(TFTP将在第15章详细介绍。)

bsdi % tftp 启动客户程序
tftp> connect 140.252.13.63 说明服务器的IP地址
tftp> get temp.foo 试图从服务器或获取一个文件
tftp: sendto: Permission denied
tftp> quit 终止客户程序

在这个例子中,程序立即产生了一个差错,但不向网络发送任何信息。产生这一切的原因在于插口提供的应用程序接口API只有进程明确打算进行广播时才允许它向广播地址发送UDP数据报。这主要是为了防止用户错误地采用了广播地址(正如此例)而应用程序却不打算广播。

在广播UDP数据报之前,使用插口中API的应用程序必须设置SO_BROADCAST 插口选项。
并非所有系统均强制使用这个限制。某些系统中无需进程进行这个说明就能广播UDP数据报。而某些系统则有更多的限制,需要有超级用户权限的进程才能广播。

下一个问题是是否转发广播数据。一些系统内核和路由器有一选项来控制允许或禁止这一特性。(见附录E)
如果我们让路由器bsdi能够转发广播数据,然后在主机slip上运行ping程序,我们能够观察到由路由器bsdi转发的子网广播数据报。转发广播数据报意味着路由器接收广播数据,确定该目的地址是对哪个接口的广播,然后用链路层广播向对应的网络转发数据报。

(见原书p.174①)

我们观察到它的确正常工作了,同时也看到了BSD系统中的ping程序检查重复的数据报序列号,如果出现重复的序列号的数据报就显示DUP! ,它意味着一个数据报已经在某处重复了,然而它正是我们所期望看到的,因为我们正向一个广播地址发送数据。
我们还可以从更远离广播所指向的络网上的主机上来进行这个试验。如果在主机angogh.cx.berkeley.edu(和我们的网络距离14跳)上运行ping程序,如果路由器sun被设置为能够转发所指向的广播,它还能正常工作。在这种情况下,这个IP数据报(传送ICMP回显请求)被路径上的每个路由器像正常的数据报一样转发,它们均不知道它们传送的实际上是广播数据。接着最后一个路由器netb看到主机号为63,就将其转发给路由器sun。路由器sun觉察到该目的IP地址事实上是一个相连子网接口上的广播地址,就以该数据报以链路层广播传往相应网络。
广播是一种应该谨慎使用功能。在许多情况下,IP多播被证明是一个更好的解决办法。

12.4 多播

IP多播提供两类服务。
1. 向多个目的地址传送数据。有许多向多个接收者传送信息的应用:例如交互式会议系统和向多个接收者分发邮件或新闻。如果不采用多播,目前这些应用大多采用TCP来完成(向每个目的地址传送一个单独的数据复制)。然而,既使使用多播,某些应用可能继续采用TCP来保证它的可靠性。
2. 客户对服务器的请求。例如,无盘工作站需要确定启动引导服务器。目前,这项服务是通过广播来提供的(正如第16章的BOOTP),但是使用多播可降低不提供这项服务主机的负担。
多播组地址
图12.2 显示了D类IP地址的格式。

图12.2 D类IP地址格式

不像图1.5 所示的其他三类IP地址(A、B和C),分配的28 bit均用作多播组号码而不再表示其他。
多播组地址包括最高4 bit为1110的类别比特和多播组号。它们通常可表示为由点分十进制数,范围从224.0.0.0到239.255.255.255。
能够接收发往一个特定多播组地址数据的主机集合称为主机组(host group)。一个主机组可跨越多个网络。主机组中成员可随时加入或离开主机组。主机组中对主机的数量没有限制,同时不属于某一主机组的主机可以向该组发送信息。
一些多播组地址被IANA确定为熟知地址。它们也被当作永久主机组,这和TCP及UDP中的熟知端口相似。同样的,这些熟知多播地址在RFC最新分配数字中列出。注意这些多播地址所代表的组是永久组,而它们的组成员却不是永久的。
例如,224.0.0.1代表“该子网内的所有系统组”,224.0.0.2代表“该子网内的所有路由器组”。多播地址224.0.1.1用作网络时间协议NTP,224.0.0.9用作RIP-2(见10.5节),224.0.1.2用作SGI公司的dogfight应用。

多播组地址到以太网地址的转换
IANA拥有一个以太网地址块,即高位24 bit为00:00:5e(十六进制表示)的以太网地址,这意味着该地址块所拥有的地址范围从00:00:5e:00:00:00到00:00:5e:ff:ff:ff。IANA将其中的一半分配为多播地址。为了指明一个多播地址,任何一个以太网地址的首字节必须是01,这意味着与IP多播相对应的以太网地址范围从01:00:5e:00:00:00到01:00:5e:7f:ff:ff。

这里对CSMA/CD或令牌网使用的是Internet标准比特顺序,和在内存中出现的比特顺序一样。这也是大多数程序设计者和系统管理者采用的顺序。IEEE文档采用了这种比特的传输顺序。Assigned Numbers RFC给出了这些表示的差别。

这个地址分配将使以太网多播地址中的23 bit与IP多播组号对应起来,这通过将多播组号中的低位23 bit映射到以太网地址中的低位23 bit实现,这个过程如图12.3。
既然多播组号中的最高5 bit在映射过程中被忽略,因此每个以太网多播地址对应的多播组是不唯一的。32个不同的多播组号被映射为一个以太网地址。例如,多播地址224.128.64.32(十六进制e0.80.40.20)和224.0.64.32(十六进制e0.00.40.20)都映射为同一以太网地址01:00:5e:00:40:20。
既然地址映射是不唯一的,这意味着设备驱动程序或IP层(见图12.1)必须进行数据报过滤,因为网卡可能接收到主机不想接收的多播数据帧。另外,如果网卡不提供足够的多播数据帧过滤功能,设备驱动程序就必须接收所有多播数据帧,然后对它们进行过滤。

图12.3 D类IP地址到以太网多播地址的映射


局域网网卡趋向两种处理类型,一种是网卡根据对多播地址的散列值实行多播过滤,这意味仍会接收到不想接收的多播数据。另一种网卡是只接收一些固定数目的多播地址,这意味着当主机想接收超过网卡预先支持多播地址以外的多播地址时,必须将网卡设置为“多播混杂(multicast promiscuous)”模式。因此,这两种类型的网卡仍需要设备驱动程序检查收到的帧是否真是主机所需要的。
既使网卡实现了完美的多播过滤(基于48 bit的硬件地址),由于从D类IP地址到48 bit的硬件地址的映射不是一对一的,过滤过程仍是必要的。
尽管存在地址映射不完美和需要硬件过滤的不足,多播仍然比广播好。

单个物理网络的多播是简单的。多播进程将目的IP地址指明为多播地址,设备驱动程序将它转换为相应的以太网地址,然后把数据发送出去。这些接收进程必须通知它们的IP层它们想接收的发往给定多播地址的数据报,并且设备驱动程序必须能够接收这些多播帧。这个过程就是“加入一个多播组”。(使用“接收进程”复数形式的原因在于对一确定的多播信息,在同一主机或多个主机上存在多个接收者,这也是为什么要首先使用多播的原因。)当一个主机收到多播数据报时,它必须向属于那个多播组的每个进程均传送一个复制。这和单个进程收到单播UDP数据报的UDP不同,使用多播,一个主机上可能存在多个属于同一多播组的进程。
当把多播扩展到单个物理网络以外需要通过路由器转发多播数据时,复杂性就增加了。需要有一个协议让多播路由器了解确定网络中属于确定多播组的任何一个主机。这个协议就是Internet组管理协议(IGMP),也是下一章介绍的内容。

FDDI和令牌环网络中的多播
FDDI网络使用相同的D类IP地址到48 bit FDDI地址的映射过程[Katz 1990]。令牌环网络通常使用不同的地址映射方法,这是因为大多数令牌控制中的限制。

12.5 小结

广播是将数据报发送到网络中的所有主机(通常是本地相连的网络),而多播是将数据报发送到网络的一个主机组。这两个概念的基本点在于当收到送往上一个协议栈的数据帧时采用不同类型的过滤。每个协议层均可以因为不同的理由丢弃数据报。
目前有四种类型的广播地址:受限的广播、指向网络的广播、指向子网的广播和指向所有子网的广播。最常用的是指向子网的广播。受限的广播通常只在系统初始启动时才会用到。
试图通过路由器进行广播而发生的问题,常常是因为路由器不了解目的网络的子网掩码。结果与多种因素有关:广播地址类型、配置参数等等。
D类IP地址被称为多播组地址。通过将其低位23 bit映射到相应以太网地址中便可实现多播组地址到以太网地址的转换。由于地址映射是不唯一的,因此需要其他的协议实现额外的数据报过滤。

习题

12.1 广播是否增加了网络通信量?
12.2 考虑一个拥有50台主机的以太网:20台运行TCP/IP而其他30台运行其他的协议族。主机如何处理来自运行另一个协议族主机的广播?
12.3 你登录到一个过去从来没有用过的Unix系统,并且打算找出所有支持广播的接口的指向子网的广播地址。你如何做到这点?
12.4 如果我们用ping程序向一个广播地址发送一个长的分组,如

(见原书p.178①)

它正常工作,但将分组的长度再增加一个字节后出现如下差错:
sun % ping 140.252.13.63 1473
PING 140.252.13.63: 1473 data bytes
sendto: Message too long

究竟出了什么问题?
12.5 重做习题10.6,假定8个RIP报文是通过多播而不是广播(使用RIP 版本2)。有什么变化?
12—1

0 评论: