ping功能實現(windows網路編程學習筆記)

一、概述
  ICMP(Internet Control Message Protocol)Internet控制報文協議。它是TCP/IP協議簇的一個子協議,用於在IP主機、路由器之間傳遞控制消息。控制消息是指網絡通不通、主機是否可達、路由是否可用等網絡本身的消息。這些控制消息雖然並不傳輸用戶數據,但是對於用戶數據的傳遞起着重要的作用。
   ICMP協議是一種面向無連接的協議,用於傳輸出錯報告控制信息。它是一個非常重要的協議,它對於網絡安全具有極其重要的意義。ICMP就是一個“錯誤偵測與回報機制”,其目的就是讓我們能夠檢測網路的連線狀況﹐也能確保連線的準確性。當路由器在處理一個數據包的過程中發生了意外,可以通過ICMP向數據包的源端報告有關事件。
  其功能主要有,偵測遠端主機是否存在,建立及維護路由資料,重導資料傳送路徑(ICMP重定向),資料流量控制。ICMP在溝通之中,主要是透過不同的類別(Type)與代碼(Code) 讓機器來識別不同的連線狀況。
  使用ping命令可以檢測指定設備的在線狀態(有些主機會屏蔽ping命令),但在寫程序的時候一般不是調用cmd命令去直接ping主機,而是通過socket想目標機器發送ICMP請求包,然後等待返回結果。
二、實現
  1.定義ICMP數據包結構
  ICMP數據包包含在IP數據包結構內,爲了在程序中對ICMP數據包做處理,我們使用結構體來描述IP數據包和ICMP數據包:
ICMP首部TYPE和CODE對應表:

TYPE CODE Description
0 0 Echo Reply——回顯應答(Ping應答)
3 0 Network Unreachable——網絡不可達
3 1 Host Unreachable——主機不可達
3 2 Protocol Unreachable——協議不可達
3 3 Port Unreachable——端口不可達
3 4 Fragmentation needed but no frag. bit set——需要進行分片但設置不分片比特
3 5 Source routing failed——源站選路失敗
3 6 Destination network unknown——目的網絡未知
3 7 Destination host unknown——目的主機未知
3 8 Source host isolated (obsolete)——源主機被隔離(作廢不用)
3 9 Destination network administratively prohibited——目的網絡被強制禁止
3 10 Destination host administratively prohibited——目的主機被強制禁止
3 11 Network unreachable for TOS——由於服務類型TOS,網絡不可達
3 12 Host unreachable for TOS——由於服務類型TOS,主機不可達
3 13 Communication administratively prohibited by filtering——由於過濾,通信被強制禁止
3 14 Host precedence violation——主機越權
3 15 Precedence cutoff in effect——優先中止生效
4 0 Source quench——源端被關閉(基本流控制)
5 0 Redirect for network——對網絡重定向
5 1 Redirect for host——對主機重定向
5 2 Redirect for TOS and network——對服務類型和網絡重定向
5 3 Redirect for TOS and host——對服務類型和主機重定向
8 0 Echo request——回顯請求(Ping請求)
9 0 Router advertisement——路由器通告
10 0 Route solicitation——路由器請求
11 0 TTL equals 0 during transit——傳輸期間生存時間爲0
11 1 TTL equals 0 during reassembly——在數據報組裝期間生存時間爲0
12 0 IP header bad (catchall error)——壞的IP首部(包括各種差錯)
12 1 Required options missing——缺少必需的選項
13 0 Timestamp request (obsolete)——時間戳請求(作廢不用)
14 Timestamp reply (obsolete)——時間戳應答(作廢不用)
15 0 Information request (obsolete)——信息請求(作廢不用)
16 0 Information reply (obsolete)——信息應答(作廢不用)
17 0 Address mask request——地址掩碼請求
18 0 Address mask reply——地址掩碼應答

IP結構體:

typedef struct IPHDR{
	   unsigned int     h_len:4;	//包頭長度
	   unsigned int     version:4;//版本號
	   unsigned char    tos://服務類型
	   unsigned short   total_len;//包總長度
	   unsigned short   ident;//唯一標識符
	   unsigned short   frag_and_flages;//標識
	   unsigned char    ttl;//生存時間
	   unsigned char    proto;//傳輸協議
	   unsigned short   checksum;//校驗和
	   unsigned int     souceIP;//源ip
	   unsigned int     destIP;//目標ip
}IpHeader;

ICMP結構體:

typedef struct ICMPHDR{
	   BYTE      i_type;//類型
	   BYTE      i_code;//編碼
	   USHORT    i_cksum;//校驗和
	   USHORT    i_id;//編號
	   USHORT    i_seq;//序號
	   ULONG     timestamp;//時間戳
}IcmpHeader;

2.代碼實現片段
填充ICMP包

/*
即初始化,先將頭部字段進行賦值,再對數據部分進行填充,用'E'來填充,不要問我爲什麼,不求甚解,哈哈
#define ICMP_MIN 8						// ICMP包的最小長度爲8個字節,只包含包頭
#define DEF_PACKET_SIZE 32			// 執行ping操作時指定發送數據包的缺省大小
#define MAX_PACKET 1024				// 執行ping操作時指定發送數據包的最大大小
#define ICMP_ECHO 8						// 表示ICMP包爲回射請求包
#define ICMP_ECHOREPLY 0			// 表示ICMP包爲回射應答包
*/
void fullICMP(char *icmp_data,int datasize){
     IcmpHeader *icmpHdr;
     char *datapart;
     icmpHdr = (IcmpHeader*)icmp_data;
     memset(icmpHdr,0,datasize);
     icmp_hdr->i_type = ICMP_ECHO;
     icmp_hdr->i_id   = (SHORT)GetCurrentThread();
     icmp_hdr->i_code = 0;
     icmp_hdr->i_seq  = 0;
     icmp_hdr->i_sum  = 0;//校驗和先設置爲0
     datapart = icmp_data + sizeof(IcmpHeader);
     memset(datapart,'E',datasize - sizeof(IcmpHeader));
}

解析ICMp迴應包

int decodeIcmpReply(char *buf,int bytes,DWORD tid){

   IpHeader *iphdr;					// IP數據包頭
	   IcmpHeader *icmphdr;			// ICMP包頭
   	unsigned short iphdrlen;		// IP數據包頭的長度
	   iphdr = (IpHeader *)buf;		// 從buf中IP數據包頭的指針
	   // 計算IP數據包頭的長度 
	   iphdrlen = iphdr->h_len * 4 ; // number of 32-bit words *4 = bytes
	   
	   // 如果指定的緩衝區長度小於IP包頭加上最小的ICMP包長度,則說明它包含的ICMP數據不完整,或者不包含ICMP數據
	   if (bytes < iphdrlen + ICMP_MIN) {
	      	return -1; 
	   }
	   
	   // 定位到ICMP包頭的起始位置
	   icmphdr = (IcmpHeader*)(buf + iphdrlen);
	   
	   // 如果ICMP包的類型不是迴應包,則不處理
	   if (icmphdr->i_type != ICMP_ECHOREPLY) {
	      	return -2;
	    }
   	// 發送的ICMP包ID和接收到的ICMP包ID應該對應
	   if (icmphdr->i_id != (USHORT)tid){ //(USHORT)GetCurrentProcessId()) {
		      return -3; 
	    }
	    // 返回發送ICMP包和接收回應包的時間差 
	    int time = GetTickCount() - (icmphdr->timestamp);
	   if(time >= 0)
		      return time;
   	else
	      	return -4; // 時間值不對
}

ping函數實現

int ping(const char *ip, DWORD timeout)
{
	   WSADATA wsaData;						// 初始化Windows Socket的數據
	   SOCKET sockRaw = NULL;			// 用於執行ping操作的套接字
	   struct sockaddr_in dest,from;		// socket通信的地址
	   struct hostent * hp;						// 保存主機信息
	   int datasize;									// 發送數據包的大小
	   char *dest_ip;								// 目的地址
	   char *icmp_data = NULL;				// 用來保存ICMP包的數據
	   char *recvbuf = NULL;					// 用來保存應答數據
	   USHORT seq_no = 0;
	   int ret = -1;
	   
	   // 初始化SOCKET
	   if (WSAStartup(MAKEWORD(2,1),&wsaData) != 0){
		      ret = -1000;// WSAStartup 錯誤
		      goto FIN;
	    }
	    
	   // 創建原始套接字
	   sockRaw = WSASocket (AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL, 0,WSA_FLAG_OVERLAPPED);
		   
	   if (sockRaw == INVALID_SOCKET) {
	     	ret = -2;// WSASocket 錯誤
	     	goto FIN;
	    }
	    
	    // 設置套接字的接收超時選項
	    int bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));
	    
	    if(bread == SOCKET_ERROR) {
	      	ret = -3;// setsockopt 錯誤
	      	goto FIN;
	    }
	    
    	// 設置套接字的發送超時選項
    	bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,
	    	sizeof(timeout));
	    if(bread == SOCKET_ERROR) {
	       	ret = -4;// setsockopt 錯誤
	        	goto FIN;
     	}
     	memset(&dest,0,sizeof(dest));

	    unsigned int addr=0;					// 將IP地址轉換爲網絡字節序
    hp = gethostbyname(ip);				// 獲取遠程主機的名稱
    	if (!hp){
       		addr = inet_addr(ip);
    	}
    	if ((!hp) && (addr == INADDR_NONE) ) {
      		ret = -5; // 域名錯誤
      		goto FIN;
    	}
	    // 配置遠程通信地址
	    if (hp != NULL)
    	   	memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length);
    	else
	       	dest.sin_addr.s_addr = addr;

	    if (hp)
       		dest.sin_family = hp->h_addrtype;
    	else
       		dest.sin_family = AF_INET;
       	dest_ip = inet_ntoa(dest.sin_addr);

	     // 準備要發送的數據
     	datasize = DEF_PACKET_SIZE;
     	datasize += sizeof(IcmpHeader);
     	char icmp_dataStack[MAX_PACKET];
     	char recvbufStack[MAX_PACKET];
  	   icmp_data = icmp_dataStack;
     	recvbuf = recvbufStack;
	     // 未能分配到足夠的空間
	     if (!icmp_data) {
	        	ret = -6; // 
	        	goto FIN;
      	}
     	memset(icmp_data,0,MAX_PACKET);
     	// 準備要發送的數據
     	fill_icmp_data(icmp_data,datasize);			// 設置報文頭
     	((IcmpHeader*)icmp_data)->i_cksum = 0;
      	DWORD startTime = GetTickCount();
	     ((IcmpHeader*)icmp_data)->timestamp = startTime;
	     ((IcmpHeader*)icmp_data)->i_seq = seq_no++;
	     ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data,datasize);
	
	     // 發送數據
	     int bwrote;		
     	bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,
     		sizeof(dest));
    	if (bwrote == SOCKET_ERROR){
        		if (WSAGetLastError() != WSAETIMEDOUT) 
        		{
            			ret = -7; // 發送錯誤
            			goto FIN;
	         	}
	      }
      	if (bwrote < datasize ) {
         		ret = -8; // 發送錯誤
         		goto FIN;
       	}
      	// 使用QueryPerformance函數用於精確判斷結果返回時間值
      	// 原有的其他的Windows函數(GetTickCount等)的方式返回值與Windows Ping應用程序相差太大。
      	LARGE_INTEGER ticksPerSecond;
      	LARGE_INTEGER start_tick;
      	LARGE_INTEGER end_tick;
      	double elapsed; // 經過的時間
      	QueryPerformanceFrequency(&ticksPerSecond); // CPU 每秒跑幾個tick
      	QueryPerformanceCounter(&start_tick); // 開始時系統計數器的位置		
      	int fromlen = sizeof(from);			// 源地址的大小
      	while(1)
      	{
       		  // 接收回應包
       		   bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,			&fromlen);
       	   	if (bread == SOCKET_ERROR){
       		   	  if (WSAGetLastError() == WSAETIMEDOUT) {
           				     ret = -1; // 超時
           				     goto FIN;
	        		    }
	        		    ret = -9; // 接收錯誤
        			    goto FIN;
         		}
		       // 對迴應的IP數據包進行解析,定位ICMP數據
	       	int time = decode_resp(recvbuf,bread,&from,GetCurrentThreadId());
	       	if( time >= 0 )	{
			         //ret = time;
			         QueryPerformanceCounter(&end_tick);  // 獲取結束時系統計數器的值
			         elapsed = ((double)(end_tick.QuadPart - start_tick.QuadPart) / ticksPerSecond.QuadPart); // 計算ping操作的用時
			          ret = (int)(elapsed*1000);
			          goto FIN;
		         } else if(GetTickCount() - startTime >= timeout || GetTickCount() < startTime){
			              ret = -1; // 超時
		              	goto FIN;
		          }
	        }

        FIN:
	       // 釋放資源
           	closesocket(sockRaw);
	           WSACleanup();
	           // 返回ping操作用時或者錯誤編號
           	return ret;
}

以下是自己寫的ping指端網段的代碼,歡迎討論並指出不足之處:
ping命令實現

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