Linux 版 (精华区)

发信人: netiscpu (网中自由鸟), 信区: Linux
标  题: Linux实用教程(部分)(7)
发信站: 哈工大紫丁香 (Thu May 20 18:12:27 1999), 转信

第十四章

网 络

Linux 和网络几乎就是同义语,Linux 就是 Internet 和 WWW 的产物。Linux 的开发

者使用网络和 Web 进行信息交换,而 Linux 本身又用于各种组织的网络支持。众所周

知,TCP/IP 协议是 Internet 的标准协议,同时也是事实上的工业标准。Linux 的网

络实现支持 BSD 套接字,支持完整的 TCP/IP 协议。本章描述 Linux 是如何支持 TC

P/IP 网络协议的。

14.1 TCP/IP 协议

本节简要描述 TCP/IP 协议,有关该协议的详细内容,读者可参阅其他文献。

TCP/IP 协议是 Internet 网的基本协议,它实际由许多协议组成,并以协议组的形式

存在,其中的主要协议有:传输控制协议(TCP)、用户数据报协议(UDP)、网际协议

(IP)、网际信报控制协议(ICMP)和地址解析协议(ARP)等。

直接连接到 Internet 网上的主机必须具有唯一的地址,这一地址称为“IP 地址”。

IP 地址由 4 个数字组成,中间用句点隔开,每个数字的取值范围一般在 0 ~ 255 之

间,例如:192.1.1.1。在 Internet 上,主机的 IP 地址分为五类,分别是 A、B、C

、D和E五类。主机的唯一 IP 地址一般从 A、B或C类地址中派生;D类地址用来将计算

机组织成一个功能组;而E类地址是试验性的,当前不可用。另外,一些特殊的 IP 地

址被保留用于特殊目的,例如,127.0.0.1 就是用来特指本地主机的回环地址。

和主机 IP 地址相关的概念还有子网掩码,TCP/IP 软件利用子网掩码判断数据传输的

目标主机是否和源主机处于同一子网中。例如,某主机的 IP 地址为 192.1.1.1,而子

网掩码为 255.255.255.0,如果目标主机的 IP 地址为 192.1.1.4,则说明目标主机和

源主机处于同一子网中,而如果目标主机的 IP 地址为 192.1.2.1,则说明不在同一子

网中。上述判断通过利用子网掩码计算两台主机所在的子网地址而实现。主机 IP 地址

和子网掩码的二进制与运算的结果称为“子网地址”,例如,主机 IP 地址 192.1.1.

1 和子网掩码 255.255.255.0 的二进制与运算的结果为 192.1.1.0,即该主机所在子

网的地址为 192.1.1.0,对地址为 192.1.1.4 的主机来说,可计算该主机所在子网地

址为 192.1.1.0,于是说明目标主机和源主机处于同一子网中,而192.1.2.1 却不在同

一子网中。

子网掩码也同时定义了子网中主机的最大数目。如果子网掩码为 255.255.255.0,在上

面的例子中,IP 地址从 192.1.1.1 到 192.1.1.254 的主机均可出现在同一子网中(

IP 地址 192.1.1.0 和 192.1.1.255 分别作为子网地址和子网中的广播地址),因此

,该子网中的主机数目最多为 254 台。

因为数字式的 IP 地址非常难于记忆,因而通常利用主机域名标识主机,例如,bbs.t

singhua.edu.cn 是清华大学 BBS 服务器的域名,其 IP 地址为 202.112.58.200。但

在利用域名标识主机的同时,也需要能够将域名转换为 IP 地址的机制,这种机制称为

“域名解析”。在一般的 TCP/IP 主机中,这一名称可通过静态的 hosts 文件指定,

也可通过 DNS (分布式名称服务器)服务器获得。在 Linux 中,/etc/hosts 文件指

定静态的主机名称,而 etc/resolv.conf 文件指定 DNS 服务器的 IP 地址。

每当连接到其他计算机进行数据传输时,该计算机的 IP 地址就用于数据的传递。数据

被划分为小的 IP 数据包发送,该数据包的前面是目标和源主机的 IP 地址,然后是数

据本身,最后是数据的校验和以及其他有用的信息。利用校验和信息,目标主机可以判

断数据的传输是否正确。由于数据包的物理传输介质不同,因此,针对不同的物理传输

介质,IP 数据包还要进一步划分为小的数据包或报文进行传输。这种情况下,目标主

机还要将小的报文重新装配成大的数据包,以便进行后续处理。

一般而言,逻辑上处于同一子网的两台主机处于同一局域网中,如果目标主机和源主机

处于同一子网,就可通过某种机制获得目标主机的网卡物理地址,从而利用局域网技术

实现数据传输。从主机的 IP 地址获得物理地址的机制称为“地址解析”,在 TCP/IP

 协议中,地址解析可由专门的协议(地址解析协议)完成。如果目标主机和源主机不

在同一子网中,这时的数据传输就要通过其他计算机完成,如果目标主机和源主机跨越

大的地理距离,则可能要通过许多计算机的参与才能实现数据的传输。跨越不同子网的

数据传输通过网关或路由器实现。当 TCP/IP 软件发现数据传输的目标主机处于其他子

网时,它首先将数据发送到网关,然后由网关选择适当的路径传输,直到数据到达目标

主机为止。Linux 维护一个路由表,每个目标 IP 地址均对应一个路由表项。利用路由

表项,Linux 可将每个跨子网的 IP 数据包发送到一个适当的主机(路由器)。系统中

的路由表实际是动态的,并随着应用程序的网络使用情况和网络拓扑结构的变化而变化



图 14-1 TCP/IP 协议层次结构

如前所述,TCP/IP 实际是以协议组的形式存在的,图 14-1 是 TCP/IP 协议层次结构

。从图中可看出,TCP/IP映射为四层的结构化模型。这一模型也称为网际协议组(Int

ernet Protocol Suit),可划分为网络接口、网际、传输和应用四层。

