如果觉得本文如果帮到你或者你想转载都可以,只需要标注出处即可。谢谢

利用ICMP数据包、C语言实现Ping命令程序,能实现基本的Ping操作,发送ICMP回显请求报文,用于测试—个主机到只一个主机之间的连通情况。通过本程序的训练,熟悉ICMP报文结构,对ICMP有更深的理解,掌握Ping程序的设计方法,掌握网络编程的方法和技巧,从而编写出功能更强大的程序。有关traceroute如果有时间我会也写一篇来进行讲解.W

windows和Linux实现ping的底层思想一样的,代码有细微的差别。如文文件不一样,参数定义不一样等。所以我们要实现ping功能的时候我们需要注意是在Windows上实现还是Linux上实现。

如果你不想看关于ping命令实现的原理,则可以直接通过以下目录跳转到‘8.实现Ping功能’即可.

本文目录

  1.ICMP简介

  2.ICMP工作原理

  3.ICMP报文格式

  4.ICMPv4类型

    4.1响应请求/应答(ping)

    4.2.目标不可到达、源抑制和超时报文

  5.ICMP应用

  6.ICMP攻击与防御方法

  7.IP报文头和ICMP的联系

  8.实现Ping功能

    8.1.ping实现步骤

    8.2.结果及心得

    8.3.完整代码

1.ICMP简介

ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。

 ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。

ICMP报文通常是由IP层本身、上层的传输协议(TCP或UDP)甚至某些情况下用户应用除法执行的。

ICMP报文是在IP数据报内被封装传输的。

ICMP分为两大类:有关IP数据报传递的ICMP报文(称为差错报文(error message)),以及有关信息采集和配置的ICMP报文(称为查询(query)或者信息类报文(informational message))。

注:ICMP并不为IP网络提供可靠性。相反,它表明了某些类别的故障和配置信息。

2.ICMP工作原理

ICMP提供一致易懂的出错报告信息。发送的出错报文返回到发送原数据的设备,因为只有发送设备才是出错报文的逻辑接受者。发送设备随后可根据ICMP报文确定发生错误的类型,并确定如何才能更好地重发失败的数据包。但是ICMP唯一的功能是报告问题而不是纠正错误,纠正错误的任务由发送方完成。

我们在网络中经常会使用到ICMP协议,比如我们经常使用的用于检查网络通不通的Ping命令(Linux和Windows中均有),这个“Ping”的过程实际上就是ICMP协议工作的过程。还有其他的网络命令如跟踪路由的Tracert命令也是基于ICMP协议的。

3.ICMP报文格式

ICMP报文包含在IP数据报中,属于IP的一个用户,IP头部就在ICMP报文的前面,所以一个ICMP报文包括IP头部、ICMP头部和ICMP报文,IP头部的Protocol值为1就说明这是一个ICMP报文,ICMP头部中的类型(Type)域用于说明ICMP报文的作用及格式,此外还有一个代码(Code)域用于详细说明某种ICMP报文的类型,所有数据都在ICMP头部后面。

ICMPICMP报文格式具体由[RFC777],[RFC792]规范。792是1981年9月更新,而777是1981年4月更新的。目前最新的ICMP报文格式RFC是2007年4月更新的[RFC488].

4.ICMPv4类型

已经定义的ICMP消息类型大约有10多种,每种ICMP数据类型都被封装在一个IP数据包中。主要的ICMP消息类型包括以下几种。

对于ICMPv4,信息类报文包括回显请求和回显应答(分别为类型8和0),以及路由器通告和路由器请求(分别为类型9和10,统一被称为路由器发现)。最常见的差错报文类型包括目的不可达(类型3)、重定向(类型5)、超时(类型11)和参数问题(类型12).下图为一些类型.更多的信息建议去RFC官方查看,Type和Code在IPv4和IPc6不尽相同,所以其中的差异需要我们自行去查看,本图为IPv4版本的,IPv6需要我们自己RFC查找。

1).响应请求/应答(ping)(ICMPv4类型为0/8,ICMPv6类型129/18)

我们日常使用最多的ping,就是响应请求(Type=8)和应答(Type=0),一台主机向一个节点发送一个Type=8的ICMP报文,如果途中没有异常(例如被路由器丢弃、目标不回应ICMP或传输失败),则目标返回Type=0的ICMP报文,说明这台主机存在,更详细的tracert通过计算ICMP报文通过的节点来确定主机与目标之间的网络距离。更多的信息我们可以通过RFC文档了解

