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

利用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:实现

最新文章

  1. nginx 添加nginx-http-concat模块
  2. 从一个故障说说Java的三个BlockingQueue
  3. Retrofit源码设计模式解析(下)
  4. storyBoard中切换应用启动的切入点方法
  5. WWDC 2015 - 概记
  6. [原创]SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT 'OpenRowset/OpenDatasource' 的访问
  7. C#读取Exeal文件
  8. SQL Join 的三种类型
  9. mysql的replication(主从同步)总结
  10. Bootstrap第一天
  11. 转账示例(三):service层面实现(线程管理Connection)(本例采用QueryRunner来执行sql语句,数据源为C3P0)
  12. Android Studio 中修改Apk名称
  13. 【SqlServer】【问题收集】必须声明标量变量
  14. caffe安装教程(Ubuntu14+GPU+pycaffe+anaconda2)
  15. js 更改对象属性名
  16. LeetCode专题-Python实现之第13题:Roman to Integer
  17. Java 时区之间时间转换
  18. 【考古向翻译】Pwn2Own 2010 Windows 7 Internet Explorer 8 exploit
  19. sqli-labs (less-8-less-10)
  20. CSS3与页面布局学习总结(四)——页面布局的多种方法

热门文章

  1. 【C++】赋值过程中类型转换
  2. 安全性只是辅助效果?解读DevSecOps的核心动机
  3. STM32读取匿名光流数据——与Guidance的光流和超声波做对比测试
  4. Java实现 蓝桥杯 算法提高 计算超阶乘(暴力)
  5. Java实现 蓝桥杯 算法提高 套正方形(暴力)
  6. Java实现 洛谷 采药
  7. Java实现 LeetCode 504 七进制数
  8. Java实现 蓝桥杯 算法提高 7-1用宏求球的体积
  9. Java实现 蓝桥杯VIP 基础练习 Sine之舞
  10. Java实现 LeetCode 257 二叉树的所有路径