网络接口层(Network Interface Layer)负责和网络的直接通讯。它必须理解正在使

用的网络结构,诸如令牌环和以太网等,并且还要提供允许网际层与之通讯的接口。网

际层负责和网络接口层之间的直接通讯。

网际层(Internet Layer)主要完成利用网际协议(IP)的路由和数据包传递。传输层

上的所有协议均要使用IP发送数据。网际协议定义如下规则:如何寻址和定向数据包;

如何处理数据包的分段和重新组装;如何提供安全性信息;以及如何识别正在使用的服

务类型等。

但是,由于IP不是基于连接的协议,因此它不能保证在线路中传输的数据不会丢失、破

坏、重复或颠倒次序。这由网络模型中的高层,即传输层或应用层负责。网际层中还有

一些其他的协议:网际信报控制协议(ICMP),网际组管理协议(IGMP)以及地址解析

协议(ARP)等。

传输层(Transport Layer)负责提供应用程序之间的通讯。这种通讯可以是基于连接

的,也可以是非基于连接的。这两种连接类型的主要差别在于是否跟踪数据以及是否确

保数据发送到目标等。传输控制协议(Transmission Control Protocol, TCP)是基于

连接的协议,能提供可靠的数据传输;而用户数据报协议(User Datagram Protocol,

 UDP)是非基于连接的协议,不能确保数据的正确传输。

Internet协议组的应用层(Application Layer)作为应用程序和网络组件之间的接口

而存在,其中存在大量的协议,包括简单网络管理协议(Simple Network Management

 Protocol,SNMP)、文件传输协议(File Transfer Protocol,FTP)、简单邮件传输

协议(Simple Mail Transfer Protocol,SMTP)等。

图 14-2 给出了 TCP 数据包的在网际协议组中的传输情况。TCP 利用 IP 数据包传输

它自己的数据包,这时,IP 数据包中的数据是 TCP 数据包本身。UDP 也利用 IP 数据

包进行数据的传输,在这种情况下,接收方的 IP 层必须能够知道接收到的 IP 数据包

要发送给传输层中的哪个协议。为此,每个 IP 数据包头中包含一个字节,专门用作协

议标识符。接收方的 IP 层利用这一标识符决定将数据包发送给传输层的哪一个协议处

理。和上面的情况类似,同一台主机上利用同一协议进行通讯的应用程序可能有许多,

因此,也需要一种机制来标识应由哪一个应用程序处理同一种数据包。为此,应用程序

利用 TCP/IP 协议进行通讯时,不仅要指定目标 IP 地址,还要指定应用程序的“端口

”地址。端口可唯一标识应用程序,标准的网络应用程序使用标准的端口地址,例如 

Web 服务器的标准端口为 80。在网络接口中,IP 地址和端口地址合称为“套接字”。

IP 协议层可利用许多不同的物理介质传输 IP 数据包,图 14-2 中,IP 数据包进一步

包装在以太网数据帧中传输。除以太网外,IP 数据包还可以在令牌环网等其他物理介

质上传输。以太网数据帧头中包含了数据帧的目标以太网地址,以太网地址实际就是以

太网卡的硬件地址或物理地址,一般由 6 位整数组成,如 00-A0-0C-13-CC-78。以太

网卡的物理地址是唯一的,一些特殊的物理地址保留用于广播等目的。因为以太网数据

帧和 IP 数据包一样,可以传输不同的协议数据,因此,数据帧中也包含一个标识协议

的整数。

 

图 14-2 TCP 数据包的传输

在以太网中,数据的传输是通过物理地址或硬件地址实现的,而 IP 地址实际只是一种

概念性的逻辑地址,因此,在类似以太网这样的网络中,必须采用地址解析协议(ARP

)将 IP 地址翻译为实际的硬件地址。ARP 负责为 IP 所请求的任意一个本地 IP 地址

找出其本地物理地址。ARP 使用本地广播来寻找主机的物理地址,并在内存的高速缓冲

区中维护最近所映射的物理地址。如果目标 IP 地址是本地地址,ARP 可发送一个本地

广播请求获取目标 IP 主机的物理地址,并将物理地址返回给 IP。如果 IP 发现目标

 IP 地址处于远程子网中,则数据包必须发送到路由器,这时,ARP 可替 IP 找到路由

器的物理地址。

14.2 Linux 的TCP/IP 网络层

图 14-3 描述了 Linux 的网络软件结构,和 TCP/IP 协议的结构类似,Linux 以分层

的软件结构实施 TCP/IP 协议组。

BSD 套接字由一般性的套接字管理软件 INET 套接字层支持。INET 套接字管理着基于

 IP 的 TCP 或 UDP 协议端。如前所述,TCP 是基于连接的协议,而 UDP 是非基于连

接的协议。在传输 UDP数据包 时,Linux 不需要关心数据包是否安全到达目的端。但

对 TCP 数据包来说,Linux 需要对数据包进行编号,数据包的源端和目的端需要协调

工作,以便保证数据包不会丢失,或以错误的顺序发送。IP 层包含的代码实施了 Int

ernet 协议,IP 层需处理数据包的报文头信息,并且必须将传入的数据包发送到 TCP

 或 UDP 两者中正确的一层处理。在 IP 层之下是 Linux 的网络设备层,其中包括以

太网设备或 PPP 设备等。和 Linux 系统中的其他设备不同,网络设备并不总代表实际

的物理设备,例如,回环设备就是一个纯软件设备,而且,网络设备并不能通过 Linu

x 的 mknod 命令建立,而是只有在底层软件发现并初始化这些设备之后才会出现在 /

dev 目录下。ARP 协议提供地址解析功能,因此它处于 IP 层和网络设备层之间。

图 14-3 Linux 的网络层

14.3 BSD 套接字接口

套接字既可看成是支持多种网络操作形式的接口,也可看成是一种进程间通讯接口。在