2).目标不可到达(ICMPv4类型3,ICMPv6类型1)、源抑制和超时报文(ICMPv4类型11,ICMPv6类型4)

这三种报文的格式是一样的,目标不可到达报文(Type=3)在路由器或主机不能传递数据报时使用,例如我们要连接对方一个不存在的系统端口(端口号小于1024)时,将返回Type=3、Code=3的ICMP报文,它要告诉我们:“嘿,别连接了,我不在家的!”,常见的不可到达类型还有网络不可到达(Code=0)、主机不可到达(Code=1)、协议不可到达(Code=2)等。源抑制则充当一个控制流量的角色,它通知主机减少数据报流量,由于ICMP没有恢复传输的报文,所以只要停止该报文,主机就会逐渐恢复传输速率。最后,无连接方式网络的问题就是数据报会丢失,或者长时间在网络游荡而找不到目标,或者拥塞导致主机在规定时间内无法重组数据报分段,这时就要触发ICMP超时报文的产生。超时报文的代码域有两种取值:Code=0表示传输超时,Code=1表示重组分段超时。更多的信息我们可以通过RFC文档了解

5.ICMP应用

1).ping 命令使用 ICMP 回送请求和应答报文在网络可达性测试中使用的分组网间探测命令 ping 能产生 ICMP 回送请求和应答报文。目的主机收到 ICMP 回送请求报文后立刻回送应答报文,若源主机能收到 ICMP 回送应答报文,则说明到达该主机的网络正常。

2).路由分析诊断程序 tracert 使用了 ICMP时间超过报文tracert 命令主要用来显示数据包到达目的主机所经过的路径。通过执行一个 tracert 到对方主机的命令,返回数据包到达目的主机所经历的路径详细信息,并显示每个路径所消耗的时间。

6.ICMP攻击

涉及ICMP的攻击主要分为3类:泛洪(flood)、炸弹(bomb)、信息泄露(information disclosure).针对TCP的ICMP攻击已经被专门记录在RFC文档中[RFC5927]

1).泛洪(flood)

泛洪将会生成大量流量,导致针对一台或者多台计算机的有效Dos攻击

2).炸弹(bomb)

炸弹类型有时也称为核弹(nuke)类型,指的是发送经过特殊构造的报文,能够导致IP或ICMP的处理崩溃或者终止。

3).信息泄露(information disclosure)

信息泄露攻击本身不会造成危害,但是能够帮助其他攻击方法避免浪费时间或者被发现了。

7.IP报文头和ICMP的联系

ICMP报文是封装在IP数据报的数据部分中进行传输的.

ICMP依靠IP来完成它的任务,它是IP的主要部分。它与传输协议(如TCP和UDP)显著不同:它一般不用于在两点间传输数据。它通常不由网络程序直接使用,除了 ping 和 traceroute 这两个特别的例子。 IPv4中的ICMP被称作ICMPv4,IPv6中的ICMP则被称作ICMPv6。

总的来说,ICMP是封装在IP数据报中进行传输的.具体更多的联系我们通过以下改文章进行详解,从Wireshark抓包然后分析数据包进行两者的区别和联系.

参考文档:https://www.cnblogs.com/CSAH/p/13170860.html

8.实现Ping功能

  首先我们注意,本文只是实现ping的最简单的功能即响应请求/应答(ping),故只能够ping IP地址,不能够ping 域名,因为域名到IP地址我们需要经过DNS解析,本文不实现该功能.关于DNS转换到IP地址的详情,有时间有机会我会补上的.

本程序使用的环境是win10+vc++6.0,如果没有安装VC++6.0的或者在Win10安装了无法使用的请查看'Win10安装vc6.0教程'。

该ping功能实现参考了TCP/IP详解 卷1 和 卷2。

1).实现步骤

首先,我们需要先定义初始化一些全局变量,接着我们对需要用到的数据类型结构进行声明定义,我们包含的数据类型结构有IP报头结构、ICMP数据类型结构、结果集类型结构等;对需要使用到的函数进行头文件的导入,主要的区别在于使用的是Windows系统还是Linux系统,导入的头文件也不尽相同。准备工作全都完成了,然后我们就可以定义main函数进行试验的验证测试。

