C++使用ICMP实现Ping程序

目录

        ICMP协议介绍

        Ping程序实现原理

        代码部分

        测试结果

ICMP协议介绍

        ICMP是internet控制报文协议,它是TCP/IP协议族的一个子协议,用于IP主机、路由器之间传递控制消息。ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。从技术角度说,ICMP就是一个“错误侦测与回报机制”,其目的就是让我们能够检测网络的连接状况,也能确保连线的准确性,其功能主要有:

        · 侦测远端主机是否存在。

        · 建立及维护路由资料。

        · 重导资料传送路径(ICMP重定向)。

        · 资料流量控制

        ICMP 是个非常有用的协议﹐尤其是当我们要对网路连接状况进行判断的时候。

        它传递差错报文以及其他需要注意的信息,经常供IP层或更高层协议(TCP或UDP)使用。所以它经常被认为是IP层的一个组成部分。它在IP数据报文中的封装如下:

        ICMP的数据报文格式如下所示。所有报文的前4个字节都是一样的,其他的因报文类型不同而不一样。类型字段可以有15个不同的值,用以描述不同的ICMP报文。校验和字段覆盖整个ICMP报文,使用了和IP首部检验和一样的算法,详细请搜索TCP/IP检验和算法。

        不同类型的报文是由类型字段和代码字段来共同决定。下表是各种类型的ICMP报文。

        根据上表可知,ICMP协议大致分为两类,一种是查询报文,一种是差错报文。查询报文是用一对请求和应答定义的,它通常有以下几种用途:

  1. ping查询
  2. 子网掩码查询(用于无盘工作站在初始化自身的时候初始化子网掩码)
  3. 时间戳查询(可以用来同步时间)

        而差错报文通常包含了引起错误的IP数据报的第一个分片的IP首部(和选项),加上该分片数据部分的前8个字节。RFC 792规范中定义的这8个字节中包含了该分组运输层首部的所有分用信息,这样运输层协议就可以向正确的进程提交ICMP差错报文。

        当传送IP数据包发生错误时,比如主机不可达,端口不可达等,ICMP协议就会把错误信息封包,然后传送回给主机。给主机一个处理错误的机会,这也就是为什么说建立在IP层以上的协议是可能做到安全的原因。由上面可知,ICMP数据包由8bit的错误类型和8bit的代码和16bit的校验和组成,而前 16bit就组成了ICMP所要传递的信息。由数据链路层所能发送的最大数据帧,即MTU(Maximum Transmission Unit)为1500,计算易知ICMP协议在实际传输中数据包为:20字节IP首部 + 8字节ICMP首部+ 1472字节(数据大小)。

        尽管在大多数情况下,错误的包传送应该给出ICMP报文,但是在特殊情况下,是不产生ICMP错误报文的。如下

  1. ICMP差错报文不会产生ICMP差错报文(出IMCP查询报文)(防止IMCP的无限产生和传送)
  2. 目的地址是广播地址或多播地址的IP数据报。
  3. 作为链路层广播的数据报。
  4. 不是IP分片的第一片。
  5. 源地址不是单个主机的数据报。这就是说,源地址不能为零地址、环回地址、广播地 址或多播地址。

Ping程序实现原理

        源主机向网络上的另一个主机系统发送ICMP报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者,如此便能探测目标主机是否在线。一般使用类型为0和8的ICMP报文。

        在此应该了解一下IP头部生存时间(time to live)TTL字段。程序实现时是向目地主机发送一个ICMP回显请求消息,初始时TTL为固定值,这样当该数据报抵达途中的第一个路由器时,TTL的值就被减1,再TTL的值不为零时,数据包继续往下传送数据包,直至TTL的值为0返回ICMP超时差错报文,或者到达目的主机返回一模一样的报文。

代码部分

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;

//IP报头
typedef struct
{
    unsigned char hdr_len:4;         //4位头部长度
    unsigned char version:4;         //4位版本号
    unsigned char tos;               //8位服务类型
    unsigned short total_len;        //16位总长度
    unsigned short identifier;       //16位标识符
    unsigned short frag_and_flags;   //3位标志加13位片偏移
    unsigned char ttl;               //8位生存时间
    unsigned char protocol;          //8位上层协议号
    unsigned short checksum;         //16位效验和
    unsigned long sourceIP;          //32位源IP地址
    unsigned long destIP;            //32位目的IP地址
}IP_HEADER;

//ICMP报头
typedef struct
{
    BYTE type;     //8位类型字段
    BYTE code;     //8位代码字段
    USHORT cksum;  //16位效验和
    USHORT id;     //16位标识符
    USHORT seq;    //16位序列号
}ICMP_HEADER;


//报文解码结构
typedef struct
{
    USHORT usSeqNo;          //序列号
    DWORD dwRoundTripTime;   //返回时间
    in_addr dwIPaddr;        //返回报文的IP地址
}DECODE_RESULT;

//计算网际效验和函数
USHORT checksum(USHORT *pBuf,int iSize)
{
    unsigned long cksum=0;
    while(iSize>1)
    {
        cksum+=*pBuf++;
        iSize-=sizeof(USHORT);
    }
    if(iSize)
    {
        cksum+=*(USHORT*)pBuf;
    }
    cksum=(cksum>>16)+(cksum&0xffff);
    cksum+=(cksum>>16);
    return(USHORT)(~cksum);
}

//对数据包进行解码
BOOL DecodeIcmpResponse(char *pBuf,int iPacketSize,DECODE_RESULT &DecodeResult,BYTE ICMP_ECHO_REPLY,BYTE ICMP_TIMEOUT)
{
    //检查数据报大小的合法性
    IP_HEADER *pIpHdr=(IP_HEADER*)pBuf;
    int iIpHdrLen=pIpHdr->hdr_len*4;
    if(iPacketSize<(int)(iIpHdrLen+sizeof(ICMP_HEADER)))
        return FALSE;
    //根据ICMP报文类型提取ID字段和序列号字段
    ICMP_HEADER *pIcmpHdr=(ICMP_HEADER*)(pBuf+iIpHdrLen);
    USHORT usID,usSquNo;
    if(pIcmpHdr->type==ICMP_ECHO_REPLY)    //ICMP回显应答报文
    {
        usID=pIcmpHdr->id;   //报文ID
        usSquNo=pIcmpHdr->seq;  //报文序列号
    }
    else if(pIcmpHdr->type==ICMP_TIMEOUT)   //ICMP超时差错报文
        {
            char *pInnerIpHdr=pBuf+iIpHdrLen+sizeof(ICMP_HEADER);  //载荷中的IP头
            int iInnerIPHdrLen=((IP_HEADER*)pInnerIpHdr)->hdr_len*4; //载荷中的IP头长
            ICMP_HEADER *pInnerIcmpHdr=(ICMP_HEADER*)(pInnerIpHdr+iInnerIPHdrLen);//载荷中的ICMP头
            usID=pInnerIcmpHdr->id;  //报文ID
            usSquNo=pInnerIcmpHdr->seq;  //序列号
        }else{
            return false;
        }
        //检查ID和序列号以确定收到期待数据报
        if(usID!=(USHORT)GetCurrentProcessId()||usSquNo!=DecodeResult.usSeqNo)
        {
            return false;
        }
        //记录IP地址并计算往返时间
        DecodeResult.dwIPaddr.s_addr=pIpHdr->sourceIP;
        DecodeResult.dwRoundTripTime=GetTickCount()-DecodeResult.dwRoundTripTime;

        //处理正确收到的ICMP数据报
        if(pIcmpHdr->type==ICMP_ECHO_REPLY||pIcmpHdr->type==ICMP_TIMEOUT)
        {
            return true;
        }else{
            return false;
        }
        return true;
}



int main()
{
    //初始化Windows sockets网络环境
    WSADATA wsa;
    WSAStartup(MAKEWORD(2,2),&wsa);
    char IpAddress[255];
    cout<<"请输入一个IP地址或域名:";
    cin>>IpAddress;
    //得到IP地址
    u_long ulDestIP=inet_addr(IpAddress);
    //转换不成功时按域名解析
    if(ulDestIP==INADDR_NONE)
    {
        hostent *pHostent=gethostbyname(IpAddress);
        if(pHostent)
        {
            ulDestIP=(*(in_addr*)pHostent->h_addr).s_addr;
        }
        else
        {
            cout<<"输入的IP地址或域名无效"<<endl;
            WSACleanup();
            return 0;
        }
    }
    cout<<"正在 Ping "<<IpAddress<<"具有 32 字节的数据:"<<endl<<endl;
    //填充目的端socket地址
    sockaddr_in destSockAddr;
    ZeroMemory(&destSockAddr,sizeof(sockaddr_in));
    destSockAddr.sin_family=AF_INET;
    destSockAddr.sin_addr.s_addr=ulDestIP;
    //创建原始套接字
    SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);
    //超时时间
    int iTimeout=3000;
    //接收超时
    setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&iTimeout,sizeof(iTimeout));
    //发送超时
    setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&iTimeout,sizeof(iTimeout));


    //构造ICMP回显请求消息,并以TTL递增的顺序发送报文
    //ICMP类型字段
    const BYTE ICMP_ECHO_REQUEST=8;   //请求回显
    const BYTE ICMP_ECHO_REPLY=0;     //回显应答
    const BYTE ICMP_TIMEOUT=11;       //传输超时

    //其他常量定义
    const int DEF_ICMP_DATA_SIZE=32;    //ICMP报文默认数据字段长度
    const int MAX_ICMP_PACKET_SIZE=1024; //ICMP报文最大长度(包括报头)
    const DWORD DEF_ICMP_TIMEOUT=3000;   //回显应答超时时间
    const int DEF_MAX_HOP=4;            //最大跳站数

    //填充ICMP报文中每次发送时不变的字段
    char IcmpSendBuf[sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE]; //发送缓冲区
    memset(IcmpSendBuf,0,sizeof(IcmpSendBuf));     //初始化发送缓冲区
    char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE];        //接收缓冲区
    memset(IcmpRecvBuf,0,sizeof(IcmpRecvBuf));     //初始化接收缓冲区

    ICMP_HEADER *pIcmpHeader=(ICMP_HEADER*)IcmpSendBuf;
    pIcmpHeader->type=ICMP_ECHO_REQUEST;             //类型为请求回显
    pIcmpHeader->code=0;                             //代码字段为0
    pIcmpHeader->id=(USHORT)GetCurrentProcessId();   //ID字段为当前进程号
    memset(IcmpSendBuf+sizeof(ICMP_HEADER),'E',DEF_ICMP_DATA_SIZE);   //数据字段
    USHORT usSeqNo=0;                //ICMP报文序列号
    int iTTL=64;                      //TTL初始值为1
    int r=0,o=0;
    BOOL bReachDestHost=FALSE;       //循环退出标志
    int iMaxHot=DEF_MAX_HOP;         //循环的最大次数
    DECODE_RESULT DecodeResult;      //传递给报文解码函数的结构化参数
    while(!bReachDestHost&&iMaxHot--)
    {
        //设置IP报头的TTL字段
        setsockopt(sockRaw,IPPROTO_IP,IP_TTL,(char*)&iTTL,sizeof(iTTL));
        //填充ICMP报文中每次发送变化的字段
        ((ICMP_HEADER*)IcmpSendBuf)->cksum=0;                 //效验和先置为0
        ((ICMP_HEADER*)IcmpSendBuf)->seq=htons(usSeqNo++);    //填充序列号
        ((ICMP_HEADER*)IcmpSendBuf)->cksum=checksum((USHORT*)IcmpSendBuf,sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE);  //计算效验和

        //记录序列号和当前时间
        DecodeResult.usSeqNo=((ICMP_HEADER*)IcmpSendBuf)->seq;    //当前序号
        DecodeResult.dwRoundTripTime=GetTickCount();              //当前时间
        //发送TCP回显请求信息
        sendto(sockRaw,IcmpSendBuf,sizeof(IcmpSendBuf),0,(sockaddr*)&destSockAddr,sizeof(destSockAddr));
        //接收ICMP差错报文并进行解析处理
        sockaddr_in from;              //对端socket地址
        int iFromLen=sizeof(from);     //地址结构大小
        int iReadDataLen;              //接收数据长度
        while(1)
        {
            //接收数据
            iReadDataLen=recvfrom(sockRaw,IcmpRecvBuf,MAX_ICMP_PACKET_SIZE,0,(sockaddr*)&from,&iFromLen);
            if(iReadDataLen!=SOCKET_ERROR)//有数据达到
            {
                //对数据包进行解码
                if(DecodeIcmpResponse(IcmpRecvBuf,iReadDataLen,DecodeResult,ICMP_ECHO_REPLY,ICMP_TIMEOUT))
                {
                    //到达目的地,退出循环
                    if(DecodeResult.dwIPaddr.s_addr==destSockAddr.sin_addr.s_addr){
                        //输出IP地址
                        if(DecodeResult.dwRoundTripTime)
                             cout<<"来自 "<<inet_ntoa(DecodeResult.dwIPaddr)<<" 的回复:字节32 时间="<<DecodeResult.dwRoundTripTime<<"ms TTL:"<<iTTL<<endl;
                        else
                             cout<<"来自 "<<inet_ntoa(DecodeResult.dwIPaddr)<<" 的回复:字节32 时间< 1ms TTL:"<<iTTL<<endl;
                        r++;
                        break;
                    }

                }
            }
            else if(WSAGetLastError()==WSAETIMEDOUT) //接收超时,输出星号
            {
                cout<<"请求超时。"<<endl;
                o++;
                break;
            }
            else{
                break;
            }
        }
    }
    cout<<endl;
    cout<<IpAddress<<" 的 Ping 统计信息:"<<endl;
    cout<<'\t'<<"数据包:已发送=4,已接收="<<r<<",丢失="<<o<<endl;
}

