原理簡介:
--------
用RAW Socket實現的ping可能比上一節的應用ICMP.DLL的程序龐大些, 但是這才是我們需要關注的東西, 我的觀點真正想做網絡開發的程序員應該靜下心來讀讀這篇文章, 相信你會從中獲益頗多. 中間我也會講解一些東西為後一章的路由追蹤做一些鋪墊.
另一個重要的要講的東西, 微軟宣布隨時不支持上節講的ping用到的開發接口, 但是本節的講的是更一般的東西. 所以它不會過時, 甚至做很小的改動就可以移植到別的系統上去. 系統移植不是我們的講的重點. 但是微軟的長期支持足以引起我們充分的重視.
如何少作變動來使的這個程序實現追蹤路由的功能, 這裡只是拋磚引玉. 將ICMP包中IP包的包頭該為特定的值就能得到那個路由器的IP(要求到達目的地的跳數大於你設的特定值).
這個程序需要windows2k/WindowsXP/WindowsNT平台和系統管理員的權限.
具體實現:
--------
這段源代碼大部分來自:
http://tangentsoft.net/wskfaq/examples/rawping.html
[bugfree]只做了少量修改,給出了大量的注釋, 最後結合經驗給出了自己的建議.
----------
/* * 程序名: rawping_driver.cpp * 說明: * 驅動程序,也是主函數 */ #include <winsock2.h> #include <iostream.h> #include "rawping.h" #define DEFAULT_PACKET_SIZE 32 // 默認ICMP包字節數 #define DEFAULT_TTL 30 // 默認TTL值 #define MAX_PING_DATA_SIZE 1024 // 最大數據塊 #define MAX_PING_PACKET_SIZE (MAX_PING_DATA_SIZE + sizeof(IPHeader)) //最大ICMP包長度 /* 為 send_buf 和 recv_buf 分配內存 * send_buf大小為 packet_size * recv_buf大小為 MAX_PING_PACKET_SIZE, 保證大於send_buf */ int allocate_buffers(ICMPHeader*& send_buf, IPHeader*& recv_buf, int packet_size); /////////////////////////////////////////////////////////////////////// // Program entry point int main(int argc, char* argv[]) { int seq_no = 0; //用在發送和接受的ICMP包頭中 ICMPHeader* send_buf = 0; IPHeader* recv_buf = 0; // 判斷命令行是否合法 if (argc < 2) { cerr << "usage: " << argv[0] << " <host> [data_size] [ttl]" << endl; cerr << "\tdata_size can be up to " << MAX_PING_DATA_SIZE << " bytes. Default is " << DEFAULT_PACKET_SIZE << "." << endl; cerr << "\tttl should be 255 or lower. Default is " << DEFAULT_TTL << "." << endl; return 1; } // 處理命令行參數 int packet_size = DEFAULT_PACKET_SIZE; int ttl = DEFAULT_TTL; if (argc > 2) { int temp = atoi(argv[2]); if (temp != 0) { packet_size = temp; } if (argc > 3) { temp = atoi(argv[3]); if ((temp >= 0) && (temp <= 255)) { ttl = temp; } } } packet_size = max(sizeof(ICMPHeader), min(MAX_PING_DATA_SIZE, (unsigned int)packet_size)); // 啟動 Winsock WSAData wsaData; if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0) { cerr << "Failed to find Winsock 2.1 or better." << endl; return 1; } SOCKET sd; // RAW Socket句柄 sockaddr_in dest, source; // 三個任務(創建sd, 設置ttl, 初試dest的值) if (setup_for_ping(argv[1], ttl, sd, dest) < 0) { goto cleanup; //釋放資源並退出 } // 為send_buf和recv_buf分配內存 if (allocate_buffers(send_buf, recv_buf, packet_size) < 0) { goto cleanup; } // 初試化IMCP數據包(type=8,code=0) init_ping_packet(send_buf, packet_size, seq_no); // 發送ICMP數據包 if (send_ping(sd, dest, send_buf, packet_size) >= 0) { while (1) { // 接受回應包 if (recv_ping(sd, source, recv_buf, MAX_PING_PACKET_SIZE) < 0) { // Pull the sequence number out of the ICMP header. If // it's bad, we just complain, but otherwise we take // off, because the read failed for some reason. unsigned short header_len = recv_buf->h_len * 4; ICMPHeader* icmphdr = (ICMPHeader*) ((char*)recv_buf + header_len); if (icmphdr->seq != seq_no) { cerr << "bad sequence number!" << endl; continue; } else { break; } } if (decode_reply(recv_buf, packet_size, &source) != -2) { // Success or fatal error (as opposed to a minor error) // so take off. break; } } } cleanup: delete[]send_buf; //釋放分配的內存 delete[]recv_buf; WSACleanup(); // 清理winsock return 0; } // 為send_buf 和 recv_buf的內存分配. 太簡單, 我略過 int allocate_buffers(ICMPHeader*& send_buf, IPHeader*& recv_buf, int packet_size) { // First the send buffer send_buf = (ICMPHeader*)new char[packet_size]; if (send_buf == 0) { cerr << "Failed to allocate output buffer." << endl; return -1; } // And then the receive buffer recv_buf = (IPHeader*)new char[MAX_PING_PACKET_SIZE]; if (recv_buf == 0) { cerr << "Failed to allocate output buffer." << endl; return -1; } return 0; } /* * 程序名: rawping.h * 說明: * 主要函數庫頭文件 */ #define WIN32_LEAN_AND_MEAN #include <winsock2.h> // ICMP 包類型, 具體參見本文的第一節 #define ICMP_ECHO_REPLY 0 #define ICMP_DEST_UNREACH 3 #define ICMP_TTL_EXPIRE 11 #define ICMP_ECHO_REQUEST 8 // 最小的ICMP包大小 #define ICMP_MIN 8 // IP 包頭 struct IPHeader { BYTE h_len:4; // Length of the header in dwords BYTE version:4; // Version of IP BYTE tos; // Type of service USHORT total_len; // Length of the packet in dwords USHORT ident; // unique identifier USHORT flags; // Flags BYTE ttl; // Time to live, 這個字段我在下一節中用來實現Tracert功能 BYTE proto; // Protocol number (TCP, UDP etc) USHORT checksum; // IP checksum ULONG source_ip; ULONG dest_ip; }; // ICMP 包頭(實際的包不包括timestamp字段, // 作者用來計算包的回應時間,其實完全沒有必要這樣做) struct ICMPHeader { BYTE type; // ICMP packet type BYTE code; // Type sub code USHORT checksum; USHORT id; USHORT seq; ULONG timestamp; // not part of ICMP, but we need it }; extern USHORT ip_checksum(USHORT* buffer, int size); extern int setup_for_ping(char* host, int ttl, SOCKET& sd, sockaddr_in& dest); extern int send_ping(SOCKET sd, const sockaddr_in& dest, ICMPHeader* send_buf, int packet_size); extern int recv_ping(SOCKET sd, sockaddr_in& source, IPHeader* recv_buf, int packet_size); extern int decode_reply(IPHeader* reply, int bytes, sockaddr_in* from); extern void init_ping_packet(ICMPHeader* icmp_hdr, int packet_size, int seq_no);