概說《TCP/IP詳解 卷2》第11章 ICMP:Internet控制報文協議

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

本文要點

  • 引言

  • ICMP結構

  • ICMP的protosw結構

  • 輸入處理:icmp_input函數

  • 差錯處理

  • 請求處理

    • 回顯詢問

    • 時間戳詢問

    • 地址掩碼詢問

    • 信息詢問

    • 路由器發現

  • 重定向處理

  • 回答處理

  • 輸出處理

  • icmp_error函數

  • icmp_reflect函數

  • icmp_send函數

  • icmp_sysctl函數

  • 小結

 

引言

    ICMP在IP系統間傳遞差錯和管理報文,是任何IP實現必要和要求的組成部分。ICMP有自己的傳輸協議號1,允許ICMP報文在IP數據內攜帶。應用程序可以直接從原始IP接口發送或者接收ICMP報文。

    ICMP報文分成兩類:差錯和查詢。查詢報文是用一對請求和回答定義的。差錯報文通常包含引起錯誤的IP數據報的第一個分片的IP首部和選項,加上該分片數據部分的前8個字節。標準假定這8個字節包含了該分組運輸層首部的所有分用信息,這樣運輸層協議可以向正確的進程提交ICMP差錯報文。

    TCP和UDP端口號在它們首部的前8個字節內出現。

    圖1和圖2顯示了所有目前定義的ICMP報文。雙線上面是ICMP請求和回答報文;雙線下面的是ICMP差錯報文。

圖1 ICMP報文類型和代碼

圖2 ICMP報文類型和代碼(續)

    圖1、2、3中含有大量信息:

  • PRC_欄顯示了Net/3處理的與協議無關的差錯碼和ICMP報文之間的映射。對請求和回答,這一列是空的,因爲這種情況下不會產生差錯。如果對一個ICMP差錯,這一行爲空,說明Net/3不識別該碼,並自動丟棄該差錯報文。

  • 圖4顯示了圖3中所列函數的位置。

  • icmp_input欄是icmp_input爲每個ICMP報文調用的函數。

  • UDP欄是爲UDP插口處理ICMP報文的函數。

  • TCP欄是爲TCP插口處理ICMP報文的函數。注意,是tcp_quence處理ICMP源站抑制差錯,而不是tcp_notify。

  • 如果errno欄爲空,內核不向進程報告ICMP報文。

  • 表的最後一行顯示,在用於接收ICMP報文的進程的接收點上,不識別的ICMP報文被提交給原來的IP協議。

圖3 ICMP報文類型和代碼(續)

圖4 ICMP輸入處理時調用的函數

    在Net/3中,ICMP是作爲IP之上的一個運輸層協議實現的,它不產生差錯和請求;它代表其它協議格式化併發送報文。ICMP傳遞到達的差錯,並向適當的運輸協議或者等待ICMP報文的進行發出回答。另一方面,ICMP用一個合適的ICMP回答響應大多數ICMP請求。圖5對此作了總結。

圖5 ICMP報文處理

 

ICMP結構

    Net/3通過圖6中的icmp結構訪問某個ICMP報文。

圖6 icmp結構

42~45 icmp_type標識特定報文,icmp_code進一步指定該報文(圖1第一欄)。計算icmp_cksum的算法與IP首部檢驗和相同,保護整個ICMP報文。

46~79 聯合icmp_hun(首部聯合)和icmp_dun(數據聯合)根據icmp_type和icmp_code訪問多種ICMP報文。每種ICMP報文都使用icmp_hun;只有一部分報文用icmp_dun。沒有使用的字段必須設置爲0。

80~86 我們已經看到,利用其它嵌套的結構,#define宏可以簡化結結構成員的訪問。

    圖7顯示了ICMP報文的整體結構,並再次強調ICMP報文是封裝在IP數據報裏的。

圖7 一個ICMP報文

 

ICMP的protosw結構

    inetsw[4](圖9)的protosw結構描述了ICMP,並且支持內核和進程對協議的訪問。圖8顯示了該結構。在內核裏,icmp_input處理到達的ICMP報文,進程產生的外出ICMP報文由rip_output處理。