其次,我们需要对每一步的遇到的问题需要写一份说明报告书,以防下次再进行实验时遇到同样的问题时,我们无需再去查找大量资料。

最后,我们对整个实验的总结,对每一步。每一个函数进行详讲.做好注释.

Ping()函数是本程序的核心部分,它基本是调用其他模块的函数来实现最终功能,其主要布骤包括:定义及初始化各个全局变量、打开socket动态库、设置接收和发送超时值、域名地址解析、分配内存、创建及初始化ICMP报文、发送ICMP请求报文、接收ICMP 应答报文以及解读应答报文和输出Ping结果。

注意:创建套接字的时候参数的以及在创建套接字之前必须首先使用WSAStartup函数。

(1)输入时不能输入目标主机名,不然ping结果为TIMEOUT

(2)该模块并非只有处理还包括判断及输出判断结果的含义

(3)程序没运行一次就只能输出四行结果(前提是输入的地址有效),欲再次PING其他地址接着输入下一个ip地址即可

2).代码实现

如果要想实现Windows下ping功能的实现,我们只需要从(1)到(8)复制到任意一个新创建filename.cpp文件中即可执行.或者最简单的方法就是到本文中最低直接复制'完整代码'到任意一个新创建filename.cpp文件中即可执行

(1).头文件、全局变量

#include<stdio.h>
#include<Winsock2.h>
#include<ws2tcpip.h>
#include<stdlib.h>
#include<malloc.h>
#include<string.h>
#pragma comment(lib , "Ws2_32.lib") #define ICMP_ECHO_REQUEST 8 //定义回显请求类型
#define DEF_ICMP_DATA_SIZE 20 //定义发送数据长度
#define DEF_ICMP_PACK_SIZE 32 //定义数据包长度
#define MAX_ICMP_PACKET_SIZE 1024 //定义最大数据包长度
#define DEF_ICMP_TIMEOUT 3000 //定义超时为3秒
#define ICMP_TIMEOUT 11 //ICMP超时报文
#define ICMP_ECHO_REPLY 0 //定义回显应答类型

  

(2).IP报头据类型

/*
*IP报头结构
*/
typedef struct
{
byte h_len_ver ; //IP版本号
byte tos ; // 服务类型
unsigned short total_len ; //IP包总长度
unsigned short ident ; // 标识
unsigned short frag_and_flags ; //标志位
byte ttl ; //生存时间
byte proto ; //协议
unsigned short cksum ; //IP首部校验和
unsigned long sourceIP ; //源IP地址
unsigned long destIP ; //目的IP地址
} IP_HEADER ;

  

(3).ICMP数据类型

/*
*定义ICMP数据类型
*/
typedef struct _ICMP_HEADER
{
byte type ; //类型-----8
byte code ; //代码-----8
unsigned short cksum ; //校验和------16
unsigned short id ; //标识符-------16
unsigned short seq ; //序列号------16
unsigned int choose ; //选项-------32
} ICMP_HEADER ;

  

(4).ping返回结果集数据类型

typedef struct
{
int usSeqNo ; //记录序列号
DWORD dwRoundTripTime ; //记录当前时间
byte ttl ; //生存时间
in_addr dwIPaddr ; //源IP地址
} DECODE_RESULT ;

  

(5).网际校验和

/*
*产生网际校验和
*/
unsigned short GenerateChecksum(unsigned short *pBuf , int iSize)
{
unsigned long cksum = 0 ; //开始时将网际校验和初始化为0
while(iSize > 1)
{
cksum += *pBuf++ ; //将待校验的数据每16位逐位相加保存在cksum中
iSize -= sizeof(unsigned short) ; //每16位加完则将带校验数据量减去16
}
//如果待校验的数据为奇数,则循环完之后需将最后一个字节的内容与之前结果相加
if(iSize)
{
cksum += *(unsigned char*)pBuf ;
}
//之前的结果产生了进位,需要把进位也加入最后的结果中
cksum = (cksum >> 16) + (cksum & 0xffff) ;
cksum += (cksum >> 16) ;
return (unsigned short)(~ cksum) ;
}

  

(6).ping信息解析

