• 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! 
    ") ;
            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!
    ") ;
            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
    ") ;
               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失败!
    ") ;
            return ;
        }
       //使用ICMP协议创建Raw Socket
       SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ;
       if(sockRaw == INVALID_SOCKET)
       {
           printf("创建Socket失败 !
    ") ;
           return ;
       }
       //设置端口属性
       int iTimeout = DEF_ICMP_TIMEOUT ;
       if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
       {
             printf("设置参数失败!
    ") ;
             return ;
       }
       if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
       {
             printf("设置参数失败!
    ") ;
             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("目的主机不可达!
    ") ;
                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
    " , inet_ntoa(stDecodeResult.dwIPaddr) ,
                             iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ;
                 }
                 break ;
             }
             else if(WSAGetLastError() == WSAETIMEDOUT)
             {
                 printf("time out !  *****
    ") ;
                 break ;
             }
             else
             {
                 printf("发生未知错误!
    ") ;
                 break ;
             }
         }
         usSeqNo++ ;
       }
       //输出屏幕信息
       printf("Ping complete...
    ") ;
       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("输入错误 ! 
    ") ;
       }
       }
       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! 
    ") ;
            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!
    ") ;
            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
    ") ;
               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失败!
    ") ;
            return ;
        }
       //使用ICMP协议创建Raw Socket
       SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ;
       if(sockRaw == INVALID_SOCKET)
       {
           printf("创建Socket失败 !
    ") ;
           return ;
       }
       //设置端口属性
       int iTimeout = DEF_ICMP_TIMEOUT ;
       if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
       {
             printf("设置参数失败!
    ") ;
             return ;
       }
       if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
       {
             printf("设置参数失败!
    ") ;
             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("目的主机不可达!
    ") ;
                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
    " , inet_ntoa(stDecodeResult.dwIPaddr) ,
                             iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ;
                 }
                 break ;
             }
             else if(WSAGetLastError() == WSAETIMEDOUT)
             {
                 printf("time out !  *****
    ") ;
                 break ;
             }
             else
             {
                 printf("发生未知错误!
    ") ;
                 break ;
             }
         }
         usSeqNo++ ;
       }
       //输出屏幕信息
       printf("Ping complete...
    ") ;
       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("输入错误 ! 
    ") ;
       }
       }
       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:实现

  • 相关阅读:
    匈牙利算法自主总结
    luogu P2071 座位安排
    luogu P1613 跑路
    luogu P1250 种树
    luogu P1744 采购特价商品
    网络流
    其他图论
    组合计数
    小技巧
    矩阵&&高斯消元
  • 原文地址:https://www.cnblogs.com/HOsystem/p/13171268.html
Copyright © 2020-2023  润新知