《TCP/IP詳解卷2:實現》筆記--Radix樹路由表
由IP完成的路由選擇是一種選路機制,它通過搜尋路由表來確定從哪個介面把分組傳送出去,它與選路策略不一樣,選路策略
是一組規則的集合,這些規則用來確定哪些路由可以編入到路由表中,Net/3核心實現選路機制,而選路守護程序,典型地如
routed或gated,實現選路策略。
1.路由表結構
下圖是某主機上的路由表。
對於Flags列需要簡單說明下。
G 該路由通向一個閘道器(路由器),這種路由被稱為間接路由。如果沒有設定本標誌,則表明路由的目的地與本機直接相連,
稱為直接路由。
H 該路由通往一臺主機,也就是說,目的地址是一個完整的主機地址。如果沒有設定本標誌,則路由通往一個網路,目的地址
是一個網路地址:一個網路號,或一個網路號與子網號的組合。
S 該路由是靜態的。
C 該路由可被克隆以產生新的路由。在本路由表中有兩條路由設定了這個標誌:一條是到本地乙太網140.252.13.32的路由,
ARP通過克隆該路由建立到乙太網中其他特定主機的路由;另一條是到多播組224的路由,克隆該路由可以建立到特定多播
組(如224.0.0.1)的路由。
L 該路由含有鏈路層地址。本標誌應用於單播地址和多播地址。由ARP從乙太網路由克隆而得到的所有主機路由都設定了本標誌。
R 環回驅動器(為設有本標誌的路由而設計的普通介面)將拒絕所有使用該路由的資料報。
Net/3路由表採用Patricia樹結構來表示主機地址和網路地址。待查詢的地址和樹中的地址都看成位元序列。這樣就可以用相同
的函式來查詢和維護不同型別的數。
查詢路由表的目的就是為了找到一個最能匹配給定目標的特定地址。我們稱這個給定的目標位查詢鍵(search key)。所謂
最能匹配的地址,也就是說,一個能夠匹配的主機地址要優於一個能夠匹配的網路地址;而一個能夠匹配的網路地址要優於
預設地址。
每條路由表項都有一個對應的網路掩碼,儘管在主機路由中沒有儲存掩碼,但它隱含了一個全1位元的掩碼。我們對查詢鍵和
路由表項的掩碼進行邏輯與運算,如果得到的值與該路由表項的目的地址相同,則稱該路由表項是匹配的。對於某個給定的
查詢鍵,我們會從路由表中找到多條這樣的匹配路由。
下圖給出了上面路由表的內部結構。
標有end的兩個陰影框是該書結構中帶有特殊標誌的葉節點,該標誌代表樹的端點。左邊的那個擁有一個全0鍵,而右邊的
擁有一個全1鍵,左邊的兩個標有end和default的框疊在一起,這兩個框有特殊的意義,它們與重複鍵有關。
方角框被稱為內部結點或簡稱為結點,圓角框被稱為葉子。每一個內部結點對應於測試查詢鍵的一個位元位,其左右各有
一個分支。每一個葉子對應於一個主機地址或者對應於一個網路地址。如果在葉子下面有一個十六進位制數,那麼這個葉子
就對應於一個網路地址,該十六程序數就是葉子的網路掩碼。如果在葉子下面沒有十六進位制的掩碼,那麼這個葉子就是一個
主機地址,其隱含的掩碼是0xffffffff。
有一些內部節點也含有網路掩碼,這些掩碼在回溯過程中使用。
位元比較式運用在插口地址結構上的,因此,在上圖給出的位元位置是從插口地址結構中的起始位置開始算的。下圖給出了
sockaddr_in結構中的位元位置。
下圖為路由表中各個IP地址的位元表示形式。
下面舉一些例子來說明路由表的查詢過程是如何完成的。
1.與主機地址匹配的例子
假定查詢鍵是地址140.252.13.35。32為1,33為0,36為1,57為0,62為1,63為1。因此查詢在140.252.13.35的葉子處
終止。查詢鍵與路由表鍵完全匹配。
2.與網路地址匹配的例子
假定查詢鍵是127.0.0.2。32為0,33為1,63為0,因此,查詢在標有127.0.0.0的葉子處終止。查詢鍵和路由表並沒有完全
匹配,因此,需要進一步看它是不是一個能夠匹配的網路地址。對查詢鍵和網路掩碼0xff000000進行邏輯與運算,得到的
結果與該路由表鍵相同,即認為該路由表項能夠匹配。
3.與預設地址匹配的例子
假定查詢鍵是10.1.2.3。32為0,33為0,因此,查詢標有end和default並帶有重複鍵的葉子處終止。在這兩個葉子中重複的
路由表鍵是0.0.0.0。查詢鍵與路由表鍵值沒有完全匹配,因此,需要進一步看它是不是一個能夠匹配的網路地址。這種匹配
運算要對每個含網路掩碼的重複鍵都試一遍。第一個鍵沒有網路掩碼,可以跳過不查。第二個鍵有一個0x00000000的掩碼。
查詢鍵和這個掩碼的邏輯與運算,所得結果和路由表鍵0相等,即認為該路由表項能夠匹配。這樣預設路由表就能用作匹配
路由。
3.帶回溯和克隆、並與主機地址相匹配的例子
假定查詢鍵是224.0.0.5。32為1,33為1,35為0,63為1,因此,查詢在標有224.0.0.1的葉子處結束。路由表的鍵值和查詢關鍵字
並不相等,並且該路由表項不包含網路掩碼,因此要進行回溯。
回溯向上移動一層,達到63對應的節點,節點含有掩碼0xff000000(如果沒有掩碼則繼續往上回溯),因此對查詢鍵和該掩碼
進行邏輯與運算,產生一個新的查詢鍵224.0.0.0。再從這個結點開始一次新的查詢。在新的查詢鍵中63為0,於是沿左分支
到達標有224.0.0.0的葉子。這個路由表鍵和邏輯與運算得到的查詢鍵相匹配,因此這個路由表項是匹配的。
該路由表設定了克隆標誌,因此,以224.0.0.5為地址建立一個新的葉子。新的路由表項是:
下圖從位元35對應的結點開始,給出了上面路由表樹右邊部分的新的排列。無論何時向樹中新增新的葉子,都需要兩個結點:
一個作為葉子,另一個作為測試某一位位元的內部結點。新建立的表項就返回給查詢225.0.0.5的呼叫者。
下圖描述了所有涉及到的資料結構。
我們解釋下圖中的幾個要點。
rf_tables是指向radix_node_head結構的指標陣列。每一個地址族都有一個數組與之對應。rt_tables[AF_INET]指向Internet
路由表樹的頂點。
radix_node_head結構包含三個radix_node結構。這三個結構式在初始化路由樹時建立的,中間的是樹的頂點。它對於上面
路由樹的bit32的結點框。三個radix_node結構中的第一個是路由樹中最左邊的葉子(與預設路由共享的重複),第三個結構
是最右邊的葉子。在一個空的路由表中,就只包含三個radix_node結構。
全域性變數mask_rnhead也指向一個radix_node_head結構。它是包含了所有掩碼的一棵獨立樹的首部結構。上面的路由樹給出
了八個掩碼可知,有一個掩碼重複了四次,有兩個掩碼重複了一次。通過把掩碼放在一棵單獨的樹中,可以做到對每一個掩碼
只需要維護它的一個備份即可。
路由表樹是用rtentry結構建立的,上圖中有兩個rtentry結構。每一個rtentry結構包含兩個radix_node結構,因為每次向樹中插入
一個新的路由時,都需要兩個結點:一個是內部結點,對應於某一位測試位元;另一個是葉子,對應於一個主機路由或一個網路
路由。
存在於每一個UDP和TCP插口中的協議控制塊PCB中包含了一個指向rtentry結構的route結構。每次傳送一個IP資料報時,UDP
和TCP輸出函式都傳遞一個指向PCB中route結構的指標,作為呼叫ip_output的引數。使用相同路由的PCB都指向相同的路由
表項。
2.選路插口
下圖給出了12種不同型別的選路訊息。訊息型別位於rt_msghdr結構中的rtm_type欄位。
3.函式呼叫
下圖顯示了各選路函式之間的關係。
rtalloc函式是由Internet協議呼叫的,用於查詢到達指定目的地的路由。圖中還給出了在選路域中建立插口的五個典型程式。
arp處理ARP快取記憶體,該ARP快取記憶體被儲存在Net/3的IP路由表中。
gated和routed是選路守護程序,他們與其他路由器進行通訊,當選路環境發生變化時,對核心的路由表進行操作。
route通常是由啟動指令碼或系統管理員執行的一個程式,用於新增或刪除路由。
rwhod在啟動時會呼叫一個選路sysctl來測定連線的介面。
4.Radix結點資料結構
每一個路由表的表頭都是一個radix_node_head結構,而選路數中所有的節點都是radix_node結構。radix_node_head結構如
下圖所示:
rnh_treetop指向路由數頂端的radix_node結構,可以看到radix_node_head結構的最後一項分配了三個radix_node結構,
其中中間的那個被初始化為樹的頂點。
從rnh_addaddr到rnh_walktree是七個函式指標,它們所指向的函式被呼叫以完成對樹的操作。下圖中,rn_inithead僅初始
化其中的四個指標,剩下的未被使用。
下圖給出了組成樹中結點的radix_node結構。
前5個成員是內部結點和葉子結點都有的成員,後面是一個union:如果結點是葉子,那麼它定義了三個成員;如果是內部
結點,那麼它定義了另外不同的三個成員。
rn_mklist是該節點掩碼連結串列的表頭。
rn_p指向該節點的父結點。
如果rn_b值大於等於0,那麼該結點為內部結點,否則為葉子。對於內部結點來說,rn_b就是要測試的位元位置。對於葉子
結點來說,rn_b是負的,它的值等於-1減去網路掩碼索引。該索引是指壓那麼中出現的第一個零的位元位置。下圖給出了
掩碼的索引。
內部結點rn_bmask是個單位元組的掩碼,用於檢測相應的位元位是0還是1。在葉子中它的值為0
下圖給出了rn_flags的三個值。
對於葉子而言,rn_key指向插口地址結構,rn_mask指向儲存掩碼的插口地址結構。如果rn_mask為空,則其掩碼為隱含的
全1值(即,該路由指向某個主機而不是某個網路)。
5.選路結構
訪問核心路由資訊的關鍵之處是:
1.rtalloc函式,用於查詢通往目的地的路由。
2.route結構,它的值由rtalloc函式填寫。
3.route結構所指向的rtentry結構。
在UDP和TCP中使用的協議控制塊(PCB),其中包含了一個route結構。
ro_dst被定義成一個一般的插口地址結構,但對於internet協議而言,它就是一個sockaddr_in結構。
下圖給出了rtentry結構的定義。
結構中包含了兩個radix_node結構,每次向路由樹中新增一個新葉子的同時也要新增一個內部結點,rt_nodes[0]為葉子,
rt_nodes[1]為內部結點。
下圖給出了儲存在rt_flags中各種常量以及netstat輸出的相應Flags字元。
如果設定了RTF_GATEWAY標誌,那麼rt_gateway所含的插口地址結構的指標就指向網路的地址。同樣,rt_gwroute就指向
該閘道器的rtentry。
rt_refcnt是一個計數器,儲存正在包含該結構的引用數目。
當分配該結構儲存空間時,rt_use被初始化為0,每次利用該路由輸出一份IP資料報時,其值會隨之遞增。
rt_ifp和rt_ifa分別指介面結構和介面地址結構。
rt_llinfo指標允許鏈路層協議在路由表中儲存該協議專用的結構指標。在介紹ARP時會描述如何使用該指標。
下圖給出了rtentry結構中含有的rt_metrics結構。
在TCP中會使用該結構中的六個成員。在ARP中會使用rmx_expire作為每一個ARP路由項的定時器。
6.初始化:route_init和rtable_init函式
下圖給出了各協議族中與domain結構相關的欄位。
PF_ROUTE域是唯一具有初始化函式的域。同樣,只有那些需要路由表的域才有dom_rtattach函式,並且該函式總是rn_inithead。
選路域和Unix域並不需要路由表。
dom_rtoffset成員是以位元為單位的選路過程中被檢測的第一個位元的偏移量(從域的插口地址結構的起始處開始計算)。
dom_maxrtkey給出了該結構的位元組長度。
下圖列出了路由表初始化過程所包含的步驟。
在系統初始化時,核心的main函式將呼叫一次domaininit函式,ADDDOMAIN巨集用於建立一個domain結構的連結串列,並呼叫
每個域的dom_init函式。
7.初始化:rn_init和rn_inithead函式
函式rn_init只被route_init呼叫一次,用於初始化radix函式使用的一些全域性變數。
呼叫rn_inithead,初始化地址掩碼路由樹的首部,並使全域性變數mask_rnhead指向radix_node_head結構。
下圖為Internet協議建立的radix_node_head結構。
這三個radix_node結構組成了一棵樹:中間的結構是樹的頂點,第一個結構式樹的最左邊的葉子,左後一個結構是樹的最
右邊的葉子,這三個結點的父指標都指向中間的那個結點。
最左邊結點的鍵是全0(rn_zeros),最右邊結點的鍵是全1(rn_ones)。
這三個結點都設定了RNF_ROOT標誌,這說明它們都是構成樹的原始結點之一。它們也是唯一具有該標誌的結點。
8.重複鍵和掩碼列表
下面介紹radix_node結構中的兩個欄位:一個是rn_dupedkey,它構成了附加的含重複鍵的radix_node結構連結串列;另一個
是rn_mklist,它是含網路掩碼的radix_mask結構連結串列的開始。在上面的路由樹中最左邊標有end和default兩個框,這就是
重複鍵。最左邊設有RNF_ROOT標誌的結點有一個為全0位元的鍵,但是它和預設路由的鍵相同。
下圖給出了兩個具有全0位元重複鍵的結點。
圖中最上面的結點即為路由樹的頂點。接下來的兩個結點是葉子(他們的rn_b為負值),其中第一個葉子的rn_dupedkey
成員指向第二個結點。
第一個葉子是rnh_nodes[0]結構,該結構是樹的左邊標有end的結點,它設有RNF_ROOT標誌。它的鍵被rn_inithead設為
rn_zeros。
第二個葉子是預設路由表項。它的rn_key指向0.0.0.0的sockaddr_in結構,並具有一個全0的掩碼。
最後一個是radix_mask結構,樹的頂結點和預設路由對應的葉子都指向這個結構,這個列表時樹的頂結點的掩碼列表,在
查詢網路掩碼時,回溯演算法將使用它。radix_mask結構列表和內部結點一起確定了運用於從該結點開始的子樹的掩碼。
應該注意:具有相同值得掩碼之間可以共享,但是具有相同值的鍵之間不能共享。
9.rn_match函式
在Internet協議中,它被稱為rnh_matchaddr函式。它將被rtallocl函式呼叫(而rtallocl函式將被rtalloc函式呼叫),具體
演算法如下:
1.從樹的頂端開始搜尋,直到到達與查詢鍵位元相應的葉子,檢測該葉子,看能夠得到一個精確的匹配。
2.檢測該葉節點,看是否能夠得到匹配的網路地址。
3.回溯。