/*
*对ping应答信息进行解析
*/
boolean DecodeIcmpResponse_Ping(char *pBuf , int iPacketSize , DECODE_RESULT *stDecodeResult)
{
IP_HEADER *pIpHrd = (IP_HEADER*)pBuf ;
int iIphedLen = 20 ;
if(iPacketSize < (int)(iIphedLen + sizeof(ICMP_HEADER)))
{
printf("size error! \n") ;
return 0 ;
}
//指针指向ICMP报文的首地址
ICMP_HEADER *pIcmpHrd = (ICMP_HEADER*)(pBuf + iIphedLen) ;
unsigned short usID , usSeqNo ;
//获得的数据包的type字段为ICMP_ECHO_REPLY,即收到一个回显应答ICMP报文
if(pIcmpHrd->type == ICMP_ECHO_REPLY)
{
usID = pIcmpHrd->id ;
//接收到的是网络字节顺序的seq字段信息 , 需转化为主机字节顺序
usSeqNo = ntohs(pIcmpHrd->seq) ;
}
if(usID != GetCurrentProcessId() || usSeqNo != stDecodeResult->usSeqNo)
{
printf("usID error!\n") ;
return 0 ;
}
//记录对方主机的IP地址以及计算往返的时延RTT
if(pIcmpHrd->type == ICMP_ECHO_REPLY)
{
stDecodeResult->dwIPaddr.s_addr = pIpHrd->sourceIP ;
stDecodeResult->ttl = pIpHrd->ttl ;
stDecodeResult->dwRoundTripTime = GetTickCount() - stDecodeResult->dwRoundTripTime ;
return 1 ;
}
return 0 ;
}

  

(7).ping功能实现集成

void Ping(char *IP)
{
unsigned long ulDestIP = inet_addr(IP) ; //将IP地址转化为长整形
if(ulDestIP == INADDR_NONE)
{
//转化不成功时按域名解析
HOSTENT *pHostent = gethostbyname(IP) ;
if(pHostent)
{
ulDestIP = (*(IN_ADDR*)pHostent->h_addr).s_addr ; //将HOSTENT转化为长整形
}
else
{
printf("TIMEOUT\n") ;
return ;
}
}
//填充目的Socket地址
SOCKADDR_IN destSockAddr ; //定义目的地址
ZeroMemory(&destSockAddr , sizeof(SOCKADDR_IN)) ; //将目的地址清空
destSockAddr.sin_family = AF_INET ;
destSockAddr.sin_addr.s_addr = ulDestIP ;
destSockAddr.sin_port = htons(0);
//初始化WinSock
WORD wVersionRequested = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(wVersionRequested,&wsaData) != 0)
{
printf("初始化WinSock失败!\n") ;
return ;
}
//使用ICMP协议创建Raw Socket
SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ;
if(sockRaw == INVALID_SOCKET)
{
printf("创建Socket失败 !\n") ;
return ;
}
//设置端口属性
int iTimeout = DEF_ICMP_TIMEOUT ;
if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
{
printf("设置参数失败!\n") ;
return ;
}
if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
{
printf("设置参数失败!\n") ;
return ;
}
//定义发送的数据段
char IcmpSendBuf[DEF_ICMP_PACK_SIZE] ;
//填充ICMP数据包个各字段
ICMP_HEADER *pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
pIcmpHeader->type = ICMP_ECHO_REQUEST ;
pIcmpHeader->code = 0 ;
pIcmpHeader->id = (unsigned short)GetCurrentProcessId() ;
memset(IcmpSendBuf + sizeof(ICMP_HEADER) , 'E' , DEF_ICMP_DATA_SIZE) ;
//循环发送四个请求回显icmp数据包
int usSeqNo = 0 ;
DECODE_RESULT stDecodeResult ;
while(usSeqNo <= 3)
{
pIcmpHeader->seq = htons(usSeqNo) ;
pIcmpHeader->cksum = 0 ;
pIcmpHeader->cksum = GenerateChecksum((unsigned short*)IcmpSendBuf , DEF_ICMP_PACK_SIZE) ; //生成校验位
//记录序列号和当前时间
stDecodeResult.usSeqNo = usSeqNo ;
stDecodeResult.dwRoundTripTime = GetTickCount() ;
//发送ICMP的EchoRequest数据包
if(sendto(sockRaw , IcmpSendBuf , DEF_ICMP_PACK_SIZE , 0 , (SOCKADDR*)&destSockAddr , sizeof(destSockAddr)) == SOCKET_ERROR)
{
//如果目的主机不可达则直接退出
if(WSAGetLastError() == WSAEHOSTUNREACH)
{
printf("目的主机不可达!\n") ;
exit(0) ;
}
}
SOCKADDR_IN from ;
int iFromLen = sizeof(from) ;
int iReadLen ;
//定义接收的数据包
char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE] ;
while(1)
{
iReadLen = recvfrom(sockRaw , IcmpRecvBuf , MAX_ICMP_PACKET_SIZE , 0 , (SOCKADDR*)&from , &iFromLen) ;
if(iReadLen != SOCKET_ERROR)
{
if(DecodeIcmpResponse_Ping(IcmpRecvBuf , sizeof(IcmpRecvBuf) , &stDecodeResult))
{
printf("来自 %s 的回复: 字节 = %d 时间 = %dms TTL = %d\n" , inet_ntoa(stDecodeResult.dwIPaddr) ,
iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ;
}
break ;
}
else if(WSAGetLastError() == WSAETIMEDOUT)
{
printf("time out ! *****\n") ;
break ;
}
else
{
printf("发生未知错误!\n") ;
break ;
}
}
usSeqNo++ ;
}
//输出屏幕信息
printf("Ping complete...\n") ;
closesocket(sockRaw) ;
WSACleanup() ;
}

  

