首页  编辑  

TCP/IP

Tags: /计算机文档/网络与安全/   Date Created:

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<UP,POINTOPOINT,LINK0>

             inet 140.252.13.65 --> 140.252.13.66 netmask ffffffe0

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

   svr4 % /usr/sbin/ifconfig emd0

   emd0: flags=23<UP,BROADCAST,NOTRAILERS>

           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<UP,BROADCAST,NOTRAILERS>

           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