目录, 显示框架, 无框架

第10章
网络


网络和 Linux 这两个术语几乎是同义词。从某种意义上说,Linux 是互联网或万维网 (WWW) 的产物。其开发者和用户使用 Web 交换信息、想法、代码,而 Linux 本身通常用于支持组织的网络需求。本章描述了 Linux 如何支持统称为 TCP/IP 的网络协议。

TCP/IP 协议旨在支持连接到 ARPANET(由美国政府资助的美国研究网络)的计算机之间的通信。ARPANET 开创了诸如分组交换和协议分层之类的网络概念,其中一个协议使用另一个协议的服务。ARPANET 于 1988 年退役,但其继任者(NSF1 NET 和 Internet)甚至变得更大。现在被称为万维网的网络是从 ARPANET 发展而来的,并且它本身也受 TCP/IP 协议的支持。Unix TM 在 ARPANET 上被广泛使用,并且发布的第一个网络版 Unix TM 是 4.3 BSD。Linux 的网络实现以 4.3 BSD 为模型,因为它支持 BSD 套接字(带有一些扩展)和全范围的 TCP/IP 网络。选择此编程接口是因为它很受欢迎,并且有助于应用程序在 Linux 和其他 Unix TM 平台之间移植。

10.1  TCP/IP 网络概述

本节概述了 TCP/IP 网络的主要原则。它并非详尽的描述,为此,我建议您阅读 . 在 IP 网络中,每台机器都分配有一个 IP 地址,这是一个唯一标识机器的 32 位数字。万维网是一个非常大且不断增长的 IP 网络,并且连接到它的每台机器都必须分配有一个唯一的 IP 地址。IP 地址由四个数字分隔点表示,例如,16.42.0.9。此 IP 地址实际上分为两部分,网络地址和主机地址。这些部分的大小可能有所不同(存在几种类型的 IP 地址),但是以16.42.0.9为例,网络地址将是16.42,主机地址0.9。主机地址进一步细分为子网主机地址。同样,以16.42.0.9为例,子网地址将是16.42.0,主机地址为 16.42.0.9。IP 地址的这种细分允许组织细分其网络。例如,16.42可能是 ACME 计算机公司的网络地址;16.42.0将是子网016.42.1将是子网1。这些子网可能位于不同的建筑物中,可能通过租用的电话线甚至微波链路连接。IP 地址由网络管理员分配,拥有 IP 子网是分发网络管理的良好方式。IP 子网管理员可以自由地在其 IP 子网中分配 IP 地址。

通常,IP 地址很难记住。名称更容易记住。linux.acme.com16.42.0.9更容易记住,但是必须存在某种机制将网络名称转换为 IP 地址。这些名称可以静态地在/etc/hosts文件中指定,或者 Linux 可以要求分布式域名服务器(DNS 服务器)为其解析该名称。在这种情况下,本地主机必须知道一个或多个 DNS 服务器的 IP 地址,这些地址在/etc/resolv.conf.

中指定。无论何时连接到另一台计算机,例如阅读网页时,都会使用其 IP 地址与该计算机交换数据。此数据包含在 IP 数据包中,每个数据包都有一个 IP 标头,其中包含源和目标计算机的 IP 地址、校验和以及其他有用的信息。校验和是从 IP 数据包中的数据派生的,并允许 IP 数据包的接收者判断 IP 数据包在传输过程中是否损坏,可能是由于噪声电话线造成的。应用程序传输的数据可能已被分解为更小的数据包,这些数据包更容易处理。IP 数据包的大小取决于连接介质;以太网数据包通常比 PPP 数据包大。目标主机必须在将数据提供给接收应用程序之前重新组装数据包。如果您通过速度较慢的串行链接访问包含大量图形图像的网页,则可以直观地看到数据的这种分段和重新组装。

连接到同一 IP 子网的主机可以直接相互发送 IP 数据包,所有其他 IP 数据包都将发送到特殊主机,即网关。网关(或路由器)连接到多个 IP 子网,它们会将在一个子网上接收到的但要发送到另一个子网的 IP 数据包转发。例如,如果子网16.42.1.016.42.0.0通过网关连接在一起,那么从子网0发送到子网1的任何数据包都必须定向到网关,以便它可以路由它们。本地主机建立路由表,使其可以将 IP 数据包路由到正确的计算机。对于每个 IP 目标,路由表中都有一条条目,告诉 Linux 将 IP 数据包发送到哪台主机才能到达其目标。这些路由表是动态的,并且随着应用程序使用网络以及网络拓扑结构的变化而随时间变化。


图 10.1:TCP/IP 协议层

IP 协议是一个传输层,其他协议可以使用它来承载其数据。传输控制协议 (TCP) 是一种可靠的端到端协议,它使用 IP 来发送和接收其自身的数据包。就像 IP 数据包有自己的标头一样,TCP 也有自己的标头。TCP 是一种基于连接的协议,其中两个网络应用程序通过单个虚拟连接连接,即使它们之间可能存在许多子网、网关和路由器。TCP 可靠地在两个应用程序之间传输和接收数据,并保证不会丢失或重复数据。当 TCP 使用 IP 传输其数据包时,IP 数据包中包含的数据就是 TCP 数据包本身。每个通信主机上的 IP 层负责发送和接收 IP 数据包。用户数据报协议 (UDP) 也使用 IP 层来传输其数据包,与 TCP 不同,UDP 不是可靠的协议,而是提供数据报服务。其他协议对 IP 的这种使用意味着,当接收到 IP 数据包时,接收 IP 层必须知道将此 IP 数据包中包含的数据提供给哪个上层协议。为了方便这一点,每个 IP 数据包标头都有一个字节,其中包含协议标识符。当 TCP 要求 IP 层传输 IP 数据包时,该 IP 数据包的标头声明它包含 TCP 数据包。接收 IP 层使用该协议标识符来决定将接收到的数据传递给哪一层,在本例中为 TCP 层。当应用程序通过 TCP/IP 通信时,它们不仅必须指定目标的 IP 地址,还必须指定应用程序的端口地址。端口地址唯一标识一个应用程序,并且标准网络应用程序使用标准端口地址;例如,Web 服务器使用端口 80。这些注册的端口地址可以在/etc/services.

中看到。这种协议分层不会随着 TCP、UDP 和 IP 而停止。IP 协议层本身使用许多不同的物理介质将 IP 数据包传输到其他 IP 主机。这些介质本身可能会添加自己的协议标头。一个这样的例子是以太网层,但是 PPP 和 SLIP 也是其他例子。以太网网络允许许多主机同时连接到单根物理电缆。每个传输的以太网帧都可以被所有连接的主机看到,因此每个以太网设备都有一个唯一的地址。发送到该地址的任何以太网帧都将被寻址的主机接收,但会被连接到网络的所有其他主机忽略。这些唯一的地址在制造时内置于每个以太网设备中,并且通常保存在以太网卡上的 SROM2 中。以太网地址长 6 个字节,一个例子是08-00-2b-00-49-A4。某些以太网地址保留用于多播目的,并且发送到这些目标地址的以太网帧将被网络上的所有主机接收。由于以太网帧可以承载许多不同的协议(作为数据),因此与 IP 数据包一样,它们在其标头中包含协议标识符。这允许以太网层正确接收 IP 数据包并将其传递到 IP 层。

为了通过诸如以太网这样的多连接协议发送 IP 数据包,IP 层必须找到 IP 主机的以太网地址。这是因为 IP 地址仅仅是一个寻址概念,而以太网设备本身拥有自己的物理地址。另一方面,IP 地址可以由网络管理员随意分配和重新分配,但网络硬件仅响应带有自身物理地址或所有机器必须接收的特殊多播地址的以太网帧。Linux 使用地址解析协议(ARP)来允许机器将 IP 地址转换为真实的硬件地址,例如以太网地址。希望知道与 IP 地址关联的硬件地址的主机,通过将其发送到多播地址,将包含其希望转换为网络上所有节点的 IP 地址的 ARP 请求数据包。拥有该 IP 地址的目标主机,会回复一个包含其物理硬件地址的 ARP 答复。ARP 不仅仅限于以太网设备,它还可以解析其他物理介质的 IP 地址,例如 FDDI。那些无法进行 ARP 的网络设备会被标记,以防止 Linux 尝试进行 ARP。还有反向功能,反向 ARP 或 RARP,可以将物理网络地址转换为 IP 地址。网关使用此功能代表远程网络中的 IP 地址响应 ARP 请求。

10.2  Linux TCP/IP 网络层


图 10.2: Linux 网络层

就像网络协议本身一样,图  10.2 显示 Linux 将互联网协议地址族实现为一系列连接的软件层。BSD 套接字由仅关注 BSD 套接字的通用套接字管理软件支持。支持它的是 INET 套接字层,它管理基于 IP 的协议 TCP 和 UDP 的通信端点。UDP(用户数据报协议)是一种无连接协议,而 TCP(传输控制协议)是一种可靠的端到端协议。当 UDP 数据包传输时,Linux 既不知道也不关心它们是否安全到达目的地。TCP 数据包被编号,并且 TCP 连接的两端都确保正确接收传输的数据。IP 层包含实现互联网协议的代码。此代码将 IP 标头添加到传输的数据,并了解如何将传入的 IP 数据包路由到 TCP 或 UDP 层。在 IP 层下,支持 Linux 所有网络的是网络设备,例如 PPP 和以太网。网络设备并不总是代表物理设备;有些像环回设备一样纯粹是软件设备。与通过以下方式创建的标准 Linux 设备不同mknod命令,只有当底层软件找到并初始化它们时,网络设备才会出现。你只会看到/dev/eth0当你构建一个内核并在其中包含适当的以太网设备驱动程序时。ARP 协议位于 IP 层和支持地址 ARP 的协议之间。

10.3  BSD 套接字接口

