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

 

posted @ 2020-06-22 02:53  HOsystem  阅读(3738)  评论(0编辑  收藏  举报