LINUX内核 之 (第十章)
第 10 章 网 络
“网络”与“Linux”在某种意义上是同义词,因为Linux是Internet与WWW(World
Wide Web)的产物。Linux的开发者与用户利用web来互通信息,交换代码。Linux本身也经
常用于对各种网络应用的支持。本章主要讨论了Linux中TCP/IP协议的实现。
TCP/IP协议最初是为了支持在ARPANET(一个由美国政府资助的带研究性质的网络)
中计算机间的通讯而设计的。在ARPANET的研究中首次提出了诸如报文交换、协议分
层(即一层协议使用另一层的服务)等网络概念。ARPANET于1988年停止使用,不过它
的后继系统(NSF1 NET与Internet)则得到了更为广泛的发展。众所周知的WWW(World
Wide Web)就是利用TCP/IP协议在ARPANET上发展起来的。UnixTM 系统在ARPANET中
得到了广泛的应用。最早发布的UnixTM网络版本是4.3 BSD。Linux中网络部份的实
现是建立在4.3 BSD上的,因而Linux支持BSD sockets (包括其扩展)及其全部TCP/IP协议。
由于在此之前4.3 BSD以得到广泛的应用,Linux选择BSD sockets这一编程接口有利于
它与其他UnixTM平台键应用程序的移植性。
10.1 TCP/IP概述
本节对TCP/IP的主要内容进行了并非详尽的概述。
在一个使用IP协议的网络中,每一台计算机都被赋予一个IP地址,IP地址是一个用
来唯一标志机器的32位整数。比如WWW就是一个庞大的、并且在不断增长的IP网络,
接入该网的所有计算机都有一个唯一的IP地址。IP地址是由四个相互间用黑点隔开
的整数表示的,比如,16.42.0.9。事实上每个IP地址由两部份组成:网络地址(network
address)与主机地址(host address)。这两部份在32位IP地址中所占位数随地址类
型的不同而不同。仍以16.42.0.9为例,其中16.42是网络地址,0.9是主机地址。另
外,IP地址还可用子网地址(subnet address)与机器地址(host address)进行分
类。在16.42.0.9中,子网地址为16.42.0,机器地址为16.42.0.9。IP地址的这种划
分为人们进一步划分他们的网络提供了手段。假设16.42是ACME Computer Company的
网络地址;则16.42.0与16.42.1就可以分别划分为子网0与子网1的子网地址。这些
子网可以分布在不同的建筑中,相互之间可以由电话线连接,也可以由微波连接。
IP地址由网络管理员分配,建立IP子网是一种将网络管理权合理分配给IP子网管理
员的较好方法。IP子网管理员可以对本子网内部的IP地址自由分配。
一般来说,IP地址是较难记忆的,而名字则相对较易记忆,比如linux.acme.com要
比16.42.0.9好记得多。这样就要求有一种从名字到IP地址的转换机制。与IP地址对应
的名字可以在文件/etc/hosts中指定;也可以由DNS服务器(Distributed Name
Server)来动态解析,在这种情况下,本地计算机必须知道一个或多个DNS服务器的
IP地址,用户可以通过文件/etc/resolv.conf来配置。
每当你连接到另外一台机器,比如当你在阅读一个网页时,IP地址就用来与那台机
器进行数据交换。交换的数据被封装在若干个IP包中,在每个IP包的报文头中包含有
源机器IP地址、目的机器的IP地址、校验和以及其他一些有用信息。校验和是根据IP报
文中的数据计算出来的,IP包的接收方可以根据它来判断报文在传输过程中是否由于传
输线路噪音等原因受到破坏。应用程序的传输数据一般会被分成几个较小的数据包,以简
化处理。IP包的大小根据物理介质的不同而变化;比如以太网的报文包一般要比PPP网
的大。目的机器在接受到数据包后要将它们重新组装成原来的数据才能交给应用程
序。当你通过一个较慢的连接访问一个含有大量图片的网站时,你就能够感觉到数
据分片与组装的过程。
同一个IP子网上的主机之间可直接传送数据。在其他情况下,IP数据包将被首先送
往一个指定的机器,即网关(或路由器)。网关一般与多个IP子网相连,它把从一个
子网上接受到的、而目的地址在另一个子网的IP包转发出去。比如,如果两个子网16.42.1.0
与16.42.0.0被一个网关连接在一起,那么从子网0发送到子网1的数据包都将被传送到
网关,由网关来决定传输路径。每台本地机都有一个路由表以使IP包能够正确的传送。对
于每一个目的地址,在路由表中都有一项来指出发往这一地址的数据应送到那一台机器才
能使其能到达正确的目的地。另外,路由表是随着应用程序对网络的应用以及网络
拓扑关系的变化而动态变化的。
图10.1 TCP/IP协议层
IP协议处于传输层,这一层为其它层的协议的数据传送提供服务。TCP(Transmission
Control Protocol)协议是一种可靠的端到端网络协议,它就是使用IP的服务来完成
自身数据的发送与接收的。与IP报文相似,TCP报文也有自己的报文头。TCP是一个面向连接
的协议,即网络上两台计算机在进行数据传输时,虽然在它们之间可能存在许多子网、
网关和路由器,但TCP进行连接时,两台机器上的应用程序之间存在一条虚连接。通
过TCP 进行的应用数据的传送与接收都是可靠的,即不会丢失数据或数据重复。当TCP利用
IP进行数据传输时,IP包中数据部份就是整个TCP包,IP层协议对自身协议包的传输负
责。UDP(User Datagram Protocol)协议是另一种利用IP协议进行传输的传输层协议,与
TCP所不同的是,UDP协议的传输不是可靠的,而仅仅提供数据报(DATAGRAM)服务。这种
对IP协议的使用使得IP协议在接收到数据后必须有能力判断所接收到的数据应该传
给哪一个上层协议。为了实现这一点,在每一个IP包的报文头中都有一个字节作为协议
标志用于指出该包的数据部份是那种协议的报文。比如当TCP请求IP层提供服务时,在相应
的IP包的报文头中协议标志就指出该包中包含一个TCP包。接收方的IP层就利用这一标志
来决定应该把数据上传给那种协议(在本例中,应该交给TCP层)。应用程序在利用TCP/IP
进行网络传输时,不仅要指出IP地址,还要指出对方应用程序的端口号,端口号与应用程序
一一对应,标准的网络服务使用标准的端口号,比如web服务器使用80号端口。端口地址的使
用情况可以在文件/etc/services中看到。
协议的层次结构并非仅限于TCP,UDP与IP,IP协议层本身就使用许多不同的物理介
质来传输IP报文,这些介质进行传送时也有自己的协议与协议头。以太网层就是一
个较好的例子,其他类似的例子还有PPP与SLIP等协议。以太网允许多台主机同时连接到一
个物理电缆上,在其上传输的所有以太帧对连接在该网上的所有机器都是可见的,因
此每一台机器都应有唯一的物理地址,从而以太帧只能被具有其目的地址的机器所
接收,而其他机器将忽略之。物理地址是内嵌在以太网卡中并且在网卡制造时确定,
通常这一地址是保存在网卡上的一个SROM2中。以太网的物理地址有6个字节,比如
08-00-2b-00-49-A4。某些特殊的以太网物理地址被保留用作广播,即以这些地址作
为目的地址的以太网物理帧将为该网上所有的主机所接收。以太帧可以通过在数据
帧的帧头上包含一个标志的方式来传送多种不同协议(数据),这就使得以太网能
够正确地接收IP数据包并将其上传给IP协议层。
为了能够通过象以太网这样的支持多连接的物理网传送IP报文,IP层协议必须能获
得对方主机的以太网物理地址。因为IP地址仅仅是一种概念上的地址体系,以太网物
理设备自身有它们自己的地址体系。另一方面,IP地址并不固定,可以为网络管理员随意
设置与修改,而以太网卡则只接收与自身地址相对应的以太帧或广播帧。Linux利用ARP
(Address Resolution Protocol)来进行从IP地址向诸如以太网的物理地址等物理
地址的转换工作。当一个主机想知道与一个IP地址相对应的物理地址时,该主机就
通过广播的方式向网上的所有机器发送ARP请求报文,该报文中包含有希望被解析的
IP地址。当使用此IP地址的机器接收到这一请求报文时,就用ARP回答报文回应,在
该报文中包含其物理地址。ARP协议并不仅仅局限于以太网设备,它还可为其它诸如
FDDI等物理介质解析IP地址。对于那些不能使用ARP进行解析的物理介质在Linux中
都被作标记,从而系统运行时将不会使用ARP。另外还有一种协议叫RARP(ReverseARP)用
于反向解析,即将物理地址解析为IP地址。这种协议一般用于网关,它对包含远地
网的IP地址地址ARP请求进行回应。
10.2 Linux中的TCP/IP网络层次结构
和网络协议一样,Linux在实现互连网地址协议簇时也将其实现为一系列相互依赖的
以层次结构组织的软件。BSD套接字由一个基本的套接字管理软件支持。而为其提供
支持的下层软件为INET套接字层,这一层软件统一管理利用TCP与UDP进行的端与端之间
的通信。正如上文所述UDP (User Datagram Protocol)是一个面向非连接的通信协
议,而TCP (Transmission Control Protocol)则是一个面向连接的可靠的端与端通
信协议。当使用UDP进行数据的传输时,Linux不知道也不关心数据包是否正确地到
达了目的端;而TCP的数据包则被编号,从而使通信对方能够确认数据被正确地接收。
IP层也包含Internet Protocol的实现代码,这些代码将IP报文头加到被传输数据之前
并且负责将接收到的IP报文上传给TCP或UDP层。在IP层之下的是网络设备层,比如PPP与
ethernet,由它们负责Linux的物理连接。网络设备并不总是指物理设备,也有可能指软件
设备比如回溯设备。与使用mknod命令所生成的Linux设备所不同的是,网络设备只有当低
层软件存在并且由这些软件完成了设备的初始化工作之后才能生成。在利用合适的以太网
设备构造了一个内核之后,你就会看到 /dev/eth0。ARP协议处于IP层与为ARP提供寻址支持
的协议之间。
图10.2 Linux 中的网络层次
10.3 BSD套接字
BSD套接字是一个通用的系统接口,它不仅支持各种形式的网络连接,同时还是一种
进程间的通信机制。套接字可以描述通信连接一端的运行状态,对于参与通信的两
个不同进程有不同的套接字与之对应。事实上,套接字可以被看作为一种管道,只
不过与管道不同的是,它对于其中所能容纳的数据量没有限制。Linux支持多种不同
类型的套接字,由于不同类型的套接字在通信时进行寻址的方法不同,套接字又被
称为地址族。Linux支持以下几种套接字地址族或套接字域:
UNIX Unix域的套接字
INET 该域的套接字提供对通过TCP/IP的通信
AX25 适用于Amateur radio X25协议
IPX 适用于Novell IPX协议
APPLETALK 适用于Appletalk DDP协议
X25 适用于X25协议
套接字系统中存在几种不同的套接字类型,不同的类型代表了不同的网络服务,但
并不是每一种地址族都支持所有类型的服务。LinuxBSD套接字系统支持以下几种套接
字类型:
Stream
这种类型的套接字提供可靠的、双向、有序的数据流传输,这种通信可以保证数
据不被丢失、破坏或重复。在Internet(INET)地址族中,Stream类型的套接字由TCP协议
支持。
Datagram
这种类型的套接字也提供双向的数据传输,只不过与stream类型的套接字不同
的是,它不保证数据能够达目的地,即使数据抵达目的地了,也不能保证数据的正
确性,即它不能保证数据不被破坏或重复。在Internet地址族中,这种类型的套接字由
UDP协议支持。
Raw
该类型的套接字允许进程直接对下层协议进行操作,这也是为何称其为raw的原因。
比如,可以使用该类型的套接字直接对以太网设备层进行操作从而的到原始的IP数
据包。
Reliable Delivered Messages
该类型与datagram基本相同,唯一区别在于它保证数据能够
到达目的地。
Sequenced Packets
该类型与stream类型相似,唯一区别在于该类型的数据包大小是固定
的。
Packet 这种类型不是标准的BSD套接字类型,它是Linux自身对套接字系统的扩展,它
允许进程直接对设备层的数据包进行操作。
利用套接字进行通信的进程使用客户机/服务器模式,即服务器提供某些服务,客户
机则使用这些服务。比如,一个Web服务器提供一些web网页,web客户端程序比如浏览
器等就可以浏览这些网页。如果一个服务端程序要通过套接字提供服务,那它首先要
创建一个套接字然后再为其绑定一个名字,这种名字实际上指明了服务程序在本地
系统中的地址,名字的格式与套接字所属的地址族相关,套接字的名字或地址用sockaddr这
一数据结构来指定。对于INET域的套接字还要绑定一个IP端口地址(端口号),端
口号的使用情况可以在文件/etc/services中看到;比如,web服务器所使用的端口
号为80。在套接字绑定地址以后,服务程序就在该地址上侦听针对这一地址的服务
请求。客户方程序提出服务请求时,也要创建一个套接字然后指定目的地址并建立
一个与对方套接字的连接。对于INET域的套接字目的地址就是服务器的IP地址与其
端口号。客户方的服务请求到达服务器后要通过各层协议一直到达服务程序的侦听
套接字,服务程序在发现服务请求后只有两种选择:接受与拒绝。如果一个服务请
求被接受,服务程序就会建立一个新的套接字而不是使用侦听套接字来继续完成相
应的服务操作,因为用于侦听的套接字是不能用于支持连接的。在正确地建立了连
接后,连接的双方就可以方便地进行数据的传送与接收,当此连接不再需要时,可
以被关闭。在整个过程中,都有相应的措施保证数据能够被正确的处理。
在BSD套接字上操作的具体含义取决于相应的地址族。比如建立一个TCP/IP连接和
建立一个amateur radio X.25连接是不同的。类似于虚拟文件系统,Linux将BSD
socket层中与应用程序关系密切的那一部份抽取出来成为socket界面,并有专门的
软件支持。在内核启动时,各地址协议族连同其BSD socket界面一起在内核中登记
注册,这样,在应用程序使用BSD socket时,就可以方便地在BSDsocket与相应支持
软件之间建立联系。这种联系是通过交叉连接的数据结构以及其它一些信息表建立
的。比如在创建新的socket时,就有相应的创建例程进行支持。
内核在配置时,各层协议的对应号码被填入协议向量中,协议向量中的每一项都由
相应的名来表示,比如,“INET”与其初始化例程的地址。在系统引导时,套接字接
口被初始化,与此同时,各协议的初始化例程也将被调用。对于该套接字协议而言,
这一步骤将会产生一组协议操作,即一组例程,其中每一个例程都将执行针对该地
址族的一些特定操作。所有这些协议操作被保存在一个指向proto_ops 结构的指针
向量pops中。
proto_ops结构包含地址族的类型以及一组指向针对该地址族的套接字操作例程指针。
Pops向量可以由地址族标识符来检索,比如,Internet地址族对应于AF_INET(在Linux
中被赋值2)。
图10.3 Linux 中BSD Socket 的数据结构
10.4 INET套接字系统
INET套接字系统支持包括TCP/IP协议在内整个internet地址协议族。由上文讨论可
知,整个协议族是层次化的,下层的协议为上层的协议提供服务,这种层次化结构
也体现在Linux的TCP/IP源代码所使用的数据结构中。各层协议与BSD套接字层的操
作界面是一组Internet域的socket操作,这些操作一般在网络初始化时在BSD套接字
层中登记,这些登记信息连同其他协议族的信息一同保存在pops向量中。BSD套接字
系统通过proto_ops结构调用INET套接字层所支持的例程来完成相应的工作,比如,
BSD套接字使用INET域中的地址提出请求时就要使用到INET层的例程。在该操作中,
BSD套接字系统把代表BSD套接字的数据结构交给INET层,INET层并不将BSD socket直
接与下层的TCP/IP协议的信息连接在一起,而是使用自身的数据结构sock与BSD socket
进行联系,如图10.3。BSD socket使用自身结构中的一个指针与sock结构连接起来,这
样就使以后的socket操作能够比较方便的访问sock结构中的数据。在sock结构中有一个
协议操作指针,它指向的内容随被请求服务的协议的不同而不同。例如,如果请求TCP服务,
则sock结构中协议指针将指向一组TCP的协议操作组,以便于进行TCP连接时使用。
10.4.1 BSD Socket的建立
使用系统调用建立一个新的socket时要用到地址族、socket类型与协议标志三个参
数。
地址族标识用于在pops中搜索对应的地址族信息。有时一个特定的地址族由一个特
定的内核模块来实现,在这种情况下kerneld daemon就必须将这一模块加载程序才能
继续执行。BSD socket是由一个socket数据结构来表示的。在实现上,socket结构是一种
VFS inode 结构,生成一个新的socket的结构也就等于申请一个VFS inode,之所以这样
是因为socket 可以象文件句柄那样用于对普通文件的操作,但文件都是用VFS inode来
表示的,因此为了实现对文件操作的支持,BSD sockets也必须用VFS inode结构来
实现。
在新创建的BSD socket数据结构中包含一个特殊的指针,该指针指向面向地址族的
socket例程,这些例程的具体信息被保存在proto_ops结构中,并且可以根据opos结
构中的信息进行检索,其中socket类型可以是SOCK_STREAM, SOCK_DGRAM等。那些面向
地址域的创建例程可以通过保存在proto_ops结构中的地址来调用。
一个新的文件描述符是从当前进程的文件描述符向量中分配的,同时该描述符所指
向的文件数据结构也被初始化。这一过程包括对文件操作指针的设置,使之指向一组
由BSD socket界面所支持的BSD socket文件操作例程,任何进一步的操作都将传送给
socket界面,而后者则会调用其相应的地址族操作例程从而将操作请求传给下层提供支
持的地址协议。
10.4.2 INET BSD Socket与地址的绑定
为了能够侦听internet上的连接请求,服务器创建一个INET BSD
socket并要给其绑定一个地址,该绑定操作一般是在TCP与UDP协议的支持下,由INET
socket层来完成。一个socket一旦被绑定了地址,就不能用于其它通信,即该socket的
状态将是TCP_CLOSE。在进行绑定操作时所使用的参数中sockaddr应该包含被绑定的
IP地址,并最好同时提供端口号。一般情况下,用于绑定的IP地址应有相应网络设
备提供支持,它应支持INET地址族并且其相应的界面可以提供服务。用户可以用ifconfig命
令来查看哪一个网络界面正在被使用。上面的IP地址还有可能是全1或全0的广播地
址,意为要将数据传给所有的机器。如果一个机器充当代理或防火墙则它的IP地址
可以被指定为任何IP,但只有拥有超级用户权力的进程才能绑定任何一个IP地址。
被绑定的IP地址被保存在sock结构中的recv_addr与saddr域中,分别用于哈希表的
搜索与发送数据,其中端口号是可选的,如果用户没有指定,则系统会自动申请一
个空余的端口,按照惯例,小于1024的端口号在没有得到管理员的允许的情况下不
应使用,网络系统自己分配端口号时,一般也只分配大于1024的端口。
下层网络设备必须将接收到的数据包交给正确的INET与BSD套接字才能进行处理。
为此,UDP与TCP利用一些哈希表进行输入IP数据的信息搜索从而将数据包正确地交
给socket/sock数据。由于TCP是一个面向连接的的协议,因而处理TCP包所需的信息要
比处理UDP包多。
UDP中有一个哈希表udp_hash,它保存了所有已分配的UDP端口。事实上,该表保存
的是指向sock数据结构的指针,可以利用端口号进行检索。由于UDP哈希表的表项要
比可能的端口号数量少的多(udp_hash只有UDP_HTABLE_SIZE=128个表项),因而该
表的某些表项是一个sock数据组成的链表。
与UDP相比,TCP协议相对就比较复杂,它要维护多个哈希表。但在绑定操作时,已
被绑定的sock数据并不立即加入TCP的哈希表中,只是检查一下所使用的的端口号是
否已被使用。Sock数据是在执行侦听操作时插入哈希表中的。
10.4.3 利用INET BSD Socket创建连接
一个socket被创建之后,如果还没有用于侦听连接请求,该socket可用来发出连接
请求。在进行连接时,面向连接的TCP协议需要在双方之间建立一条虚拟链路,而对面
向非连接的UDP协议而言则不需作这些工作。
连接请求的发出只能在某些处于特定状态的INET BSD
socket上进行,也就是说,只有那些还没有建立连接并且也没有用于侦听连接请求
的socket上才能发出连接请求。在系统中,BSD socket应处于SS_UNCONNECTED状态。
UDP协议不需建立虚连接,而直接以数据报的形式进行通信,同时发出的数据不能保
证能够到达目的地。但该协议支持对BSDsocket上的连接,只不过在建立连接时,不
建立虚连接而只设置远地程序的IP地址与IP端口,同时设置路由缓存,以使以后在
该socket上传送的UDP数据报不再需要使用路由数据库(除非已有的路由不再有效)。
路由缓存由INET sock结构中的ip_route_cache指向,在没有提供地址信息时,路由缓存
中的信息(路由与IP地址)将被自动地使用。同时,该socket的状态也将被设为
TCP_ESTABLISHED。
在TCP BSD socket上进行连接时,TCP协议将向对方发送一个包含连接信息的TCP
数据包。该数据包中有消息的初始序列号、可接受数据的大小、发送与接收窗口大
小等连接信息。在TCP中,所有的数据都是被编号的,初始序列号就是第一个消息的
编号。Linux一般选择一个随机的编号作为初始序列号以防恶意的破坏。通过TCP连
接发送的所有数据在对方成功接收后都会得到确认,没被确认的数据将被重发。发
送与接收窗口的大小是指已发送但尚未被确认的数据包的最大数量。网络传输数据
包的大小取决于下层网络设备,当通信双方的大小不一时,取较小者。发出TCP连接
请求的一方必须等待对方的回应(接受或拒绝),此时它必须将自身加入
tcp_listening_hash表中,从而传入的数据能够交给它;与此同时,TCP还启动若干时钟
以便当对方没有回答时进行超时处理。
10.4.4 在INET BSD Socket上的侦听
一旦一个socket被绑定了地址后,它就可以侦听针对于它的连接请求。有时socket在
没有绑定时也可以侦听连接请求,在这种情况下,INET socket层会为该socket自动
地绑定一个尚未使用的端口号。在此之后socket层的侦听函数listen会将该socket置为
TCP_LISTEN 并执行其它一些操作从而可以处理连接请求。
对UDP协议而言,以上的操作已经足够了,但TCP还要将该socket中的sock数据加入
两张哈希表:tcp_bound_data与tcp_listening_hash,这两张表都是由一个基于IP端
口号的哈希函数来进行检索的。
对于一个正在侦听的socket,当它接收到一个连接请求时,TCP协议就为其创建一个
新的sock数据来处理与该连接相关的通信操作。在sock结构中,还包含用于保存连
接请求信息的sk_buff结构的复制体,该sk_buff结构被放入用于侦听的sock结构中的一
个队列中:receive_queue,sk_buff中有一个指针指向新创建的sock结构。
10.4.5 连接请求的接受
UDP协议不支持连接,TCP协议中才涉及INET socket上连接请求的接受。一个处于
侦听状态的socket执行接受操作时会在该侦听socket的基础上克隆(clone)一个新
的socket,然后就将接受操作交给下层支持协议(如INET)来处理。如果该协议的
支撑协议不支持连接,如UDP,连接接受操作就会失败,否则接受操作将被进一步交
给完成实质性操作的协议如TCP。连接的接受操作可以分为阻塞方式与非阻塞方式两
种。对于阻塞方式而言,执行接受操作的应用进程会在一个等待队列中等待直到接
收到一个TCP连接请求为止。对非阻塞方式而言,应用进程不进行等待,如果没有连
接请求该操作就失败返回,新生成的socket数据也将废弃。在一个连接请求被正确
地接收后,用于保存请求信息的sk_buff结构就被废弃,同时将一个sock结构返回给
INET层并被放入新生成的socket结构中。网络应用程序此时会得到一个新socket的
文件描述符,由该描述符就可继续进行网络应用的后续操作。
10.5 IP层
10.5.1 Socket缓冲
在层次化的网络协议结构中,一层协议将为另一层提供服务,因此每一协议在接收
与发送数据时,都要对数据的协议头与协议尾进行处理,其中包括确定协议头与协议
尾的位置,这样就使在各层协议间数据的传递很困难。一种解决方法就是在各层之
间拷贝数据,这种方法一般效率不高,在Linux中使用另一种方法,即socket缓存方
法。Linux使用sk_buffs结构来完成协议层之间的数据传送,该结构包含一些指针与
长度域以使个协议能利用标准的函数或方法来处理应用数据。
图10.4 socket缓冲区(sk_buff)
Sk_buff的数据结构如图10.4所示,sk_buff使用以下四个指针对其自身的数据块进
行操作:
head
指向整个数据区的开始位置,当sk_buff结构及其相关的数据块被分配后,该指针的
值就确定了。
data
指向当前协议数据区的开始位置,该指针的值随sk_buff所处协议层的不同而改变。
tail
指向当前协议数据区的结束位置,与data类似,该指针的值也随sk_buff所处协议层
的不同而改变。
end
指向整个数据区域的结束位置,与head类似,该指针的值也在sk_buff结构分配时确
定。
在sk_buff中有两个长度域:len与truesize,分别描述协议数据与整个数据缓存的
大小。系统对sk_buff进行处理的代码提供了对应用数据增删协议头与协议尾的操作,这些
操作能正确地对sk_buff中的头指针、尾指针以及长度域进行正确的修改。
push
该操作将一块数据加到数据区的头部,同时修改len的值。该操作用于将协议头等数
据加到传输数据上。
pull
该操作修改data指针,使之与数据区的尾不接近,同时减少len的值。在删除接收数
据中的协议头时用到这一操作。
put
该操作将tail指针向数据区的尾移动,同时增加len的值。该操作用于将协议信息加
到数据的尾部。
trim
该操作将tail指针向数据区的头移动,同时减少len的值。该操作用于协议信息从数
据的尾部删除。
sk_buff结构中还包含用于将多个sk_buff链接成双向链表的辅助指针,同时系统提
供向该链表增删sk_buff结构的例程。
10.5.2 IP包的接收
Chapter设备驱动程序描述了Linux中的网络设备是如何加载到内核并初始化的,此
过程的结果是生成dev_base链表,该链表的表项描述了设备信息。每一个表项都包含了
一组回调函数,当上层协议需要网络设备提供服务时就调用这些函数,它们一般用于数据
的传输以及对网络设备地址的操作。当网卡接收到数据后就将其转换成sk_buff结构,然后
这些sk_buff将被放在backlog队列中。
backlog队列的大小有一定的限制,超过该限制后收到的sk_buff将被丢弃,同时,
网络系统的也被标识为运行状态。
底层网络协议运行时,先处理发送出去的数据包,然后处理sk_buff的backlog队列,
将数据传给对应的协议进行处理。
Linux的网络层协议被启动并初始化后,每一协议都将自身的packet_type数据加到
ptype_all链表或ptype_base哈希表中,从而在系统中登记。Packet_type结构中包
含了协议类型、指向网络设备的指针、指向进行接收处理的协议例程的指针以及指向
链表或哈希表中下一个packet_type项的指针。ptype_all链用来探测那些已接收到,
但还没有被正常使用的数据包。Ptype_base表利用协议标志符对接收到的数据进行哈希
排列,同时决定由哪一个上层协议来处理接收到的网络数据。在接收数据时,系统
将sk_buff数据与两张表中的packet_type表项进行匹配。某些情况下,比如对网络
数据进行侦听时,会有多个表项与之匹配,这时sk_buff将被复制为多份。在此之后,
sk_buff数据将被传给匹配的协议例程进行处理。
10.5.3 IP包的发送
当程序需要交换数据或网络协议要建立连接时,就要发送数据包。但不管因为何种
原因发送数据包,都要分配一个sk_buff结构来存储接收到的数据,当该sk_buff结构
在各协议之间传递时,还会被加上各种协议头信息。
只有把sk_buff交给网络设备层后数据才能被传递,为了实现这一点,首先要由某一
协议(如IP)来决定数据应由哪种设备传送,这一判断一般取决于哪一条路径最好。
对于那些通过modem上网的系统来说,它们使用PPP协议,路由的选择由于网络的结构的
简单性而变的容易,数据要么通过loopback设备交给本地机,要么交给在modem另一端
的网关。对于以太网上的计算机而言,路由的选择则比较困难,因为在同一个网上连
接有许多计算机。在发送IP数据包时,IP协议使用路由表来解析目的地的IP地址,解析
出的IP地址会以rtable结构的形式返回,该结构包含了相应的IP地址和网卡的物理地址,
有时还会包含一个硬件信息头。硬件信息头一般都是针对特定网卡的,其中包含了源机与
目的机的物理地址以及其它一些与传输媒体有关的信息。对于以太网的情况,硬件信息头
就是图10.1中所示的那样,其源与目的地址就是相应以太网地址。由于在同一条路由上发
送IP包所用的硬件信息头都是相同的,为了减少重复构造的开销,每一信息头都是与对应
的路由一同保存在内存缓冲里的。有时,硬件信息头中的物理地址必须通过ARP解析之后才
能得到,在这种情况下,报文就要等到地址被解析之后才能发送。一旦物理地址被成功的解
析并创建了相应的硬件信息头,该信息就被保存起来以便以后使用同一条路由发送IP包时
就不用重复ARP的解析了。
10.5.4 数据分片
对每一个网络而言,网卡所能发送数据桢的大小是有限制的,为了适应这种限制,
IP协议能够将数据包分割成若干较小的报文,在IP协议头中也有一个分片域,指出数
据分片的偏移量。
在IP包准备发送时,IP协议就在IP路由表中找到与其对应的网络设备。每一个网络
设备都有一个mtu数据描述其所能发送的数据桢的最大值,如果mtu小于IP包的大小,
IP协议就必须将自身的IP包分割成mtu大小的分片。每一分片仍用sk_buff来保存,只
不过其IP协议头将标记它为一个分片,同时指出该分片在原来数据中的偏移量,最后一
个分片也要有标记来指明那是最后一个分片。但如果在分片过程中,IP无法分配新的sk_buff,
则传输失败。
对IP分片的接收要比发送困难,因为IP分片可以以任何顺序到达,而接收方只有在
所有的分片都到达后才能进行组装。因而接收方每收到一个IP包都要检查它是否一
个IP包的分片。当收到第一个数据分片时,IP协议就建立一个新的ipq数据结构,同时该
数据将被链入IP分片重组队列ipqueue中,随着更多的IP分片的到达,就能识别真正的ipq结
构,同时生成新的ipfrag结构对分片进行描述。ipq结构描述了IP包的分片信息,包
括源与目的地的IP地址,上层协议标志符以及本IP包的标志。当所有的分片都收到后,
IP就将它们组装成一个sk_buff,同时上传给相应协议继续处理。在ipq中还设有一个时
钟,该时钟在每一个IP包到达时启动,如果时钟超时,该ipq与ipfrag数据就将被废弃,系
统认为相应数据在传送中丢失,上层协议必须重传。
10.6 地址解析协议ARP
ARP(Address Resolution Protocol)用来将IP地址转换成与其对应的物理地址,
比如在以太网中就是以太地址。在IP将数据以sk_buff的形式交给网卡进行传送时就需要
这种转换。
ARP协议会进行多种检测来判断相应设备是否需要硬件信息头,如果需要,就要为该
数据包创建。在Linux系统中,硬件信息头会被缓存以避免重复的创建。硬件信息头
由相应设备提供的例程来创建。对以太网设备而言,它们使用相同的创建例程,其中在
进行IP地址向物理地址的转换时就要用到ARP。
ARP协议是比较简单的,只包含ARP请求与ARP回答两种报文类型。ARP请求报文
中提供了需要进行解析的IP地址,而ARP回答报文则提供了与该IP地址相对应的物理
地址。ARP进行工作时,将ARP请求报文在本网中进行广播,以使网上的所有机器都能
收到该请求报文。拥有ARP请求报文中的IP地址的机器就用ARP回答报文进行回答,在
该报文中提供其物理地址。
在Linux中,ARP协议是以arp_table这一表为核心进行工作的,该表的每一项对应一
个由IP地址向物理地址的转变。该表中的表项在进行IP地址解析时创建,当这种解
析长时间不用时就会被删除。Arp_table结构包含以下各域:
last used 记录ARP表最后一次被使用的时间,
last updated 记录ARP表最后一次被修改的时间,
flags 描述表项的状态,比如是否完整等等,
IP address 解析所涉及到的IP地址
hardware address 解析出的硬件地址
hardware header 指向缓存中硬件信息头的指针,
timer 这是一个timer_list表项,用于当ARP请求没有回应时的超时操作,
retries 该ARP请求被使用的次数,
sk_buff queue 等待对IP地址进行解析的sk_buff队列
ARP表包含一组指针(又称为arp_tables向量),它们指向arp_table中的表项链,
arp_table的表项一般被缓存以便于访问,操作时以IP地址的最后两个字节作为索引检索到相
应的指针,然后再在该链中进行严格的匹配从而找到相应的表项。Linux还针对arp_table中
的表项预先建立硬件头信息并保存在hh_cache结构中。
当一个表项的IP地址没有在arp_table中出现时,ARP协议就发送相应的ARP请求报
文进行解析。此时,要在arp_table表中建立新的一项,并将需要进行转换的sk_buff链
接在该表项上。ARP在发出ARP请求报文的同时,还要设置一个ARP超时时钟。在对于其
ARP请求没有回应的情况下,ARP本身会重复几次操作,如果全部失败,就将新建的
arp_table表项删除,对应的sk_buff也将交给上层协议进行处理。UDP对这种情况不
进行任何处理,而TCP则可能会在已有的TCP连接上重传几次。但如果网络对于ARP请
求进行了正确的回答,ARP就能进行正确的地址转换,从而新建立的arp_table表项
就被标识为完整的,链在该项上的sk_buff结构也就能继续进行传送了,在此之前,
物理地址将被置入每一个sk_buff中。
ARP协议必须对那些针对于自身IP地址的ARP请求报文进行回答,此时协议将对其
协议类型(ETH_P_ARP)进行登记,同时生成packet_type类型的数据结构。这意味着
物理层所收到的所有ARP报文都应传给ARP协议。
网络的拓扑结构可能随着时间的变化而变化,相同的IP地址也可能被赋予不同的物
理地址,例如,拨号上网服务一般就将同一IP地址用于不同的连接。为了使ARP表中的
数据不过期,ARP使用一个周期性时钟对arp_table进行维护,将超时的表项删除。维
护时要注意的一点就是不要删除保存有硬件信息头的表项,因为其它的数据结构有可能要
使用这些信息。Arp_table中有一些表项是永久性的,不能删除。由于arp_table使
用的是内核空间,故该表不能太大,当该表已经到达所允许的上限值时,如果还要
增加新的表项,就将不得不删除表中存在时间最长的项。
10.7 IP路由
IP的路由功能决定应如何发送IP报文。事实上,传送IP包时要进行大量的信息判断,
比如目的地能否到达?如果目的地能到达,应使用那一个网卡?如果有多个网卡可
供使用,用哪一个更好些?IP路由信息数据库就是用来提供回答这些问题的信息。
该数据库主要包含两个部份,其中发送信息数据库(Forwarding Information Database)
是最重要的一个,它包含了所有已知的IP及到达该IP的最佳路由。另外,还有一个
路由cache,用于快速搜索IP路由。与其它cache一样,路由cache只包含发送信息数
据库中最常使用的那些路由。
路由是通过BSD socket界面的IOCTL请求进行填加与删除的,这些操作都将交给相
应的协议进行处理。INET协议层只允许拥有超级用户权力的进程对IP路由进行操作。
IP路由可以是固定的,也可以是随时间动态变化的。除路由器之外的大部份系统都使
用固定的IP路由。而路由器则不停地运行路由协议,对已有的路由进行检查。非路
由器系统一般都称为端系统,其中的路由协议以后台程序(如GATED)的形式对路由
进行维护,当然,也可以在BSD socket的IOCTL请求下对路由进行显式的增、删操作。
10.7.1 路由Cache
在查询IP路由时,系统首先在路由cache中查找,在找不到的情况下再在发送信息库
中搜索。如果两次都找不到,该IP包的发送就不能成功,由应用程序进行出错处理。
如果找到的路由在发送信息库中但不在路由cache中,则路由cache就将被修改,即在cache中
创建一个新表项并将该路由信息加入。路由cache事实上也是一个指针(ip_rt_hash_table),
其中每一项都指向一条由rtable数据组成的链表。对该表的检索也是以IP地址的后
两字节作为关键字进行,因为这两个字节能够较好地区别两个不同目的地,从而较好地对
该表进行哈希操作。Rtable的表项描述了有关路由的信息,比如目的IP地址、对应
的网卡、在该网卡上每次能传送数据的最大量等等,同时还包含索引计数、使用计
数以及最后一次使用的时戳等信息。其中索引计数值表示使用该路由的网络连接的
数量,它随着连接的变化而变化。使用记数信息记录了路由表被使用的次数,同时
它被用于对rtable中的表项进行排序。最近使用时戳被用来周期性的检测路由cache,
将过时的表项(即长时间不用的表项)删除。路由cache中的各路由按其使用频率进
行排列,使用频繁的排在前面,以便于路由的搜索操作。
10.7.2 发送信息数据库(Forwarding Information Database)
图10.5 发送信息数据库
发送信息数据库(如图10.5所示)指出了某时刻系统所能使用的IP路由。系统虽然
对数据库进行了合理有效的组织,但由于使用的数据结构较复杂,搜索的效率并不
是很高,尤其是在对每一个IP包都进行搜索时。系统使用路由cache来解决这一问题,
路由cache保存了一些已知的较优路径以加速IP包的传送。如上文所述,路由cache是
根据发送信息数据库来建立的。
IP子网信息保存在fib_zone结构中,而一个系统中的所有fib_zone结构都由fib_zones
哈希表中的指针进行控制。用IP子网的掩码作为对该表进行哈希操作的关键字。同
一子网中的路由信息保存在fib_node与fib_info这一对数据结构中,多条路由的信息就以
队列的形式组织,并由fib_zone中的fz_list指针指向该队列。如果某一子网中的路由数目
很大,系统就会使用一中哈希函数来进行对fib_node的搜索。
在某些情况下,通往某一IP子网的路由有多条并且这些路由可选择多个网关。但在
IP层,不允许通往同一子网的多条路由使用同一个网关,即如果到达某一子网有多
条路由,那么这些路由必须使用互不相同的网关。同时,还有一个参数(metric)
描述每一条路由的优越性,事实上,该参数记录了为了到达目的子网所必须经过的
IP子网数,显然,参数值越大,路由越差。
发布人:netbull 来自:linuxeden