这是一个通用接口,不仅支持各种形式的网络,而且还是进程间通信机制。一个套接字描述了通信链路的一端,两个通信进程将各自拥有一个套接字,用于描述它们之间通信链路的端点。套接字可以被认为是管道的一种特殊情况,但与管道不同,套接字对其可以包含的数据量没有限制。Linux 支持几种套接字类,这些类被称为地址族。这是因为每个类都有其自己的通信寻址方法。Linux 支持以下套接字地址族或域

UNIXUnix 域套接字,
INET互联网地址族支持通过以下方式进行通信
TCP/IP 协议
AX25业余无线电 X25
IPXNovell IPX
APPLETALKAppletalk DDP
X25X25

有几种套接字类型,它们代表支持连接的服务类型。并非所有地址族都支持所有类型的服务。Linux BSD 套接字支持多种套接字类型

流式
这些套接字提供可靠的双向顺序数据流,并保证数据在传输过程中不会丢失、损坏或重复。流式套接字由互联网 (INET) 地址族的 TCP 协议支持。
数据报
这些套接字也提供双向数据传输,但与流式套接字不同,不能保证消息会到达。即使它们确实到达,也不能保证它们会按顺序到达,甚至不会被复制或损坏。这种类型的套接字由互联网地址族的 UDP 协议支持。
原始
这允许进程直接(因此是“原始”)访问底层协议。例如,可以打开一个指向以太网设备的原始套接字,并查看原始 IP 数据流量。
可靠传输消息
这些非常像数据报套接字,但保证数据能够到达。
序列化数据包
这些与流式套接字类似,只不过数据包大小是固定的。
数据包
这不是标准的 BSD 套接字类型,它是 Linux 特定的扩展,允许进程直接在设备级别访问数据包。

使用套接字进行通信的进程使用客户端-服务器模型。服务器提供服务,客户端使用该服务。一个例子是 Web 服务器,它提供网页,以及 Web 客户端或浏览器,它读取这些页面。使用套接字的服务器首先创建一个套接字,然后将名称绑定到该套接字。此名称的格式取决于套接字的地址族,实际上,它是服务器的本地地址。套接字的名称或地址使用sockaddr数据结构指定。INET 套接字将绑定到它一个 IP 端口地址。注册的端口号可以在/etc/services; 中看到,例如,Web 服务器的端口号是 80。将地址绑定到套接字后,服务器会侦听传入的连接请求,指定绑定的地址。请求的发起者(客户端)创建一个套接字并在其上发出连接请求,指定服务器的目标地址。对于 INET 套接字,服务器的地址是其 IP 地址和端口号。这些传入的请求必须通过各种协议层,然后在服务器的侦听套接字上等待。一旦服务器收到传入的请求,它会接受或拒绝它。如果要接受传入的请求,服务器必须创建一个新的套接字来接受它。一旦套接字被用于侦听传入的连接请求,它就不能用于支持连接。建立连接后,两端都可以自由发送和接收数据。最后,当不再需要连接时,可以将其关闭。要小心,以确保正确处理传输中的数据包。

BSD 套接字上的操作的确切含义取决于其底层地址族。设置 TCP/IP 连接与设置业余无线电 X.25 连接非常不同。与虚拟文件系统一样,Linux 使用 BSD 套接字层抽象套接字接口,BSD 套接字层负责应用程序的 BSD 套接字接口,该接口又由独立的地址族特定软件支持。在内核初始化时,内置于内核中的地址族会向 BSD 套接字接口注册自身。稍后,当应用程序创建和使用 BSD 套接字时,将在 BSD 套接字及其支持的地址族之间建立关联。这种关联是通过交叉链接数据结构和地址族特定支持例程表建立的。例如,有一个地址族特定的套接字创建例程,当应用程序创建新套接字时,BSD 套接字接口会使用该例程。

配置内核时,许多地址族和协议都构建到协议向量中。每个都由其名称表示,例如“INET”及其初始化例程的地址。在引导时初始化套接字接口时,会调用每个协议的初始化例程。对于套接字地址族,这导致它们注册一组协议操作。这是一组例程,每个例程都执行特定于该地址族的特定操作。注册的协议操作保存在pops向量中,它是指向proto_ops数据结构的指针的向量。

proto_ops数据结构包含地址族类型和指向特定于特定地址族的套接字操作例程的一组指针。pops向量由地址族标识符索引,例如 Internet 地址族标识符 (AF_INET 为 2)。


图 10.3: Linux BSD 套接字数据结构

10.4  INET 套接字层

INET 套接字层支持包含 TCP/IP 协议的互联网地址族。如上所述,这些协议是分层的,一个协议使用另一个协议的服务。Linux 的 TCP/IP 代码和数据结构反映了这种分层。它与 BSD 套接字层的接口是通过它在网络初始化期间向 BSD 套接字层注册的一组 Internet 地址族套接字操作。这些保存在pops向量中,以及其他注册的地址族。BSD 套接字层从注册的 INETproto_ops数据结构调用 INET 层套接字支持例程,以为其执行工作。例如,BSD 套接字创建请求将地址族作为 INET 将使用底层 INET 套接字创建函数。BSD 套接字层将socket数据结构,表示 BSD 套接字,传递给每个操作中的 INET 层。INET 套接字层使用其自己的数据结构,而不是使 BSDsocket混乱 TCP/IP 特定信息它链接到 BSDsocket数据结构。 这种链接可以在图 10.3中看到。 它将数据结构链接到 BSDsocket数据结构,使用数据指针,位于 BSDsocket中。 这意味着后续的 INET 套接字调用可以轻松地检索数据结构。数据结构的协议操作指针也在创建时设置,它取决于所请求的协议。 如果请求 TCP,那么数据结构的协议操作指针将指向 TCP 连接所需的 TCP 协议操作集。

10.4.1  创建 BSD 套接字

用于创建新套接字的系统调用传递其地址族、套接字类型和协议的标识符。

首先,请求的地址族用于搜索pops向量以寻找匹配的地址族。 某个特定的地址族可能作为内核模块实现,在这种情况下,kerneld守护程序必须先加载该模块,然后才能继续。 分配一个新的socket数据结构来表示 BSD 套接字。 实际上,socket数据结构是 VFS 的物理组成部分inode数据结构,分配套接字实际上意味着分配 VFSinode。 除非你考虑到套接字的操作方式与普通文件完全相同,否则这似乎很奇怪。 由于所有文件都由 VFS 表示inode数据结构,为了支持文件操作,BSD 套接字也必须由 VFS 表示inode数据结构。

新创建的 BSDsocket数据结构包含指向地址族特定套接字例程的指针,该指针设置为从proto_ops向量中检索到的pops数据结构。 其类型设置为所请求的套接字类型; SOCK_STREAM、SOCK_DGRAM 等之一。 使用保存在proto_ops数据结构。

中地址调用地址族特定创建例程。从当前进程的fd向量中分配一个空闲的文件描述符,并初始化它指向的数据结构。 这包括将文件操作指针设置为指向 BSD 套接字接口支持的 BSD 套接字文件操作集。 任何未来的操作都将被定向到套接字接口,它将依次调用其地址族操作例程,将其传递给支持的地址族。

10.4.2  将地址绑定到 INET BSD 套接字

为了能够侦听传入的互联网连接请求,每个服务器必须创建一个 INET BSD 套接字并将其地址绑定到它。 绑定操作主要在 INET 套接字层中处理,并得到底层 TCP 和 UDP 协议层的支持。 绑定了地址的套接字不能用于任何其他通信。 这意味着socket的状态必须是TCP_CLOSEsockaddr传递给绑定操作的参数包含要绑定的 IP 地址,以及可选的端口号。 通常,绑定的 IP 地址是被分配给支持 INET 地址族的网络设备,且其接口已启动并可使用的地址。 您可以使用ifconfig命令查看系统中当前处于活动状态的网络接口。 IP 地址也可以是全 1 或全 0 的 IP 广播地址。 这些是特殊的地址,意思是“发送给所有人”3。 如果机器充当透明代理或防火墙,IP 地址也可以指定为任何 IP 地址,但只有具有超级用户权限的进程才能绑定到任何 IP 地址。 绑定的 IP 地址保存在数据结构的recv_addrsaddr字段中。 这些字段分别用于哈希查找和作为发送 IP 地址。 端口号是可选的,如果未指定,则会向支持的网络请求一个空闲端口。 按照惯例,端口号小于 1024 的端口不能被没有超级用户权限的进程使用。 如果底层网络确实分配了端口号,它总是分配大于 1024 的端口号。

当底层网络设备接收到数据包时,它们必须被路由到正确的 INET 和 BSD 套接字,以便可以对其进行处理。 因此,UDP 和 TCP 维护哈希表,用于查找传入 IP 消息中的地址,并将它们定向到正确的socket/对。 TCP 是一种面向连接的协议,因此处理 TCP 数据包比处理 UDP 数据包涉及更多信息。

UDP 维护一个已分配 UDP 端口的哈希表,即udp_hash表。 它由指向数据结构的指针组成,这些指针通过基于端口号的哈希函数进行索引。 由于 UDP 哈希表比允许的端口号数量小得多(udp_hash仅为 128 或UDP_HTABLE_SIZE个条目),因此表中的某些条目指向使用每个'snext指针链接在一起的

数据结构的链。数据结构添加到其哈希表中,它只是检查所请求的端口号当前是否正在使用。数据结构在 *listen* 操作期间添加到 TCP 的哈希表中。

审核说明 输入的路由呢?

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

一旦创建了套接字,并且假设它没有被用于侦听入站连接请求,它就可以用于发出出站连接请求。 对于像 UDP 这样的无连接协议,此套接字操作并没有做太多事情,但对于像 TCP 这样的面向连接的协议,它涉及在两个应用程序之间构建一个虚拟电路。