圖8 ICMP的inetsw項

圖9 數值爲1的ip_p選擇了inetsw[4]

 

輸入處理:icmp_input函數

    回想起ipintr對數據報進行分用是根據IP首部中的傳輸協議編號ip_p。對於ICMP報文,ip_p是1,並通過ip_protox選擇inetsw[4]。

    當一個ICMP報文到達時,IP層通過inetsw[4]的pr_input函數,間接調用icmp_input。

    我們看到,在icmp_input中,每個ICMP報文要被處理3次:被icmp_input處理一次;被與ICMP差錯報文中的IP分組相關聯的傳輸協議處理一次;被記錄收到ICMP報文的進程處理一次。ICMP輸入處理過程的總的構成情況如圖10所示。

圖10 ICMP的輸入處理過程

    我們將分五個部分討論icmp_input:

  • 驗證收到的報文

  • ICMP差錯報文

  • ICMP請求報文

  • ICMP重定向報文

  • ICMP回答報文

    icmp_input函數的第一部分如圖11所示。

圖11 icmp_input函數

    a. 靜態結構

131~134 因爲icmp_input是在中斷時調用的,此時堆棧的大小是有限的。所以,爲了在每次調用icmp_input時,避免動態分配千萬的延遲,以及使堆棧最小,這4個結構是靜態分配的。icmp_input把這4個結構用作臨時變量。

    icmpsrc的命名引起誤解,因爲icmp_input把它用作臨時sockaddr_in變量,而它也從未包含過源站地址。這是Net/2版本的歷史遺留問題,不予以討論。

    b. 確認報文

135~139 icmp_input中參數m是指向一個ICMP數據報的指針,hlen是該數據報IP首部的字節長度。圖12列出了幾個在icmp_input裏用於簡化檢測無效ICMP報文的常量。

圖12 ICMP引用的用來驗證報文的常量

140~160 icmp_input從ip_len取出ICMP報文的大小,並把它存放在icmplen中。第8章講過,ipintr從ip_len中排除了IP首部的長度。如果報文長度太短,不是有效報文,就生成icps_tooshort,並丟棄該報文。如果在第一個mbuf中,ICMP首部和IP首部不是連續的,則由m_pullup保證ICMP首部以及封裝的IP分組的IP首部在同一個mbuf中。

    c. 驗證檢驗和

161~170 icmp_input隱藏mbuf中的IP首部,並且in_cksum驗證ICMP的檢驗和。如果報文被破壞,就增加icps_checksum,並丟棄該報文。

    d. 驗證類型

171~175 如果報文類型(icmp_type)不在識別範圍內,icmp_input就跳過switch執行raw語句。如果在識別範圍內,icmp_input複製icmp_code,switch按照icmp_type處理該報文。

    在ICMP switch語句處理完後,icmp_input向rip_input發送ICMP報文,後者把ICMP報文發佈給準備接收的進程。只有那些被破壞的報文以及只由內核處理的ICMP請求報文才不傳給rip_input。在這兩種情況下,icmp_input都立即返回,並跳過raw處的源程序。

    e. 原始ICMP輸入

317~325 icmp_input把到達的報文傳給rip_input,rip_input依據報文裏含有的協議及源站和目的站地址信息,把報文發佈給正在監聽的進程。

    原始IP機制允許進程直接發送和接收ICMP報文,有如下幾個原因:

  • 新的ICMP報文可以由進程處理而無需修改內核(例如路由器通告)。

  • 可以用進程而無需用內核模塊來實現ICMP請求和處理回答機制(ping和traceroute)。

  • 進程可以增加對報文的內核處理。例如,內核在更新完它的路由表後,會把ICMP重定向報文傳給一個路由守護程序。

 

差錯處理

    我們首先考慮ICMP差錯報文。當主機發出的數據報無法成功地提交給目的主機時,它就接收這些報文。目的主機或者中間路由器生成這些報文,並將它們返回到原來的系統。圖13顯示了多種ICMP差錯報文的格式。

圖13 ICMP差錯報文

    圖14中的源程序來自圖11中的switch語句。

圖14 icmp_input函數:差錯報文

