一、在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 ;
}