只能在处于正确状态的 INET BSD 套接字上发出出站连接; 也就是说,一个尚未建立连接且未被用于侦听入站连接的套接字。 这意味着 BSDsocket数据结构必须处于状态SS_UNCONNECTED。 UDP 协议不在应用程序之间建立虚拟连接,发送的任何消息都是数据报,一次性消息,可能到达或无法到达其目的地。 但是,它确实支持 *connect* BSD 套接字操作。 在 UDP INET BSD 套接字上的连接操作只是设置远程应用程序的地址; 它的 IP 地址和 IP 端口号。 此外,它还设置路由表条目的缓存,以便在此 BSD 套接字上发送的 UDP 数据包不需要再次检查路由数据库(除非此路由无效)。 缓存的路由信息从 INETip_route_cache指针指向数据结构。 如果未给出寻址信息,则此缓存的路由和 IP 寻址信息将自动用于使用此 BSD 套接字发送的消息。 UDP 将的状态更改为TCP_ESTABLISHED.

。 对于 TCP BSD 套接字上的连接操作,TCP 必须构建一个包含连接信息的 TCP 消息,并将其发送到给定的 IP 目的地。 TCP 消息包含有关连接的信息、唯一的起始消息序列号、启动主机可以管理的最大大小的消息、传输和接收窗口大小等等。 在 TCP 中,所有消息都被编号,初始序列号用作第一个消息号。 Linux 选择一个相当随机的值来避免恶意协议攻击。 TCP 连接的一端发送并被另一端成功接收的每条消息都会被确认为已成功且未损坏地到达。 未确认的消息将被重新传输。 传输和接收窗口大小是可以存在的未发送确认的未完成消息的数量。 最大消息大小基于在请求的启动端使用的网络设备。 如果接收端的网络设备支持较小的最大消息大小,则连接将使用两者中的最小值。 发出出站 TCP 连接请求的应用程序现在必须等待来自目标应用程序的响应以接受或拒绝连接请求。 由于 TCP现在期望传入的消息,因此它被添加到tcp_listening_hash中,以便可以将传入的 TCP 消息定向到此数据结构。 TCP 还启动计时器,以便如果在目标应用程序没有响应请求的情况下,可以使出站连接请求超时。

10.4.4  侦听 INET BSD 套接字

一旦套接字绑定了一个地址,它就可以侦听传入的连接请求,指定绑定的地址。 网络应用程序可以在侦听套接字之前不先将其绑定到地址; 在这种情况下,INET 套接字层会找到一个未使用的端口号(对于此协议),并自动将其绑定到套接字。 侦听套接字函数将套接字移动到状态TCP_LISTEN,并执行允许传入连接所需的任何网络特定工作。

对于 UDP 套接字,更改套接字的状态就足够了,但是 TCP 现在将套接字的数据结构添加到两个哈希表中,因为它现在处于活动状态。 它们是tcp_bound_hash表和tcp_listening_hash。 两者都通过基于 IP 端口号的哈希函数进行索引。

每当收到活动侦听套接字的传入 TCP 连接请求时,TCP 都会构建一个新的数据结构来表示它。 此数据结构将成为 TCP 连接的下半部分,当它最终被接受时。 它还会克隆传入的sk_buff,其中包含连接请求,并将其排队到侦听receive_queue数据结构。 克隆sk_buff包含指向新创建的数据结构。

10.4.5  接受连接请求

UDP 不支持连接的概念,接受 INET 套接字连接请求仅适用于 TCP 协议,因为侦听套接字上的 accept 操作会导致从原始侦听socket克隆一个新的socket。 然后,将 accept 操作传递给支持的协议层,在本例中为 INET,以接受任何传入的连接请求。 如果底层协议(例如 UDP)不支持连接,则 INET 协议层将使 accept 操作失败。 否则,accept 操作将传递给实际协议,在本例中为 TCP。 accept 操作可以是阻塞的也可以是非阻塞的。 在非阻塞情况下,如果没有要接受的传入连接,accept 操作将失败,并且新创建的socket数据结构将被丢弃。 在阻塞情况下,执行 accept 操作的网络应用程序将被添加到等待队列中,然后挂起,直到收到 TCP 连接请求。 一旦收到连接请求,包含请求的sk_buff将被丢弃,并且数据结构返回到INET套接字层,并在此链接到新的socket之前创建的数据结构。新文件的文件描述符(从当前进程的)编号socket返回到网络应用程序,然后应用程序可以使用该文件描述符在新创建的INET BSD套接字上执行套接字操作。

10.5  IP层

10.5.1  套接字缓冲区

拥有多个网络协议层,每一层都使用另一层提供的服务,存在的问题之一是每个协议都需要在数据传输时向数据添加协议头和尾,并在处理接收到的数据时删除它们。这使得协议之间传递数据缓冲区变得困难,因为每一层都需要找到其特定的协议头和尾。一种解决方案是在每一层复制缓冲区,但这样效率很低。相反,Linux使用套接字缓冲区或sk_buffs在协议层和网络设备驱动程序之间传递数据。sk_buffs包含指针和长度字段,允许每个协议层通过标准函数或“方法”来操作应用程序数据。


图10.4: 套接字缓冲区(sk_buff)

图  10.4 显示了sk_buff数据结构;每个sk_buff都有一个与之关联的数据块。该sk_buff有四个数据指针,用于操作和管理套接字缓冲区的数据

head
指向内存中数据区域的开始。这是在sk_buff及其关联的数据块被分配时固定的,
数据
指向协议数据的当前开始位置。此指针根据当前拥有sk_buff,
tail
的协议层而变化,指向协议数据的当前结束位置。同样,此指针根据拥有它的协议层而变化,
end
指向内存中数据区域的末尾。这是在sk_buff被分配时固定的。

有两个长度字段lentruesize,它们分别描述了当前协议数据包的长度和数据缓冲区的总大小。该sk_buff处理代码提供了用于向应用程序数据添加和删除协议头和尾的标准机制。这些安全地操作数据, taillen中的sk_buff:

push
这会将数据指针朝着数据区域的开始移动,并递增len字段。这用于将数据或协议头添加到要传输的数据的开头,

pull
这会将数据从开头向数据区域的末尾移动指针,并递减len字段。这用于从已接收数据的开头删除数据或协议头,

put
这会将tail指针朝着数据区域的末尾移动,并递增len字段。这用于将数据或协议信息添加到要传输的数据的末尾,

trim
这会将tail指针朝着数据区域的开始移动,并递减len字段。这用于从接收到的数据包中删除数据或协议尾。

sk_buff数据结构还包含一些指针,这些指针在处理期间存储在sk_buff的双向链表循环列表中使用。有通用的sk_buff例程用于添加sk_buffs到这些列表的前面和后面,以及用于删除它们。

10.5.2  接收IP数据包

第  dd-chapter 章描述了Linux的网络驱动程序是如何构建到内核中并初始化的。这导致一系列device数据结构链接在一起在dev_base列表中。每个device数据结构描述了其设备,并提供了一组回调例程,网络协议层在需要网络驱动程序执行工作时调用这些例程。这些函数主要涉及传输数据和网络设备的地址。当网络设备从其网络接收到数据包时,它必须将接收到的数据转换为sk_buff数据结构。这些接收到的sk_buff被添加到backlog队列中,由网络驱动程序在接收到它们时进行添加。

如果backlog队列增长过大,则接收到的sk_buff将被丢弃。网络底层半部被标记为准备运行,因为有工作要做。

当网络底层半部处理程序由调度程序运行时,它会处理任何等待传输的网络数据包,然后再处理backlog队列,确定将接收到的数据包传递到哪个协议层。sk_buff由于Linux网络层已初始化,因此每个协议通过将

packet_type数据结构添加到ptype_all列表或添加到ptype_base哈希表中来注册自身。该数据结构包含协议类型、指向网络设备的指针、指向协议的接收数据处理例程的指针以及指向列表或哈希链中下一个数据结构添加到数据结构的指针。该数据结构添加到链用于侦听从任何网络设备接收的所有数据包,通常不使用。该列表或添加到哈希表按协议标识符进行哈希处理,用于决定哪个协议应接收传入的网络数据包。网络底层半部将传入的哈希表中来注册自身。该的协议类型与任一表中一个或多个sk_buff条目进行匹配。该协议可能匹配多个条目,例如,当侦听所有网络流量时,在这种情况下,数据结构添加到将被克隆。该sk_buff传递给匹配协议的处理例程。sk_buff传递给匹配协议的处理例程。

10.5.3  发送IP数据包

数据包通过应用程序交换数据来传输,或者由网络协议在支持已建立的连接或正在建立的连接时生成。无论数据如何生成,都会构建一个sk_buff以包含数据,并且在数据包通过协议层时,协议层会添加各种标头。

sk_buff需要将数据包传递到网络设备才能进行传输。但首先,协议(例如IP)需要决定使用哪个网络设备。这取决于数据包的最佳路由。对于通过调制解调器连接到单个网络(例如通过PPP协议)的计算机,路由选择很容易。数据包应通过环回设备发送到本地主机,或发送到PPP调制解调器连接末端的网关。对于连接到以太网的计算机,选择更加困难,因为有许多计算机连接到网络。

对于每个传输的IP数据包,IP都使用路由表来解析目标IP地址的路由。在路由表中成功查找的每个IP目标都返回一个rtable

数据结构,描述要使用的路由。这包括要使用的源IP地址、网络device数据结构的地址,有时还包括预构建的硬件标头。此硬件标头是网络设备特定的,包含源和目标物理地址以及其他媒体特定信息。如果网络设备是以太网设备,则硬件标头如图  10.1 所示,并且源和目标地址将是物理以太网地址。硬件标头与路由一起缓存,因为它必须附加到在此路由上传输的每个IP数据包,并且构建它需要时间。硬件标头可能包含必须使用ARP协议解析的物理地址。在这种情况下,传出数据包会停滞,直到地址被解析。一旦地址被解析并构建了硬件标头,硬件标头就会被缓存,以便将来使用此接口发送的IP数据包不必进行ARP。

10.5.4  数据分片

