TCP/IP協議筆記5-ICMP協議及其應用
1 ICMP協議概述
ICMP(Internet Control Message Protocol)協議是因特網控制報文協議,ICMP常被認為是網路層協議,它的報文存在於IP資料報的資料部分,如圖。

ICMP協議棧
因為ICMP是基於IP資料報的,所以跟TCP不同的是,它是不需要指定埠的,更沒有建立連線一說。而且,通常來說ICMP協議都是核心幫你實現的,系統自身就支援了,並不像TCP/HTTP等還要自己開個服務監聽對應埠啥的。可能有人會有疑問了,既然沒有埠來標識了,那我有時候開多個ping程序,這些響應訊息是怎麼對應到不同的ping程序的? 這個就是ICMP報文裡面的識別符號的作用了。識別符號會在響應中帶回來,這樣傳送方就能根據識別符號將請求和應答匹配了。在ping中,這個識別符號就是程序ID。
ICMP報文有多種型別,如地址掩碼請求和應答、時間戳請求和應答、請求回顯和回顯應答等。ICMP報文通用格式如下,不同型別的報文內容有所不同。ICMP協議在 ping,traceroute等工具中有典型應用,下面都分析一下。

ICMP報文格式
2 Ping 原理分析
ping使用的是 ICMP 的請求回顯/回顯應答型別的報文,格式如下。它的內容包括識別符號、序列號以及回顯資料3部分,報文大小預設為 64 位元組(header的8位元組+body的56位元組)。

ICMP回顯報文格式
- 請求回顯型別是8,回顯應答型別是0,他們程式碼都是 0,校驗和是包內容根據演算法生成用於校驗資料完整性。
- 識別符號在 Linux/macOS 中用的是程序ID。
- 序列號在 Linux/macOS 中是從0遞增的,每個程序獨立的。
- 回顯資料包括髮送ping請求的時間戳(在macOS佔8位元組,在Linux佔16位元組),以及一串填充資料,在Linux這串數字預設是0x10...37,共40位元組。在macOS中是0x08090a...37,共48位元組。填充資料你也可以通過
-p pattern
指定,比如ping -pff 192.168.33.10
,則填充資料全部是 ff。 - 預設TTL是64,你可以通過
-t ttl
指定TTL值。 - ping請求時間 = 接收到回顯應答的時間 - 應答回顯資料中的時間
例項分析
在測試機ping我的虛擬機器 ping -c2 192.168.33.10
,192.168.33.10是我測試用的虛擬機器IP,wireshark抓包如下:

Ping請求

Ping響應
可以驗證前面的分析。第2對請求和應答跟第一對類似,只是序列號,校驗和等不同罷了。
關於校驗和
ICMP報文頭部中的校驗和生成/校驗方式也比較簡單。
- 生成:先將校驗和置為0,然後將ICMP報文的header+body按16bit分組求和。如果結果溢位,則將高16位和低16位求和,直到高16位為0。最後求反就是檢驗和的值。
- 校驗:將報文的header+body按16bit分組求和(包括校驗和欄位),看看結果是否全是1,如果不是,則校驗失敗。

ICMP校驗和演算法
如何自己寫一個ping?可以參考下這位朋友的ping工具的 python實現。 Lingerhk: icmp_ping_tool.py
3 Traceroute 原理分析
traceroute 用於檢視IP資料報從一臺主機傳到另一臺主機所經過的路由。其實,在IP資料報的頭部的選項欄位有一個 IP記錄路由選項(RR),它也可以記錄路由。為什麼不直接用它而是另外弄出個traceroute工具,這是因為:
- 1)IP首部長度限制,導致記錄的IP地址最多9個 ,遠遠不夠。
- 2)並不是所有路由器都支援記錄路由選項,因此某些路徑無法使用。
traceroute 用到ICMP協議和TTL欄位。TTL欄位是資料報的生存週期,初始值通常預設是64,每個處理資料報的路由器都需要把TTL值減去1或者資料報在路由器停留的秒數(因為絕大多數路由器轉發資料報時延都小於1秒,因此通常都是減去1,而且很多路由器的實現即便超過1秒也是減去1,因此可以把TTL看做一個跳站計數器)。路由器接收到一份IP資料報時,如果TTL為0或者1,則路由器不轉發該資料報,而是丟棄並給源機器傳送一份ICMP超時報文,而ICMP資訊中的IP報文中源地址正是路由器的IP地址。
traceroute的原理就是:
- 先發送一份TTL為1的報文,這樣第一個路由器會將TTL減1然後丟棄該報文,併發回一個ICMP超時報文,這樣就得到了第一個路由器的IP地址;接著傳送一個TTL為2的報文,可以得到第二個路由器的IP地址;繼續該過程直到目的主機。
- 但是目的主機即便收到TTL為1的報文,它也不會丟棄該資料報併發回一份ICMP超時報文,因為此時資料報已經到了目的地。為了判斷是否到達目的主機,traceroute傳送的報文采用了UDP資料報,它選擇一個很大的埠值如30000以上的,以保證沒有其他應用程式使用該埠,然後目的主機會返回一個埠不可達的ICMP報文,這樣就可以知道什麼時候結束。
- traceroute針對每個TTL會發3次UDP報文,並列印每次的往返時間。如果5秒內沒有收到3次報文中任何一個的響應,則列印*號繼續下一個TLL的報文傳送。3次報文選擇的UDP目的埠分別是 33435,33436,33437,UDP報文資料長度為24位元組,內容為全0(macOS環境)。
例項分析
執行 traceroute 119.75.217.109
,可以看到wireshark抓包的前幾跳資訊,TTL最開始是1,然後是2...,埠是33435到33437,每個TTL發3次報文,在沒有達到目的主機前,返回的是ICMP超時報文。到達主機後,則會返回ICMP埠不可達報文。

traceroute 請求的UDP報文

traceroute 響應的ICMP超時報文

traceroute 目的主機響應的ICMP埠不可達報文
由於IP路由通常都是動態的,每個路由器都要判斷資料報接下來要轉發到哪個路由器,應用程式對路由策略並不控制。而traceroute程式的IP源站選路選項( -g gateway
)可以實現傳送者指定路由,比如指定必須經過哪些路由IP,這裡就不展開了。有興趣的可以參見 《TCP/IP詳解 卷1:協議》的第8章。
參考資料
- 《TCP/IP詳解 卷1:協議》第6、7、8章
- https://zh.scribd.com/doc/7074846/ICMP-and-Checksum-Calc (注:本文中引用的ICMP報文結構圖都來自這裡)
- https://linux.die.net/man/8/ping
- https://github.com/Lingerhk/hacking_script/blob/master/net_attacking/icmp_ping_tool.py