承接前三篇 ICMP 協定原理、WinSock 介紹與 ICMP.dll 應用,本文展示使用 RAW Socket 直接操作 IP/ICMP 封包實作 Ping 功能。雖然程式碼較 ICMP.dll 版本龐大,但這才是網路程式設計的核心功夫——微軟已宣佈不再長期支援 ICMP.dll,而 RAW Socket 屬標準 BSD Socket 延伸,可移植性極強。
RAW Socket (SOCK_RAW, IPPROTO_ICMP) 允許應用程式自行填寫 IP 標頭與 ICMP 承載,接收時包含完整 IP 標頭。關鍵結構定義:
// IP 標頭 (位元欄位對應 RFC 791)
struct IPHeader {
BYTE h_len:4; // 標頭長度 (32-bit 字數)
BYTE version:4; // IP 版本 (4)
BYTE tos; // 服務類型
USHORT total_len; // 總長度 (位元組)
USHORT ident; // 識別碼
USHORT flags; // 旗標 + 片段位移
BYTE ttl; // 生存時間 (TTL) —— Traceroute 關鍵
BYTE proto; // 協定號 (ICMP=1)
USHORT checksum; // IP 標頭檢驗和
ULONG source_ip; // 來源 IP
ULONG dest_ip; // 目的 IP
};
// ICMP 標頭 (Echo Request/Reply)
struct ICMPHeader {
BYTE type; // 8=Echo Request, 0=Echo Reply, 11=TTL Expired
BYTE code; // 子代碼
USHORT checksum; // ICMP 檢驗和
USHORT id; // 識別碼 (用 Process ID)
USHORT seq; // 序號
ULONG timestamp; // 非標準欄位,用於計算 RTT
};
WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, ...) 需系統管理員權限setsockopt(sd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))inet_addr → 失敗則 gethostbynamesendtorecvfrom → 跳過 IP 標頭 (h_len * 4) 取得 ICMP 標頭 → 驗證 ID/序號 → 解碼回應類型
USHORT ip_checksum(USHORT* buffer, int size) {
unsigned long cksum = 0;
while (size > 1) { cksum += *buffer++; size -= sizeof(USHORT); }
if (size) cksum += *(UCHAR*)buffer; // 處理奇數位元組
cksum = (cksum >> 16) + (cksum & 0xffff); // 折疊進位
cksum += (cksum >> 16);
return (USHORT)(~cksum); // 取反碼
}
注意:計算前必須將檢驗和欄位歸零。
Type 0 (Echo Reply):正常回應,顯示跳數與 RTTType 11 (TTL Expired):中繼路由器回報,可用於 TracerouteType 3 (Destination Unreachable):目標不可達跳數估算假設初始 TTL 為 256 (實際作業系統不同:Windows XP=128, Linux=64 等),作者提供啟發式修正邏輯。
現行 recvfrom 同步等待,網路異常時會無限阻塞。三種改良方向:
WaitForSingleObjecttimeval 超時,非阻塞輪詢 Socket 可讀性修改發送封包的 IP 標頭 TTL = 1, 2, 3...,收到 Type 11 (TTL Expired) 即為該跳路由器 IP。本程式碼已奠定基礎,僅需在迴圈中遞增 TTL 並解析 ICMP_TTL_EXPIRE 回應的來源位址。
完整原始碼包含 rawping_driver.cpp、rawping.h、rawping.cpp 三個檔案,註解詳盡,是學習 RAW Socket 網路程式設計的極佳教材。