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 (代码) | 内容 |
---|---|---|
0 | 0 | 回送应答(Echo Reply) |
3 | 0 | 网络不可达 |
3 | 1 | 主机不可达 |
3 | 2 | 协议不可达 |
3 | 3 | 端口不可达 |
3 | 4 | 需要进行分片,但设置为不分片 |
3 | 5 | 源站选路失败 |
3 | 6 | 目的网络不认识 |
3 | 7 | 目的主机不认识 |
3 | 9 | 目标网络被强制禁止 |
3 | 10 | 目标主机被强制禁止 |
3 | 11 | 由于TOS,网络不可达 |
3 | 12 | 由于TOS,主机不可达 |
3 | 13 | 由于过滤,通信被强制禁止 |
3 | 14 | 主机越权 |
3 | 15 | 优先权终止生效 |
4 | 0 | 源端被关闭 |
5 | 0 | 对网络重定向 |
5 | 1 | 对主机重定向 |
5 | 2 | 对服务类型和网络重定向 |
5 | 3 | 对服务类型和主机重定向 |
8 | 0 | 请求应答(Ping 请求) |
9 | 0 | 路由器通告 |
10 | 0 | 路由器请求通告 |
11 | 0 | 传输期间生存时间为0 |
12 | 0 | 坏的IP首部 |
12 | 1 | 缺少必要的选项 |
17 | 0 | 地址掩码请求 |
18 | 0 | 地址掩码应答 |
③ 、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
- 先将
ICMP
协议的校验和字段置为0
0x08 0x00 0x00 0x00 0x00 0x0e 0x00 0x1f 0x00 0x01 0x02 0x03 0x04 0x05 0x06
- 将
ICMP
协议报文中的每两个字节(16位)(需要注意大小端问题,网络字节序是大端模式,如0xebc9
, 对于小端主机来说,组成的是0xc9eb
,应转化为主机字节序:0xebc9
)两两相加,得到一个累加和。若报文长度为奇数,则最后一个字节(8-bit)作为高8位,再用0填充一个字节(低8-bit)扩展到16-bit,之后再和前面的累加和继续相加得到一个新的累加和。
0x0800 + 0x0000 + 0x000e + 0x001f + 0x0001+ 0x0203 + 0x0405 + 0x0600 = 0x1436
- 将累加和的高16位和低16位相加,直到最后只剩下16位
0x0000 + 0x1436 = 0x1436
- 将最后得到的16位结果取反(按位取反)作为校验和字段的值
0001 0100 0011 0110 (0x1436 取反)
1110 1011 1100 1001 (0xebc9)
3、 Ping帧抓包分析
① 、 Linux系统
Linux系统上,ICMP
数据部分会携带当前时间戳,然后数据内容是从10
开始填充。
Ping请求
Ping响应
② 、 Windows系统
Windows系统上,ICMP
数据内容是从61
(ascii
码a
)开始填充。
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 256typedef 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 NumberDWORD 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//Datafor (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帧获取TTLuint8 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;
}
备注: 注意,程序需要以管理员权限运行。
运行效果如下: