IPv4實際上是一個被設計的很勉強的協定,遠遠沒有TCP等傳輸層協定設計的好。對于它的更新版,IPv6,實際上我也一樣不看好,雖然它解決了很多問題,擴充了位址空間,增加了協定堆棧化的支援...
對于IPv4而言,其起初的位址空間是分類的,此時的路由表是以也是分類的,最顯然的高效率實作方式就是每一類别一張表,路由器需要在查表邏輯之前有一個分類器來将IP資料報的目的位址分類,然後根據分類結果來查找相應的路由表。
此後,IP位址又可以劃分子網了,同時又有了超網一說,然後就是路由彙聚,所有這一切都需要基于分類的路由表項生成算法以及查找算法作相應的修改。Cisco的路由器就支援兩種邏輯,一種是分類的,另一個是無類的。
終于,CIDR出現了,路由查找算法也相應的變成了“最長字首比對算法”,這種算法統一了所有的路由查找算法,必要時侯也可以退化到相容分類路由的程度。路由子產品如何實作不重要,重要的是你是把IP位址當成一個平坦的空間看呢,還是将它看成一個分級的位址空間呢?舉個例子吧,32位系統中的記憶體位址也是32位,和IP位址一樣,我們看到處理器和主機闆晶片組中存在那麼多精巧的小晶片,它們精巧的邏輯正是在處理路由和位址變換,正是它們讓元器件之間以及其與外設之間的“絕妙互聯”成為可能,然而這種精妙的邏輯并不是直接被設計出來的,而是在有了分級的位址空間之後才設計的。頁表之類的處理邏輯正是做這個的。
如果你不對位址作規劃,而隻是将其作為一個平坦的一維空間看待的話,它标示的東西早晚會不可控。另外就是如何分級的問題,起初IP位址被分成了5個類别,這是一種固定的分級方式,而如今的CIDR則是一種更加動态的分級方式,二者相比各有千秋,估計法實作簡單,效率高,但是管理不便,動态法則更容易滿足動态的需求。如果我們看路由查找的實作算法,Cisco的256叉樹是一種固定的算法,而Linux的Trie樹則是動态的算法。
如主機闆晶片組規劃一樣,IP位址規劃的好壞也會影響到“世界網際網路”這塊大主機闆的設計。什麼事一旦有人參與就複雜了,我們不會行走于散熱片之間,然而我們卻每天都在敲擊着鍵盤。
IP協定是分組交換的核心協定,是沙漏的中心。是以它被設計成了最簡單的無連接配接協定。可是恰恰在核心網上,正有一股勢力偏離了IP協定的初衷,這就是基于标簽的協定,比如MPLS。MPLS實際上是一個有連接配接的協定,隻不過這個連接配接不是建立在端到端的,而是分組交換意義上的,将“基于下一跳查找的路由”變成了“基于标簽的交換路由”,這明顯增加了效率。
以往MPLS之類的協定出來以前,即使是Cisco這類巨頭也隻是使用CEF之類的技術來加速轉發,沒有明說,然而CEF實則就是一種交換技術,在CEF之前,使用的Cache之類的加速技術。CEF将路由和轉發交換相分離,不再為資料包再去查找路由表,而是先根據路由表生成一張“轉發表”,然後資料包通過“交換技術”直接根據轉發表來被發送到合适的鍊路。這張轉發表是如何建立的呢?實際上是“默默地”建立的,系統在沒有包要發送的時候,默默地通過ARP得到每一條路由下一跳的鍊路層位址,然後建構轉發表...
MPLS隻是更加邏輯化的CEF,它也是默默地建立起标簽-端口映射表,通過标簽交換協定可以很容易做到。然後在IP資料報的外表打上标簽,每一個“MPLS交換機”基于标簽進行快速交換,在無連接配接的IP協定外面默默建構了一條虛拟的标簽通道,并且為了能表現IP位址分級的邏輯,标簽可以嵌套,根據标簽層次的不同,我們也就能映射到每一層标簽所代表的IP位址級别。堆棧化的标簽協定所要表達的正如CIDR中的字首(prefix,掩碼)所要表達的意思一樣。
IPv4頭中有一個Identification字段,它可了不得啊,關于它的讨論可謂多矣。然而它卻隻不過和分片重組有關系(可能還和壓縮有關),如果沒有分段重組,我們完全可以撇掉這個字段。它的存在就是為了标示屬于同一個IP資料報的IP分片。
Identification隻有16位,是以隻能在65535,這在快速網絡上很快就會回繞,為了協定的緊湊,該字段又不能太大,是以就定在了16位。至于如何防止回繞,RFC791并沒有細說:
Identification
The choice of the Identifier for a datagram is based on the need to
provide a way to uniquely identify the fragments of a particular
datagram. The protocol module assembling fragments judges fragments
to belong to the same datagram if they have the same source,
destination, protocol, and Identifier. Thus, the sender must choose
the Identifier to be unique for this source, destination pair and
protocol for the time the datagram (or any fragment of it) could be
alive in the internet.
It seems then that a sending protocol module needs to keep a table
of Identifiers, one entry for each destination it has communicated
with in the last maximum packet lifetime for the internet.
However, since the Identifier field allows 65,536 different values,
some host may be able to simply use unique identifiers independent
of destination.
It is appropriate for some higher level protocols to choose the
identifier. For example, TCP protocol modules may retransmit an
identical TCP segment, and the probability for correct reception
would be enhanced if the retransmission carried the same identifier
as the original transmission since fragments of either datagram
could be used to construct a correct TCP segment.
實際上,使用一個字段無法标示“唯一性”,那就使用多個字段,于是源位址,目的位址,協定都用上了(由于IP協定隻能控制到它自己的字段,是以不能假定任何猜測,是以就不能使用5元素了,畢竟像很多協定沒有端口的概念)。這下可以了,基本不會回繞了,在Linux的實作上,針對每一個目的地,都綁定了一個peer字段,其ID在peer結構體中,也就是說,Linux實作的是針對每一個目的地的Identification,而不是全局的。對于“不能分段”的IP資料報,就無所謂了,就算重複了也無所謂。
RFC中還說了,建議讓高層協定去維護這個Identification字段,不過這樣可能會帶來很多競态,不利于多處理器并行。
除此之外,IP分段的Identification還會被NAT裝置沖刷掉,使得資料分片到達目的地的時候重組失敗。RFC隻是說Identification由各個主機分别自行産生,沒有提到任何其他的,是以比如說内網兩台主機H1和H2同時使用http經由SNAT到達同一台目的地S1,恰巧兩個主機的兩個分段的Identification相同,穿越SNAT裝置的時間也相差不久,它們的源位址會被改成相同的NAT裝置的外網口位址,S1最終會認為來自H1和H2的分片是同一個IP資料報的分片,因為按照RFC的說法,它們的源位址,目的位址,協定,Identification字段均相同...
Linux為了避免IP分片的NAT問題,它是這樣實作的:在進行連接配接追蹤之前,會進行分片重組,進而避免了上述的問題,然而犧牲了效率。見下面的HOOK鈎子優先級定義:
另一個方式就是不為NAT重組分片,見下圖(該圖在之前的文字中用過,然而第一個YES/NO畫反了):

我們需要在ip_conntrack以及分片資訊表項中儲存一個NAT裝置新生成的Identification,如果裝置對資料報進行了NAT,那麼就連Identification也一并轉換了。如下圖所示:
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1268983