每个网络设备都有一个最大数据包大小,并且它无法传输或接收大于此大小的数据包。IP协议允许这样做,并将数据分成更小的单元,以适应网络设备可以处理的数据包大小。IP协议标头包括一个片段字段,该字段包含一个标志和片段偏移量。

当IP数据包准备好传输时,

IP会找到用于发送IP数据包的网络设备。该设备是从IP路由表中找到的。每个device都有一个字段描述其最大传输单元(以字节为单位),这就是mtu字段。如果设备的mtu小于等待传输的IP数据包的数据包大小,则必须将IP数据包分解为更小的(mtu大小)片段。每个片段由一个sk_buff表示;其IP标头被标记为显示它是一个片段以及此IP数据包包含的数据偏移量。最后一个数据包被标记为最后一个IP片段。如果在分片期间,IP无法分配一个sk_buff,则传输将失败。

接收IP片段比发送它们要困难一些,因为IP片段可以按任何顺序接收,并且必须在重新组装之前全部接收。每次收到IP数据包时,都会检查它是否为IP片段。第一次收到消息的片段时,IP会创建一个新的ipq数据结构,并将其链接到等待重组的IP片段的ipqueue列表中。当收到更多IP片段时,会找到正确的ipq数据结构,并创建一个新的ipfrag数据结构来描述此片段。每个ipq数据结构唯一地描述了一个分片的IP接收帧,包括其源和目标IP地址、上层协议标识符以及此IP帧的标识符。当收到所有片段后,它们将被组合成一个sk_buff,并传递到下一个协议层进行处理。每个ipq都包含一个计时器,每次收到有效片段时都会重新启动。如果此计时器到期,则ipq数据结构及其ipfrag将被拆除,并且该消息被假定为在传输过程中丢失。然后,由更高级别的协议重新传输消息。

10.6  地址解析协议 (ARP)

地址解析协议(ARP)的作用是将IP地址转换为物理硬件地址,例如以太网地址。 IP需要在将数据(以sk_buff形式)传递给设备驱动程序以进行传输之前进行此转换。

它执行各种检查,以查看此设备是否需要硬件标头,如果需要,数据包的硬件标头是否需要重建。 Linux会缓存硬件标头,以避免频繁重建它们。 如果需要重建硬件标头,它会调用设备特定的硬件标头重建例程。 所有以太网设备都使用相同的通用标头重建例程,

该例程又使用ARP服务将目标IP地址转换为物理地址。

ARP协议本身非常简单,由两种消息类型组成:ARP请求和ARP响应。 ARP请求包含需要转换的IP地址,而响应(希望如此)包含已转换的IP地址,即硬件地址。 ARP请求会广播到连接到网络的所有主机,因此,对于以太网网络,连接到以太网的所有机器都会看到ARP请求。 请求中拥有IP地址的机器将使用包含其自身物理地址的ARP响应来响应ARP请求。

Linux中的ARP协议层围绕着一个arp_table数据结构构建,每个数据结构描述了一个IP到物理地址的转换。 这些条目是在需要转换IP地址时创建的,并在它们随着时间的推移而变得陈旧时删除。 每个arp_table数据结构具有以下字段:

上次使用时间上次使用此ARP条目的时间,
上次更新时间上次更新此ARP条目的时间,
标志这些描述了此条目的状态,例如它是否完整等等,
IP地址此条目描述的IP地址
硬件地址已转换的硬件地址
硬件标头这是指向缓存的硬件标头的指针,
计时器这是一个timer_list用于对未收到响应的ARP请求进行超时的条目,
即未收到响应的 ARP 请求的时间。
重试次数此ARP请求已
重试的次数,
sk_buff队列等待sk_buff此IP地址的解析的条目的列表
要被解析

ARP表由一个指针表(arp_tables向量)组成,这些指针指向arp_table条目的链。 条目被缓存以加快对其的访问速度。通过获取其IP地址的最后两个字节来生成到表中的索引,然后跟随条目链直到找到正确的条目来找到每个条目。 Linux还在arp_table条目中缓存预构建的硬件标头,形式为hh_cache数据结构的指针的向量。

当请求IP地址转换并且没有相应的arp_table条目时,ARP必须发送ARP请求消息。 它在表中创建一个新的arp_table条目,并将sk_buff包含需要地址转换的网络数据包的数据包排队到新条目的sk_buff队列中。 它发送一个ARP请求并启动ARP过期计时器。 如果没有响应,则ARP将重试该请求多次,如果仍然没有响应,则ARP将删除该arp_table条目。 任何sk_buff等待IP地址被转换的数据结构将被通知,并且由传输它们的协议层来处理此失败。 UDP不在乎丢失的数据包,但是TCP将尝试在已建立的TCP链接上重新传输。 如果IP地址的所有者使用其硬件地址进行响应,则该arp_table条目将被标记为完成,并且任何排队的sk_buff将被从队列中删除,并将继续传输。 硬件地址被写入每个sk_buff.

的硬件标头中。ARP协议层还必须响应指定其IP地址的ARP请求。 它注册其协议类型(ETH_P_ARP数据结构添加到),生成一个device数据结构。

