前言
接上一篇,对ICMP的学习,用C++实现基于ICMP协议的ping工具,用来判断设备的在线状态。
对比ICMP报文格式进行验证学习,再次,将ICMP报文格式粘贴过来,如下:
设计思路
- 定义IP数据包头结构(仅解析时用到)
- 定义ICMP数据包头结构
- 发包之前进行ICMP包的校验和
- 用sendto发送ICMP报文
- 用recvfrom接收返回的IP数据包
- 解析出ICMP包进行状态判断
代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <winsock2.h>
#pragma comment(lib,"WS2_32") //链接到WS2_32.lib
using namespace std;
//IP数据包头结构 8位1字节 共20字节
struct IPHeader
{
unsigned char m_VerHen; //4位版本+4位首部长度
unsigned char m_byTOS; //8服务类型
unsigned short m_byTotalLen; //16总长度
unsigned short m_usID; //16标识
unsigned short m_usFlagFragOffset; //3位标识+13位片偏移
unsigned char m_byTTL; //8TTL
unsigned char byProtocol; //8位协议
unsigned short m_usHChecksum; //16位首部检验和
unsigned int m_ulSrcIP; //32源IP地址
unsigned int m_ulDestIP; //32目的IP地址
};
//ICMP数据包头结构 8字节
struct ICMPHeader
{
unsigned char m_byType; //类型
unsigned char m_byCode; //代码
unsigned short m_usChecksum; //检验和
unsigned short m_usID; //标识符
unsigned short m_usSeq; //序号
};
//计算校验和
unsigned short CalCheckSum(unsigned short *pBuffer, int nSize)
{
unsigned long ulCheckSum = 0;
while (nSize > 1)
{
ulCheckSum += *pBuffer++;
nSize -= sizeof(unsigned short);
}
if (nSize)
{
ulCheckSum += *(unsigned short *)pBuffer;
}
ulCheckSum = (ulCheckSum >> 16) + (ulCheckSum & 0xffff);
ulCheckSum += (ulCheckSum >> 16);
return (unsigned short)(~ulCheckSum);
}
ICMPHeader *pICMPHeader = NULL;
//填充数据包
void fillICMPHeader(char *icmpData,int dataSize)
{
pICMPHeader = (ICMPHeader*)icmpData;
pICMPHeader->m_byType = 8;
pICMPHeader->m_byCode = 0;
pICMPHeader->m_usID = (unsigned short)GetCurrentThreadId();
pICMPHeader->m_usSeq = 0;
pICMPHeader->m_usChecksum = CalCheckSum((unsigned short *)icmpData, dataSize);
}
//解析
int resolutionResponse(char* recvbuf, int bytes, sockaddr_in *recv, int tid)
{
IPHeader *ipHead = (IPHeader *)recvbuf;
unsigned short ipHeadLen = ((ipHead->m_VerHen & 0x0f) * 4);
if (bytes<ipHeadLen + 8) //ICMP数据不完整
{
return -1;
}
ICMPHeader *pICMPHeader = (ICMPHeader*)(recvbuf + ipHeadLen);
if (pICMPHeader->m_byType!=0)//0表示存在回显应答
{
return -2;
}
if (pICMPHeader->m_usID != (unsigned short)tid)
{
return -3;
}
return 0; //正确
}
int ping(DWORD dwDestIP, unsigned int timeout)
{
WSADATA WSAData;
if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)
{ //如果初始不成功则报错,GetLastError()返回发生的错误消息
cout << "WSAStarup() failed :" << GetLastError << endl;
return -5;
}
SOCKET m_sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (m_sockRaw == INVALID_SOCKET)
{
cerr << "WSASocket() failed :" << WSAGetLastError() << "eeee" << endl;
return -6;
}
setsockopt(m_sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));//接收超时选项
setsockopt(m_sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));//发送超时选项
//准备发送的数据
char icmpData[40] = { 0 };
char data[32] = {0};//ICMP数据部分
memcpy(data, "abcdefghijklmnopqrstuvwabcdefghi", 32);
memcpy(icmpData + sizeof(ICMPHeader), &data, 32);//在ICMP包头后,添加数据部分
int dataSize = sizeof(ICMPHeader) + sizeof(data);
fillICMPHeader(icmpData, dataSize);
//远程通信
sockaddr_in sockaddrDest;//目标地址
memset(&sockaddrDest, 0, sizeof(sockaddrDest));
sockaddrDest.sin_family = AF_INET;
sockaddrDest.sin_addr.S_un.S_addr = dwDestIP;
//发送数据
sendto(m_sockRaw, icmpData, dataSize, 0, (sockaddr*)&sockaddrDest, sizeof(sockaddrDest));
int iRet = -1;
sockaddr_in sockaddrRecv;//收到的反馈
int recvLen = sizeof(sockaddrRecv);
while (1)
{
char recvBuf[1024] = { 0 };
int iRecv = recvfrom(m_sockRaw, recvBuf, 1024, 0, (sockaddr*)&sockaddrRecv, &recvLen);
int ret = resolutionResponse(recvBuf, iRecv, &sockaddrRecv, GetCurrentThreadId());
if (ret == 0)
{
iRet = 0;
cout << "在线" << endl;
break;
}
else if (ret==-2)
{
iRet = -1;
cout << "离线" << endl;
break;
}
else
{
iRet = -2;
cout << "超时" << endl;
break;
}
}
//释放套接字
closesocket(m_sockRaw);
WSACleanup();
return iRet;
}
int main()
{
string ip = "192.168.1.66";
int ret = ping(inet_addr(ip.c_str()), 3000);
system("pause");
return 0;
}
结果:在线
WireShark抓包结果如下