一条通讯连接中,每个参与通讯的进程有一个套接字描述相应的连接端。我们可以将套

接字看成是某种特殊类型的管道,但和管道不同的是,套接字并不限制其中可以包含的

数据数量。Linux 支持多种套接字种类,不同的套接字种类称为“地址族”,这是因为

每种套接字种类拥有自己的通讯寻址方法。Linux 所支持的套接字地址族见表 14-1。

表 14-1 Linux 支持的套接字地址族

套接字地址族

 描述

 

UNIX

 UNIX 域套接字。

 

INET

 通过 TCP/IP 协议支持的 Internet 地址组。

 

AX25

 Amater radio X25

 

IPX

 Novell IPX

 

APPLETALK

 Appletalk DDP

 

X25

 X25

 

和虚拟文件系统类似,Linux将上述套接字地址族抽象为统一的 BSD 套接字接口,应用

程序关心的只是 BSD 套接字接口,而 BSD 套接字由各地址族专有的软件支持。一般而

言,BSD 套接字可支持多种套接字类型,不同的套接字类型提供的服务不同,Linux 所

支持的 BSD 套接字类型见表 14-2,表 14-1 中的套接字地址族并不一定全部支持这些

套接字类型。

表 14-2 Linux 所支持的 BSD 套接字类型

BSD 套接字类型

 描述

 

流(stream)

 这种套接字提供了可靠的双向顺序数据流,并可保证数据不会在传输过程中丢失、破

坏或重复出现。流套接字通过 INET 地址族的 TCP 协议实现。 

数据报(datagram)

 这种套接字也提供双向的数据传输,但是并不对数据的传输提供担保,也就是说,数

据可能会以错误的顺序传递,甚至丢失或破坏。这种类型的套接字通过 INET 地址族的

 UDP 协议实现。 

原始(raw)

 利用这种类型的套接字,进程可以直接访问底层协议(因此称为原始)。例如,可在

某个以太网设备上打开原始套接字,然后获取原始的 IP 数据传输信息。 

可靠发送的消息

 和数据报套接字类似,但保证数据被正确传输到目的端。

 

顺序数据包

 和流套接字类似,但数据包大小是固定的。

 

数据包(packet)

 这并不是标准的 BSD 套接字类型,它是 Linux 专有的 BSD 套接字扩展,可允许进程

直接在设备级访问数据包。 

进程在利用套接字进行通讯时,采用客户-服务器模型。服务器提供某种服务,而客户

使用这种服务。WWW 服务器就是很好的例子,WWW 服务器提供 web 页,而客户程序,

即浏览器则读取 web 页并显示 web 页的内容。服务器首先创建一个套接字,并将某个

名称绑定到该套接字上,套接字的名称依赖于套接字的底层地址族,但通常是服务器的

本地地址。套接字的名称或地址通过 sockaddr 数据结构指定。对于 INET 套接字来说

,服务器的地址由两部分组成,一个是服务器的 IP 地址,另一个是服务器的端口地址

。已注册的标准端口可查看 /etc/services 文件。将地址绑定到套接字之后,服务器

就可以监听请求链接该绑定地址的传入连接。连接请求由客户生成,它首先建立一个套

接字,并指定服务器的目标地址以请求建立连接。传入的连接请求通过不同的协议层最

终到达服务器的监听套接字。服务器接收到传入的请求后,如果能够接受该请求,服务

器必须创建一个新的套接字来接受该请求并建立通讯连接(用于监听的套接字不能用来

建立通讯连接),这时,服务器和客户就可以利用建立好的通讯连接传输数据。

BSD 套接字上的详细操作和具体的底层地址族有关,底层地址族的不同实际意味着寻址

方式、采用的协议等的不同,因此 TCP/IP 连接的建立过程和 AX25 连接的建立过程有

很大的区别。前面提到,Linux 利用 BSD 套接字层抽象了不同的套接字接口。在内核

的初始化阶段,内建于内核的不同地址族分别以 BSD 套接字接口在内核中注册。然后

,随着应用程序创建并使用 BSD 套接字,内核负责在 BSD 套接字和底层的地址族之间

建立联系。这种联系通过交叉链接数据结构以及地址族专有的支持例程表建立。例如,

每个地址族具有专有的套接字创建例程,应用程序在建立新的套接字时,BSD 套接字接

口可利用该例程建立新的 BSD 套接字。

在内核中,地址族和协议信息保存在 protocols 向量中。每个地址族由其名称(例如

“INET”)以及相应的初始化例程地址代表。在引导阶段初始化套接字接口时,内核调

用每个地址族的初始化例程,这时,每个地址族注册自己的协议操作集。协议操作集实

际是一个例程集合,其中每个例程执行一个特定的操作。注册的协议操作集保存在 po

ps 向量中,向量中包含指向 proto_ops 数据结构的指针。proto_ops 数据结构由地址

族类型和一组套接字操作例程指针组成,这些例程是特定的地址族所特有的。pops 向

量的索引是地址族的标识符,例如,INET 地址族的标识符为 2。

14.4 INET 套接字层

INET 套接字层是用于支持 Internet 地址族的套接字层。它和 BSD 套接字之间的接口

通过 Internet 地址族套接字操作集实现,如前所述,这些操作集实际是一组协议的操

作例程。网络的初始化过程中,这一操作集在 BSD 套接字层中注册,并且和其他注册

的地址族操作集一起保存在 pops 向量中。BSD 套接字层通过调用 proto_ops 结构中

的相应函数执行任务,例如,当应用程序给定 INET 地址族来创建 BSD 套接字时,将

利用 INET 套接字创建函数来执行这一任务。在每次的套接字操作函数调用中,BSD 套

接字层向 INET 套接字层传递 socket 数据结构来代表一个 BSD 套接字,但在 INET 

套接字层中,它利用自己的 sock 数据结构来代表该套接字,因此,这两个结构之间存

在着链接关系,这一关系可从图 14-4 中看出。

