C++实现基于ICMP协议的ping命令

前言

上一篇,对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抓包结果如下
在这里插入图片描述

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,使用ICMP(Internet Control Message Protocol)实现traceroute(也称为tracert或路径跟踪)需要使用socket编程,特别是套接字的底层网络功能。ICMP协议主要用于错误报告和路由信息查询,而traceroute利用ICMP的"Time to Live" (TTL)字段来追踪数据包从源到目的地的路径。 以下是一个简单的traceroute实现步骤概述: 1. 包含必要的头文件: ```cpp #include <iostream> #include <string> #include <vector> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <net/ethernet.h> #include <net/if.h> ``` 2. 创建套接字并绑定本地地址: ```cpp int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sock < 0) { // 处理错误 } struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_port = htons(0); // 不关心端口 if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { // 处理错误 } ``` 3. 发送ICMP Echo Request(用于探测目的主机): ```cpp std::string host_ip = "目标IP地址"; struct iphdr *iph = (struct iphdr *) sendmsg(sock, &msg, 0, sizeof(msg), NULL, 0, NULL); ``` 这里`msg`包含ICMP请求报文,包括目的IP地址。 4. 接收ICMP Echo Reply并处理数据: ```cpp char buffer[2048]; while (true) { ssize_t len = recvfrom(sock, buffer, sizeof(buffer), 0, NULL, NULL); if (len < 0) { break; } // 解析ICMP报文,提取TTL值,并记录路径 // ... } close(sock); ``` 5. 在`while`循环中,你需要检查ICMP报文的类型和代码,通常使用`ping()`库或者第三方库来解析这些信息,并根据TTL递减更新路径记录。 请注意,这个过程涉及到复杂的网络编程和协议解析,而且在某些系统上可能受到权限限制。实际编写代码时,你可能需要查阅相关的技术文档和库(如libpcap)来辅助实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值