172~216 對ICMP差錯的處理是最少的,因爲這主要是運輸層協議的責任。icmp_input把icmp_type和icmp_code映射到一個與協議無關的差錯碼集上,該差錯碼是由PRC_常量(圖15)表示的。PRC_常量有一個隱含的順序,正好與ICMP的code相對應。這就解釋了爲什麼code是按一個PRC_常量遞增的。

217~225 如果識別出type和code,icmp_input就跳轉到deliver。如果沒有識別出來,就跳到badcode。

圖15 與協議無關的差錯碼

    如果對所報文的差錯而言,報文長度不正確,icps_badlen加1,並丟棄該報文。Net/3總是丟棄無效的ICMP報文,也不生成有關該無效報文的ICMP差錯。這樣, 就避免在兩個有缺陷的實現之間形成無限的差錯報文序列。

226~231 icmp_input調用運輸層協議的pr_ctlinput函數,該函數根據原始數據報的ip_p,把到達分組分用到正確的協議,從而構造出原始的IP數據報。差錯碼(code)、原始IP數據報的目的地址(icmpsrc)以及一個指向無效數據報的指針(icmp_ip)被傳給pr_ctlinput。

232~234 最後,icps_datacode加1,並終止switch語句的執行。

 

請求處理

    Net/3響應具有正確格式的ICMP請求報文,但把無效ICMP報文傳給rip_input。

    除路由器通告報文外,大多數Net/3所接收的ICMP報文都生成回答報文。爲避免爲回答報文分配新的mbuf,icmp_input把請求的緩存轉換成回答的緩存,並返回給發送方。

1. 回顯詢問:ICMP_ECHO和ICMP_ECHOREPLY

    儘管ICMP非常簡單,但是ICMP回顯請求和回答卻是網絡管理員最有力的診斷工具。發出ICMP回顯語法稱爲“ping”一個主機,也就是調用ping程序一次。許多系統提供該程序來手工發送ICMP回答請求。

    圖16是ICMP回顯請求和回答報文的結構。

圖16 ICMP回顯請求和回答

    icmp_code總是0。icmp_id和icmp_seq設置成請求的發送方,回答中也不做修改。源系統可以用這些字段匹配請求和回答。icmp_data中到達的所有數據也被反射。圖17是ICMP回顯處理和icmp_input實現反射ICMP請求的源程序。

圖17 icmp_input函數:回顯請求和回答

235~237 通過把icmp_type變成ICMP_ECHOREPLY,並跳轉到reflect發送回答,icmp_input把回顯請求轉換成了回顯回答。

277~282 在爲每個ICMP請求構造回答之後,icmp_input執行reflect處的程序。在這裏,存儲數據報正確的長度被恢復,在icps_reflect和icps_outhist[]中分別計算請求的數量和ICMP報文的類型。icmp_reflect把回答發回給請求方。

2. 時間戳詢問:ICMP_TSTAMP和ICMP_TSTAMPREPLY

    ICMP時間戳報文如圖18所示。

圖18 ICMP時間戳請求和回答

    icmp_code總是0。icmp_id和icmp_seq的作用與它們在ICMP回顯報文中的一樣。請求的發送方設置icmp_otime(發出請求的時間);icmp_rtime(收到請求的時間)和icmp_ttime(發出回答的時間)由回答的發送方設置。所有時間都是從UTC午夜開始的毫秒數。

    圖19是實現時間戳報文的程序。

圖19 icmp_input函數:時間戳請求和回答

238~246 icmp_input對ICMP的響應,包括:把icmp_type改爲ICMP_TSTAMPREPLY,記錄當前icmp_rtime和icmp_ttime,並跳轉到reflect發送回答。

3. 地址掩碼詢問:ICMP_MASKREQ和ICMP_MASKREPLY

    ICMP地址掩碼請求和回答如圖20所示。

圖20 ICMP地址掩碼請求和回答

    除非系統被明確地配置成地址掩碼的授權代理,否則禁止向其發送掩碼回答。這樣,就避免系統與所有向它發出請求的系統共享不正確的地址掩碼。如果沒有管理員授權回答,系統也要忽略地址掩碼請求。

    如果全局整數icmpmaskrepl非零,Net/3會響應地址掩碼請求。icmpmaskrepl的默認值是0,icmp_sysctl可以通過sysctl(8)程序修改它。

    地址掩碼報文的處理如圖21所示。