在 BSD 的 socket 数据结构中存在一个 data 指针,该指针将 BSD socket 数据结构

和 sock 数据结构链接了起来。通过这种链接关系,随后的 INET 套接字调用就可以方

便地检索到 sock 数据结构。实际上,sock 数据结构可适用于不同的地址族,在建立

套接字时,sock 数据结构的协议操作集指针指向所请求的协议操作集。如果请求 TCP

 协议,则 sock 数据结构的协议操作集指针将指向 TCP 的协议操作集。

14.4.1 建立 BSD 套接字

在利用 Linux 系统调用建立新的套接字时,需要传递套接字的地址族标识符、套接字

类型以及协议。内核首先利用套接字的地址族标识符搜索 pops 向量。如果指定的地址

族以内核模块的形式实现,kerneld 守护进程负责装入相应的模块。如果能够建立该 

BSD 套接字,内核将为该套接字分配新的 socket 数据结构。socket 数据结构实际是

 VFS 索引节点数据结构的一部分,分配新的 socket 数据结构实际就是分配新的 VFS

 索引节点。因为可以和操作普通的文件一样操作套接字,而所有的文件均由一个 VFS

 索引节点代表,因此,为了在套接字上支持文件操作,也必须以 VFS 索引节点来代表

 BSD 套接字。

图 14-4 Linux BSD 套接字数据结构

参照图 14-4,新创建的 BSD socket 数据结构包含有指向地址族专有的套接字例程的

指针,这一指针实际就是 proto_ops 数据结构的地址,proto_ops 数据结构可从 pop

s 向量中检索到。BSD 套接字的套接字类型设置为所请求的 SOCK_STREAM 或 SOCK_DG

RAM 等。然后,内核利用 proto_ops 数据结构中的信息调用地址族专有的创建例程。

之后,内核从当前进程的 fd 向量中分配空闲的文件描述符,该描述符指向的 file 数

据结构被初始化。初始化过程包括将文件操作集指针指向由 BSD 套接字接口支持的 B

SD 文件操作集。所有随后的套接字(文件)操作都将定向到该套接字接口,而套接字

接口则会进一步调用地址族的操作例程,从而将操作传递到底层地址族。

14.4.2 在 INET BSD 套接字上绑定地址

为了监听传入的 Internet 连接请求,每个服务器都需要建立一个 INET BSD 套接字,

并且将自己的地址绑定到该套接字。绑定操作主要在 INET 套接字层中进行,还需要底

层 TCP 层和 IP 层的某些支持。将地址绑定到某个套接字上之后,该套接字就不能用

来进行任何其他的通讯,因此,该 socket 数据结构的状态必须为 TCP_CLOSE。传递到

绑定操作的 sockaddr 数据结构中包含要绑定的 IP 地址,以及一个可选的端口地址。

通常而言,要绑定的地址应该是赋予某个网络设备的 IP 地址,而该网络设备应该支持

 INET 地址族,并且该设备是可用的。利用 ifconfig 命令可查看当前活动的网络接口

。被绑定的 IP 地址保存在 sock 数据结构的 recv_addr 和 saddr 字段中,这两个字

段分别用于哈希查找(下面讲述)和发送用的 IP 地址。端口地址是可选的,如果没有

指定,底层的支持网络会选择一个空闲的端口。因为小于 1024 的端口号作为标准端口

号已经分配给了标准应用程序,因此,底层网络只会分配大于 1024 的端口号,而且,

没有超级用户的权限,进程不能使用小于 1024 的端口号。

当底层网络设备接受到数据包时,它必须将数据包传递到正确的 INET 和 BSD 套接字

以便进行处理,因此,UDP 和 TCP 各自维护一个或多个哈希表,用来查找传入 IP 消

息的地址,并将它们定向到正确的 socket/sock 对。因为 TCP 是面向连接的协议,因

此在处理 TCP 数据包时涉及到的内容比起处理 UDP 数据包来多。

UDP 维护的哈希表(upd_hash 表)包含了已分配的 UDP 端口。这一哈希表由 sock 数

据结构的指针组成,并可基于端口号经哈希函数索引。当 UDP 哈希表远远小于可容忍

的端口数量时(upd_hash 表中只能容纳 128 即 UDP_HTABLE_SIZE 个表项),哈希表

中的某些表项指向由 sock 数据结构链接起来的链表(利用 sock 的 next 指针)。

TCP 维护的哈希表不止一个,因此更加复杂一些。但是,TCP 并不在绑定过程中将绑定

的 sock 数据结构添加到哈希表中,在这一过程中,它仅仅判断所请求的端口号当前是

否正在使用。在监听操作中,该 sock 结构才被添加到 TCP 的哈希表中。

14.4.3 在 INET BSD 套接字上建立连接

创建一个套接字之后,该套接字不仅可以用于监听入站的连接请求,也可以用于建立出

站的连接请求。对类似 UDP 的非连接协议,该套接字操作涉及的内容不多,但对面向

连接的协议,如 TCP 来说,该操作涉及到一个重要的过程:建立两个应用程序之间的

虚拟电路。

出站连接只能建立在处于正确状态的 INET BSD 套接字上,因此,不能建立于已建立连

接的套接字,也不能建立于用于监听入站连接的套接字。也就是说,该 BSD socket 数

据结构的状态必须为 SS_UNCONNECTED。UDP 协议并不在应用程序之间建立虚拟电路,

它所发送的信息均是数据报,有可能不能正确到达目标应用程序,但是,它也支持 BS

D 套接字的 connet 操作。在 UDP INET BSD 套接字上的连接操作只是简单地设置远程

应用程序的地址,即 IP 地址和 IP 端口号。另外,它还建立一个路由表项的缓存,这

样,在该 BSD 套接字上发送的 UDP 数据包不需要再次检查路由数据库(除非该路由非

法),INET sock 数据结构中的 ip_route_cache 指针指向这一缓存路由信息。如果没

