zoukankan      html  css  js  c++  java
  • ping 实现设计ICMP

    发送ICMP报文时,必须程序自己计算校验和,将它填入ICMP头部对应的域中。

    校验和的计算方法:

      将数据以字为单位累加到一个双字中,如果数据长度为奇数,最后一个字节将被扩展到字,累加的结果是一个双字,最后将这个双字的高16位,低16位相加后取反,便得到了校验和。

    下面是checksum的计算校验和的代码:

    USHORT checksum(USHORT* buff, int size)
    {
        unsigned long cksum = 0;
        while(size>1)
        {
            cksum += *buff++;
            size -= sizeof(USHORT);
        }
        // 是奇数
        if(size)
        {
            cksum += *(UCHAR*)buff;
        }
        // 将32位的chsum高16位和低16位相加,然后取反
        cksum = (cksum >> 16) + (cksum & 0xffff);
        cksum += (cksum >> 16);            
        return (USHORT)(~cksum);
    }

    Ping程序实例:

    Ping用来检查主机是否存在,是否可达。

    下面是Ping的执行步骤:

    1 创建协议类型为IPPROTO_ICMP的原始套接字

    2 创建并初始化ICMP封包

    3 调用sendto函数向远程主机发送ICMP请求

    4 调用recvfrom函数接收ICMP响应

    完整代码如下:

    ///////////////////////////////////////////
    // ping.cpp文件
    
    #include "../common/initsock.h"
    #include "../common/protoinfo.h"
    #include <stdio.h>
    #include <winsock2.h>
    #include <windows.h>
    #include "Ws2tcpip.h"
    CInitSock theSock;
    
    typedef struct icmp_hdr
    {
        unsigned char   icmp_type;        // 消息类型
        unsigned char   icmp_code;        // 代码
        unsigned short  icmp_checksum;    // 校验和
        // 下面是回显头
        unsigned short  icmp_id;        // 用来惟一标识此请求的ID号,通常设置为进程ID
        unsigned short  icmp_sequence;    // 序列号
        unsigned long   icmp_timestamp; // 时间戳
    } ICMP_HDR, *PICMP_HDR;
    
    USHORT checksum(USHORT* buff, int size);
    BOOL SetTTL(SOCKET s, int nValue);
    BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv = TRUE);
    
    USHORT checksum(USHORT* buff, int size)
    {
        unsigned long cksum = 0;
        while(size>1)
        {
            cksum += *buff++;
            size -= sizeof(USHORT);
        }
        // 是奇数
        if(size)
        {
            cksum += *(UCHAR*)buff;
        }
        // 将32位的chsum高16位和低16位相加,然后取反
        cksum = (cksum >> 16) + (cksum & 0xffff);
        cksum += (cksum >> 16);            
        return (USHORT)(~cksum);
    }
    
    BOOL SetTTL(SOCKET s, int nValue)
    {
        int ret = ::setsockopt(s, IPPROTO_IP, IP_TTL, (char*)&nValue, sizeof(nValue));
        return ret != SOCKET_ERROR;
    }
    
    BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv)
    {
        int ret = ::setsockopt(s, SOL_SOCKET, 
            bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime));
        return ret != SOCKET_ERROR;
    }
    
    
    
    
    int main()
    {
        // 目的IP地址,即要Ping的IP地址
        char szDestIp[] = "127.0.0.1";    // 127.0.0.1
    
        // 创建原始套节字
        SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    
        // 设置接收超时
        SetTimeout(sRaw, 1000, TRUE);
    
        // 设置目的地址
        SOCKADDR_IN dest;
        dest.sin_family = AF_INET;
        dest.sin_port = htons(0);
        dest.sin_addr.S_un.S_addr = inet_addr(szDestIp);
    
        // 创建ICMP封包
        char buff[sizeof(ICMP_HDR) + 32];
        ICMP_HDR* pIcmp = (ICMP_HDR*)buff;
        // 填写ICMP封包数据
        pIcmp->icmp_type = 8;    // 请求一个ICMP回显
        pIcmp->icmp_code = 0;
        pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();
        pIcmp->icmp_checksum = 0;
        pIcmp->icmp_sequence = 0;
        // 填充数据部分,可以为任意
        memset(&buff[sizeof(ICMP_HDR)], 'E', 32);
        
        // 开始发送和接收ICMP封包
        USHORT    nSeq = 0;
        char recvBuf[1024];
        SOCKADDR_IN from;
        int nLen = sizeof(from);
        while(TRUE)
        {
            static int nCount = 0;
            int nRet;
            if(nCount++ == 4)
                break;
            pIcmp->icmp_checksum = 0;
            pIcmp->icmp_timestamp = ::GetTickCount();
            pIcmp->icmp_sequence = nSeq++;
            pIcmp->icmp_checksum = checksum((USHORT*)buff, sizeof(ICMP_HDR) + 32);
            nRet = ::sendto(sRaw, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest));
            if(nRet == SOCKET_ERROR)
            {
                printf(" sendto() failed: %d \n", ::WSAGetLastError());
                return -1;
            }
            nRet = ::recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen);
            if(nRet == SOCKET_ERROR)
            {
                if(::WSAGetLastError() == WSAETIMEDOUT)
                {
                    printf(" timed out\n");
                    continue;
                }
                printf(" recvfrom() failed: %d\n", ::WSAGetLastError());
                return -1;
            }
    
            // 下面开始解析接收到的ICMP封包
            int nTick = ::GetTickCount();
            if(nRet < sizeof(IPHeader) + sizeof(ICMP_HDR))
            {
                printf(" Too few bytes from %s \n", ::inet_ntoa(from.sin_addr));
            }
            // 接收到的数据中包含IP头,IP头大小为20个字节,所以加20得到ICMP头
            ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + 20); // (ICMP_HDR*)(recvBuf + sizeof(IPHeader));
            if(pRecvIcmp->icmp_type != 0)    // 回显
            {
                printf(" nonecho type %d recvd \n", pRecvIcmp->icmp_type);
                return -1;
            }
    
            if(pRecvIcmp->icmp_id != ::GetCurrentProcessId())
            {
                printf(" someone else's packet! \n");
                return -1;
            }
            
            printf(" %d bytes from %s:", nRet, inet_ntoa(from.sin_addr));
            printf(" icmp_seq = %d. ", pRecvIcmp->icmp_sequence);
            printf(" time: %d ms", nTick - pRecvIcmp->icmp_timestamp);
            printf(" \n");
    
            ::Sleep(1000);
        }
    
        return 0;
    }

    执行结果:

  • 相关阅读:
    MAUI预览版发布 (.NET 6 Preview2)
    ASP.NET Core 基础系列(6)(中间件)
    ASP.NET Core 基础系列(5)(appSetting)
    ASP.NET Core 基础系列(4)(Startup)
    ASP.NET Core 基础系列(3)(启动设置)
    ASP.NET Core 基础系列(2)(托管模型)
    ASP.NET Core 基础系列(1)(Main方法)
    Android开发学习之路-PopupWindow和仿QQ左滑删除
    部分病毒进程任务管理器终止不怎么办
    intellij idea 历史版本下载地址
  • 原文地址:https://www.cnblogs.com/xing901022/p/2733003.html
Copyright © 2011-2022 走看看