圖21 icmp_input函數:地址掩碼請求和回答

247~256 如果沒有配置響應掩碼請求,或者該請求報文太短,就中止switch的執行,並把報文傳給rip_input(圖11)。

    a. 選擇子網掩碼

257~267 如果地址掩碼請求被髮到0.0.0.0或255.255.255.255,源地址被保存 icmpdst中。在這裏,ifaof_ifpforaddr把icmpdst作爲源站地址,在同一網絡上查找in_ifaddr結構。如果源站地址是0.0.0.0或255.255.255.255,ifaof_ifpforaddr返回一個指針,該指針指向與接收接口相關的第一個IP地址。

    default情況爲ifaof_ifpforaddr保存目的地址。

    b. 轉換成回答

269~270 通過改變icmp_type,並把所選子網掩碼ia_sockmask複製到icmp_mask,就完成了把請求轉換成回答的工作。

    c. 選擇目的地址

271~276 如果請求的源站地址全0,並且源站不知道自己的地址,Net/3必須廣播這個回答,使源站系統接收到這個報文。在這種情況下,如果接收接口位於某個廣播或點到點網絡上,該回答的目的地址將分別是ia_broadaddr和ia_dstaddr。icmp_input把回答的目的地址放在ip_src裏,因爲reflect處的程序會把源站和目的站地址倒過來。不改變單播請求的地址。

4. 信息詢問:ICMP_IREQ和ICMP_IREQREPLY

    ICMP信息報文已經過時了。它們企圖廣播一個源和目的站地址字段的網絡部分爲全0的請求,使系統發現連接的IP網絡的數據。響應該請求的主機將回返一個填好網絡號的報文。主機還需要其它辦法找到地址的主機部分。

5. 路由器發現:ICMP_ROUTERADVERT和ICMP_ROUTERSOLICIT

    Net/3內核不直接處理這些報文,而由rip_input把它們傳給一個用戶級守護程序,由它發送和響應這種報文。

 

重定向處理

    圖22顯示了ICMP重定向報文的格式。

圖22 ICMP重定向報文

    icmp_input中要討論的最後一個case是ICMP_REDIRECT。如概說《TCP/IP詳解 卷2》第8章 IP:網際協議討論,當分組被髮給錯誤的路由器時,產生重定向報文。該路由器把分組轉發給正確的路由器,併發回一個ICMP重定向報文,系統把信息記入它自己的路由表。

    圖23顯示了icmp_input用來處理重定向報文的程序。

圖23 icmp_input函數:重定向報文

    a. 驗證

282~290 如果重定向報文中含有未識別的ICMP碼,icmp_input就跳到badcode;如果報文具有無效長度或者封裝的IP分組具有無效首部長度,則中止switch。圖12顯示了ICMP差錯報文的最小長度是36(ICMP_ADVLENMIN)。ICMP_ADVLEN是當icp所指向的分組有IP選項時,ICMP差錯報文的最小長度。

291~300 icmp_input分別把重定向報文的源站地址,爲原始分組推薦的路由器(第一跳目的地)和原始分組的最終目的地址分配給icmpgw、icmpdst和icmpsrc。

    b. 更新路由

301~306 重定向信息被傳給rtredirect,由這個函數更新路由表。重定向的目的地址(保存在icmpsrc)被傳給pfctlinput,由它通告重定向的所有協議域,使協議有機會把緩存的目的站的路由作廢。

 

回答處理

    內核不處理任何ICMP回答報文。ICMP請求由進程產生,內核從不產生請求。所以,內核把它接收到的所有回答傳給等待ICMP報文的進程。另外,ICMP路由器發現報文被傳給rip_input。圖24顯示了回答處理的源代碼。

圖24 icmp_input函數:回答報文

307~322 內核無需對ICMP回答報文做出任何反應,所以在switch語句後的raw語句繼續執行。注意,switch語句的default情況也把控制傳給在raw處的代碼。

 

