ICMP(Ping)功能原理及其C++实现简介

ICMP(Ping)功能原理及其应用简介

一、 Ping功能简介

1、 原始套接字(Raw Socket)

原始套接字(‌Raw Socket)‌是一种特殊的网络编程接口,‌它允许直接接收和发送网络层的数据包,‌而不是通过传输层。‌这种套接字可以接收本机网卡上的数据帧或者数据包,‌对于监听网络的流量和分析网络数据非常有用。

Ping属于原始套接字的应用。

2 、 ICMP帧简介

① 、帧结构
|    8bit    |    8bit    |        16bit        |   
|____________|____________|_____________________|______
|            |            |                     ||    Type    |    Code    |      Checksum       |  ICMP首部
|            |            |                     ||____________|____________|_____________________|______
|                                               ||                     Data                      |  ICMP数据 
|                                               ||_______________________________________________|______
                 #ICMP协议报文结构(标准)


|    8bit    |    8bit    |        16bit        |   
|____________|____________|_____________________|______
|    Type    |    Code    |      Checksum       ||____________|____________|_____________________|  ICMP首部
|       Identifier        |   Sequence Number   ||_________________________|_____________________|______
|                                               || 可选数据区: 请求报文方发送,应答方重复报文内容  |  ICMP数据 
|                                               ||_______________________________________________|______
            #ICMP协议常用的请求与请求响应结构(ping)
② 、 常用Type、Code 字段含义
Type(类型)Code (代码)内容
00回送应答(Echo Reply)
30网络不可达
31主机不可达
32协议不可达
33端口不可达
34需要进行分片,但设置为不分片
35源站选路失败
36目的网络不认识
37目的主机不认识
39目标网络被强制禁止
310目标主机被强制禁止
311由于TOS,网络不可达
312由于TOS,主机不可达
313由于过滤,通信被强制禁止
314主机越权
315优先权终止生效
40源端被关闭
50对网络重定向
51对主机重定向
52对服务类型和网络重定向
53对服务类型和主机重定向
80请求应答(Ping 请求)
90路由器通告
100路由器请求通告
110传输期间生存时间为0
120坏的IP首部
121缺少必要的选项
170地址掩码请求
180地址掩码应答
③ 、Checksum校验和

对于ICMP协议校验和计算方式,参考RFC官方文档给出的说明:

The checksum is the 16-bit ones's complement of the one's
complement sum of the ICMP message starting with the ICMP Type.
For computing the checksum , the checksum field should be zero.
If the total length is odd, the received data is padded with one
octet of zeros for computing the checksum.  This checksum may be
replaced in the future.

步骤如下:

一、获取ICMP报文(首部+数据部分)

二、将ICMP报文中的校验和字段置为0。

三、将ICMP协议报文中的每两个字节(16位,需要注意大小端问题)两两相加,得到一个累加和。若报文长度为奇数,则最后一个字节(8-bit)  作为高8位,再用0填充一个字节(低8-bit)扩展到16-bit,之后再和前面的累加和继续相加得到一个新的累加和。

四、(若有溢出)将累加和的高16位和低16位相加,直到最后只剩下16位。

五、将最后得到的16位结果取反(按位取反)作为校验和的值。

ICMP协议校验和计算代码示例

/**                                                            
 * icmp_checksum:
 * @size: the icmp data packet length
 * @icmp_data: icmp protocol packet both header and data
 *
 * Calc icmp's checksum:
 * if we divide the ICMP data packet is 16 bit words and sum each of them up
 * then hihg 16bit add low 16bit to sum get a value,  
 * If the total length is odd, 
 * the last byte is padded with one octet of zeros for computing the checksum.
 * Then hihg 16bit add low 16bit to sum get a value,
 * finally do a one's complementing 
 * then the value generated out of this operation would be the checksum.
 * 
 * Return: unsigned short checksum
 */
unsigned short icmp_checksum(char * icmp_packet, int size)
{
 	unsigned short * sum = (unsigned short *)icmp_packet;
 	unsigned int checksum = 0;
 	while (size > 1)
    {
  		checksum += ntohs(*sum++);
  		size -= sizeof(unsigned short);
	 }
 	
    if (size) 
    {
  		*sum = *((unsigned char*)sum);
		  checksum += ((*sum << 8) & 0xFF00);
	 }
 
 	checksum = (checksum >> 16) + (checksum & 0xffff);
 	checksum += checksum >> 16;
 
 	return (unsigned short)(~checksum);
}

校验和计算示例