有给定寻址信息的话,缓存的路由和 IP 寻址信息将自动用于消息的发送。此后,UDP

 将 sock 的状态设置为 TCP_ESTABLISHED。

对于 TCP BSD 套接字上的连接操作,TCP 必须建立一个 TCP 消息,该消息包含连接信

息并发送到给定的目标 IP。TCP 消息中包含了有关连接的消息、一个唯一的消息起始

顺序号,以及传输和接收窗口的大小等信息。在 TCP 中,所有的消息均是编号的,初

始的顺序号用作第一个消息编号,Linux 选择合理的随机数以避免受到恶意的协议攻击

。由 TCP 连接的某一端发送的每条消息,如果由另一端成功接收到,则接收端发送确

认消息,那些未经确认的消息则需要重新发送。传输和接收窗口的尺寸,是指在未获得

确认的情况下可发送出站的消息数量。最大的消息尺寸根据发出连接请求一端的网络设

备决定,如果接收端的网络设备所支持的最大消息尺寸较小,则该连接将使用较小的最

大消息尺寸。这时,建立出站连接请求的应用程序必须等待目标应用程序的接受或拒绝

响应。如果 TCP sock 正在等待传入消息,则该 sock 结构添加到 tcp_listening_ha

sh 表中,这样,传入的 TCP 消息就可以定向到该 sock 数据结构。当出站连接请求发

送出去之后,TCP 将启动一个定时器,如果在该定时器到期之前目标应用程序没有响应

,则该连接请求最终会因超时而失败。

14.4.4 监听 INET BSD 套接字

当某个套接字被绑定了地址之后,该套接字就可以用来监听专属于该绑定地址的传入连

接。网络应用程序也可以在未绑定地址之前监听套接字,这时,INET 套接字层将利用

空闲的端口编号并自动绑定到该套接字。套接字的监听函数将 socket 的状态改变为 

TCP_LISTEN。

对于 UDP 套接字来说,套接字状态的改变已经足够了,但对 TCP 来说,它现在需要将

套接字的 sock 数据结构添加到两个哈希表中,这两个哈希表分别为 tcp_bound_hash

 和 tcp_listening_hash,这两个哈希表的哈希函数均以端口号为参数。

当接收到某个传入的 TCP 连接请求时,TCP 建立一个新的 sock 数据结构来描述该连

接。当该连接最终被接受时,新的 sock 数据结构将变成该 TCP 连接的内核底半部分

,这时,它要克隆包含连接请求的传入 sk_buff 中的信息,并在监听 sock 数据结构

的 receive_queue 队列中将克隆的信息排队。克隆的 sk_buff 中包含有指向新 sock

 数据结构的指针。

14.4.5 接受连接请求

UDP 不支持连接的概念,接受 INET 套接字的连接请求只应用于 TCP 协议。接受操作

在监听套接字上进行,最终从原始的监听 socket 中克隆一个新的 socket 数据结构。

其过程如下所述。接受操作首先传递到支持协议层,即 INET 中,以便接受任何传入的

连接请求。如果底层的协议,例如 UDP 不支持连接,则 INET 协议层上的接受操作将

失败。相反,接受操作进一步传递到实际的协议,例如 TCP 上。接受操作可以是阻塞

的,也可以是非阻塞的。接受操作为非阻塞的情况下,如果没有可接受的传入连接,则

接受操作将失败,而新建立的 socket 数据结构被抛弃。接受操作为阻塞的情况下,执

行阻塞操作的网络应用程序将添加到等待队列中,并保持挂起直到接收到一个 TCP 连

接请求为至。当连接请求到达之后,包含连接请求的 sk_buff 被丢弃,而由 TCP 建立

的新 sock 数据结构返回到 INET 套接字层,在这里,sock 数据结构和先前建立的新

 socket 数据结构建立链接。而新 socket 的文件描述符(fd)被返回到网络应用程序

,此后,应用程序就可以利用该文件描述符在新建立的 INET BSD 套接字上进行套接字

操作。

14.5 IP 层

14.5.1 套接字缓冲区

图 14-5 Linux 的套接字缓冲区(sk_buff)

网络协议的分层实施也带来了一些问题,其中比较明显的一个问题是,每个协议都需要

在发送数据时添加协议头和协议尾,而在接收数据时去掉协议头和协议尾,因此,当数

据在不同的协议之间传递时,由于每层都要寻找自己特定的协议头和协议尾,从而导致

数据缓冲区的传递非常困难。一种解决办法是在不同的协议层之间复制数据缓冲区,但

这种方法的效率较低,为此,Linux 利用套接字缓冲区(sk_buff)在协议层和网络设

备之间传送数据。sk_buff 包含了一些指针和长度信息,从而可让协议层以标准的函数

或方法对应用程序的数据进行处理。

如图 14-5 所示,每个 sk_buff 均包含一个数据块、四个数据指针以及两个长度字段

。利用四个数据指针,各协议层可操纵和管理套接字缓冲区的数据,这四个指针的用途

在表 14-2 中列出。

表 14-2 sk_buff 的四个数据指针

指针名称

 用途

 

head

 指向内存中数据区的起始地址。sk_buff 和相关数据块在分配之后,该指针的值是固

定的。 

data

 指向协议数据的当前起始地址。该指针的值随当前拥有 sk_buff 的协议层的变化而变

化。 

tail

 指向协议数据的当前结尾地址。和 data 指针一样,该指针的值也随当前拥有 sk_bu

ff 的协议层的变化而变化。 

end

 指向内存中数据区的结尾。和 head 指针一样,sk_buff 被分配之后,该指针的值也

固定不变。 

sk_buff 的两个长度字段,len 和 truesize,分别描述当前协议数据包的长度和数据

缓冲区的实际长度。sk_buff 的处理代码提供了一些标准函数用来为应用程序数据添加

或去除协议头及协议尾,利用这些函数可安全地操纵 sk_buff 结构中的 data 及 tai

l 字段,这些函数如表 14-3 所示。

表 14-3 sk_buff 指针字段的标准操作函数

函数名称

 功能

 

push

 该函数将 data 指针移向数据区的前端并增加 len 字段的值。在发送数据的过程中,

利用该函数可在数据的前端添加数据或协议头。 

pull

 该函数和 push 函数的功能相反,它将 data 指针移向数据区的末尾,并减小 len 字

段的值。该函数可用于从接收到的数据头上移去数据或协议头。 

put

 该函数将 tail 指针移向数据区的末尾并增加 len 字段的值。在发送数据的过程中,

利用该函数可在数据的末端添加数据或协议尾。 

trim

 该函数和 put 函数的功能相反,它将 tail 指针移向数据区的前端,并减小 len 字

段的值。该函数可用于从接收到的数据尾上移去数据或协议尾。 

在 sk_buff 数据结构中,还包含一些用来实现 sk_buff 循环双向链表结构的指针。内

核中也包含一些在上述链表头上或链表尾上添加或删除 sk_buff 结构的一般例程。

14.5.2 接收 IP 数据包

在第十二章中讲到,各设备的 device 数据结构保存在 dev_base 表中,网络设备和其

他设备一样,也在系统的引导初始化过程中在 dev_base 表中注册自身的 device 数据

结构。每个网络设备的 device 数据结构描述了设备自身,并提供一组回调函数,各协

议层可以在需要网络驱动程序完成任务时调用这些函数。一般而言,调用这些函数时需

要指定要传输的数据以及网络设备的地址。当网络设备从网络上接收到数据包时,它必

须将接收到的数据转换为 sk_buff 数据结构。在网络设备接收到这些数据时,经转换

的 sk_buff 数据结构添加到 backlog 队列中排队。当 backlog 队列变得很大时,接

收到的 sk_buff 数据将会被丢弃。当新的 sk_buff 添加到 backlog 队列时,网络的

底半部分被标志为运行就绪状态,从而可让调度程序选择底半处理程序进行处理。

调度程序最终会运行网络的底半处理程序,这时,网络底半处理程序将处理任何等待传

输的数据包,但在这之前,底半处理程序会首先处理 sk_buff 结构的 backlog 队列。

底半处理程序必须确定将接收到的数据包传递到哪个协议层。

在 Linux 进行网络层的初始化时,每个协议要在 ptype_all 链表或 ptype_base 哈希

表中添加 packet_type 数据结构以进行注册。packet_type 数据结构包含协议类型、

指向网络设备的指针、指向协议的接收数据处理例程的指针,以及在链表或哈希表中指

向下一 packet_type 结构的指针等。内核利用 ptype_all 用来检查从任意网络设备上

接收到的数据包,但通常不使用 ptype_all 链表。ptype_base 是一个哈希表,其哈希

函数以协议标识符为参数,内核利用该哈希表判断应当接收传入的网络数据包的协议。

通过检查上述两个表,网络底半处理程序可以找出与传入 sk_buff 的协议类型匹配的

 packet_type 项。匹配的 packet_type 协议可能不止一个(如监视所有的网络流量时

),这种情况下,网络底半处理程序会复制新的 sk_buff,最终,sk_buff 会传递到一

个或多个目标协议的处理例程。

14.5.3 发送 IP 数据包

数据包可以由应用程序生成,也可以网络协议生成(如建立连接时)。不管数据是如何

生成的,网络处理代码必须建立 sk_buff 结构以包含该数据,并且在协议层之间传递

数据时,需要添加不同的协议头和协议尾。

sk_buff 需要传递到某个网络设备上传输,首先,IP 协议需要决定要使用的网络设备

,网络设备的选择依赖于数据包的最佳路由。对于只利用调制解调器和 PPP 协议连接

的计算机来说,路由的选择比较容易。数据包可能发送到本地的回环设备,或 PPP 调

制解调器连接端的网关。但对连接到以太网的计算机来说,路由的选择是比较复杂的,

这是因为同一以太网中可能连接着许多台计算机。

对每个要传输的 IP 数据包,IP 利用路由表解析目标 IP 地址的路由。对每个可从路

由表中找到路由的目标 IP 地址,路由表返回一个 rtable 数据结构描述可使用的路由

。这包括要使用的源 IP 地址、网络设备的 device 数据结构的地址以及预先建立的硬

件头信息。该硬件头信息和网络设备相关,包含了源和目标的物理地址以及其他的介质

信息。对以太网来说,硬件头信息可见图 14-2,而源和目标地址就是以太网卡的物理

地址。硬件头和路由信息一起缓存,这是因为该硬件头信息需要添加到所有利用该路由

传输的 IP 数据包,而硬件头信息的建立需要花费时间。硬件头中的物理地址信息有时

需要利用 ARP 协议获得,这种情况下,传出的数据包要延迟到 ARP 将目标物理地址解

析之后才能发送。将解析到的硬件头信息和路由一起缓存,可避免利用该路由发送的 

IP 数据包再次利用 ARP 解析物理地址。

14.5.4 数据包的分段和重组

每个网络设备有其最大的数据包尺寸,它不能传输和接收大于最大尺寸的数据包。当数

据包的尺寸大于最大允许尺寸时,IP 可以将数据分段成较小的单元以适合于网络设备

能够允许的最大数据包尺寸。为此,IP 协议头中包含有分段字段,由一个标志和分段

偏移量组成。

当 IP 数据包能够传输时,IP 从 IP 路由表中找到发送该 IP 数据包的网络设备,网

络设备对应的 device 数据结构中包含有一个 mtu 字段,该字段描述最大的传输单元

(字节为单位)。如果设备的 mtu 小于等待发送的 IP 数据包的大小,就需要将该 I

P 数据包划分为小的片段(mtu 指定的大小)。每个片段由一个 sk_buff 代表,其中

的 IP 头标记为数据包片断,以及该片断在 IP 数据包中的偏移。最后的数据包被标志

