Windows7/10实现ICMP(ping命令)
如果觉得本文如果帮到你或者你想转载都可以,只需要标注出处即可。谢谢
利用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命令)的更多相关文章
- ping命令基于ICMP协议的返回信息分析
Ping是潜水艇人员的专用术语,表示回应的声纳脉冲,在网络中 Ping 是一个十分好用的 TCP/IP 工具.它主要的功能是用来检测网络的连通情况和分析网络速度.可以利用 PING 命令检查网络连通状 ...
- 解决:Ubuntu12.04下使用ping命令返回ping:icmp open socket: Operation not permitted的解决
ping命令在运行中采用了ICMP协议,需要发送ICMP报文.但是只有root用户才能建立ICMP报文.而正常情况下,ping命令的权限应为-rwsr-xr-x,即带有suid的文件,一旦该权限被修改 ...
- Ping命令与ICMP协议
ICMP协议 ICMP是"Internet Control Message Ptotocol"(Internet控制消息协议)的缩写.它是TCP/IP协议族的一个子协议,用于在IP ...
- ICMP协议Ping命令的应用
ICMP的全称是 Internet Control Message Protocol ,它是TCP/IP协议族的一个子协议,属于网络层协议,用于在IP主机.路由器之间传递控制消息.从技术角度来讲,就是 ...
- Zabbix使用外部命令fping处理ICMP ping的请求
Zabbix使用外部命令fping处理ICMP ping的请求,fping不包含在zabbix的发行版本中,需要额外去下载安装fping程序, 安装完毕之后需要在zabinx_server.conf中 ...
- ICMP协议和ping命令
当网络不通的情况下,通常会想到ping命令,ping一下,但是ping命令内部如何执行的,可能并不清楚,其实ping是基于ICMP协议进行工作的. 一.ICMP协议的格式 ICMP是在RFC 792 ...
- ICMP、ARP协议介绍和ping命令
交换机工作原理和常用的简单命令 一.ICMP协议 1)ICMP协议的封装 二.ARP协议 1)什么是ARP协议 2)ARP相关命令 三.Ping命令的使 ...
- ICMP 协议仿真及ping命令用途
1.实验目的 加深对 IPv4 协议首部各定义域的理解,掌握路由表的结构和基本配置命令,熟悉 ICMP 的调试操作. 2.实验原理 IPv4 协议定义,网络层协议的相关 RFC 定义和描述. 3.实验 ...
- 利用ICMP协议的PING命令获取客户端当前网络质量 by徐文棋
无论在windows下,linux也好,unix也好,都可以通过 Ping命令获得当前设备的网络延迟,延迟是客户端到服务端的网络响应时间.通常延迟越低,反应速度越快 这里使用了SimplePing ...
随机推荐
- [SD心灵鸡汤]007.每月一则 - 2015.11
1.不要因为世界太过复杂,而背叛了你的单纯. 2.人的一生要疯狂一次,无论是为一个人,一段情,一段路途或一个梦想. 3.时间真的很神奇,你永远不知道它会如何改变你.换句话说:以前难吃的蔬菜.苦涩的啤酒 ...
- 运用惰性删除和定时删除实现可过期的localStorage缓存
localStorage简介 使用localStorage可以在浏览器中存储键值对的数据.经常被和localStorage一并提及的是sessionStorage,它们都可以在当浏览器中存储键值对的数 ...
- PIX防火墙配置A/S故障切换
PIX防火墙配置A/S故障切换 1.基本命令 failover show failover failover lan enable failover lan interface zwish e2 fa ...
- Rocket - debug - DMI
https://mp.weixin.qq.com/s/70BoeS7z4aBZK24zxdZzXA 简单介绍DMI的实现. 1. DMIConsts 定义DMI使用的常量: 其中: a. dmiDat ...
- DataGuard VS Beedup & GoldenGate灾备方案参数对比
世上本无完美产品,只有合适的才是最好的! 用户重视灾备数据站点的建设,毋庸置疑必备品.如果考虑带宽及事务完整性保证,存储灾备和操作系统级灾备局限性显而易见. 商用价值一般用于解决数据库自带辅助功能的短 ...
- Linux(六)文件系统
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.根目录内容 1.概述 Linux系统中只有一个文件系统,以“/”作为根目录,从根目录出发可以找到任何 ...
- Java实现 LeetCode 740 删除与获得点数(递推 || 动态规划?打家劫舍Ⅳ)
740. 删除与获得点数 给定一个整数数组 nums ,你可以对它进行一些操作. 每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数.之后,你必须删除每个等于 nums[ ...
- Java实现 LeetCode 640 求解方程(计算器的加减法计算)
640. 求解方程 求解一个给定的方程,将x以字符串"x=#value"的形式返回.该方程仅包含'+',' - '操作,变量 x 和其对应系数. 如果方程没有解,请返回" ...
- Java实现 蓝桥杯 算法训练 最大最小公倍数
算法训练 最大最小公倍数 时间限制:1.0s 内存限制:256.0MB 问题描述 已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少. 输入格式 输入一个正整数N. 输出格式 ...
- Java实现 LeetCode 383 赎金信
383. 赎金信 给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成.如果可以构成,返回 t ...