①.inet_addr:可以转化字符串,主要用来将一个十进制的数转化为二进制的数,用途多于ipv4的IP转化。

②.if(IpAddress == INADDR_NONE):INADDR_NONE 是个宏定义,代表IpAddress是否为无效的IP地址。

③.ckaddr_in:定义目的地址信息;

④.ZeroMemory:用0来填充一块内存区域.ZeroMemory只能用于windows平台.

⑤.WSASocket:创建一个原始套接字。使用时需要包含winsock2.h 头文件和链接ws2_32.lib库。

⑥.SOCKET socket==INVALID_SOCKET:如果socket为无效套接字,则结果为true;

⑦.DEF_ICMP_TIMEOUT:报文超时时间.

⑧.setsockopt:选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。

⑨.while(usSeqNo <= 3){}:该部分就是实验要求我们一次测试的进行发包4次

(8).Test测试

int main(int argc , char* argv[])
{
char com[10] , IP[20] ;
while(1){
printf("command>>") ;
scanf("%s %s" , com , IP) ;
if(strcmp(com , "ping") == 0)
{
Ping(IP) ;
}
else
{
printf("输入错误 ! \n") ;
}
}
return 0 ;
}

  

2).结果及心得

(1).查看本机IP

(2).ping网关IP

(3).ping本机IP

(4).ping局域网内IP

(5).问题与解决方案

①.问题:telnet是23端口,ssh是22端口,那么ping是什么端口?

答:ping基于ICMP,是在网络层运行的。而端口号为传输层的内容。所以在ICMP中根本就不需要关注端口号这样的信息。

②.Win7、win10 在VC6.0运行时WSASocket 返回错误 10013

3).完整代码