为最后的 IP 片断。如果分段过程中 IP 不能分配 sk_buff,则传输失败。

IP 片断的接收较片断的发送更加复杂一些,因为 IP 片断可能以任意的顺序接收到,

而在重组之前,必须接收到所有的片断。每次接收到 IP 数据包时,IP 要检查是否是

一个分段数据包。当第一次接收到分段的消息时,IP 建立一个新的 ipq 数据结构,并

将它链接到由等待重组的 IP 片断形成的 ipqueue 链表中。随着其他 IP 片断的接收

,IP 找到正确的 ipq 数据结构,同时建立新的 ipfrag 数据结构描述该片断。每个 

ipq 数据结构中包含有其源和目标IP 地址、高层协议的标识符以及该 IP 帧的标识符

,从而唯一描述了一个分段的 IP 接收帧。当所有的片断接收到之后,它们被组合成单

一的 sk_buff 并传递到上一级协议层处理。如果定时器在所有的片断到达之前到期,

ipq 数据结构和 ipfrag 被丢弃,并假定消息已经在传输中丢失,这时,高层协议需要

请求源主机重新发送丢失的消息。

14.6 地址解析协议

地址解析协议的任务,就是将 IP 地址翻译为物理的硬件地址,例如以太网地址。在 

IP 将数据以 sk_buff 的形式传递到网络设备传输时,IP 需要进行上述翻译。IP 首先

检查设备是否需要硬件头,如果需要,则继续检查数据包的硬件头是否需要重新建立。

如前所述,Linux 利用缓存以避免频繁重建硬件头信息。如果硬件头需要重新建立,则

调用设备特有的硬件头重建例程。所有的以太网设备使用相同的一般性硬件头重建例程

,而这些例程实际使用 ARP 服务将目标 IP 地址转换为物理地址。

ARP 协议本身是非常简单的,它包含两种消息类型:ARP 请求以及 ARP 回应。ARP 请

求包含了需要转换的 IP 地址,而 ARP 回应则包含要转换的 IP 地址对应的硬件地址

。ARP 请求广播到网络上,因此,对于以太网来讲,所有连接到以太网的计算机均会看

到该 ARP 请求。最终,拥有该 IP 地址的计算机将响应该 ARP 请求,从而生成包含物

理地址的 ARP 回应消息。

Linux 中的 ARP 协议层建立有一个 arp_table 数据结构表,其中的每个表项描述一个

 IP 地址到物理地址的翻译。这些表项在需要翻译 IP 地址时建立,而在表项失效时删

除。每个 arp_table 数据结构中包含的字段如表 14-4 所示。

表 14-4 arp_table 数据结构中的字段

字段

 功能

 

最近使用时间

 该 ARP 表项最近使用过的时间。

 

最近更新时间

 该 ARP 表项最近更新过的时间。

 

标志

 表述该表项状态的字段,例如表项是否完整等。

 

IP 地址

 该表项所描述的 IP 地址。

 

硬件地址

 经翻译的硬件地址。

 

硬件头

 指向缓存硬件头的指针。

 

定时器

 这是 timer_list 的一个表项,用来跟踪 ARP 请求的响应超时。

 

重试次数

 该 ARP 请求重试过的次数。

 

sk_buff 队列

 等待解析该 IP 地址的 sk_buff 项链表。

 

ARP 表由一个指针表(即 arp_tables 向量)组成,而这些指针指向 arp_table 项形

成的链。这些表项是经过缓存的,从而可提高表项的访问速度。利用其 IP 地址的最后

两个字节可生成访问上述指针表的索引,最终可从表项链中查找到正确的 arp_table 

项。如前所述,Linux 还对预先建立的硬件头进行缓存,实际是将 arp_table 缓存在

 hh_cache 数据结构中。

当请求进行某个 IP 地址的翻译,而当前没有对应的 arp_table 项时,ARP 必须发送

 ARP 请求消息。它首先在表中建立一个新的 arp_talbe 表项,然后将包含网络数据包

的 sk_buff 在 arp_table 表项的 sk_buff 队列中排队。对于这些排队等待翻译 IP 

地址的 sk_buff 来说,传输该 sk_buff 的协议层将接收到通知,说明需要等待以应付

 ARP 地址的翻译。UDP 协议不关心丢失的数据包,而 TCP 却需要在建立好的 TCP 链

路上传输。如果拥有该 IP 地址的计算机返回了它的硬件地址,arp_table 就被标志为

完整表项,而排队的 sk_buff 将从该队列中移走以继续传输。硬件地址被写入每个 s

k_buff 硬件头。

ARP 协议层也必须响应指定其 IP 地址的 ARP 请求。ARP 协议层通过生成一个 packe

t_type 数据结构而注册自己的协议类型,这样,网络设备接收到的 ARP 数据包均会被

传递到 ARP 协议层处理。在响应 ARP 请求时,它利用保存在接收设备的 device 数据

结构中的硬件地址生成 ARP 回应消息。

网络拓扑结构可能随时间而改变,而 IP 地址也有可能赋予不同的网络设备。例如,在

 DHCP 系统中,网络设备的 IP 地址是动态分配的。为了保证 ARP 表中包含最新的表

项, ARP 运行一个周期的定时器检查所有的 arp_table 是否过期。但这时不能移去包

含缓存硬件头的表项,删除这些由其他数据结构依赖的表项是非常危险的。有些 arp_

table 表项是永久性的,因此它们是不会被释放的。系统不允许 ARP 表增长得太大,

因为每个 arp_table 均要花费一定的内核内存。当需要分配新的表项,而 ARP 表已经

到达其最大尺寸时,系统搜索最旧的表项,通过删除这些旧表项而减小 ARP 表的尺寸



14.7 IP 路由

IP 的路由功能决定将定向于特定 IP 地址的 IP 数据包发送到哪里。在传输 IP 数据

包时,可能会有多种选择。目标地址能否到达?如果能够到达,应利用哪个网络设备发

