首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

ICMP Ping 程式原始碼解析 (Raw Socket 實作)

     編輯:VC++

核心原理

使用 Raw Socket (SOCK_RAW, IPPROTO_ICMP) 手工構造 ICMP Echo Request 封包 (Type 8, Code 0),發送至目標主機,接收 Echo Reply (Type 0) 計算往返時間。並示範 IP 選項 Record Route (RR) 記錄路徑 (最多 9 跳)。

關鍵資料結構


// IP 標頭 (位元欄位對應 RFC 791)
struct IpHeader {
    unsigned int h_len:4;        // 標頭長度 (4 bytes 為單位)
    unsigned int version:4;      // 版本 (4)
    unsigned char tos;           // 服務類型
    unsigned short total_len;    // 總長度
    unsigned short ident;        // 識別碼
    unsigned short frag_and_flags; // 片段旗標/位移
    unsigned char ttl;           // 生存時間
    unsigned char proto;         // 協定 (ICMP=1)
    unsigned short checksum;     // 標頭檢驗和
    unsigned int sourceIP;       // 來源 IP
    unsigned int destIP;         // 目的 IP
};

// ICMP 標頭 (擴充 timestamp 欄位用於計算 RTT)
struct IcmpHeader {
    BYTE i_type;      // 8=Echo Request, 0=Echo Reply
    BYTE i_code;      // 0
    USHORT i_cksum;   // 檢驗和
    USHORT i_id;      // 行程 ID 識別
    USHORT i_seq;     // 序號
    ULONG timestamp;  // 發送時間 (GetTickCount)
};

// IP 選項標頭 - Record Route
struct IpOptionHeader {
    unsigned char code;      // 0x07 (RR)
    unsigned char len;       // 選項總長度 (39)
    unsigned char ptr;       // 目前指標位移 (起始 4)
    unsigned long addr[9];   // 最多記錄 9 個 IP
};

核心演算法

1. 校驗和計算 (RFC 1071)


USHORT 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); // 反碼
}

2. 發送迴圈 (含逾時控制)


SOCKET sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
// 設定接收/發送逾時 1 秒
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));

// 啟用 Record Route 選項
if (bRecordRoute) {
    IpOptionHeader opt = {0}; opt.code = 0x07; opt.ptr = 4; opt.len = 39;
    setsockopt(sock, IPPROTO_IP, IP_OPTIONS, (char*)&opt, sizeof(opt));
}

for (int i=0; i<4; i++) {
    icmp_hdr->i_cksum = 0;
    icmp_hdr->timestamp = GetTickCount();
    icmp_hdr->i_seq = seq++;
    icmp_hdr->i_cksum = checksum((USHORT*)icmp_data, data_size);

    sendto(sock, icmp_data, data_size, 0, (sockaddr*)&dest, sizeof(dest));
    recvfrom(sock, recv_buf, MAX_PACKET, 0, (sockaddr*)&from, &fromlen);
    DecodeICMPHeader(recv_buf, bytes, &from); // 解析回應
    Sleep(1000);
}

3. 回應解析與路徑顯示


void DecodeICMPHeader(char* buf, int bytes, sockaddr_in* from)
{
    IpHeader* ip = (IpHeader*)buf;
    int ip_hdr_len = ip->h_len * 4;
    if (ip_hdr_len == 60 && !icmp_count) DecodeIPOptions(buf, bytes); // 解析 RR

    IcmpHeader* icmp = (IcmpHeader*)(buf + ip_hdr_len);
    if (icmp->i_type != 0 || icmp->i_id != GetCurrentProcessId()) return;

    printf("%d bytes from %s: icmp_seq=%d time=%d ms\n",
           bytes, inet_ntoa(from->sin_addr), icmp->i_seq,
           GetTickCount() - icmp->timestamp);
    icmp_count++;
}

編譯與執行


// 編譯指令 (VC6/VC++)
cl Ping.c ws2_32.lib /Zp1
// 執行
Ping [-r] <主機名或IP> [封包大小<1024>
  -r : 啟用 Record Route 記錄路徑

注意事項

  • Raw Socket 需要**系統管理員權限** (Windows 2000/XP 之後限制)。
  • WSA_FLAG_OVERLAPPED 必須指定,否則同步 Socket 配合 SO_RCVTIMEO 會導致核心永久阻塞。
  • 記錄路徑最多 9 跳,超過則截斷。
  • 防火牆可能封鎖 ICMP,導致無回應。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved