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命令实现

发布了21 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/psl1234554321/article/details/101037034