ICMP协议的ping回送响应报文(大端)为例:

0x08 0x00 0xeb 0xc9 0x00 0x0e 0x00  0x1f 0x00 0x01 0x02 0x03 0x04 0x05 0x06 
  1. 先将ICMP协议的校验和字段置为0
0x08 0x00 0x00 0x00 0x00 0x0e 0x00 0x1f 0x00 0x01 0x02 0x03 0x04 0x05 0x06 
  1. ICMP协议报文中的每两个字节(16位)(需要注意大小端问题,网络字节序是大端模式,如0xebc9, 对于小端主机来说,组成的是0xc9eb,应转化为主机字节序:0xebc9)两两相加,得到一个累加和。若报文长度为奇数,则最后一个字节(8-bit)作为高8位,再用0填充一个字节(低8-bit)扩展到16-bit,之后再和前面的累加和继续相加得到一个新的累加和。
0x0800 + 0x0000 + 0x000e + 0x001f + 0x0001+ 0x0203 + 0x0405 + 0x0600 = 0x1436
  1. 将累加和的高16位和低16位相加,直到最后只剩下16位
0x0000 + 0x1436 = 0x1436
  1. 将最后得到的16位结果取反(按位取反)作为校验和字段的值
0001 0100 0011 0110 (0x1436 取反)
1110 1011 1100 1001 (0xebc9)

3、 Ping帧抓包分析

① 、 Linux系统

Linux系统上,ICMP 数据部分会携带当前时间戳,然后数据内容是从10开始填充。

Ping请求

在这里插入图片描述

Ping响应

在这里插入图片描述

② 、 Windows系统

Windows系统上,ICMP 数据内容是从61asciia)开始填充。

Ping请求

在这里插入图片描述

Ping响应

在这里插入图片描述

4、往返时间和TTL

当返回ICMP回显应答时,要打印出序列号和TTL,并计算往返时间。TTL位于IP首部的生存时间字段。ping程序通过在ICMP报文数据字段中存放发送请求的时间值来计算往返时间。当应答返回时,用当前时间减去存放在ICMP报文中的时间值,即是往返时间。

①、 TTL

TTL(‌Time to Live)‌值表示IP数据包在网络中的最大生存时间,‌即数据包在网络中存在的时间长度。‌TTL值由IP数据包的发送者设置,‌并且在IP数据包从源到目的地的整个转发路径上,‌每经过一个路由器,‌路由器都会将TTL字段的值减1。‌如果IP包的TTL值在到达目的IP之前减少为0,‌路由器将会丢弃该IP包并向IP包的发送者发送ICMP time exceeded消息。‌

在执行ping命令时,‌TTL值可以帮助我们了解数据包在传输过程中经过的路由器数量。‌具体来说,‌TTL值等于系统默认的TTL值减去经过的路由个数。‌例如,‌如果ping命令的结果显示TTL值为54,‌而系统的默认TTL值为64,‌那么可以推断出数据包经过了10个路由器(‌因为64 - 54 = 10)‌。‌

不同的操作系统默认下TTL值是不同的。‌例如,‌Linux系统的TTL值通常为64或255,‌Windows NT/2000/XP系统的TTL值为128,‌Windows 98系统的TTL值为32,‌UNIX主机的TTL值为255。‌通过分析ping命令返回的TTL值,‌我们可以大致判断出目标主机所使用的操作系统类型。‌

②、Windows Ping TTL分析

在Windows环境下,ping www.baidu.com ,分析报文。

在这里插入图片描述

Ping请求

在这里插入图片描述

Ping响应

在这里插入图片描述

5、 C++实现Ping功能

①、 Windows实现
#include <iostream>
#include <string>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")


#define ICMP_PING_DATA_SIZE           32     //填充数据长度;
#define ICMP_TYPE_PING_REQUEST        8
#define ICMP_TYPE_PING_REPLY          0

#define ICMP_PING_TIMES               4      //Ping次数
#define MAX_BUFFER_SIZE               256


typedef unsigned char        uint8;
typedef unsigned short		 uint16;
typedef unsigned int         uint32;

//ICMP校验和计算
uint16 MakeChecksum(char* icmp_packet, int size)
{
	uint16 * sum = (uint16*)icmp_packet;
	uint32 checksum = 0;
	while (size > 1)
	{
		checksum += ntohs(*sum++);
		size -= sizeof(uint16);
	}

	if (size)
	{
		*sum = *((uint8*)sum);
		checksum += ((*sum << 8) & 0xFF00);
	}

	checksum = (checksum >> 16) + (checksum & 0xffff);
	checksum += checksum >> 16;

	return (uint16)(~checksum);
}

