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;
}

执行结果:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Hongten

pygame系列_箭刺Elephant游戏_源码下载

http://www.pygame.org/docs/tut/chimp/ChimpLineByLine.html

18040
来自专栏PPV课数据科学社区

【学习】七天搞定SAS(一):数据的导入、数据结构

SAS的数据类型 ? 首先,sas的编程大概就两块:Data和PROC,这个倒是蛮清晰的划分。然后目前关注data部分。 SAS的数据类型还真的只有两种:数字和...

416120
来自专栏黑白安全

ctf工具包 ctf Toolkit 渗透测试工具包 隐秘数据破解

│ ├── caidao-20160620-www.maicaidao.com.7z

1K20
来自专栏landv

Peer-to-Peer (P2P) communication across middleboxes

Internet Draft                                                   B. Ford Docum...

10930
来自专栏zaking's

RFC2616-HTTP1.1-Header Field Definitions(头字段规定部分—单词注释版)

16120
来自专栏Rindew的iOS技术分享

iOS实现三列表格点选(附Demo)

21730
来自专栏bboysoul

linux下的彩蛋和各种有趣的命令

循环输出 for ((i=1;i<=30;i++));do linux_logo -f -L $i;sleep 0.1;done

16940
来自专栏曾大稳的博客

使用SoudTouch实现变速变调

32020
来自专栏华仔的技术笔记

Spectrum光谱链共识算法的分析

Spectrum(光谱链)是SmartMesh生态下的公链,承载去中心化Mesh网络实现万物互联dapp的底层公链。由Payment Channel的建构的Sm...

10530
来自专栏数据结构与算法

P2668 斗地主 贪心+深搜

题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系...

41490

扫码关注云+社区

领取腾讯云代金券