测试结果

  • 8
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
下面是一个简单的 C++ 实现 Ping 程序的示例代码: ```c++ #include <iostream> #include <cstdlib> #include <cstring> #include <unistd.h> #include <sys/time.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <netinet/ip_icmp.h> using namespace std; const int PACKET_SIZE = 4096; const int ICMP_HEAD_SIZE = 8; const int MAX_WAIT_TIME = 5; // 最大等待时间,单位秒 const int MAX_COUNT = 4; // 最大尝试次数 // 计算校验和 unsigned short CalcChecksum(unsigned short *addr, int len) { unsigned int sum = 0; while (len > 1) { sum += *addr++; len -= 2; } if (len == 1) { sum += *(unsigned char*)addr; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)(~sum); } // 发送 ICMP 报文 int SendIcmpPacket(int sock_fd, const struct sockaddr *dest_addr, int seq) { char packet[PACKET_SIZE]; memset(packet, 0, PACKET_SIZE); struct icmp *icmp_hdr = (struct icmp*)packet; icmp_hdr->icmp_type = ICMP_ECHO; icmp_hdr->icmp_code = 0; icmp_hdr->icmp_id = getpid(); icmp_hdr->icmp_seq = seq; memset(icmp_hdr->icmp_data, 0xa5, PACKET_SIZE - ICMP_HEAD_SIZE); icmp_hdr->icmp_cksum = CalcChecksum((unsigned short*)icmp_hdr, PACKET_SIZE); int ret = sendto(sock_fd, packet, PACKET_SIZE, 0, dest_addr, sizeof(struct sockaddr)); if (ret == -1) { cerr << "sendto error" << endl; return -1; } return 0; } // 接收 ICMP 报文 int RecvIcmpPacket(int sock_fd, struct timeval &tv, struct sockaddr *from_addr) { char packet[PACKET_SIZE]; socklen_t addr_len = sizeof(struct sockaddr); int ret = recvfrom(sock_fd, packet, PACKET_SIZE, 0, from_addr, &addr_len); if (ret == -1) { cerr << "recvfrom error" << endl; return -1; } struct iphdr *ip_hdr = (struct iphdr*)packet; int ip_hdr_len = ip_hdr->ihl * 4; struct icmp *icmp_hdr = (struct icmp*)(packet + ip_hdr_len); int icmp_hdr_len = ret - ip_hdr_len; if (icmp_hdr->icmp_type == ICMP_ECHOREPLY && icmp_hdr->icmp_id == getpid()) { tv.tv_sec = time(NULL) - tv.tv_sec; cout << icmp_hdr_len << " bytes from " << inet_ntoa(((struct sockaddr_in*)from_addr)->sin_addr) << ": icmp_seq=" << icmp_hdr->icmp_seq << " time=" << tv.tv_sec << "s" << endl; return 0; } return -1; } // Ping 操作 int Ping(const char *ip_addr) { int sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sock_fd < 0) { cerr << "socket error" << endl; return -1; } struct sockaddr_in dest_addr; memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; dest_addr.sin_addr.s_addr = inet_addr(ip_addr); int seq = 0; int count = 0; while (count < MAX_COUNT) { if (SendIcmpPacket(sock_fd, (struct sockaddr*)&dest_addr, seq) == -1) { cerr << "SendIcmpPacket error" << endl; return -1; } struct timeval tv; gettimeofday(&tv, NULL); fd_set read_fds; FD_ZERO(&read_fds); FD_SET(sock_fd, &read_fds); struct timeval timeout; timeout.tv_sec = MAX_WAIT_TIME; timeout.tv_usec = 0; int ret = select(sock_fd + 1, &read_fds, NULL, NULL, &timeout); if (ret < 0) { cerr << "select error" << endl; return -1; } else if (ret == 0) { cout << "Request timeout for icmp_seq " << seq << endl; } else if (FD_ISSET(sock_fd, &read_fds)) { if (RecvIcmpPacket(sock_fd, tv, (struct sockaddr*)&dest_addr) == -1) { cerr << "RecvIcmpPacket error" << endl; return -1; } } seq++; count++; sleep(1); } close(sock_fd); return 0; } int main(int argc, char *argv[]) { if (argc != 2) { cerr << "Usage: " << argv[0] << " <ip address>" << endl; return -1; } if (Ping(argv[1]) == -1) { cerr << "Ping error" << endl; return -1; } return 0; } ``` 该程序使用了原始套接字进行 ICMP 报文的发送和接收,并采用了 select 函数进行超时等待。在主函数中,程序需要传入目标 IP 地址进行 Ping 操作。程序会尝试发送指定次数的 ICMP 报文,并等待回应,如果超时则输出“Request timeout”,如果收到回应则输出“icmp_seq=xx time=xx”,其中 xx 表示 ICMP 报文的序列号和延迟时间。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我的书包哪里去了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值