int main(int argc, char* argv[])
{
	std::cout << "请输入扫描主机IP地址: ";
	char input[256] = { 0 };
	char* ptr = fgets(input, 256, stdin);
	if (ptr == nullptr)
	{ 
		std::cout << "输入参数异常,结束程序!" << std::endl;
		return 1;
	}

	std::string strHost = std::string(ptr);
	strHost = strHost.substr(0, strHost.length() - 1);

	struct sockaddr_in pingaddr;
	memset((char *)&pingaddr, 0, sizeof(pingaddr));
	pingaddr.sin_family = AF_INET;
	pingaddr.sin_port = 0;
	pingaddr.sin_addr.s_addr = inet_addr(strHost.c_str());


	//初始化Windows套接字;
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		std::cout << "WSAStartup失败!" << std::endl;
		return 1;
	}

	//创建Raw套接字
	SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

	uint16 usTxID = 1;
	uint16 usTxSequeNum = 1;
	for (int ii = 0; ii < ICMP_PING_TIMES;++ii)
	{
		//组帧
		uint8 ucCmdBuf[MAX_BUFFER_SIZE] = { 0 };
		uint8* pCurr = ucCmdBuf;

		*pCurr++ = ICMP_TYPE_PING_REQUEST;		 //Type
		*pCurr++ = 0x00;						 //Code
		*pCurr++ = 0x00;						 //Checksum
		*pCurr++ = 0x00;						 //Checksum
		*pCurr++ = HIBYTE(usTxID);				 //Identifier
		*pCurr++ = LOBYTE(usTxID);				 //Identifier
		*pCurr++ = HIBYTE(usTxSequeNum);         //Sequence Number
		*pCurr++ = LOBYTE(usTxSequeNum);	     //Sequence Number

		DWORD dwSendTime = GetTickCount();
		*pCurr++ = HIBYTE(HIWORD(dwSendTime));   //Current TickCount
		*pCurr++ = LOBYTE(HIWORD(dwSendTime));   //Current TickCount
		*pCurr++ = HIBYTE(LOWORD(dwSendTime));   //Current TickCount
		*pCurr++ = LOBYTE(LOWORD(dwSendTime));   //Current TickCount

		//Data
		for (int k = 0; k < ICMP_PING_DATA_SIZE - 4;++k)
		{
			*pCurr++ = 0x61 + k;
		}

		uint16 usCheckSum = MakeChecksum((char*)ucCmdBuf, pCurr - ucCmdBuf);
		ucCmdBuf[2] = HIBYTE(usCheckSum);
		ucCmdBuf[3] = LOBYTE(usCheckSum);


		int txlen = sendto(sock, (char *)&ucCmdBuf, pCurr - ucCmdBuf, 0, (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in));

		uint8 ucRxBuf[MAX_BUFFER_SIZE] = { 0 };
		struct sockaddr rxaddr;
		int iSize = sizeof(rxaddr);
		int rxlen = recvfrom(sock, (char *)&ucRxBuf, sizeof(ucRxBuf), 0, &rxaddr, &iSize);
		if (rxlen >= 0)
		{
			//IP帧获取TTL
			uint8 ucTTL = ucRxBuf[8];

			//跳过IP Header 20个字节
			uint8* pIcmpFrame = ucRxBuf + 20;

			//比对ID和序号
			uint16 iRxID = pIcmpFrame[4] * 256 + pIcmpFrame[5];
			uint16 iRxSequeNum = pIcmpFrame[6] * 256 + pIcmpFrame[7];
			if (iRxID == usTxID && iRxSequeNum == usTxSequeNum)
			{
				DWORD dwTimeTransmit = pIcmpFrame[8] * 256 * 256 * 256 + pIcmpFrame[9] * 256 * 256 + pIcmpFrame[10] * 256 + pIcmpFrame[11];
				DWORD dwTimeDiff = GetTickCount() - dwTimeTransmit;
				std::cout << "时间=" << dwTimeDiff << "ms" << std::endl;
				std::cout << "TTL=" << (int)ucTTL << std::endl;
			}
			else
			{
				std::cout << "超时" << std::endl;
			}

		}

		usTxID++;
		usTxSequeNum++;

		Sleep(1000);
	}

	
	closesocket(sock);

	WSACleanup();

	system("pause");

	return 0;
}

备注: 注意,程序需要以管理员权限运行。

运行效果如下:

在这里插入图片描述

  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值