第 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資料包將被首先送
往一個指定的機器,即通訊閘(gateway)(或路由器)。通訊閘(gateway)一般與多個IP子網相連,它把從一個
子網上接受到的、而目的位址在另一個子網的IP包轉發出去。比如,如果兩個子網16.42.1.0
與16.42.0.0被一個通訊閘(gateway)連接在一起,那麼從子網0發送到子網1的資料包都將被傳送到
通訊閘(gateway),由通訊閘(gateway)來決定傳輸路徑。每台本地機都有一個路由表以使IP包能夠正確的傳送。對
於每一個目的位址,在路由表中都有一項來指出發往這一位址的資料應送到那一台機器才
能使其能到達正確的目的地。另外,路由表是隨著應用程式對網路的應用以及網路
拓樸關系的變化而動態變化的。
圖10.1 TCP/IP通訊協定層
IP通訊協定處於傳輸層,這一層為其它層的通訊協定的資料傳送提供服務。TCP(Transmission
Control Protocol)通訊協定是一種可靠的端到端網路通訊協定,它就是使用IP的服務來完成
自身資料的發送與接收的。與IP封包相似,TCP封包也有自己的封包頭。TCP是一個面向連接
的通訊協定,即網路上兩台電腦在進行資料傳輸時,雖然在它們之間可能存在許多子網、
通訊閘(gateway)和路由器,但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位址,還要指出對方應用程式的port號,port號與應用程式
一一對應,標準的網路服務使用標準的port號,比如web伺服器使用80號port。port位址的使
用情況可以在檔案/etc/services中看到。
通訊協定的層次結構並非僅限於TCP,UDP與IP,IP通訊協定層本身就使用許多不同的實體介
質來傳輸IP封包,這些介質進行傳送時也有自己的通訊協定與通訊協定頭。已太網層就是一
個較好的例子,其他類似的例子還有PPP與SLIP等通訊協定。已太網允許多台主機同時連接到一
個實體電纜上,在其上傳輸的所有以太frame對連接在該網上的所有機器都是可見的,因
此每一台機器都應有唯一的實體位址,從而以太frame只能被具有其目的位址的機器所
接收,而其他機器將忽略之。實體位址是內嵌在已太網路卡中並且在網路卡制造時確定,
通常這一位址是保存在網路卡上的一個SROM2中。已太網的實體位址有6個位元,比如
08-00-2b-00-49-A4。某些特殊的已太網實體位址被保留用作廣播,即以這些位址作
為目的位址的已太網實體frame將為該網上所有的主機所接收。以太frame可以通過在資料
frame的frame頭上包含一個標誌的方式來傳送多種不同通訊協定(資料),這就使得已太網能
夠正確地接收IP資料包並將其上傳給IP通訊協定層。
為了能夠通過像已太網這樣的支援多連接的實體網傳送IP封包,IP層通訊協定必須能獲
得對方主機的已太網實體位址。因為IP位址僅僅是一種概念上的位址體系,已太網實
體設備自身有它們自己的位址體系。另一方面,IP位址並不固定,可以為網路管理員隨意
設置與修改,而已太網路卡則只接收與自身位址相對應的以太frame或廣播frame。Linux利用ARP
(Address Resolution Protocol)來進行從IP位址向諸如已太網的實體位址等實體
位址的轉換工作。當一個主機想知道與一個IP位址相對應的實體位址時,該主機就
通過廣播的方式向網上的所有機器發送ARP請求封包,該封包中包含有希望被解析的
IP位址。當使用此IP位址的機器接收到這一請求封包時,就用ARP回答封包回應,在
該封包中包含其實體位址。ARP通訊協定並不僅僅侷限於已太網設備,它還可為其它諸如
FDDI等實體介質解析IP位址。對於那些不能使用ARP進行解析的實體介質在Linux中
都被作標記,從而系統運行時將不會使用ARP。另外還有一種通訊協定叫RARP(ReverseARP)用
於反向解析,即將實體位址解析為IP位址。這種通訊協定一般用於通訊閘(gateway),它對包含遠地
網的IP位址位址ARP請求進行回應。
10.2 Linux中的TCP/IP網路層次結構
和網路通訊協定一樣,Linux在實現互連網位址通訊協定簇時也將其實現為一系列相互依賴的
以層次結構組織的軟體。BSDsocket由一個基本的socket管理軟體支援。而為其提供
支援的下層軟體為INETsocket層,這一層軟體統一管理利用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 BSDsocket
BSDsocket是一個通用的系統介面,它不僅支援各種形式的網路連接,同時還是一種
程序間的通信機制。socket可以描述通信連接一端的運行狀態,對於參與通信的兩
個不同程序有不同的socket與之對應。事實上,socket可以被看作為一種管道,只
不過與管道不同的是,它對於其中所能容納的資料量沒有限制。Linux支援多種不同
類型的socket,由於不同類型的socket在通信時進行尋址的方法不同,socket又被
稱為位址族。Linux支援以下幾種socket位址族或socket域:
UNIX Unix域的socket
INET 該域的socket提供對通過TCP/IP的通信
AX25 適用於Amateur radio X25通訊協定
IPX 適用於Novell IPX通訊協定
APPLETALK 適用於Appletalk DDP通訊協定
X25 適用於X25通訊協定
socket系統中存在幾種不同的socket類型,不同的類型代表了不同的網路服務,但
並不是每一種位址族都支援所有類型的服務。LinuxBSDsocket系統支援以下幾種套接
字類型:
Stream
這種類型的socket提供可靠的、雙向、有序的資料流傳輸,這種通信可以保証數
據不被丟失、破壞或重復。在Internet(INET)位址族中,Stream類型的socket由TCP通訊協定
支援。
Datagram
這種類型的socket也提供雙向的資料傳輸,只不過與stream類型的socket不同
的是,它不保証資料能夠達目的地,即使資料抵達目的地了,也不能保証資料的正
確性,即它不能保証資料不被破壞或重復。在Internet位址族中,這種類型的socket由
UDP通訊協定支援。
Raw
該類型的socket允許程序直接對下層通訊協定進行操作,這也是為何稱其為raw的原因。
比如,可以使用該類型的socket直接對已太網設備層進行操作從而的到原始的IP數
據包。
Reliable Delivered Messages
該類型與datagram基本相同,唯一區別在於它保証資料能夠
到達目的地。
Sequenced Packets
該類型與stream類型相似,唯一區別在於該類型的資料包大小是固定
的。
Packet 這種類型不是標準的BSDsocket類型,它是Linux自身對socket系統的擴展,它
允許程序直接對設備層的資料包進行操作。
利用socket進行通信的程序使用客戶端/伺服器模式,即伺服器提供某些服務,客戶
機則使用這些服務。比如,一個Web伺服器提供一些web網頁,web客戶端程式比如瀏覽
器等就可以瀏覽這些網頁。如果一個服務端程式要通過socket提供服務,那它首先要
創建一個socket然後再為其綁定一個名字,這種名字實際上指明了服務程式在本地
系統中的位址,名字的格式與socket所屬的位址族相關,socket的名字或位址用sockaddr這
一資料結構來指定。對於INET域的socket還要綁定一個IPport位址(port號),端
口號的使用情況可以在檔案/etc/services中看到﹔比如,web伺服器所使用的port
號為80。在socket綁定位址以後,服務程式就在該位址上偵聽針對這一位址的服務
請求。客戶方程式提出服務請求時,也要創建一個socket然後指定目的位址並建立
一個與對方socket的連接。對於INET域的socket目的位址就是伺服器的IP位址與其
port號。客戶方的服務請求到達伺服器後要通過各層通訊協定一直到達服務程式的偵聽
socket,服務程式在發現服務請求後只有兩種選擇:接受與拒絕。如果一個服務請
求被接受,服務程式就會建立一個新的socket而不是使用偵聽socket來繼續完成相
應的服務操作,因為用於偵聽的socket是不能用於支援連接的。在正確地建立了連
接後,連接的雙方就可以方便地進行資料的傳送與接收,當此連接不再需要時,可
以被關閉。在整個過程中,都有相應的措施保証資料能夠被正確的處理。
在BSDsocket上操作的具體含義取決於相應的位址族。比如建立一個TCP/IP連接和
建立一個amateur radio X.25連接是不同的。類似於虛擬檔案系統,Linux將BSD
socket層中與應用程式關系密切的那一部份抽取出來成為socket界面,並有專門的
軟體支援。在核心啟動時,各位址通訊協定族連同其BSD socket界面一起在核心中登記
註冊,這樣,在應用程式使用BSD socket時,就可以方便地在BSDsocket與相應支援
軟體之間建立聯繫。這種聯繫是通過交叉連接的資料結構以及其它一些信息表建立
的。比如在創建新的socket時,就有相應的創建例程進行支援。
核心在配置時,各層通訊協定的對應號碼被填入通訊協定向量中,通訊協定向量中的每一項都由
相應的名來表示,比如,“INET”與其初始化例程的位址。在系統引導時,socket接
口被初始化,與此同時,各通訊協定的初始化例程也將被調用。對於該socket通訊協定而言,
這一步驟將會產生一組通訊協定操作,即一組例程,其中每一個例程都將執行針對該地
址族的一些特定操作。所有這些通訊協定操作被保存在一個指向proto_ops 結構的指標
向量pops中。
proto_ops結構包含位址族的類型以及一組指向針對該位址族的socket操作例程指標。
Pops向量可以由位址族辨識器來檢索,比如,Internet位址族對應於AF_INET(在Linux
中被賦值2)。
圖10.3 Linux 中BSD Socket 的資料結構
10.4 INETsocket系統
INETsocket系統支援包括TCP/IP通訊協定在內整個internet位址通訊協定族。由上文討論可
知,整個通訊協定族是層次化的,下層的通訊協定為上層的通訊協定提供服務,這種層次化結構
也體現在Linux的TCP/IP原始碼所使用的資料結構中。各層通訊協定與BSDsocket層的操
作界面是一組Internet域的socket操作,這些操作一般在網路初始化時在BSDsocket
層中登記,這些登記信息連同其他通訊協定族的信息一同保存在pops向量中。BSDsocket
系統通過proto_ops結構調用INETsocket層所支援的例程來完成相應的工作,比如,
BSDsocket使用INET域中的位址提出請求時就要使用到INET層的例程。在該操作中,
BSDsocket系統把代表BSDsocket的資料結構交給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 可以像檔案handler那樣用於對普通檔案的操作,但檔案都是用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位址,並最好同時提供port號。一般情況下,用於綁定的IP位址應有相應網路設
備提供支援,它應支援INET位址族並且其相應的界面可以提供服務。用戶可以用ifconfig命
令來查看哪一個網路界面正在被使用。上面的IP位址還有可能是全1或全0的廣播地
址,意為要將資料傳給所有的機器。如果一個機器充當代理或防火牆則它的IP位址
可以被指定為任何IP,但只有擁有超級用戶權力的程序才能綁定任何一個IP位址。
被綁定的IP位址被保存在sock結構中的recv_addr與saddr域中,分別用於雜湊表的
搜索與發送資料,其中port號是可選的,如果用戶沒有指定,則系統會自動申請一
個空餘的port,按照慣例,小於1024的port號在沒有得到管理員的允許的情況下不
應使用,網路系統自己分配port號時,一般也只分配大於1024的port。
下層網路設備必須將接收到的資料包交給正確的INET與BSDsocket才能進行處理。
為此,UDP與TCP利用一些雜湊表進行輸入IP資料的信息搜索從而將資料包正確地交
給socket/sock資料。由於TCP是一個面向連接的的通訊協定,因而處理TCP包所需的信息要
比處理UDP包多。
UDP中有一個雜湊表udp_hash,它保存了所有已分配的UDPport。事實上,該表保存
的是指向sock資料結構的指標,可以利用port號進行檢索。由於UDP雜湊表的表項要
比可能的port號數量少的多(udp_hash只有UDP_HTABLE_SIZE=128個表項),因而該
表的某些表項是一個sock資料組成的鏈結串列。
與UDP相比,TCP通訊協定相對就比較復雜,它要維護多個雜湊表。但在綁定操作時,已
被綁定的sock資料並不立即加入TCP的雜湊表中,只是檢查一下所使用的的port號是
否已被使用。Sock資料是在執行偵聽操作時插入雜湊表中的。
10.4.3 利用INET BSD Socket創建連接
一個socket被創建之後,如果還沒有用於偵聽連接請求,該socket可用來發出連接
請求。在進行連接時,面向連接的TCP通訊協定需要在雙方之間建立一條虛擬鏈路,而對面
向非連接的UDP通訊協定而言則不需作這些工作。
連接請求的發出只能在某些處於特定狀態的INET BSD
socket上進行,也就是說,只有那些還沒有建立連接並且也沒有用於偵聽連接請求
的socket上才能發出連接請求。在系統中,BSD socket應處於SS_UNCONNECTED狀態。
UDP通訊協定不需建立虛連接,而直接以資料報的形式進行通信,同時發出的資料不能保
証能夠到達目的地。但該通訊協定支援對BSDsocket上的連接,只不過在建立連接時,不
建立虛連接而只設置遠地程式的IP位址與IPport,同時設置路由快取,以使以後在
該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自動
地綁定一個尚未使用的port號。在此之後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另一端
的通訊閘(gateway)。對於已太網上的電腦而言,路由的選擇則比較困難,因為在同一個網上連
接有許多電腦。在發送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子網的路由有多條並且這些路由可選擇多個通訊閘(gateway)。但在
IP層,不允許通往同一子網的多條路由使用同一個通訊閘(gateway),即如果到達某一子網有多
條路由,那麼這些路由必須使用互不相同的通訊閘(gateway)。同時,還有一個參數(metric)
描述每一條路由的優越性,事實上,該參數記錄了為了到達目的子網所必須經過的
IP子網數,顯然,參數值越大,路由越差。