概說《TCP/IP詳解 卷2》第9章 選項處理
本文要點
-
引言
-
選項格式
-
ip_dooptions函式
-
記錄路由選項
-
源站和記錄路由選項
-
save_rte函式
-
ip_srcroute函式
-
-
時間戳選項
-
ip_insertoptions函式
-
ip_pcbopts函式
-
其它
引言
在概說《TCP/IP詳解 卷2》第8章 IP:網際協議中,IP輸入函式(ipintr)在驗證分組格式(檢驗和、長度等)之後,確定分組是否到達目的地之前,對選項進行處理。這表明,分組所遇到的每個路由器以及最終的目的主機都要對分組的選項進行處理。
本文將討論大多數IP選項的格式和處理;同時介紹運輸協議如何指定IP資料報內的IP選項。
IP分組內可以包含某些在分組被轉發或者被接收之前處理的可選欄位。IP實現可以按任意順序處理選項;圖1顯示了標準IP首部之後最多可跟40位元組的選項。
圖1 一個IP首部可以有0~40位元組的IP選項
選項格式
IP選項欄位可能包含0個或者多個單獨選項。選項有兩種型別,單位元組和多位元組,如圖2所示。
圖2 單位元組和多位元組IP選項的結構
所有選項都以1位元組型別(type)欄位開始。在多位元組選項中,型別欄位後面緊接著一個長度(len)欄位,其它的位元組是資料(data)。許多選項資料欄位的第一個位元組是1位元組的位移(offset)欄位,指向資料欄位內的某個位元組。長度位元組的計算覆蓋了型別、長度和資料欄位。型別被繼續分成三個子欄位:1bit備份(copied)標誌、2bit類(class)欄位和5bit數字(number)欄位。圖3列出了目前定義的IP選項。前兩個選項是單欄位選項,其它的是多位元組選項。
圖3 常用IP選項
第1列顯示了Net/3的選項常量,第2列和第3列是該型別的十進位制和二進位制值,第4列是選項的長度。Net/3列顯示的是在Net/3中由ip_dooptions實現的選項。IP必須自動忽略所有它不識別的選項。
當Net/3對一個有選項的分組進行分片時,它將檢查copied標誌位。該標誌位指出是否把所有選項都備份到每個分片的IP首部。class欄位把相關的選項按圖4所示進行分組。在圖3中,除時間戳選項具有class為2外,所有選項的class為0。
圖4 class欄位
ip_dooptions函式
ip_dooptions是一個長函式,我們將分幾個部分來介紹。第一部分初始化一個for迴圈,處理首部中的各選項。
當處理每個選項時,cp指向選項的第一個位元組。如圖5所示,當可用時,如何從cp的常量位移訪問type、length和offset欄位。
圖5 用常量位移訪問IP選項欄位
位移(offset)的值是某個位元組在該選項內的序號(從type開始,序號為1),所以位移的最小值是4(IPOPT_MINOFF),它指向的是多位元組選項中資料欄位的第一個位元組。
圖6顯示了ip_dooptions函式的整體結構。
圖6 ip_dooptions函式
a. EOL和NOP過程
555~582 for迴圈按照每個選項在分組中出現的順序分別對它們進行處理。EOL選項以及一個無效的選項長度都將終止該迴圈。當出現NOP選項時,忽略它。switch語句的default情況要求系統忽略未知的選項。
下面的內容描述了switch語句處理的每個選項。如果ip_dooptions在處理分組選項時沒有出錯,就把控制交給switch下面的程式碼。
b. 源路由轉發
719~724 如果分組需要被轉發,例如SSRR或LSRR選項處理程式碼就把forward置位。分組被傳給ip_forward,並且第2個引數為1,表明分組是按源路由選擇的。
如果轉發了分組,則ip_dooptions返回1。如果分組中沒有源路由,則返回0給ipintr,表明需要對該資料報進一步處理。注意,只有當系統被配置成路由器時(ipforwarding為1),才發生源路由轉發。
c. 差錯處理
725~730 如果switch語句裡出現錯誤,ip_dooptions就跳到bad。從分組長度中把IP首部長度減去,因為icmp_error假設首部長度不包含在分組長度裡。icmp_error發出適當差錯報文,ip_dooptions返回1,避免ipintr再次處理該分組。
記錄路由選項
1. 路由選項
記錄路由選項使得分組在穿過網際網路時所經過的路由被記錄在分組內部。項的大小是源主機在構造時確定的,必須足夠儲存所有預期的地址。我們記得在IP分組的首部,選項最多隻有40位元組。記錄路由選項可以有3個位元組的開銷,後面緊跟地址列表,每個地址4位元組。如果該選項是唯一的選項,則最多可以有9個(3+4x9)地址出現。一旦分配給該選項的空間被填滿,就按通常的情況對分組進行轉發,中間的系統就不再記錄地址。
圖7說明了一個記錄路由選項的格式,圖8是其源程式。
圖7 記錄路由選項,其中n必須<=9
圖8 函式ip_dooptions:記錄路由選項的處理
647~657 如果位移選項太小,則ip_dooptions就傳送一個ICMP引數問題差錯。如果變數code被設定成分組內無效選項的位元組位移量,並且bad標號語句的執行產生錯誤,則發出的ICMP引數問題差錯報文中就具有該code值。如果選項中沒有附加地址空間,則忽略該選項,並繼續處理下一個選項。
658~673 如果ip_dst是某個系統地址(分組已到達目的地),則把接收介面的地址記錄在選項中,否則把ip_rtaddr提供的外出介面地址記錄下來。把位移更新為選項中下一個可用地址位置。如果ip_rtaddr無法找到目的地的路由,就傳送一個ICMP主機不可達差錯報文。
2. ip_rtaddr函式
函式ip_rtaddr查詢路由快取,必要時查詢完整的路由表,來找到到給定IP地址的路由。它返回一個指向in_ifaddr結構的指標,該指標與該路由的外出介面有關。圖9顯示了該函式。
圖9 函式ip_rtaddr:尋找外出的介面
a. 檢查IP轉發快取
735~741 如果路由快取為空,或者如果ip_rtaddr的唯一引數dest與路由快取中的目的地址不匹配,則必須查詢路由表選項一個外出介面。
b. 確定路由
742~750 舊的路由(如果有的話)被丟棄,並把新的路由儲存在*sin(這是轉發快取的ro_dst成員)。rtalloc搜尋路由表,尋找到目的地的路由。
c. 返回路由資訊
751~754 如果沒有路由可用,就返回一個空指標;否則,就返回一個指標,它指向與所選路由相關聯的介面地址結構。
源站和記錄路由選項
通常情況,路由轉發是由中間路由器所選擇的路徑所決定。源站和記錄路由選項允許源站明確指定一條到目的地的路由,覆蓋掉中間路由器的路由選擇決定。而且,在分組到達目的地的過程中,把該路由記錄下來。
嚴格路由包含了源站和目的站之間的每個中間路由器的地址;寬鬆路由只指定某些中間路由器的地址。在寬鬆路由中,路由器可以自由選擇兩個系統之間的任何路徑;而在嚴格路由中,則不允許路由器這樣做。我們用圖10說明源路由處理。
圖10 源路由舉例
A、B和C是路由器,HS和HD是源和目的主機。因為每個介面都有自己的IP地址,所以我們看到路由器A有三個地址:A1、A2和A3。同樣,路由器B和C也有多個地址。圖11顯示了源站和記錄路由選項的格式。
圖11 嚴格和寬鬆源路由選項
IP首部的源和目的地址以及在選項中列出的位移和地址表,指定了路由以及分組目前在該路由中所處的位置。圖12顯示,當分組按照這個寬鬆源路由從HS經過A、B、C到HD時,資訊是如何改變的。每行代表當分組被第1列顯示的系統傳送時的狀態。最後一行顯示分組被HD接收。圖13顯示了相關程式碼。
圖12 當分組通過該路由器時,源跌幅選項被修改
符號“.”表示位移與路由中地址的相對位置。注意,每個系統都把出口地址放到選項去。特別的,原來的路由指定A3為第一跳目的地,但是外出介面A2被記錄在路由中。通過這種方法,分組所採用的路由被記錄在選項中。被記錄的路由將被目的地系統倒轉過來放到所有應答分組上,讓它們沿著原始的路由的逆方向傳送。
圖13 函式ip_dooptions:LSRR和SSRR選項處理
583~612 如果選項位移小於4,即IPOPT_MINOFF,則Net/3傳送一個ICMP引數差錯,並帶上相應的code值。如果分組的目的地址與本地地址沒有一個匹配,且選項是嚴格源路由(IPOPT_SSRR),則傳送一個源路由失敗差錯。如果本地地址不在路由中,則上一個系統把分組傳送到錯誤的主機上了。對寬鬆路由(IPOPT_LSRR)來說,這不是錯誤;僅意味著IP必須把分組轉發到目的地。
a. 源路由的結束
613~620 減少off,把它轉換成從選項開始的位元組位移。如果IP首部的ip_dst是某個本地地址,並且off所指向的走過了源路由的末尾,源路由中沒有地址了,則分組已經到達目的地。save_rte複製在靜態結構ip_srcrt中的路由,並儲存在全域性ip_nhops(圖16)里路由中的地址個數。
b. 為下一跳更新分組
621~637 如果ip_dst是一個本地地址,並且offset指向選項內的一個地址,則該系統是源路由中指定的一箇中間系統,分組也沒有到達目的地。在嚴格路由中,下一個系統必須位於某個直接相連的網路上。ifa_ifwithdst和ifa_ifwithnetefpd配置的介面中搜索匹配的目的地址(一個點到點介面)或者匹配的網路地址(廣播介面)來尋找一條到下一個系統的路由。而在寬鬆路由中,ip_rtaddr(圖9)通過查詢路由表尋找到下一個系統的路由。如果沒找到到下一系統的介面或者路由,就傳送一個ICMP源路由失敗差錯報文。
638~644 如果找到一個介面或者一條路由,則ip_dooptions把ip_dst設定成off指向的IP地址。在源路由選項內,用外出介面地址代替中間系統的地址,把位移增加,指向路由中的下一個地址。
c. 多播目的地
645~646 如果新的目的地址不是多播地址,就將forward設定成1,表明在處理完所有選項後,應該把分組轉發而不是返回給ipintr。
1. save_rte函式
在最終目的地,運輸協議必須能夠使用分組中被記錄下來的路由。運輸協議必須把該路由倒過來並附在所有應答的分組上。圖16顯示的save_rte函式,把源路由儲存在圖14所示的ip_srcrt結構中。
圖14 結構ip_srcrt
57~63 該程式碼定義了ip_srcrt結構,並聲明瞭靜態變數ip_srcrt。只有兩個函式訪問ip_srcrt:save_rte,把到達分組的源路由複製到ip_srcrt中;ip_srcroute,建立一個與源路方向相逆的路由。圖15說明了源路由處理過程。
圖15 對求逆後的源路由的處理
圖16 函式save_rte
759~771 當一個源路由選擇的分組到達目的地時,ip_dooptions呼叫save_rte。option是一個指向分組源路由選項的指標,dst是從分組首部來的ip_src(也就是,返回路由的目的地)。如果選項長度超過ip_srcrt結構,save_rte立即返回。實際上,這種情況永遠不會發生,因為ip_srcrt結構比最大選項長度(40位元組)要大。
save_rte把該選項複製到ip_srcrt,計算儲存ip_nhops中源路由的跳數,把返回路由的目的地儲存在dst中。
2. ip_srcroute函式
當響應某個分組時,ICMP和標準的運輸層協議必須把分組帶的任意源路由逆轉。逆轉源路由是通過ip_srcroute構造的,如圖17所示。
圖17 ip_srcroute函式
777~783 ip_srcroute把儲存在ip_srcrt結構中的源路由逆轉後,返回與ipoption結構(圖24)格式類似的結果。如果ip_nhops是0,則沒有儲存的路由,所以ip_srcroute返回一個空指標。
784~786 如果ip_nhops非0,ip_srcroute就分配一個mbuf,並把m_len設定成足夠大,以便包含第一跳目的地、選項首部資訊以及逆轉後的路由。如果分配失敗,則返回一個空指標,跟沒有源路由一樣。
p被初始化為指向到達路由的末尾,ip_srcroute把最後記錄的地址複製到mbuf前面,在這裡這為外出的第一跳目的地開始逆轉後的路由。然後該函式把一份NOP選項源路由資訊複製到mbuf中。
805~818 while迴圈把其餘IP地址從源路由以相反的順序複製到mbuf中。路由的最後一個地址被設定成到達分組中被save_rte放在ip_srcrt.dst中的源站地址。返回一個指向mbuf的指標。圖18說明了對圖10的路由如何構造逆轉的路由。
圖18 ip_srcroute逆轉ip_srcrt中的路由
時間戳選項
當分組穿過一個網際網路時,時間戳選項使各個系統把它當前的時間表示記錄在分組的選項內。時間是以從UTC的午夜開始計算的毫秒計,被記錄在一個32bit的欄位裡。
有三種時間戳型別,Net/3通過如圖20所示的ip_timestamp結構訪問。
114~133 如同ip結構一樣,#ifs保證位元欄位訪問選項中正確的位元位。圖19列出了由ipt_flg指定的三種時間戳選項型別。
圖19 ipt_flg可能的值
圖20 ip_timestamp結構和常量
初始主機必須構造一個具有足夠大的資料區存放可能的時候戳和地址的時間戳選項。對於ipt_flg為3的時間戳選項,初始主機在構造該選項時,填寫要記錄時間戳的系統的地址。圖21顯示了有三種時間戳選項的結構。
圖21 三種時間戳選項
因為IP選項最多隻能有40個位元組,所以時間戳選項只能有9個時間戳或者4個地址和時間戳對。圖22顯示了對三種不同時間戳選項型別的處理。
圖22 函式ip_dooptions:時間戳選項處理
674~684 如果選項長度小於5個位元組(時間戳選項最小長度),則 ip_dooptions發出一個ICMP引數問題差錯報文。oflw欄位統計由於選項資料區滿而無法登記時間戳的系統個數。如果資料區滿,則oflw加1;當它本身超過16(4bit)而溢位時,發出一個ICMP引數問題差錯報文。
a. 只有時間戳
685~687 對於ipt_flg為0的時間戳選項(IPOPT_TS_TSONLY),所有的工作都在switch語句之後再做。
b. 時間戳和地址
688~700 對於ipt_flg為1的時間戳選項(IPOPT_TS_TSANDADDR),如果資料區還有空間的話,接收介面的地址被記錄下來,選項的指標前進一步。因為Net/3支援一個接收介面上的多個IP地址,所以ip_dooptions呼叫ifaof_ifpforaddr選擇與分組的初始目的地址(也就是在任何源路由選擇發生之前的目的地)最匹配的地址。如果沒有匹配,則跳過時間戳選項。
c. 預定地址上的時間戳
701~710 如果ipt_flg的值為3(IPOPT_TR_PRESPEC),ifa_ifwithaddr確定選項中的指定的下一個地址是否與系統的某個地址匹配。如果不匹配,該選項要求在這個系統上不處理;continue使ip_dooptions繼續處理下一下選項。如果下一個地址與系統的某個地址匹配,則選項的指標前進到下一下位置,控制交給switch的後面。
d. 插入時間戳
711~713 default截獲無效的ipt_flg值,並把控制傳遞給bad。
714~719 時間戳用switch語句後面的程式碼寫入選項中。iptime返回自UTC午夜起到現在的毫秒數,ip_dooptions記錄此時間戳,並增加此選項相對於下一個位置的偏移。
圖23顯示了iptime的實現。
圖23 函式iptime
458~466 microtime返回從UTC1970年1月1號午夜以來的時間,放在timeval結構中。從午夜以來的毫秒用atv計算,並以網路位元組返回。
ip_insertoptions函式
在概說《TCP/IP詳解 卷2》第8章 IP:網際協議中,函式ip_output接收一個分組和選項。當ip_forward呼叫該函式時,選項已經是分組的一部分,所以ip_forward總是把一個空選項指標傳給ip_output。但是,運輸層協議可能會把由ip_insertoptions合併到分組中的選項傳遞給ip_forward。
ip_insertoption希望選項在ipoption結構中被格式化,如圖24所示。
圖24 結構ipoption
92~95 該結構只有兩個成員:ipopt_dst,如果選項表中有源路由,則其中有第一跳目的地,ipopt_list,是一個最多擁有40(MAX_IPOPTLEN)位元組的選項矩陣,其格式我們在本章中已做了描述。如果選項表中沒有源路由,則ipopt_dst全為0。
注意,ip_srcrt結構(圖14)和由ip_srcroute(圖17)返回的mbuf都符合由ipoption結構所指定的格式。圖25把結構ip_srcrt和ipoption作了比較。
圖25 結構ip_srcrt和ipoption
結構ip_srcrt比ipoption多4個位元組。路由矩陣的最後一個入口(route[9])永遠都不會填上,因為這樣的話,源路由選項將會有44位元組,比IP首部所能容納的要大。
函式ip_insertoptions如圖26所示。
圖26 函式ip_insertoptions
352~364 ip_insertoptions有三個引數:m,外出的分組;opt,在結構中格式化的選項;phlen,一個指向整數的指標,在這裡返回新首部的長度(在插入之後)。如果插入選項分組長度超過最大分組長度65535位元組,則自動將選項丟棄。ip_dooptions認為ip_insertoptions永遠都不會失敗,所以無法報告差錯。幸好很少有應用程式會試圖傳送最大長度的資料報。
365~366 如果ipopt_dst.s_addr指定了一個非零地址,則選項中包括了源路由,並且分組首部的ip_dst被源路由的第一跳目的地代替。
TCP呼叫MGETHDR為IP和TCP首部分配一個單獨的mbuf。圖27顯示了在執行367~378行程式碼之前,一個TCP報文段的mbuf結構。
圖27 函式ip_insertoptions:TCP報文段
如果被插入的選項佔據了多於16位元組,則第367行的測試為真,並呼叫MGETHDR分配另一個mbuf。圖28顯示了選項被複制到新的mbuf後,該快取的結構。
圖28 函式ip_insertoptions:在選項被複制後的TCP報文段
367~378 如果分組首部被存放一簇,或者第一個快取中沒有多餘選項的空間,則ip_insertoptions分配一個新的分組首部mbuf,初始化它的長度,從舊的快取中把該IP首部擷取下來,並把該首部從舊快取中移動到新快取中。
對於UDP,它使用M_PREPEND把UDP和IP首部放置到快取的最後,與資料分離,如圖29所示。因為首部是放在快取的最後,所以在快取中總是有空間存放選項,對UDP來說,第367行的條件總為假。
圖29 函式ip_insertoptions:UDP報文段
379~384 如果分組在快取資料區的開始部分有存放選項的空間,則修改m_data和m_len,以包含optlen更多的位元組。並且當前的IP首部被ovbcopy移走,為選項騰出位置。
385~390 ip_insertoptions現在可以把ipoption結構的成員ipopt_list直接複製到緊接在IP首部後面的快取中。把新的首部長度存放在*phlen中,修改資料報長度(ip_len),並返回一個指向分組首部快取的指標。
ip_pcbopts函式
函式ip_pcbopts把IP選項表及IP_OPTIONS插口選項轉換成ip_output希望的格式:ipoption結構。如圖30所示。
圖30 函式ip_pcbopts
559~562 第一個引數,pcbopt引用指向當前選項表的指標。然後該函式用一個指向新選項表的指標來代替該指標,這個新選項表是由第二個引數m指向的快取鏈所指定的選項構造而來。該過程所準備的選項表,將被包含在IP_OPTIONS插口選項中,除了LSRR和SSRR選項的格式外,看起來像一個標準的IP選項表。對這些選項,第一跳目的地址作為路由的第一個地址出現的。圖12中,在外出的分組中,第一跳目的地址是作為目的地址出現的,而不是路由的第一個地址。
a. 扔掉以前的選項
563~580 所有被m_free和*pcbopt扔掉的選項都被清除。如果該過程傳過來一個空快取或者根本不傳遞任何新的選項,並立即返回。
如果新選項表沒有填充到4bit的邊界,則ip_pcbopts跳到bad,扔掉該表,並返回EINVAL。
該函式的其餘部分重新安排該表,使其看起來像一個ipoption結構。圖31顯示了這個過程。
圖31 ip_pcbopts選項表處理
b. 為第一跳目的地騰出位置
581~592 如果快取中沒有位置,則把所有的資料都向後快取的末尾移動4個位元組(一個in_addr結構的大小)。ovbcopy完成複製,bzero清除快取開始的4個位元組。
c. 掃描選項表
593~606 for迴圈掃描選項表,尋找LSRR和SSRR選項。對於多位元組選項,該迴圈也驗證選項的長度是否合理。
d. 重新安排LSRR和SSRR選項
607~638 當該迴圈找到一個LSRR或者SSRR選項時,它把快取的大小、迴圈變數和選項長度減去4,因為選項的第一個地址將被移走,並被移到快取的前面。
bcopy把第一個地址移走,ovbcopy把選項的其它部分移動4個位元組,來填補第一個地址留下的空隙。
e. 清除
639~646 迴圈結束後,選項表的大小(包括第一跳地址)必須不能超過44位元組(MAX_IPOPTLEN+4)。更長的選項表無法放入IP分組的首部。該表被儲存在*pcbopt中,函式返回。
更多最新文章盡在公眾號:大白愛爬山,歡迎關注!