Linux Kernel 核心中文手冊(10)--網絡

Networks (網絡)
 
    Linux 和網絡幾乎是同義詞。實際上 Linux 是 Internet 或 WWW 的產物。它的開
發者和用戶使用 web 交換信息、想法、代碼而 Linux 自身也常用於支持一些組織的聯
網需求。本章描述了 Linux 如何支持統稱爲 TCP/IP 的網絡協議。
    TCP/IP 協議設計用來支持連接在 ARPANET 上的計算機之間的通訊。 ARPANET 是美
國政府投資的一個美國的研究網絡。 ARPANET 是一些網絡概念的先驅,例如報文交換和
協議分層,讓一種協議利用其它協議提供的服務。 ARPANET 於 1988 年退出,但是它的
後繼者( NSF NET 和 Internet )發展的甚至更大。現在所知的 World Wide Web 是在
 ARPANET 中發展的,它本身也是由 TCP/IP 協議支持的。 Unix 在 ARPANET 上大量使
用,第一個發佈的網絡版的 Unix 是 4.3BSD 。 Linux 的網絡實現是基於 4.3BSD 的模
型,它支持 BSD socket (和一些擴展)和全系列的 TCP/IP 網絡功能。選擇這種編程
接口是因爲它的流行程度,而且可以幫助程序在 Linux 和其它 Unix 平臺之間移植。


10.1 An Overview of TCP/IP Networking ( TCP/IP 網絡概覽)
    本節爲 TCP/IP 網絡的主要原理給出了一個概覽。這並不是一個詳盡的描述。要更
詳細的描述,閱讀第 10 本參考書(附錄)。
    在一個 IP 網絡中,每一個機器都分配一個 IP 地址,這是一個 32 位的數字,唯
一標識這一臺機器。 WWW 是一個非常巨大、不斷增長的 IP 網絡,每一個連接在上面的
機器都分配了一個獨一無二的 IP 地址。 IP 地址用點分隔的四個數字表示,例如, 1
6.42.0.9 。 IP 地址實際上分爲兩個部分:網絡地址和主機地址。這些地址的大小(尺
寸)可能不同(有幾類 IP 地址),以 16.42.0.9 爲例,網絡地址是 16.42 ,主機地
址是 0.9 。主機地址可以進一步劃分成爲子網( subnetwork )和主機地址。再次以
16.42.0.9 爲例,子網地址可以是 16.42.0 ,主機地址爲 16.42.0.9 。對於 IP 地址
進行進一步劃分允許各個組織劃分它們自己的網絡。例如,假設 16.42 是 ACME 計算機
公司的網絡地址, 16.42.0 可以是子網 0 , 16.42.1 可以是子網 1 。這些子網可以
在分離的大樓裏,也許通過電話專線或者甚至通過微波連接。 IP 地址由網絡管理員分
配,使用 IP 子網是分散網絡管理任務的一個好辦法。 IP 子網的管理員可以自由地分
配他們自己子網內的 IP 地址。
    但是,通常 IP 地址難於記憶,而名字更容易記憶。 Linux.acme.com 比 16.42.0
.9 更好記。必須使用一種機制把網絡名字轉換爲 IP 地址。這些名字可以靜態地存在
/etc/hosts 文件中或者讓 Linux 詢問一個分佈式命名服務器( Distributed Name Se
rver DNS )來解析名字。這種情況下,本地主機必須知道一個或多個 DNS 服務器的 I
P 地址,在 /etc/resolv.conf 中指定。
    不管什麼時候你連接另外一臺機器的時候,比如讀取一個 web page ,都要使用它
的 IP 地址和那臺機器交換數據。這種數據包括在 IP 報文( packet )中,每一個報


文都有一個 IP 頭(包括源和目標機器的 IP 地址,一個校驗和和其它有用的信息。這
個校驗和是從 IP 報文的數據中得到的,可以讓 IP 報文的接收者判斷傳輸過程中 IP
報文是否損壞(可能是一個噪音很大的電話線)。應用程序傳輸的數據可能被分解成容
易處理的更小的報文。 IP 數據報文的大小依賴於連接的介質而變化:以太網報文通常
大於 PPP 報文。目標主機必須重新裝配這些數據報文,然後才能交給接收程序。如果你
通過一個相當慢的串行連接訪問一個包括大量圖形圖像的 web 頁,你就可以用圖形的方
式看出數據的分解和重組。
    連接在同一個 IP 子網的主機可以互相直接發送 IP 報文,而其它的 IP 報文必須
通過一個特殊的主機(網關)發送。網關(或路由器)連接在多於一個子網上,它們會
把一個子網上接收的 IP 報文重新發送到另一個子網。例如,如果子網 16.42.1.0 和
16.42.0.0 通過一個網關連接,那麼所有從子網 0 發送到子網 1 的報文必須先發送到
網關,這樣才能轉發。本地的主機建立一個路由表,讓它可以把要轉發的 IP 報文發送
到正確的機器。對於每一個 IP 目標,在路由表中都有一個條目,告訴 Linux 要到達目
標需要先把 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 地址,也要指定目標應用程序的端口( port )地址。一個端口地址
唯一標識一個應用程序,標準的網絡應用程序使用標準的端口地址:例如 web 服務器使
用端口 80 。這些已經註冊的端口地址可以在 /etc/services 中查到。
    協議分層不僅僅停留在 TCP 、 UDP 和 IP 。 IP 協議本身使用許多不同的物理介
質和其它 IP 主機傳輸 IP 報文。這些介質自己也可能增加它們自己的協議頭。這樣的
例子有以太網層、 PPP 和 SLIP 。一個以太網允許許多主機同時連接在一個物理電纜上
。每一個傳送的以太幀可以被所有連接的主機看到,所以每一個以太網設備都有一個獨
一無二的地址。每一個傳送到那個地址的以太網幀會被那個地址的主機接收,而被連接
到這個網絡的其它主機忽略掉。這個獨一無二的地址當每一個以太網設備製造的時候內
建在設備裏邊,通常保存在以太網卡的 SROM 中。以太地址由 6 個字節長,例如,可能
是 08-00-2b-00-49-4A 。一些以太網地址保留用於多點廣播,用這種目標地址發送的以
太網幀會被網絡上的所有的主機接收。因爲以太網幀中可能運載許多不同的協議(作爲
數據),和 IP 報文一樣,它們的頭中都包含一個協議標識符。這樣以太網層可以正確
地接收 IP 報文並把數據傳輸到 IP 層。
    爲了通過多種連接協議,例如通過以太網來傳輸 IP 報文, IP 層必須找出這個 I
P 主機的以太網地址。這是因爲 IP 地址只是一個尋址的概念,以太網設備自己有自己


的物理地址。 IP 地址可以由網絡管理員根據需要分配和再分配,而網絡硬件則只響應
具有它自己物理地址的以太網幀,或者特殊的多點廣播地址(所有的機器都必須接收)
。 Linux 使用地址解析協議( ARP )讓機器把 IP 地址轉換成真實的硬件地址例如以
太網地址。爲了得到一個 IP 地址所聯繫的硬件地址,一個主機會發送一個 ARP 請求包
,包含它希望轉換的 IP 地址,發送到一個多點廣播地址,讓網絡上所有的點都可以收
到。具有這個 IP 地址的目標主機用一個 ARP 迴應來應答,這中間包括了它的物理硬件
地址。 APR 不僅僅限制在以太網設備,它也可以解析其它物理介質的 IP 地址,例如
FDDI 。不能進行 ARP 的設備會有標記,這樣 Linux 就不需要試圖對它們進行 ARP 。
也有一個相反的功能,反向 ARP ,或 RARP ,把物理地址轉換到 IP 地址。這用於網關
,迴應對於代表遠端網絡的 IP 地址的 ARP 請求。
10.2 The Linux TCP/IP Networking Layers ( Linux TCP/IP 網絡分層)
    象網絡協議一樣,圖 10.2 顯示了 Linux 對於 internet 協議地址族的實現就好像
一系列連接的軟件層。 BSD socket 由只和 BSD socket 相關的通用的 socket 管理軟
件來支持。支持這些的是 INET socket 層,它管理以 IP 爲基礎的協議 TCP 和 UDP 的
通訊端點。 UDP 是一個無連接的協議,而 TCP 是一個可靠的端到端的協議。當傳送 U
DP 報文的時候, Linux 不知道也不關心它們是否安全到達目的地。 TCP 報文進行了編
號, TCP 連接的每一端都要確保傳送的數據正確地接收到。 IP 層包括了網際協議(
Internet Protocol )的代碼實現。這種代碼在傳送的數據前增加 IP 頭,而且知道如
何把進來的 IP 報文轉送到 TCP 或者 UDP 層。在 IP 層之下,支持 Linux 聯網的是網
絡設備,例如 PPP 和以太網。網絡設備並非總是表現爲物理設備:其中一些比如 loop
back 設備只是純粹的軟件設備。不象標準的 Linux 設備用 mknod 命令創建,網絡設備
只有在底層的軟件找到並且初始化它們之後纔出現。你只有在建立俄一個包含恰當的以


太望設備驅動程序的核心之後你才能看到設備文件 /dev/eth0 。 ARP 協議位於 IP 層
和支持 ARP 的協議之間。
10.3 The BSD Socket Interface ( BSD socket 接口)
    這是一個通用的接口,不僅僅支持多種形式的聯網,也是一種進程間通訊機制。一
個 socket 描述了通訊連接的一端,兩個通訊進程每一個都會有一個 socket ,描述它
們之間通訊連接的自己部分。 Socket 可以想象成一種特殊形式的管道,但是和管道不
同, socket 對於可以容納的數據量沒有限制。 Linux 支持幾種類型的 socket ,這些
類叫做 address families (地址族)。這是因爲每一類都有自己通訊尋址方式。 Lin
ux 支持以下 socket address families 或 domain :
UNIX Unix domain sockets,
INET The Internet address family supports communications via
TCP/IP protocols
AX25 Amateur radio X25
IPX Novell IPX
APPLETALK Appletalk DDP
X25 X25
    有幾種 socket 類型,每一種都代表了連接上支持的服務的類型。並非所有的 add
ress families 都支持所有類型的服務。 Linux BSD socket 支持以下 socket 類型。
 
    Stream 這種 socket 提供了可靠的、雙向順序的數據流,保證傳輸過程中數據不會
丟失、損壞或重複。 Stream socket 在 INET address family 中由 TCP 協議支持
    Datagram 這種 socket 也提供了雙向的數據傳輸,但是和 stream socket 不同,


它不保證消息會到達。甚至它到達了也不保證它們會順序到達或沒有重複或損壞。這種
類型的 socket 在 Internet address family 中由 UDP 協議支持。
    RAW 這允許進程直接(所以叫“ raw ”)訪問底層的協議。例如,可以向一個以太
網設備打開一個 raw socket ,觀察 raw IP 數據流。
    Reliable Delivered Messages 這很象數據報但是數據保證可以到達
    Sequenced Packets 象 stream socket 但是數據報文大小是固定的
    Packet 這不是標準的 BSD socket 類型,它是 Linux 特定的擴展,允許進程直接
在設備層訪問報文
    使用 socket 通訊的進程用一個客戶服務器的模型。服務器提供服務,而客戶使用
這種服務。一個這樣的例子是一個 Web 服務器,提供 web page 和一個 web 客戶(或
瀏覽器),讀取這些頁。使用 socket 的服務器,首先創建一個 socket ,然後爲它 b
ind 一個名字。這個名字的格式和 socket 的 address family 有關,它是服務器的本
地地址。 Socket 的名字或地址用 sockaddr 數據結構指定。一個 INET socket 會綁定
一個 IP 端口地址。註冊的端口編號可以在 /etc/services 中看到:例如, web 服務
器的端口是 80 。在 socket 上綁定一個地址後,服務器就 listen 進來的對於綁定的
地址的連接請求。請求的發起者,客戶,創建一個 socket ,並在上面執行一個連接請
求,指定服務器的目標地址。對於一個 INET socket ,服務器的地址是它的 IP 地址和
它的端口地址。這些進來的請求必須通過大量的協議層,找到它的路徑,然後就在服務
器的監聽端口等待。一旦服務器接收到了進來的請求,它可以接受( accept )或者拒
絕它。如果要接受進來的請求,服務器必須創建一個新的 socket 來接受它。一旦一個
 socket 已經用於監聽進來的連接請求,它就不能再用於支持一個連接。連接建立之後
,兩端都可以自由地發送和接收數據。最後,當一個連接不再需要的時候,它可以被關


閉。必須小心,保證正確地處理正在傳送的數據報文。
    一個 BSD socket 上的操作的確切意義依賴於它底層的地址族。建立一個 TCP/IP
連接和建立一個業餘無線電 X.25 連接有很大的不同。象虛擬文件系統一樣, Linux 在
和獨立的地址族相關的軟件所支持的 BSD socket 層抽象了 BSD socket 和應用程序之
間的 socket 接口。當核心初始化的時候,建立在覈心的地址族就向 BSD socket 接口
登記自己。稍後,當應用程序創建和使用 BSD socket 的時候,在 BSD socket 和它的
支撐地址族之間建立一個聯繫。這種聯繫是通過交叉的數據結構和地址族支持例程表實
現的。例如,當應用程序創建一個新的 socket 的時候, BSD socket 接口就使用地址
族相關的 socket 創建例程。
    當配置核心的時候,一組地址族和協議都建立到了 protocols 向量表中。每一個都
用它的名稱(例如“ INET ”)和它的初始化例程的地址來代表。當啓動的時候, soc
ket 接口初始化,每一個協議的初始化代碼都要被調用。對於 socket 地址族,它們裏
邊會登記一系列協議操作。這都是一些例程,每一個都執行一個和地址族相關的特殊操
作。登記的協議操作保存在 pops 向量表中,這個向量表保存指向 proto_ops 數據結構
的指針。 Proto_ops 數據結構包括協議族類型和一批和特定地址族相關的 socket 操作
例程的指針。 Pops 向量表用地址族的標識符作爲索引,例如 Internet address fami
ly 的標識符( AF_INET 是 2 )。
參見 include/linux/net.h
10.4 The INET Socket Layer
    INET socket 層支持包含 TCP/IP 協議的 internet address family 。象上面討論
的,這些協議是分層的,每一個協議都使用其它協議的服務。 Linux 的 TCP/IP 代碼和
數據結構反映了這種分層。它和 BSD socket 層的接口是通過網絡初始化的時候它向 B


SD socket 層登記的 internet address family socket 操作進行的。這些和其它登記
的地址族一起放在 pops 向量表中。 BSD socket 層通過調用在登記的 proto_ops 數據
結構中的 INET 層的 socket 支持例程完成它的工作。例如,一個地址族是 INET 的 B
SD socket 創建請求會使用底層的 INET socket 創建函數。每一次操作 BSD socket 層
都把代表 BSD socket 的 socket 數據結構傳遞給 INET 層。 INET socket 層使用它自
己的數據結構 socket ,連接到 BSD socket 數據結構,而不是用 TCP/IP 相關的信息
把 BSD socket 搞亂。這種連接參見圖 10.3 。它使用 BSD socket 中的 data 指針把
 sock 數據結構和 BSD socket 數據結構連接起來。這意味着後續的 INET socket 調用
可以很容易地獲取這個 sock 數據結構。在創建的時候 sock 數據結構的協議操作指針
也被建立,這些指針依賴於請求的協議。如果請求 TCP ,則 sock 數據結構的協議操作
指針會指向 TCP 連接所需要的一系列 TCP 協議的操作。
參見 include/net/sock.h
10.4.1 Creating a BSD Socket (創建一個 BSD Socket )
    創建一個新的 socket 的系統調用需要傳遞它的地址族的標識符、 socket 的類型
和協議。首先,用請求的地址族在 pops 向量表中查找一個匹配的地址族。它可能是一
個使用核心模塊實現的特殊的地址族,如果這樣, kerneld 核心進程必須加載這個模塊
,我們才能繼續。然後分配一個新的 socket 數據結構來表示這個 BSD socket 。實際
上這個 socket 數據結構物理上是 VFS inode 數據結構的一部分,分配一個 socket 實
際上就是分配一個 VFS inode 。這看起來比較奇怪,除非你考慮讓 socket 可以用和普
通文件一樣的方式進行操作。象所有文件都用 VFS inode 數據結構表示一樣,爲了支持
文件操作, BSD socket 也必須用一個 VFS inode 數據結構表示。
    這個新創建的 BSD socket 數據結構包括一個指針指向和地址族相關的 socket 例


程,這個指針被設置到從 pops 向量表中取出的 proto_ops 數據結構。它的類型被設置
成請求的 socket 類型: SOCK_STREAM 、 SOCK_DGRAM 等等其中之一,然後用 proto_
ops 數據結構中保存的地址調用和地址族相關的創建例程。
    然後從當前進程的 fd 向量表中分配一個空閒的文件描述符,它所指向的 file 數
據結構也被初始化。這包括設置文件操作指針,指向 BSD socket 接口支持的 BSD soc
ket 文件操作例程。所有將來的操作會被定向到 socket 接口,依次通過調用支撐的地
址族的操作例程傳遞到相應的地址族。
10.4.2 Binding an Address to an INET BSD Socket (爲一個 INET BSD socket 綁定
一個地址)
    爲了監聽進來的網際連接請求,每一個服務器必須創建一個 INET BSD socket 並把
自己的地址綁定到它上面。 Bind 的操作大部分由 INET socket 層處理,另一些需要底
層的 TCP 和 UDP 協議層的支持。已經綁定了一個地址的 socket 不能用於其它通訊。
這意味着這個 socket 的狀態必須是 TCP_CLOSE 。傳遞給 bind 操作的 sockaddr 包括
要綁定的 IP 地址和一個端口號(可選)。通常,綁定的地址會是分配給支持 INET 地
址族的網絡設備的地址之中的一個,而且接口必須是開啓的並能夠使用。你可以用 ifc
onfig 命令看系統中哪一個網絡接口當前是激活的。 IP 地址也可以是 IP 廣播地址(
全是 1 或 0 )。這是意味着“發送給每一個人”的特殊地址。如果這個機器作爲一個
透明的 proxy 或者防火牆,這個 IP 地址也可以設置成任何 IP 地址。不過只有具有超
級用戶特權的進程可以綁定任意 IP 地址。這個綁定的 IP 地址被存在 sock 數據結構
的 recv_addr 和 saddr 域中。它們分別用於 hash 查找和發送 IP 地址。端口號是可
選的,如果沒有設置,會向支撐的網絡請求一個空閒的。按照慣例,小於 1024 的端口
號不能被沒有超級用戶特權的進程使用。如果底層的網絡分配端口號,它總是分配一個


大於 1024 的端口。
    當底層的網絡設備接收報文的時候,這些報文必須被轉到正確的 INET 和 BSD soc
ket 才能被處理。爲此, UDP 和 TCP 維護 hash table ,用於查找進來的 IP 信息的
地址,把它們轉到正確的 socket/sock 對。 TCP 是一個面向連接的協議,所以處理 T
CP 報文比處理 UDP 報文所包括的信息要多。
    UDP 維護一個已經分配的 UDP 端口的 hash table , udp_table 。這包括 sock
數據結構的指針,用一個根據端口號的 hash 函數作爲索引。因爲 UDP hash table 比
允許的端口號要小的多( udp_hash 只有 128 , UDP_HTABLE_SIZE )表中的一些條目
指向一個 sock 數據結構的鏈表,用每一個 sock 的 next 指針連接在一起。
    TCP 更加複雜,因爲它維護幾個 hast table 。但是,在綁定操作中, TCP 實際上
並不把綁定的 sock 數據結構加到它的 hash table 中,它只是檢查請求的端口當前沒
有被使用。在 listen 操作中 sock 數據結構才加到 TCP 的 hash table 中。
10.4.3 Making a Connection to an INET BSD Socket
    一旦創建了一個 socket ,如果沒有用於監聽進來的連接請求,它就可以用於建立
向外的連接請求。對於無連接的協議,比如 UDP ,這個 socket 操作不需要做許多,但
是對於面向連接的協議如 TCP ,它涉及在兩個應用程序之間建立一個虛擬電路。
    一個向外的連接只能在一個正確狀態的 INET BSD socket 上進行:就是說還沒有建
立連接,而且沒有用於監聽進來的連接。這意味着這個 BSD socket 數據結構必須在 S
S_UNCONNECTED 狀態。 UDP 協議不在兩個應用程序之間建立虛擬連接,所有發送的消息
都是數據報,發出的消息可能到到也可能沒有到達它的目的地。但是,它也支持 BSD s
ocket 的 connect 操作。在一個 UDP INET BSD socket 上的一個連接操作只是建立遠
程應用程序的地址:它的 IP 地址和它的 IP 端口號。另外,它也要建立一個路由表條


目的緩存區,這樣,在這個 BSD socket 上發送的 UDP 數據報不需要在檢查路由表數據
庫(除非這個路由變成無效)。這個緩存的路由信息被 INET sock 數據結構中的 ip_r
oute_cache 指針指向。如果沒有給出地址信息,這個 BSD socket 發送的消息就自動使
用這個緩存的路由和 IP 地址信息。 UDP 把 sock 的狀態改變成爲 TCP_ESTABLISHED

    對於在一個 TCP BSD socket 上進行的連接操作, TCP 必須建立一個包括連接信息
的 TCP 消息,併發送到給定的 IP 目標。這個 TCP 消息包括連接的信息:一個獨一無
二的起始消息順序編號、發起主機可以管理的消息的最大尺寸、發送和接收的窗口大小
等等。在 TCP 中,所有的消息都編了號,初始順序編號用作第一個消息編號。 Linux
選擇一個合理的隨機數以避免惡意的協議攻擊。每一個從 TCP 連接的一端發送,被另一
端成功接收的消息被確認,告訴它成功地到達,而且沒有損壞。沒有確認的消息會被重
發。發送和接收窗口大小是確認前允許的消息的數目。如果接收端的網絡設備支持的最
大消息尺寸比較小,則這個連接會使用兩個中間最小的一個。執行向外的 TCP 連接請求
的應用程序現在必須等待目標應用程序的響應,是接受還是拒絕這個連接請求。對於期
望進來的消息的 TCP sock ,它被加到了 tcp_listening_hash ,這樣進來的 TCP 消息
可以定向到這個 sock 數據結構。 TCP 也啓動計時器,這樣如果目標應用程序對於請求
不響應,向外的連接請求會超時。
10.4.4 Listening on an INET BSD Socket
    一旦一個 socket 擁有了一個綁定的地址,它就可以監聽指定這個綁定地址的進來
的連接請求。一個網絡應用程序可以不綁定地址直接在一個 socket 上監聽,這種情況
下, INET socket 層找到一個未用的端口號(對於這種協議而言),自動把它綁定到這
個 socket 上。這個 socket 的 listen 函數把 socket 變成 TCP_LISTEN 的狀態,並


且執行所需的和網絡相關的工作,一邊允許進來的連接。
    對於 UDP socket ,改變 socket 的狀態已經足夠,但是 TCP 已經激活它現在要把
 socket 的 sock 數據結構加到它的兩個 hash table 中。這是 tcp_bound_hash 和 t
cp_listening_hash 表。這兩個表都通過一個基於 IP 端口號的 hash 函數進行索引。
 
    不論何時接收到一個對於激活的監聽 socket 的進來的 TCP 連接請求, TCP 都要
建立一個新的 sock 數據結構表示它。這個 sock 數據結構在它最終被接受之前成爲這
個 TCP 連接的 buttom half 。它也克隆包含連接請求的進來的 sk_buff 並把它排在監
聽的 sock 數據結構的 receive_queue 隊列中。這個克隆的 sk_buff 包括一個指針,
指向這個新創建的 sock 數據結構。
10.4.5 Accepting Connection Requests
    UDP 不支持連接的概念,接受 INET socket 的連接請求只應用於 TCP 協議,在一
個監聽的 sock 上進行接受( accept )操作會從原來的監聽的 socket 克隆出一個新
的 socket 數據結構。然後這個 accept 操作傳遞給支撐的協議層,在這種情況下,是
 INET 去接受任何進來的連接請求。如果底層的協議,比如 UDP 不支持連接, INET 協
議層的 accept 操作會失敗。否則,連接的請求會傳遞到真正的協議,在這裏,是 TCP
 。這個 accept 操作可能是阻塞,也可能是非阻塞的。在非阻塞的情況下,如果沒有需
要 accept 的進來的連接,這個 accept 操作會失敗,而新創建的 socket 數據結構會
被廢棄。在阻塞的情況下,執行 accept 操作的網絡應用程序會被加到一個等待隊列,
然後掛起,直到接收到一個 TCP 的連接請求。一旦接收到一個連接請求,包含這個請求
的 sk_buff 會被廢棄,這個 sock 數據結構被返回到 INET socket 層,在這裏它被連
接到先前創建的新的 socket 數據結構。這個新的 socket 的文件描述符( fd )被返


回給網絡應用程序,應用程序就可以用這個文件描述符對這個新創建的 INET BSD sock
et 進行 socket 操作。
10.5 The IP Layer ( IP 層)
10.5.1 Socket Buffers
    使用分成許多層,每一層使用其它層的服務,這樣的網絡協議的一個問題是,每一
個協議都需要在傳送的時候在數據上增加協議頭和尾,而在處理接收的數據的時候需要
刪除。這讓協議之間傳送數據緩衝區相當困難,因爲每一層都需要找出它的特定的協議
頭和尾在哪裏。一個解決方法是在每一層都拷貝緩衝區,但是這樣會沒有效率。替代的
, Linux 使用 socket 緩衝區或者說 sock_buffs 在協議層和網絡設備驅動程序之間傳
輸數據。 Sk_buffs 包括指針和長度域,允許每一協議層使用標準的函數或方法操縱應
用程序數據。
 
 
圖 10.4 顯示了 sk_buff 數據結構:每一個 sk_buff 都有它關聯的一塊數據。 Sk_bu
ff 有四個數據指針,用於操縱和管理 socket 緩衝區的數據
參見 include/linux/skbuff.h
head 指向內存中的數據區域的起始。在 sk_buff 和它相關的數據塊被分配的時候確定
的。
Data 指向協議數據的當前起始爲止。這個指針隨着當前擁有這個 sk_buff 的協議層不
同而變化。
Tail 指向協議數據的當前結尾。同樣,這個指針也隨擁有的協議層不同而變化。
End 指向內存中數據區域的結尾。這是在這個 sk_buff 分配的時候確定的。


    另有兩個長度字段 len 和 truesize ,分別描述當前協議報文的長度和數據緩衝區
的總長度。 Sk_buff 處理代碼提供了標準的機制用於在應用程序數據上增加和刪除協議
頭和尾。這種代碼安全地操縱了 sk_buff 中的 data 、 tail 和 len 字段。
Push 這把 data 指針向數據區域的起始移動,並增加 len 字段。用於在傳送的數據前
面增加數據或協議頭
參見 include/linux/skbuff.h skb_push()
Pull 把 data 指針從數據區域起始向結尾移動,並減少 len 字段。用於從接收的數據
中刪除數據或協議頭。
參見 include/linux/skbuff.h skb_pull()
Put 把 tail 指針向數據區域的結尾移動並增加 len 字段,用於在傳輸的數據尾部增加
數據或協議信息
參見 include/linux/skbuff.h skb_put()
trim 把 tail 指針向數據區域的開始移動並減少 len 字段。用於從接收的數據中刪除
數據或協議尾
參見 include/linux/skbuff.h skb_trim()
sk_buff 數據結構也包括一些指針,使用這些指針,在處理過程中這個數據結構可以存
儲在 sk_buff 的雙向環形鏈表中。有通用的 sk_buff 例程,在這些列表的頭和尾中增
加 sk_buffs 和刪除其中的 sk_buff 。
10.5.2 Receiving IP Packets
    第 8 章描述了 Linux 的網絡設備驅動程序如何建立到核心以及被初始化。這產生
了一系列 device 數據結構,在 dev_base 列表中鏈接在一起。每一個 device 數據結
構描述了它的設備並提供了一組回調例程,當需要網絡驅動程序工作的時候網絡協議層


可以調用。這些函數大多數和傳輸數據以及網絡設備的地址有關。當一個網絡設備從它
的網絡上接收到數據報文的時候,它必須把接收到的數據轉換到 sk_buff 數據結構。這
些接收的 sk_buff 在接收的時候被網絡驅動程序增加到 backlog 隊列。如果 backlog
 隊列增長的太大,那麼接收的 sk_buff 就被廢棄。如果有工作要執行,這個網絡的 b
utton half 標記成準備運行。
參見 net/core/dev.c netif_rx()
    當網絡的 bottom half 處理程序被調度程序調用的時候,它首先處理任何等待傳送
的網絡報文,然後才處理 sk_buff 的 backlog backlo 隊列,確定接收到的報文需要
傳送到那個協議層。當 Linux 網絡層初始化的時候,每一個協議都登記自己,在 ptyp
e_all 列表或者 ptype_base hash table 中增加一個 packet_type 的數據結構。這個
 packet_type 數據結構包括協議類型,一個網絡驅動設備的指針,一個協議的數據接收
處理例程的指針和一個指針,指向這個列表或者 hash table 下一個 packet_type 數據
類型。 Ptype_all 鏈表用於探測( snoop )從任意網絡設備上接收到的所有的數據報
文,通常不使用。 Ptype_base hash table 使用協議標識符 hash ,用於確定哪一種協
議應該接收進來的網絡報文。網絡的 bottom half 把進來的 sk_buff 的協議類型和任
一表中的一個或多個 packet_type 條目進行匹配。協議可能會匹配一個或多個條目,例
如當窺測所有的網絡通信的時候,這時,這個 sk_buff 會被克隆。這個 sk_buff 被傳
遞到匹配的協議的處理例程。
參見 net/core/dev.c net_bh()
參見 net/ipv4/ip_input.c ip_recv()
10.5.3 Sending IP Packets
    報文在應用程序交換數據的過程中傳送,或者也可能是爲了支持已經建立的連接或


爲了建立連接而由網絡協議產生產生。不管數據用什麼方式產生,都建立一個包含數據
的 sk_buff ,並當它通過協議層的時候增加許多頭。
    這個 sk_buff 需要傳遞到進行傳輸的網絡設備。但是首先,協議,例如 IP ,需要
決定使用哪一個網絡設備。這依賴於這個報文的最佳路由。對於通過 modem 連接到一個
網絡的計算機,比如通過 PPP 協議,這種路由選擇比較容易。報文應該要麼通過 loop
back 設備傳送給本地主機,要麼傳送到 PPP modem 連接的另一端的網關。對於連接到
以太網的計算機而言,這種選擇比較困難,因爲網絡上連接了許多計算機。
    對於傳送的每一個 IP 報文, IP 使用路由表解析目標 IP 地址的路由。對於每一
個 IP 目標在路由表中進行的查找,成功就會返回一個描述要使用的路由的 rtable 數
據結構。包括使用的源 IP 地址,網絡 device 數據結構的地址,有時候還會有一個預
先建立的硬件頭。這個硬件頭和網絡設備相關,包含源和目的物理地址和其它同介質相
關的信息。如果網絡設備是以太網設備,硬件頭會在圖 10.1 中顯示,其中的源和目的
地址會是物理的以太網地址。硬件頭和路由緩存在一起,因爲在這個路由傳送的每一個
 IP 報文都需要追加這個頭,而建立這個頭需要時間。硬件頭可能包含必須使用 ARP 協
議才能解析的物理地址。這時,發出的報文會暫停,直到地址解析成功。一旦硬件地址
被解析,並建立了硬件頭,這個硬件頭就被緩存,這樣以後使用這個接口的 IP 報文就
不需要進行 ARP 。
參見 include/net/route.h
10.5.4 Data Fragmentation
    每一個網絡設備都有一個最大的報文尺寸,它無法傳送或接收更大的數據報文。 I
P 協議允許這種數據,會把數據分割成網絡設備可以處理的報文大小的更小的單元。 I
P 協議頭包含一個分割字段,包含一個標記和分割的偏移量。


    當要傳輸一個 IP 報文的時候, IP 查找用來發送 IP 報文的網絡設備。通過 IP
路由表來查找這個設備。每一個設備都有一個字段描述它的最大傳輸單元(字節),這
是 mtu 字段。如果設備的 mtu 比等待傳送的 IP 報文的報文尺寸小,那麼這個 IP 報
文必須被分割到更小的碎片( mtu 大小)。每一個碎片用一個 sk_buff 代表:它的 I
P 頭標記了它被分割,以及這個 IP 報文在數據中的偏移量。最後一個報文被標記爲最
後一個 IP 碎片。如果在分割成碎片的過程中, IP 無法分配一個 sk_buff ,這次傳送
就失敗。
    接收 IP 碎片比發送更難,因爲 IP 碎片可能以任意順序被接收,而且它們必須在
重組之前全部接收到。每一次一個 IP 報文被接收的時候,都檢查它是否是一個 IP 碎
片。收到一個消息的第一個碎片, IP 就建立一個新的 ipq 數據結構,並連接到等待組
裝的 IP 碎片的 ipqueue 列表中。當更多的 IP 碎片接收到的時候,就查到正確的 ip
q 數據結構並建立一個新的 ipfrag 數據結構來描述這個碎片。每一個 ipq 數據結構都
唯一描述了一個成爲碎片的 IP 接收幀,包括它的源和目標 IP 地址,上層協議標識符
和這個 IP 幀的標識符。當接收到所有的碎片的時候,它們被組裝在一起成爲一個單一
的 sk_buff ,並傳遞到下一個協議層去處理。每一個 ipq 包括一個計時器,每一次接
收到一個有效的碎片的時候就重新啓動。如果這個計時器過期,這個 ipq 數據結構和它
的 ipfrag 就被去除,並假設這個消息在傳輸過程中丟失了。然後由高層的協議負責重
新傳輸這個消息。
參見 net/ipv4/ip_input.c ip_rcv()
10.6 The Address Resolution Protocol (ARP)
    地址解析協議的任務是提供 IP 地址到物理硬件地址的轉換,例如以太網地址。 I
P 在它把數據(用一個 sk_buff 的形式)傳送到設備驅動程序進行傳送的時候才需要這


種轉換。它進行一些檢查,看這個設備是否需要一個硬件頭,如果是,這個報文的硬件
頭是否需要重建。 Linux 緩存硬件頭以免頻繁地重建。如果硬件頭需要重建,它就調用
和設備相關的硬件頭重建例程。所有的一臺設備使用相同的通用的頭重建例程,然後使
用 ARP 服務把目標的 IP 地址轉換到物理地址。
參見 net/ipv4/ip_output.c ip_build_xmit()
參見 net/ethernet/eth.c rebuild_header()
    ARP 協議本身非常簡單,包含兩種消息類型: ARP 請求和 ARP 應答。 ARP 請求包
括需要轉換的 IP 地址,應答(希望)包括轉換的 IP 地址和硬件地址。 ARP 請求被廣
播到連接到網絡的所有的主機,所以,對於一個以太網所有連在以太網上的機器都可以
看到這個 ARP 請求。擁有這個請求中包括的 IP 地址的機器會迴應這個 ARP 請求,用
包含它自己物理地址的 ARP 應答。
    Linux 中的 ARP 協議層圍繞着一個 arp_table 數據結構的表而建立。每一個描述
一個 IP 和物理地址的對應。這些條目在 IP 地址需要轉換的時候創建,隨着時間推移
變得陳舊的時候被刪除。每一個 arp_table 數據結構包含以下域:
Last used 這個 ARP 條目上一次使用的時間
Last update 這個 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 的條目鏈接在一
起。這些條目被緩存,以加速對它們的訪問。每一個條目用它的 IP 地址的最後兩個字
節做表的索引進行查找,然後跟蹤這個條目鏈,直到找到正確的條目。 Linux 也緩存從
 arp_table 條目預先建立的硬件頭,用 hh_cache 數據結構的形式進行緩存。
    當請求一個 IP 地址轉換的時候,沒有對應的 arp_table 條目, ARP 必鬚髮送一
個 ARP 請求消息。它在表中創建一個新的 arp_table 條目,並把需要地址轉換的包括
了網絡報文的 sk_buff 放到這個新的條目的 sk_buff 隊列。它發出一個 ARP 請求並讓
 ARP 過時計時器運行。如果沒有迴應, ARP 會重試幾次。如果仍舊沒有迴應, ARP 會
刪除這個 arp_table 條目。任何排隊等待這個 IP 地址進行轉換的 sk_buff 數據結構
會被通知,由傳輸它們的上層協議負責處理這種失敗。 UDP 不關心丟失的報文,但是
TCP 會在一個建立的 TCP 連接上試圖重新發送。如果這個 IP 地址的屬主用它的硬件地
址應答,這個 arp_table 條目標記爲完成,任何排隊的 sk_buff 會被從對隊列中刪除
,繼續傳送。硬件地址被寫到每一個 sk_buff 的硬件頭中。
ARP 協議層也必須迴應指明它的 IP 地址的 ARP 請求。它登記它的協議類型( ETH_P_
ARP ),產生一個 packet_type 數據結構。這意味着網絡設備接收到的所有的 ARP 報
文都會傳給它。象 ARP 應答一樣,這也包括 ARP 請求。它使用接收設備的 device 數
據結構中的硬件地址產生 ARP 應答。
    網絡拓撲結構不斷變化, IP 地址可能被重新分配到不同的硬件地址。例如,一些
撥號服務爲它建立的每一個連接分配一個 IP 地址。爲了讓 ARP 表中包括最新的條目,
 ARP 運行一個定期的計時器,檢查所有的 arp_table 條目,看哪一個超時了。它非常
小心,不刪除包含包含一個或多個緩存的硬件頭的條目。刪除這些條目比較危險,因爲


其它數據結構依賴它們。一些 arp_table 條目是永久的,並被標記,所以它們不會被釋
放。 ARP 表不能增長的太大:每一個 arp_table 條目都要消耗一些核心內存。每當需
要分配一個新的條目而 ARP 表到達了它的最大尺寸的時候,就查找最舊的條目並刪除它
們,從而修整這個表。
10.7 IP Routing
    IP 路由功能確定發向一個特定的 IP 地址的 IP 報文應該向哪裏發送。當傳送 IP
 報文的時候,會有許多選擇。目的地是否可以到達?如果可以,應該使用哪一個網絡設
備來發送?是不是有不止一個網絡設備可以用來到達目的地,哪一個最好? IP 路由數
據庫維護的信息可以回答這些問題。有兩個數據庫,最重要的是轉發信息數據庫( For
warding Information Database )。這個數據庫是已知 IP 目標和它們最佳路由的詳盡
的列表。另一個小一些,更快的數據庫,路由緩存( route cache )用於快速查找 IP
 目標的路由。象所有緩存一樣,它必須只包括最常訪問的路由,它的內容是從轉發信息
數據庫中得來的。
    路由通過 BSD socket 接口的 IOCTL 請求增加和刪除。這些請求被傳遞到具體的協
議去處理。 INET 協議層只允許具有超級用戶權限的進程增加和刪除 IP 路由。這些路
由可以是固定的,或者是動態的,不斷變化的。多數系統使用固定路由,除非它們本身
是路由器。路由器運行路由協議,不斷地檢查所有已知 IP 目標的可用的路由。不是路
由器的系統叫做末端系統( end system )。路由協議用守護進程的形式來實現,例如
 GATED ,它們也使用 BSD socket 接口的 IOCTL 來增加和刪除路由。
10.7.1 The Route Cache
    不論何時查找一個 IP 路由的時候,都首先在路由緩存中檢查匹配的路由。如果在
路由緩存中沒有匹配的路由,才查找轉發信息數據庫。如果這裏也找不到路由, IP 報


文發送會失敗,並通知應用程序。如果路由在轉發信息數據庫而不在路由緩存中,就爲
這個路由產生一個新的條目並增加到路由緩存中。路由緩存是一個表( ip_rt_hash_ta
ble ),包括指向 rtable 數據結構鏈的指針。路由表的索引是基於 IP 地址最小兩字
節的 hash 函數。這兩個字節通常在目標中有很大不同,讓 hash value 可以最好地分
散。每一個 rtable 條目包括路由的信息:目標 IP 地址,到達這個 IP 地址要使用的
網絡設備( device 結構),可以使用的最大的信息尺寸等等。它也有一個引用計數器
( refrence count ),一個使用計數器( usage count )和上次使用的時間戳(在
jiffies 中)。每一次使用這個路由的時候這個引用計數器就增加,顯示利用這個路由
的網絡連接數目,當應用程序停止使用這個路由的時候就減少。使用計數器每一次查找
路由的時候就增加,用來讓這個 hash 條目鏈的 rtable 條目變老。路由緩存中所有條
目的最後使用的時間戳用於定期檢查這個 rtable 是否太老。如果這個路由最近沒有使
用,它就從路由表中廢棄。如果路由保存在路由緩存中,它們就被排序,讓最常用的條
目在 hash 鏈的前面。這意味着當查找路由的時候找到這些路由會更快。
參見 net/ipv4/route.c check_expire()
 
10.7.2 The Forwarding Information Database
 
    轉發信息數據庫(圖 10.5 顯示)包含了當時從 IP 的觀點看待系統可用的路由。
它是非常複雜的數據結構,雖然它已經進行了合理有效的安排,但是它對於參考而言並
不是一個快速的數據庫。特別是如果每一個傳輸的 IP 報文都在這個數據庫中查找目標
會非常慢。這也是爲什麼要有路由緩存:加速已經知道最佳路由的 IP 報文的傳送。路
由緩存從這個轉發信息數據庫得到,表示了它最常用的條目。


表指向。 Hash 索引取自 IP 子網掩碼。所有通向同一子網的路由都用排在每一個 fib
_zone 數據結構的 fz_list 隊列中得的成對的 fib_node 和 fib_info 數據結構來描述
。如果這個子網的路由數目變得太大,就生成一個 hash table ,讓 fib_node 數據結
構的查找更容易。
    對於同一個 IP 子網,可能存在多個路由,這些路由可能穿過多個網關之一。 IP
路由層不允許使用相同的一個網關對於一個子網有多於一個路由。換句話說,如果對於
一個子網有多個路由,那麼要保證每一個路由都是用不同的網關。和每一個路由關聯的
是它的量度( metric ),這是用來衡量這個路由的益處。一個路由的量度,基本上,
是它在到達目標子網之前必須跳過的子網數目。這個量度越高,路由越差。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章