輸出處理

    有幾種方法產生外出的ICMP報文。比如icmp_error來產生和發送ICMP差錯報文;icmp_reflect發送ICMP回答報文。同時,進程也可能通過原始ICMP協議生成ICMP報文。圖25顯示了這些函數與ICMP外出處理之間的關係。

圖25 ICMP外出處理

 

icmp_error函數

    icmp_error在IP或運輸層協議的請求下,構造一個ICMP差錯請求報文,並把它傳給icmp_reflect,在那裏該報文被返回無效數據報的源站。

    我們分三個部分介紹這個函數:

  • 確認該報文(圖26)

  • 構造首部(圖28)

  • 把原來的數據報包含進來(圖29)

圖26 icmp_error函數:驗證

46~57 參數:n,指向包含無效數據報緩存鏈的指針;type和code,ICMP差錯類型和代碼;dest,ICMP重定向報文中的下一跳路由器地址;以及destifp,指向原始IP分組外出接口的指針。mtod把緩存鏈指針n轉換成oip,oip是指向緩存中ip結構的指針。原始IP分組的字節長度保存在ioplen中。

58~75 icps_error統計除重定向報文以外的所有ICMP差錯。Net/3不把重定向報文看作錯誤。

    icmp_error丟棄無效數據報oip,並有在以下情況下,不發送差錯報文:

  • 除IP_MF和IP_DF外,ip_off的某些位非零。這表明oip不是數據報的第一個分片,而且ICMP決不能爲跟蹤數據報的分片而生成差錯報文。

  • 無效數據報本身是一個ICMP差錯報文。如果icmp_type是ICMP請求或者響應類型,則ICMP_INFOTYPE返回真;如果是一個差錯類型,則返回假。

  • 數據報作爲鏈路層廣播或者多播到達(由M_BCASH和M_MCAST標誌表明)。

  • 該數據報是發給IP廣播或者IP多播地址的。

  • 數據報的源站地址不是單播地址(也即,源站地址是一個全零地址、環回地址、廣播地址、多播地址或E類地址)。

    這些限制的目的是爲了避免有錯的廣播數據報觸發網絡上所有主機都發出ICMP差錯報文。當網絡上所有主機同時要發送差錯報文時,產生的廣播風暴會使整個網絡的通信崩潰。

    圖27是ICMP差錯報文的構造 ,圖28的程序構造差錯報文。

圖27 ICMP差錯報文的構造

圖28 icmp_error函數:報文首部構造

76~106 icmp_error以下面的方式構造ICMP差錯報文首部:

  • m_gethdr分配一個新的分組首部緩存。MH_ALIGN定位緩存的數據指針,使ICMP首部、IP首部(選項)和最多8字節的數據被放在緩存的最後。

  • icmp_type、icmp_code、icmp_gwaddr(用於重定向)、icmp_pptr(用於參數問題)和icmp_nextmtu(用於要求分片報文)被初始化。

    一旦構造好ICMP首部,就必須把原始數據報的一部分附到首部上,如圖29所示。

圖29 icmp_error函數:包含原始數據報

107~125 無效數據取的IP首部、選項和數據(icmplen)字節被複制到ICMP差錯報文中。同時,首部的長度被加回無效數據報的ip_len中,因爲之前ip_len減去了首部長度。

    因爲MH_ALIGN把ICMP報文分配在緩存的最後,所以緩存的前面應該有足夠的空間存放IP首部。無效數據報的IP首部(除選項外)被複制到ICMP報文的前面。

    然後再恢復正確的數據報長度(ip_len)、首部長度(ip_hl)和協議(ip_p)後,IP首部就完整了。TOS字段(ip_tos)被清除。

126~129 完整的報文被傳給icmp_reflect,由icmp_reflect把它發回源主機。丟棄無效數據報。

 

icmp_reflect函數

    icmp_reflect把ICMP回答或差錯報文發回給請求或者無效數據報的源站。注意,icmp_reflect在發送數據報之前,把它的源站地址和目的地址倒過來。與ICMP報文的源站和目的站地址有關的規則非常複雜,圖30對其中幾個函數的作用作了小結。