#include<stdio.h>
#include<Winsock2.h>
#include<ws2tcpip.h>
#include<stdlib.h>
#include<malloc.h>
#include<string.h>
#pragma comment(lib , "Ws2_32.lib") #define ICMP_ECHO_REQUEST 8 //定义回显请求类型
#define DEF_ICMP_DATA_SIZE 20 //定义发送数据长度
#define DEF_ICMP_PACK_SIZE 32 //定义数据包长度
#define MAX_ICMP_PACKET_SIZE 1024 //定义最大数据包长度
#define DEF_ICMP_TIMEOUT 3000 //定义超时为3秒
#define ICMP_TIMEOUT 11 //ICMP超时报文
#define ICMP_ECHO_REPLY 0 //定义回显应答类型
/*
*IP报头结构
*/
typedef struct
{
byte h_len_ver ; //IP版本号
byte tos ; // 服务类型
unsigned short total_len ; //IP包总长度
unsigned short ident ; // 标识
unsigned short frag_and_flags ; //标志位
byte ttl ; //生存时间
byte proto ; //协议
unsigned short cksum ; //IP首部校验和
unsigned long sourceIP ; //源IP地址
unsigned long destIP ; //目的IP地址
} IP_HEADER ;
/*
*定义ICMP数据类型
*/
typedef struct _ICMP_HEADER
{
byte type ; //类型-----8
byte code ; //代码-----8
unsigned short cksum ; //校验和------16
unsigned short id ; //标识符-------16
unsigned short seq ; //序列号------16
unsigned int choose ; //选项-------32
} ICMP_HEADER ; typedef struct
{
int usSeqNo ; //记录序列号
DWORD dwRoundTripTime ; //记录当前时间
byte ttl ; //生存时间
in_addr dwIPaddr ; //源IP地址
} DECODE_RESULT ; /*
*产生网际校验和
*/
unsigned short GenerateChecksum(unsigned short *pBuf , int iSize)
{
unsigned long cksum = 0 ; //开始时将网际校验和初始化为0
while(iSize > 1)
{
cksum += *pBuf++ ; //将待校验的数据每16位逐位相加保存在cksum中
iSize -= sizeof(unsigned short) ; //每16位加完则将带校验数据量减去16
}
//如果待校验的数据为奇数,则循环完之后需将最后一个字节的内容与之前结果相加
if(iSize)
{
cksum += *(unsigned char*)pBuf ;
}
//之前的结果产生了进位,需要把进位也加入最后的结果中
cksum = (cksum >> 16) + (cksum & 0xffff) ;
cksum += (cksum >> 16) ;
return (unsigned short)(~ cksum) ;
} /*
*对ping应答信息进行解析
*/
boolean DecodeIcmpResponse_Ping(char *pBuf , int iPacketSize , DECODE_RESULT *stDecodeResult)
{
IP_HEADER *pIpHrd = (IP_HEADER*)pBuf ;
int iIphedLen = 20 ;
if(iPacketSize < (int)(iIphedLen + sizeof(ICMP_HEADER)))
{
printf("size error! \n") ;
return 0 ;
}
//指针指向ICMP报文的首地址
ICMP_HEADER *pIcmpHrd = (ICMP_HEADER*)(pBuf + iIphedLen) ;
unsigned short usID , usSeqNo ;
//获得的数据包的type字段为ICMP_ECHO_REPLY,即收到一个回显应答ICMP报文
if(pIcmpHrd->type == ICMP_ECHO_REPLY)
{
usID = pIcmpHrd->id ;
//接收到的是网络字节顺序的seq字段信息 , 需转化为主机字节顺序
usSeqNo = ntohs(pIcmpHrd->seq) ;
}
if(usID != GetCurrentProcessId() || usSeqNo != stDecodeResult->usSeqNo)
{
printf("usID error!\n") ;
return 0 ;
}
//记录对方主机的IP地址以及计算往返的时延RTT
if(pIcmpHrd->type == ICMP_ECHO_REPLY)
{
stDecodeResult->dwIPaddr.s_addr = pIpHrd->sourceIP ;
stDecodeResult->ttl = pIpHrd->ttl ;
stDecodeResult->dwRoundTripTime = GetTickCount() - stDecodeResult->dwRoundTripTime ;
return 1 ;
}
return 0 ;
} void Ping(char *IP)
{
unsigned long ulDestIP = inet_addr(IP) ; //将IP地址转化为长整形
if(ulDestIP == INADDR_NONE)
{
//转化不成功时按域名解析
HOSTENT *pHostent = gethostbyname(IP) ;
if(pHostent)
{
ulDestIP = (*(IN_ADDR*)pHostent->h_addr).s_addr ; //将HOSTENT转化为长整形
}
else
{
printf("TIMEOUT\n") ;
return ;
}
}
//填充目的Socket地址
SOCKADDR_IN destSockAddr ; //定义目的地址
ZeroMemory(&destSockAddr , sizeof(SOCKADDR_IN)) ; //将目的地址清空
destSockAddr.sin_family = AF_INET ;
destSockAddr.sin_addr.s_addr = ulDestIP ;
destSockAddr.sin_port = htons(0);
//初始化WinSock
WORD wVersionRequested = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(wVersionRequested,&wsaData) != 0)
{
printf("初始化WinSock失败!\n") ;
return ;
}
//使用ICMP协议创建Raw Socket
SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ;
if(sockRaw == INVALID_SOCKET)
{
printf("创建Socket失败 !\n") ;
return ;
}
//设置端口属性
int iTimeout = DEF_ICMP_TIMEOUT ;
if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
{
printf("设置参数失败!\n") ;
return ;
}
if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
{
printf("设置参数失败!\n") ;
return ;
}
//定义发送的数据段
char IcmpSendBuf[DEF_ICMP_PACK_SIZE] ;
//填充ICMP数据包个各字段
ICMP_HEADER *pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
pIcmpHeader->type = ICMP_ECHO_REQUEST ;
pIcmpHeader->code = 0 ;
pIcmpHeader->id = (unsigned short)GetCurrentProcessId() ;
memset(IcmpSendBuf + sizeof(ICMP_HEADER) , 'E' , DEF_ICMP_DATA_SIZE) ;
//循环发送四个请求回显icmp数据包
int usSeqNo = 0 ;
DECODE_RESULT stDecodeResult ; while(usSeqNo <= 3)
{
pIcmpHeader->seq = htons(usSeqNo) ;
pIcmpHeader->cksum = 0 ;
pIcmpHeader->cksum = GenerateChecksum((unsigned short*)IcmpSendBuf , DEF_ICMP_PACK_SIZE) ; //生成校验位
//记录序列号和当前时间
stDecodeResult.usSeqNo = usSeqNo ;
stDecodeResult.dwRoundTripTime = GetTickCount() ;
//发送ICMP的EchoRequest数据包
if(sendto(sockRaw , IcmpSendBuf , DEF_ICMP_PACK_SIZE , 0 , (SOCKADDR*)&destSockAddr , sizeof(destSockAddr)) == SOCKET_ERROR)
{
//如果目的主机不可达则直接退出
if(WSAGetLastError() == WSAEHOSTUNREACH)
{
printf("目的主机不可达!\n") ;
exit(0) ;
}
}
SOCKADDR_IN from ;
int iFromLen = sizeof(from) ;
int iReadLen ;
//定义接收的数据包
char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE] ;
while(1)
{
iReadLen = recvfrom(sockRaw , IcmpRecvBuf , MAX_ICMP_PACKET_SIZE , 0 , (SOCKADDR*)&from , &iFromLen) ;
if(iReadLen != SOCKET_ERROR)
{
if(DecodeIcmpResponse_Ping(IcmpRecvBuf , sizeof(IcmpRecvBuf) , &stDecodeResult))
{
printf("来自 %s 的回复: 字节 = %d 时间 = %dms TTL = %d\n" , inet_ntoa(stDecodeResult.dwIPaddr) ,
iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ;
}
break ;
}
else if(WSAGetLastError() == WSAETIMEDOUT)
{
printf("time out ! *****\n") ;
break ;
}
else
{
printf("发生未知错误!\n") ;
break ;
}
}
usSeqNo++ ;
}
//输出屏幕信息
printf("Ping complete...\n") ;
closesocket(sockRaw) ;
WSACleanup() ;
}
int main()
{
char com[10] , IP[20] ;
while(1){
printf("command>>") ;
scanf("%s %s" , com , IP) ;
if(strcmp(com , "ping") == 0)
{
Ping(IP) ;
}
else
{
printf("输入错误 ! \n") ;
}
}
return 0 ;
}

  

