概說《TCP/IP詳解 卷2》第9章 選項處理

原文鏈接:https://mp.weixin.qq.com/s/dD88rS1PD4xoSXLgvY5wSA

本文要點

  • 引言

  • 選項格式

  • 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函數

    在概說《TCP/IP詳解 卷2》第8章 IP:網際協議圖8中,我們看到ipintr函數在檢測分組的目的地址之前調用ip_dooptions。ip_dooptions被傳給一個指針m,該指針指向某個分組,ip_dooptions處理分組中它所知道的選項。如果ip_dooptions轉發該分組,如在處理LSRR和SSRR選項時,或由於某個差錯而丟棄該分組時,它返回1。如果它不轉發分組,ip_dooptions返回0,由ipintr繼續處理該分組。

    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中,函數返回。

 

更多最新文章盡在公衆號:大白愛爬山,歡迎關注!

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