圖30 ICMP丟棄和地址小結

    我們分三個部分討論icmp_reflect函數:源站和目的站地址選擇、選項構造及組裝和發送。圖31顯示了該函數的第一部分。

圖31 icmp_reflect函數:地址選擇

    a. 設置目的地址

329~345 icmp_reflect一開始,就複製了ip_dst,並把請求或者差錯報文的源站地址ip_src移到ip_dst。icmp_error和icmp_reflect保證:ip_src對差錯報文而言是有效的目的地址。ip_output丟掉所在發往廣播地址的分組。

    b. 選擇源站地址

346~371 icmp_reflect在in_ifaddr中找到具有單播或者廣播地址的接口,該接口地址與原始數據報的目的地址匹配,這樣icmp_reflect就爲報文選好源地址。在多接口主機上,匹配的接口可能不是接收該數據報的接口。如果沒有匹配,就選擇正在接收的接口的in_ifaddr結構,或者in_ifaddr中的第一個地址。該函數把ip_src設成所選的地址,並把ip_ttl改爲255,因爲這是一個新的數據報。

    程序的下一部分(圖32)爲ICMP報文構造選項。

圖32 icmp_reflect函數:選項構造

    c. 取得逆轉後的源路由

372~385 如果到達的數據報沒有選項,控制被傳給430行(圖33)。icmp_error傳給icmp_reflect的差錯報文從來沒有IP選項,所以後面的程序只用於那些被轉換成回答並直接傳給icmp_reflect的ICMP請求。

    cp指向回答的選項的開始。ip_srcroute逆轉並返回所有在ipintre處理數據報時保存下來的源路由選項。如果ip_srcroute返回0,即請求中沒有源路路由選項,icmp_reflect分配並初始化一個mbuf,作爲空的ipoption結構。

    d. 加上記錄路由和時間戳選項

386~416 如果opts指向某個緩存,for循環搜索原始IP首部選項,在ip_srcroute返回的源路由後面加上記錄路由和時間戳選項。

    在ICMP報文發送之前必須移走原始首部裏的選項。這由圖33中的程序完成。

圖33 icmp_reflect函數:最後的組裝

    e. 移走原始選項

417~429 icmp_reflect把ICMP報文移到IP首部的後面,這樣就從原始請求中移走了選項。如圖34所示,新選項在opt所指向的mbuf裏,被ip_output再次插入。

圖34 icmp_reflect:移走選項

    f. 發送報文和清除

430~435 在報文和選項被傳給icmp_send之前,要明確地清除廣播和多播標誌位。然後釋放掉存放選項的緩存。

 

icmp_send函數

    icmp_send(圖35)處理所有輸出的ICMP報文,並在它們傳給IP層之前計算ICMP檢驗和。

圖35 icmp_send函數

440~457 與icmp_input檢測ICMP檢驗和一樣,Net/3調整緩存的數據指針和長度,隱藏IP首部,讓in_cksum只看到ICMP報文。計算好的檢驗和放在首部的icmp_cksum,然後把數據報和所有選項傳給ip_output。ICMP層並不維護路由調整緩存,所以icmp_send只會給ip_output一個空指針(第4個參數),而不是控制標誌。特別是不傳IP_ALLOWBROADCAST,所以ip_output丟棄所有具有廣播目的地址的ICMP報文。

 

icmp_sysctl函數

    IP的icmp_sysctl函數只支持圖36列出的選項。系統管理員可以用sysctl程序修改該選項。

圖36 icmp_sysctl參數

圖37顯示了icmp_sysctl函數。

圖37 icmp_sysctl函數

468~478 如果缺少所要求的ICMP sysctl名,就返回ENOTDIR。

479~486 ICMP級以下沒有選項,所以,如果不識別選項,該函數就調用sysctl_int修改icmpmaskrepl或者返回ENOPROTOOPT。

 

小結

    ICMP協議是作爲IP上面的運輸層實現的,但它與IP層緊密結合一起。我們看到,內核直接響應ICMP請求報文,但把差錯和回答傳給合適的運輸層或者應用程序處理。

 

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

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