参考文档:https://zhidao.baidu.com/question/1946506262344388308.html

https://docs.microsoft.com/zh-cn/windows/win32/api/winsock2/nf-winsock2-wsasocketa?redirectedfrom=MSDN

https://zhidao.baidu.com/question/541753723.html

TCP/IP网络原理技术[清华大学出版社 周明天,汪文勇]

互联网控制消息协议[维基百科]

TCP/IP详解 卷1:协议

TCP/IP详解 卷2:实现

Windows7/10实现ICMP(ping命令)的更多相关文章

  1. ping命令基于ICMP协议的返回信息分析

    Ping是潜水艇人员的专用术语,表示回应的声纳脉冲,在网络中 Ping 是一个十分好用的 TCP/IP 工具.它主要的功能是用来检测网络的连通情况和分析网络速度.可以利用 PING 命令检查网络连通状 ...

  2. 解决:Ubuntu12.04下使用ping命令返回ping:icmp open socket: Operation not permitted的解决

    ping命令在运行中采用了ICMP协议,需要发送ICMP报文.但是只有root用户才能建立ICMP报文.而正常情况下,ping命令的权限应为-rwsr-xr-x,即带有suid的文件,一旦该权限被修改 ...

  3. Ping命令与ICMP协议

    ICMP协议 ICMP是"Internet Control Message Ptotocol"(Internet控制消息协议)的缩写.它是TCP/IP协议族的一个子协议,用于在IP ...

  4. ICMP协议Ping命令的应用

    ICMP的全称是 Internet Control Message Protocol ,它是TCP/IP协议族的一个子协议,属于网络层协议,用于在IP主机.路由器之间传递控制消息.从技术角度来讲,就是 ...

  5. Zabbix使用外部命令fping处理ICMP ping的请求

    Zabbix使用外部命令fping处理ICMP ping的请求,fping不包含在zabbix的发行版本中,需要额外去下载安装fping程序, 安装完毕之后需要在zabinx_server.conf中 ...

  6. ICMP协议和ping命令

    当网络不通的情况下,通常会想到ping命令,ping一下,但是ping命令内部如何执行的,可能并不清楚,其实ping是基于ICMP协议进行工作的.  一.ICMP协议的格式 ICMP是在RFC 792 ...

  7. ICMP、ARP协议介绍和ping命令

    交换机工作原理和常用的简单命令    一.ICMP协议      1)ICMP协议的封装    二.ARP协议      1)什么是ARP协议      2)ARP相关命令    三.Ping命令的使 ...

  8. ICMP 协议仿真及ping命令用途

    1.实验目的 加深对 IPv4 协议首部各定义域的理解,掌握路由表的结构和基本配置命令,熟悉 ICMP 的调试操作. 2.实验原理 IPv4 协议定义,网络层协议的相关 RFC 定义和描述. 3.实验 ...

  9. 利用ICMP协议的PING命令获取客户端当前网络质量 by徐文棋

    无论在windows下,linux也好,unix也好,都可以通过 Ping命令获得当前设备的网络延迟,延迟是客户端到服务端的网络响应时间.通常延迟越低,反应速度越快 这里使用了SimplePing   ...

随机推荐

  1. [SD心灵鸡汤]007.每月一则 - 2015.11

    1.不要因为世界太过复杂,而背叛了你的单纯. 2.人的一生要疯狂一次,无论是为一个人,一段情,一段路途或一个梦想. 3.时间真的很神奇,你永远不知道它会如何改变你.换句话说:以前难吃的蔬菜.苦涩的啤酒 ...

  2. 运用惰性删除和定时删除实现可过期的localStorage缓存

    localStorage简介 使用localStorage可以在浏览器中存储键值对的数据.经常被和localStorage一并提及的是sessionStorage,它们都可以在当浏览器中存储键值对的数 ...

  3. PIX防火墙配置A/S故障切换

    PIX防火墙配置A/S故障切换 1.基本命令 failover show failover failover lan enable failover lan interface zwish e2 fa ...

  4. Rocket - debug - DMI

    https://mp.weixin.qq.com/s/70BoeS7z4aBZK24zxdZzXA 简单介绍DMI的实现. 1. DMIConsts 定义DMI使用的常量: 其中: a. dmiDat ...

  5. DataGuard VS Beedup & GoldenGate灾备方案参数对比

    世上本无完美产品,只有合适的才是最好的! 用户重视灾备数据站点的建设,毋庸置疑必备品.如果考虑带宽及事务完整性保证,存储灾备和操作系统级灾备局限性显而易见. 商用价值一般用于解决数据库自带辅助功能的短 ...

  6. Linux(六)文件系统

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.根目录内容 1.概述 Linux系统中只有一个文件系统,以“/”作为根目录,从根目录出发可以找到任何 ...

  7. Java实现 LeetCode 740 删除与获得点数(递推 || 动态规划?打家劫舍Ⅳ)

    740. 删除与获得点数 给定一个整数数组 nums ,你可以对它进行一些操作. 每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数.之后,你必须删除每个等于 nums[ ...

  8. Java实现 LeetCode 640 求解方程(计算器的加减法计算)

    640. 求解方程 求解一个给定的方程,将x以字符串"x=#value"的形式返回.该方程仅包含'+',' - '操作,变量 x 和其对应系数. 如果方程没有解,请返回" ...

  9. Java实现 蓝桥杯 算法训练 最大最小公倍数

    算法训练 最大最小公倍数 时间限制:1.0s 内存限制:256.0MB 问题描述 已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少. 输入格式 输入一个正整数N. 输出格式 ...

  10. Java实现 LeetCode 383 赎金信

    383. 赎金信 给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成.如果可以构成,返回 t ...