极其常用的Ping命令通过向计算机发送ICMP Echo请求报文并且监听回应报文的返回,以校验与远程计算机或本地计算机的连接。
使用ICMP.DLL实现Ping
在Windows平台编程中实现Ping的一个最简单方法是调用ICMP.DLL这个动态链接库,引用ICMP.DLL中的三个函数即可:
HANDLE IcmpCreateFile(void); 这个函数打开个ICMP Echo请求能使用的句柄;
BOOL IcmpCloseHandle(HANDLE IcmpHandle); 这个函数关闭由IcmpCreateFile打开的句柄;
DWord IcmpSendEcho( HANDLE IcmpHandle, // IcmpCreateFile打开的句柄 IPAddr DestinationAddress, //Echo请求的目的地址 LPVOID RequestData, //发送数据buffer WORD RequestSize, //发送数据长度 PIP_OPTION_INFORMATION RequestOptions, // IP_OPTION_INFORMATION指针 LPVOID ReplyBuffer, //接收回复buffer DWORD ReplySize, //接收回复buffer大小 DWORD Timeout //等待超时 ); 这个函数发送Echo请求并等待回复或超时。
把这个函数和相关数据封装成一个类CPing,CPing类的头文件如下:
class CPing { public: CPing(); ~CPing(); BOOL Ping(char* strHost); private: // ICMP.DLL 导出函数指针 HANDLE (WINAPI *pIcmpCreateFile)(VOID); BOOL (WINAPI *pIcmpCloseHandle)(HANDLE); DWORD (WINAPI *pIcmpSendEcho)(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD); HANDLE hndlIcmp; // 加载ICMP.DLL库句柄 BOOL bValid; //是否构造(获得ICMP.DLL导出函数指针和初始化WinSock)成功 }; CPing类的构造函数获得ICMP.DLL中导出函数的指针并初始化WinSock:
CPing::CPing() { bValid = FALSE; WSADATA wsaData; int nRet; // 动态加载ICMP.DLL hndlIcmp = LoadLibrary("ICMP.DLL"); if (hndlIcmp == NULL) { ::MessageBox(NULL, "Could not load ICMP.DLL", "Error:", MB_OK); return; } // 获得ICMP.DLL中导出函数指针 pIcmpCreateFile = (HANDLE (WINAPI *)(void))GetProcAddress((HMODULE)hndlIcmp,"IcmpCreateFile"); pIcmpCloseHandle = (BOOL (WINAPI *)(HANDLE))GetProcAddress((HMODULE)hndlIcmp,"IcmpCloseHandle"); pIcmpSendEcho = (DWORD (WINAPI *)(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD)) GetProcAddress((HMODULE)hndlIcmp,"IcmpSendEcho"); // 检查所有的指针 if (pIcmpCreateFile == NULL pIcmpCloseHandle == NULL pIcmpSendEcho == NULL) { ::MessageBox(NULL, "Error loading ICMP.DLL", "Error:", MB_OK); FreeLibrary((HMODULE)hndlIcmp); return; }
// 初始化WinSock nRet = WSAStartup(0x0101, &wsaData ); if (nRet) { ::MessageBox(NULL, "WSAStartup() error:", "Error:", MB_OK); WSACleanup(); FreeLibrary((HMODULE)hndlIcmp); return; } // 检查WinSock的版本 if (0x0101 != wsaData.wVersion) { ::MessageBox(NULL, "No WinSock version 1.1 support found", "Error:", MB_OK); WSACleanup(); FreeLibrary((HMODULE)hndlIcmp); return; } bValid = TRUE; }
CPing类的析构函数完成相反的动作:
CPing::~CPing() { WSACleanup(); FreeLibrary((HMODULE)hndlIcmp); } CPing类的Ping函数是最核心的函数,实现真正的ping操作:
int CPing::Ping(char *strHost) { strUCt in_addr iaDest; // Internet地址结构体 LPHOSTENT pHost; // 主机入口结构体指针 DWORD *dwAddress; // IP地址 IPINFO ipInfo; // IP选项结构体 ICMPECHO icmpEcho; // ICMP Echo回复buffer HANDLE hndlFile; // IcmpCreateFile函数打开的句柄
if (!bValid) { return FALSE; }
//使用inet_addr()以判定ping目标为地址还是名称 iaDest.s_addr = inet_addr(strHost); if (iaDest.s_addr == INADDR_NONE) pHost = gethostbyname(strHost); else pHost = gethostbyaddr((const char*) &iaDest, sizeof(struct in_addr),AF_INET); if (pHost == NULL) { return FALSE; }
// 拷贝IP地址 dwAddress = (DWORD*)(*pHost->h_addr_list);
// 获得ICMP Echo句柄 hndlFile = pIcmpCreateFile();
// 设置发送信息缺省值 ipInfo.Ttl = 255; ipInfo.Tos = 0; ipInfo.IPFlags = 0; ipInfo.OptSize = 0; ipInfo.Options = NULL; icmpEcho.Status = 0; // 请求一个ICMP echo pIcmpSendEcho(hndlFile, *dwAddress, NULL, 0, &ipInfo, &icmpEcho, sizeof(struct tagICMPECHO), 1000);
//设置结果 iaDest.s_addr = icmpEcho.Source; if (icmpEcho.Status) { return FALSE; }
// 关闭ICMP Echo句柄 pIcmpCloseHandle(hndlFile); return TRUE; } 其中所使用的相关结构体定义为:
typedef struct tagIPINFO { u_char Ttl; // TTL u_char Tos; // 服务类型 u_char IPFlags; // IP标志 u_char OptSize; // 可选数据大小 u_char *Options; // 可选数据buffer } IPINFO, *PIPINFO;
typedef struct tagICMPECHO { u_long Source; // 源地址 u_long Status; // IP状态 u_long RTTime; // RTT u_short DataSize; // 回复数据大小 u_short Reserved; // 保留 void *pData; // 回复数据buffer IPINFO ipInfo; // 回复IP选项 } ICMPECHO, *PICMPECHO;
使用Raw Socket实现Ping
仅仅采用ICMP.DLL并不能完全实现ICMP灵活多变的各类报文,只有使用Raw Socket才是ICMP的终极解决之道。
使用Raw Socket发送ICMP报文前,我们要完全依靠自己的代码组装报文:
//功能:初始化ICMP的报头, 给data部分填充数据, 计算校验和 void init_ping_packet(ICMPHeader *icmp_hdr, int packet_size, int seq_no) { //设置ICMP报头字段 icmp_hdr->type = ICMP_ECHO_REQUEST; icmp_hdr->code = 0; icmp_hdr->checksum = 0; icmp_hdr->id = (unsigned short)GetCurrentProcessId(); icmp_hdr->seq = seq_no; icmp_hdr->timestamp = GetTickCount();
// 填充data域 const unsigned long int deadmeat = 0xDEADBEEF; char *datapart = (char*)icmp_hdr + sizeof(ICMPHeader); int bytes_left = packet_size - sizeof(ICMPHeader); while (bytes_left > 0) { memcpy(datapart, &deadmeat, min(int(sizeof(deadmeat)), bytes_left)); bytes_left -= sizeof(deadmeat); datapart += sizeof(deadmeat); }
// 计算校验和 icmp_hdr->checksum = ip_checksum((unsigned short*)icmp_hdr, packet_size); }
计算校验和(Checksum)的函数为:
//功能:计算ICMP包的校验和 unsigned short ip_checksum(unsigned short *buffer, int size) { unsigned long cksum = 0;
// 将所有的16数相加 while (size > 1) { cksum += *buffer++; size -= sizeof(unsigned short); } if (size) //加上最后一个BYTE { cksum += *(unsigned char*)buffer; }
//和的前16位和后16位相加 cksum = (cksum >> 16) + (cksum &0xffff); cksum += (cksum >> 16);
return (unsigned short)(~cksum); } 在真正发送Ping报文前,需要先初始化Raw Socket:
// 功能:初始化RAW Socket, 设置ttl, 初始化目标地址 // 返回值:<0 失败 int setup_for_ping(char *host, int ttl, SOCKET &sd, sockaddr_in &dest) { // 创建原始套接字 sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0); if (sd == INVALID_SOCKET) { cerr << "Failed to create raw socket: " << WSAGetLastError() << endl; return - 1; }
if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*) &ttl, sizeof(ttl)) ==SOCKET_ERROR) { cerr << "TTL setsockopt failed: " << WSAGetLastError() << endl; return - 1; }
// 初始化目标主机信息块 memset(&dest, 0, sizeof(dest));
// 将第1个参数转换为目标IP地址 unsigned int addr = inet_addr(host); if (addr != INADDR_NONE) { // 为IP地址 dest.sin_addr.s_addr = addr; dest.sin_family = AF_INET; } else { // 非IP地址,进行主机名和IP地址的转换 hostent *hp = gethostbyname(host); if (hp != 0) { // 查找主机名对应的IP地址 memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length); dest.sin_family = hp->h_addrtype; } else { // 不能识别的主机名 cerr << "Failed to resolve " << host << endl; return - 1; } } return 0; } 下面可以利用Raw Socket发送生成的ICMP报文:
//功能:发送生成的ICMP包 //返回值:<0 发送失败 int send_ping(SOCKET sd, const sockaddr_in &dest, ICMPHeader *send_buf, int packet_size) { // 发送send_buf缓冲区中的报文 cout << "Sending " << packet_size << " bytes to " << inet_ntoa(dest.sin_addr) << "..." << flush; int bwrote = sendto(sd, (char*)send_buf, packet_size, 0, (sockaddr*) &dest,sizeof(dest)); if (bwrote == SOCKET_ERROR) { cerr << "send failed: " << WSAGetLastError() << endl; return - 1; } else if (bwrote < packet_size) { cout << "sent " << bwrote << " bytes..." << flush; } return 0; } 发送Ping报文后,我们需要接收Ping回复ICMP报文:
//功能:接收Ping回复 //返回值: <0 接收失败 int recv_ping(SOCKET sd, sockaddr_in &source, IPHeader *recv_buf, int packet_size) { // 等待Ping回复 int fromlen = sizeof(source); int bread = recvfrom(sd, (char*)recv_buf, packet_size + sizeof(IPHeader), 0,(sockaddr*) &source, &fromlen); if (bread == SOCKET_ERROR) { cerr << "read failed: "; if (WSAGetLastError() == WSAEMSGSIZE) { cerr << "buffer too small" << endl; } else { cerr << "error #" << WSAGetLastError() << endl; } return - 1; } return 0; } 并使用如下函数对接收到的报文进行解析:
// 功能:解析接收到的ICMP报文 // 返回值: -2忽略, -1失败, 0 成功 int decode_reply(IPHeader *reply, int bytes, sockaddr_in *from) { // 偏移到ICMP报头 unsigned short header_len = reply->h_len *4; ICMPHeader *icmphdr = (ICMPHeader*)((char*)reply + header_len);
// 报文太短 if (bytes < header_len + ICMP_MIN) { cerr << "too few bytes from " << inet_ntoa(from->sin_addr) << endl; return - 1; } // 解析回复报文类型 else if (icmphdr->type != ICMP_ECHO_REPLY) { //非正常回复 if (icmphdr->type != ICMP_TTL_EXPIRE) { //ttl减为零 if (icmphdr->type == ICMP_DEST_UNREACH) { //主机不可达 cerr << "Destination unreachable" << endl; } else { //非法的ICMP包类型 cerr << "Unknown ICMP packet type " << int(icmphdr->type) <<" received" << endl; } return - 1; } } else if (icmphdr->id != (unsigned short)GetCurrentProcessId()) { //不是本进程发的包, 可能是同机的其它ping进程发的 return - 2; }
// 指出往返时间TTL int nHops = int(256-reply->ttl); if (nHops == 192) { // TTL came back 64, so ping was probably to a host on the // LAN -- call it a single hop. nHops = 1; } else if (nHops == 128) { // Probably localhost nHops = 0; }
// 输出信息 cout << endl << bytes << " bytes from " << inet_ntoa(from->sin_addr) <<", icmp_seq " << icmphdr->seq << ", "; if (icmphdr->type == ICMP_TTL_EXPIRE) { cout << "TTL expired." << endl; } else { cout << nHops << " hop" << (nHops == 1 ? "" : "s"); cout << ", time: " << (GetTickCount() - icmphdr->timestamp) << " ms." <<endl; } return 0; }
为了在Visual C++中更加方便地使用发送和接收ICMP报文,我们可以使用由Jay Wheeler编写的CIcmp(An ICMP Class For MFC)类,在著名的开发网站的如下地址可以下载。
这个类的简要框架如下:
class CIcmp: public CSocket { // Attributes public: BOOL OpenNewSocket(HWND hWnd, unsigned int NotificationMessage, long NotifyEvents); BOOL OpenNewSocket(HWND hWnd, unsigned int NotificationMessage, long NotifyEvents, int AFamily, int AType, int AProtocol); int CloseIcmpSocket(void); BOOL Connect(int ReceiveTimeout, int SendTimeout); BOOL Connect(LPINT ReceiveTimeout, LPINT SendTimeout, int AFamily, int AType, int AProtocol); int SetTTL(int TTL); int SetAsynchNotification(HWND hWnd, unsigned int Message, long Events); int Receive(LPSTR pIcmpBuffer, int IcmpBufferSize); unsigned long GetIPAddress(LPSTR iHostName); int Ping(LPSTR pIcmpBuffer, int IcmpBufferSize); unsigned short IcmpChecksum(unsigned short FAR *lpBuf, int Len); void DisplayError(CString ErrorType, CString FunctionName); // Operations public: CIcmp(void); CIcmp(CIcmp ©); ~CIcmp(void); public: // I/O Buffer Pointers LPIcmpHeader pIcmpHeader; LPIpHeader pIpHeader;
SOCKET icmpSocket; SOCKADDR_IN icmpSockAddr; SOCKADDR_IN rcvSockAddr; DWORD icmpRoundTripTime; DWORD icmpPingSentAt; DWORD icmpPingReceivedAt;
int icmpRcvLen; int icmpHops; int icmpMaxHops; int icmpCurSeq; int icmpCurId; int icmpPingTimer; int icmpSocketError; int icmpSocketErrorMod; unsigned long icmpHostAddress; protected: }; 初始化网络连接的函数:
BOOL CIcmp::Connect(LPINT ReceiveTimeout, LPINT SendTimeout, int AFamily, int AType, int AProtocol) { int Result; icmpSocket = NULL; icmpSocket = socket(AFamily, AType, AProtocol);
if (icmpSocket == INVALID_SOCKET) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 1; return FALSE; }
// // Set receive timeout //
Result = setsockopt(icmpSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)ReceiveTimeout, sizeof(int)); if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 2; closesocket(icmpSocket); icmpSocket = INVALID_SOCKET; return FALSE; }
// // Set send timeout // Result = setsockopt(icmpSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)SendTimeout,sizeof(int)); if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 3; closesocket(icmpSocket); icmpSocket = INVALID_SOCKET; return FALSE; } icmpCurSeq = 0; icmpCurId = (USHORT)GetCurrentProcessId(); icmpHops = 0;
return TRUE; } 接收的函数:
int CIcmp::Receive(LPSTR pIcmpBuffer, int IcmpBufferSize) { LPSOCKADDR pRcvSockAddr = (LPSOCKADDR)&rcvSockAddr;
int Result; int RcvIpHdrLen;
icmpPingReceivedAt = GetTickCount();
icmpCurId = 0;
rcvSockAddr.sin_family = AF_INET; rcvSockAddr.sin_addr.s_addr = INADDR_ANY; rcvSockAddr.sin_port = 0;
RcvIpHdrLen = sizeof rcvSockAddr;
Result = recvfrom (icmpSocket, pIcmpBuffer, IcmpBufferSize, 0, pRcvSockAddr, &RcvIpHdrLen);
if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 1; DisplayError ("Receive","CIcmp::Receive"); return Result; }
icmpRcvLen = Result;
pIpHeader = (LPIpHeader)pIcmpBuffer;
RcvIpHdrLen = pIpHeader->HeaderLength * 4; if (Result < RcvIpHdrLen + ICMP_MIN) { // // Too few bytes received // MessageBox(NULL, "Short message!", "CIcmp::Receive", MB_OKMB_SYSTEMMODAL); icmpSocketErrorMod = 2; return Result; }
pIcmpHeader = (LPIcmpHeader)(pIcmpBuffer + RcvIpHdrLen);
icmpCurId = pIcmpHeader->IcmpId; icmpRoundTripTime = icmpPingReceivedAt - pIcmpHeader->IcmpTimestamp;
if (pIcmpHeader->IcmpType != ICMP_ECHOREPLY) { // // Not an echo response! // return Result; }
icmpCurSeq = pIcmpHeader->IcmpSeq;
return Result; } 异步通知主窗口:
int CIcmp::SetAsynchNotification(HWND hWnd, unsigned int Message, long Events) { int Result = WSAAsyncSelect (icmpSocket,hWnd, Message, Events); if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 1; icmpSocket = INVALID_SOCKET; } return Result; } 设置TTL:
int CIcmp::SetTTL(int TTL) { int Result; Result = setsockopt (icmpSocket, IPPROTO_IP, IP_TTL, (LPSTR)&TTL, sizeof(int));
if (Result == SOCKET_ERROR) { icmpSocketErrorMod = 1; icmpSocketError = WSAGetLastError(); } return Result; } Ping命令的函数:
int CIcmp::Ping (LPSTR pIcmpBuffer, int DataLen) { int Result; int IcmpBufferSize = DataLen + IcmpHeaderLength; pIcmpHeader = (LPIcmpHeader)pIcmpBuffer;
memset (pIcmpBuffer, 'E', IcmpBufferSize); memset (pIcmpHeader, 0, IcmpHeaderLength);
pIcmpHeader->IcmpType = ICMP_ECHO; pIcmpHeader->IcmpCode = 0; pIcmpHeader->IcmpChecksum = 0; pIcmpHeader->IcmpId = icmpCurId; pIcmpHeader->IcmpSeq = icmpCurSeq; pIcmpHeader->IcmpTimestamp = GetCurrentTime();
pIcmpHeader->IcmpChecksum = IcmpChecksum ((USHORT FAR *)pIcmpBuffer,IcmpBufferSize);
icmpPingSentAt = GetCurrentTime(); Result = sendto (icmpSocket, pIcmpBuffer, IcmpBufferSize, 0, (LPSOCKADDR)&icmpSockAddr, sizeof icmpSockAddr);
if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 1; } return Result; }
(出处:网侠)
|