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

測試結果

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章