数据结构。 这意味着它将传递由网络设备接收到的所有ARP数据包。 除了ARP响应之外,这还包括ARP请求。 它使用保留在接收设备的arp_table中的硬件地址生成ARP响应。网络拓扑会随着时间的推移而发生变化,并且IP地址可以重新分配给不同的硬件地址。 例如,某些拨号服务在建立每个连接时分配一个IP地址。 为了使ARP表包含最新的条目,ARP会运行一个定期计时器,该计时器会检查所有arp_table条目,以查看哪些条目已超时。 它非常小心,不要删除包含一个或多个缓存的硬件标头的条目。 删除这些条目很危险,因为其他数据结构依赖于它们。 一些arp_table条目是永久性的,这些条目被标记为不会被解除分配。 不允许ARP表增长过大;每个

10.7  IP路由

IP路由功能确定将目标IP地址为特定IP地址的IP数据包发送到何处。 在传输IP数据包时,有很多选择要做。 目标是否可以到达? 如果可以到达,应该使用哪个网络设备来传输它? 如果有多个可以用于到达目标地的网络设备,哪个更好? IP路由数据库维护提供这些问题答案的信息。 有两个数据库,最重要的是转发信息数据库。 这是已知IP目标及其最佳路由的详尽列表。 一个更小,更快的数据库,路由缓存用于快速查找IP目标的路由。 像所有缓存一样,它必须仅包含频繁访问的路由;它的内容来自转发信息数据库。

路由通过对BSD套接字接口的IOCTL请求添加和删除。 这些被传递到协议进行处理。 INET协议层只允许具有超级用户权限的进程添加和删除IP路由。 这些路由可以是固定的,也可以是动态的,并且会随着时间的推移而发生变化。 大多数系统使用固定路由,除非它们本身是路由器。 路由器运行路由协议,这些协议不断检查到所有已知IP目标的路由的可用性。 不是路由器的系统被称为终端系统。 路由协议被实现为守护进程,例如GATED,它们也通过IOCTL BSD套接字接口添加和删除路由。

10.7.1  路由缓存

每当查找IP路由时,首先检查路由缓存中是否存在匹配的路由。 如果在路由缓存中没有匹配的路由,则在转发信息数据库中搜索路由。 如果在那里找不到任何路由,则IP数据包将无法发送并且应用程序会收到通知。 如果路由在转发信息数据库中但不在路由缓存中,则会生成一个新条目并将其添加到此路由的路由缓存中。 路由缓存是一个表(ip_rt_hash_table),其中包含指向rtable数据结构的链的指针。 进入路由表的索引是基于IP地址的最低有效两个字节的哈希函数。 这些是最有可能在目的地之间不同的两个字节,并且提供最佳的哈希值分布。 每个rtable条目包含有关路由的信息;目标IP地址,网络device用于到达该IP地址,可以使用的消息的最大大小等等。 它还具有一个引用计数,一个使用计数和一个上次使用它们的时间戳(以jiffies)。 每次使用路由时,引用计数都会递增,以显示使用此路由的网络连接的数量。 当应用程序停止使用路由时,引用计数会递减。 每次查找路由时,使用计数都会递增,并且用于对其rtable在哈希条目的链中进行排序。 定期检查路由缓存中所有条目的上次使用的时间戳,以查看rtable是否太旧

。 如果最近未使用该路由,则会从路由缓存中丢弃该路由。 如果路由保留在路由缓存中,则对其进行排序,以使最常用的条目位于哈希链的前面。 这意味着在查找路由时可以更快地找到它们。

10.7.2  转发信息数据库


图10.5:转发信息数据库

转发信息数据库(如图 10.5所示)包含IP目前对该系统可用的路由的视图。它是一个相当复杂的数据结构,尽管它的排列相当有效,但并不是一个快速的数据库来进行查询。特别是,为每个传输的IP数据包在此数据库中查找目标会非常慢。这就是存在路由缓存的原因:使用已知的良好路由来加速IP数据包的传输。路由缓存来自转发数据库,并表示其常用条目。

每个IP子网都由一个fib_zone数据结构表示。所有这些都从fib_zones哈希表中指向。哈希索引来自IP子网掩码。到同一子网的所有路由都由fib_nodefib_info数据结构对描述,这些数据结构对被排队到每个fz_list数据结构的fib_zone中。如果此子网中的路由数量变得很大,则会生成一个哈希表,以使查找fib_node数据结构更容易。

可能存在到同一IP子网的多个路由,并且这些路由可以通过多个网关之一。IP路由层不允许使用同一网关到子网的多个路由。换句话说,如果存在到子网的多个路由,则保证每个路由都使用不同的网关。与每个路由关联的是其度量。这是衡量此路由优劣的指标。路由的度量本质上是必须跨越才能到达目标子网的IP子网的数量。度量越高,路由越差。


脚注

1 国家科学基金会

2 同步只读存储器

3 duh? 用途是什么?


文件从TEX by TTH, version 1.0.
章节顶部, 目录, 显示框架, 无框架
1996-1999 David A Rusling 版权声明.