Windows下C語言實現計算機網絡底層Ping功能

一、在Windows環境下,實現ping(即發送一個ICMP的echo報文並對目標返回的迴應報文進行正確的解析)

二、ICMP(Internet Control Message Protocol,網際控制協議),它允許主機或路由器報告差錯情況和提供有關異常情況的報告。

ICMP提供的功能:錯誤診斷、擁塞控制、路徑控制和查詢服務

如當一個分組無法到達目的站點或TTL超時後,路由器就會丟棄此分組,並向源站點返回一個目的站點不可到達的ICMP報文。

實現ping主要涉及到ICMP回顯請求和應答報文以及ICMP超時報文。回顯ICMP協議數據包基本格式如下:
這裏寫圖片描述
1、8bits類型和8bits代碼字段一起決定了ICMP報文的類型:
類型8、代碼報文0———-回顯請求數據包
類型0、代碼報文0———-回顯應答數據包
類型11、代碼字段爲0——TTL超時
類型11、代碼字段爲1——數據包重組時間超時

2、16bits校驗和字段:
包括數據在內的整個ICMP報文的校驗和

3、16bits標識符字段:
用於標識本ICMP進程

4、16bits序列號字段:
用於判斷回顯應答報文

三、Ping程序的實現方法

主機向遠程計算機發出ICMP回顯請求後,遠程計算機會攔截住這個請求,然後生成一條回顯應答請求,再通過網絡傳回給主機。假如因某方面的原因,不能抵達目標主機,就會生成對應的ICMP錯誤消息(例如:主機不可達),由通信路徑上的某臺路由器返回。假定與遠程主機的物理連接並不存在問題,但遠程主機已經關閉或沒有設置對應的網絡事件作出響應,便需由自己的程序來執行超時鑑定,偵測出這樣的情況。

四、ping程序代碼

#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! \n") ;
        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!\n") ;
        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("地址解析失敗!\n") ;
           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失敗!\n") ;
        return ;
    }
   //使用ICMP協議創建Raw Socket
   SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ;
   if(sockRaw == INVALID_SOCKET)
   {
       printf("創建Socket失敗 !\n") ;
       return ;
   }
   //設置端口屬性
   int iTimeout = DEF_ICMP_TIMEOUT ;
   if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
   {
         printf("設置參數失敗!\n") ;
         return ;
   }
   if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
   {
         printf("設置參數失敗!\n") ;
         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("目的主機不可達!\n") ;
            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\n" , inet_ntoa(stDecodeResult.dwIPaddr) ,
                         iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ;
             }
             break ;
         }
         else if(WSAGetLastError() == WSAETIMEDOUT)
         {
             printf("time out !  *****\n") ;
             break ;
         }
         else
         {
             printf("發生未知錯誤!\n") ;
             break ;
         }
     }
     usSeqNo++ ;
   }
   //輸出屏幕信息
   printf("Ping complete...\n") ;
   closesocket(sockRaw) ;
   WSACleanup() ;
}
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("command error ! \n") ;
   }
   }
   return 0 ;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章