送?如果有多个网络设备可用于发送数据包,那么哪一个网络设备用于传输更好一些?

IP 路由数据库中维护的信息可回答上面的这些问题。系统中包含有两个数据库,最重

要的是转发信息数据库,其中包含了一个详尽的、由已知的 IP 地址和对应的最佳路由

组成的清单。一个小的、更加快速的数据库,即路由缓存,可用于快速查找目标 IP 的

路由。和所有的缓存一样,其中只包含一些最频繁访问的路由,这些路由信息是从转发

信息数据库中派生出来的。

路由信息通过 BSD 套接字接口的 IOCTL 请求执行添加和删除操作。这些操作最终传递

到协议处理。INET 协议层只允许具备超级用户权限的进程添加和删除 IP 路由。这些

路由信息可能是固定的,也有可能是随时间变化的动态路由。绝大多数系统使用固定路

由,除非该系统作为路由器使用。路由器运行路由协议,路由协议可经常性地检查所有

已知 IP 地址的路由可用性。非路由器系统称为末端系统。路由协议以守护进程(gat

ed)的方式实现,也通过 BSD 套接字接口的 IOCTL 添加和删除路由。

14.7.1 路由缓存

在查找一个 IP 路由时,首先检查路由缓存。如果路由缓存中没有匹配的路由,系统就

在转发信息数据库中查找路由。如果在转发信息数据库中也找不到路由,IP 数据包就

不能成功发送,而应用程序会接收到发送失败的通知。如果路由不在路由缓存,而在转

发信息数据库中,则生成新的缓存项并添加到缓存路由中。路由缓存是一个哈希表 (

ip_rt_hash_table),其中包含的指针指向由 rtable 数据结构形成的链。路由表的索

引通过基于 IP 地址的两个最低字节的哈希函数计算出来。这两个字节是不同目标 IP

 地址中最可能不同的部分,从而可提供最大的哈希值分布。每个 rtable 项包含路由

信息,即目标 IP 地址、到达该目标 IP 地址而使用的网络 device 结构地址,以及可

用来发送的最大消息尺寸等信息。其中也包含一个引用计数、使用计数以及最后使用过

的时间戳(以 jiffies 度量)。每次使用该路由时,引用计数增加以表明使用该路由

的网络连接数量;当应用程序停止使用该路由时,该计数减少。每次查找该路由并且在

哈希项指向的 rtable 项链中定位时,使用计数增加。路由缓存中所有项的最近使用时

间戳会周期性地检查,以查看 rtable 是否太旧,如果某个路由最近尚未使用,则从路

由缓存中!丢弃该 rtable 表项。如果路由一直保留在路由缓存中,则系统对它们进行

排序以便将使用得最多的表项排列在哈希链的前面,这样,当查找该路由时,能够快速

找到。

14.7.2 转发信息数据库

如图 14-6 所示,是系统中的转发信息数据库结构。该数据库是一个相当复杂的数据结

构,虽然该结构的组织相当有效,但仍然不能算是一个快速的数据库。如果对每一个要

传输 IP 数据包,均要从该数据库中查找路由,则系统性能将非常差。利用路由缓存则

可以避免重复地在转发信息数据库中查找路由。

每个 IP 子网由一个 fib_zone 数据结构代表。包含在 fib_zones 哈希表中的指针指

向所有的 fib_zone,该哈希表的哈希索引由 IP 子网地址生成。到达同一子网的所有

路由由一对 fib_node 和 fib_info 数据结构描述,这两个数据结构排列在 fib_zone

 数据结构中的fz_list 上。当该子网中的路由数量变大时,则生成一个哈希表以便快

速找到 fib_node 数据结构。

对于相同的 IP 子网,可能存在多个路由,而这些路由可通过多个网关中的其中一个网

关。IP 路由层不能允许到某个子网的多个路由使用相同的网关,也就是说,如果到某

个子网的路由有多个,则必须保证每个路由使用不同的网关。和每个路由相关的是它的

 metric,它用来度量路由的优劣。从本质上讲,路由的 metric 指到达指定子网前,

该路由要经过的 IP 子网数量,路由的 metric 值越高,该路由越坏。

图 14-6 转发信息数据库结构

14.8 相关系统工具和系统调用

由于篇幅的限制,本书不具体讲解和网络相关的系统工具,读者可参阅相关著作。本书

第三部分中有一些专题专门讨论 Linux 的网络应用。

表 14-5 简要列出了和网络(套接字)相关的系统调用。标志列中各字母的意义可参见

表 10-1 的说明。

表 14-5 相关系统调用

系统调用

 说明

 标志

 

accept

 接收套接字上连接请求

 m!c

 

bind

 在套接字绑定地址信息

 m!c

 

connet

 连接两个套接字

 m!c

 

getpeername

 获取已连接端套接字的地址

 m!c

 

getsockname

 获取套接字的地址

 m!c

 

getsockopt

 获取套接字上的设置选项

 m!c

 

listen

 监听套接字连接

 m!c

 

recv

 从已连接套接字上接收消息

 m!c

 

recvfrom

 从套接字上接收消息

 m!c

 

send

 向已连接的套接字发送消息

 m!c

 

sendto

 向套接字发送消息

 m!c

 

setdomainname

 设置系统的域名

 mc

 

sethostid

 设置唯一的主机标识符

 mc

 

sethostname

 设置系统的主机名称

 mc

 

setsockopt

 修改套接字选项

 mc

 

shutdown

 关闭套接字

 m!c

 

socket

 建立套接字通讯的端点

 m!c

 

socketcall

 套接字调用多路复用转换器

 -

 

socketpair

 建立两个连接套接字

 m!c

 

 

 

 

 

 

 

 

 

 

 

 

 


--
☆ 来源:.哈工大紫丁香 bbs.hit.edu.cn.[FROM: bin@mtlab.hit.edu.cn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:213.762毫秒