icmp(Internet control message protocol),是一种网络上用来传递错误报文的协议,根据类型和代码,可以分为很多类型的,为了实现icmp扫描,我们这里只需要用到请求回显(type=8,code=0)和回显应答(type=0,code=0),具体情况,请查看icmp,报文格式。

原理,像需要扫描的ip发送icmp请求回显,如果收到icmp回显应答,则该ip处于活动状态,否则不是,这里我开发环境是win7+vs2013,建立的普通控制条程序,使用socket即可。

1.定义header.h头文件,用于定于一些需要使用的头部定义

#include"winsock2.h"
#pragma comment(lib,"ws2_32")
#include"WS2TCPIP.H"
#include "mstcpip.h"
typedef struct _iphdr
{
	unsigned char h_lenver;
	unsigned char tos;
	unsigned short total_len;
	unsigned short ident;
	unsigned short frag_and_flags;
	unsigned char ttl;
	unsigned char proto;
	unsigned short checksum;
	unsigned int sourceIP;
	unsigned int destIP;
}IPHEADER;
//icmp头部
typedef struct _icmphdr
{
	BYTE i_type;
	BYTE i_code;
	USHORT i_cksum;
	USHORT i_id;
	USHORT i_seq;
	ULONG timestamp;
}ICMPHEADER;

//解码结果
typedef struct
{
	USHORT usSeqNo;		  //包序列号
	DWORD dwRoundTripTime; //往返时间
	in_addr dwIPaddr;	  //对端IP地址
} DECODE_RESULT;//ip头部定义,具体含义,参考ip头部格式

2.发送数据包的函数

//在主函数中调用,用于发送icmp数据包
//返回值是执行结构,成功0,失败-1
//参数与分别是定义好的套接字,目标地址的结构体指针
int SendEchoRequest(SOCKET s, LPSOCKADDR_IN lpstToAddr)
{
	int nRet; //
	char IcmpSendBuf[sizeof(ICMPHEADER)];//存放包的缓存区

	//填充数据包
	memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf));
	ICMPHEADER *pIcmpHeader = (ICMPHEADER*)IcmpSendBuf;
	pIcmpHeader->i_type = 8; //回写请求消息
	pIcmpHeader->i_code = 0;
	pIcmpHeader->i_id = (USHORT)GetCurrentProcessId();//可以随便给,这里为了方便就给当前线程号
	pIcmpHeader->i_seq = 0x0;//
	pIcmpHeader->i_cksum = 0;
	pIcmpHeader->timestamp = 0x01020304;//数据,随意,大小也是随意,这里我定义的4B
	pIcmpHeader->i_cksum = CheckSum((USHORT*)pIcmpHeader, sizeof(ICMPHEADER));//校验和计算,函数如下:

	nRet = sendto(s, IcmpSendBuf, sizeof(IcmpSendBuf),0, (LPSOCKADDR)lpstToAddr, sizeof(SOCKADDR_IN));//发送

	if (nRet == SOCKET_ERROR)
	{
		cout << "sento Error" << endl;
		return -1;
	}
	return 0;
}
//计算检验和函数
USHORT CheckSum(USHORT *buffer, int size)
{
<span >	</span>unsigned long cksum = 0;
<span >	</span>while (size > 1)
<span >	</span>{
<span >		</span>cksum += *buffer++;
<span >		</span>size -= sizeof(USHORT);
<span >	</span>}
<span >	</span>if (size)
<span >	</span>{
<span >		</span>cksum += *(UCHAR*)buffer;
<span >	</span>}
<span >	</span>while (cksum >> 16)
<span >		</span>cksum = (cksum >> 16) + (cksum & 0xffff);
<span >	</span>return (USHORT)(~cksum);
}


3.用于解析接收到的数据包的函数

/********************************************************
函数名:DecodeIcmpResponse
输入参数:char* pBuf:接收到的原始数据包(包括IP首部)
int iPacketSize:原始数据包大小
DECODE_RESULT *stDecodeResult:解析结构指针
输出参数:操作成功的标志,true:成功,false:失败
功能:解析收到的原始数据包,将收到的ICMP错误报文和响应报文分别处理
*********************************************************/
BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult)
{
	IPHEADER* pIpHdr = (IPHEADER*)pBuf;
	int iIpHdrLen = 20;//ip头部,固定20字节
	
	//ip首部占用20字节,定位到icmp报文
	ICMPHEADER* pIcmpHdr = (ICMPHEADER*)(pBuf + iIpHdrLen);
	USHORT usID, usSquNo;
	if (pIcmpHdr->i_type == 0)//回写应答
	{
		usID = pIcmpHdr->i_id;
		usSquNo = pIcmpHdr->i_seq;
	}
	else
		return FALSE;
	//处理正确收到的ICMP数据报,因为type=0的icmp只有一种报文,所以不用检查code
	if (pIcmpHdr->i_type == 0 )
	{
		//返回解码结果
		stDecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
		stDecodeResult.dwRoundTripTime = GetTickCount() - stDecodeResult.dwRoundTripTime;
		return TRUE;
	}
	return FALSE;
}


4.接收数据包函数

/********************************************************
函数名:RecvEchoReply
输入参数:SOCKET s:原始套接字
SOCKADDR_IN *saFrom:数据包的来源地址
SOCKADDR_IN *saDest:数据包的目标地址
DECODE_RESULT *stDecodeResult:解析结构指针
输出参数:操作成功的标志,true:成功,false:失败
功能:接收数据,并调用DecodeIcmpResponse对接收到的ICMP响应进行解析
*********************************************************/
DWORD RecvEchoReply(SOCKET s, SOCKADDR_IN *saFrom, SOCKADDR_IN *saDest, DECODE_RESULT *stDecodeResult)
{
	int nRet;
	int nAddrLen = sizeof(struct sockaddr_in);

	//创建ICMP包接收缓冲区
	char IcmpRecvBuf[1024];
	memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf));

	// 接收	
	nRet = recvfrom(s,					// socket
		(LPSTR)&IcmpRecvBuf,	// buffer
		1024,	// size of buffer
		0,					// flags
		(LPSOCKADDR)saFrom,	// From address
		&nAddrLen);			// pointer to address len


	//打印输出
	if (nRet != SOCKET_ERROR) //接收没有错误
	{
		//解码得到的数据包,如果解码正确则跳出接收循环发送下一个EchoRequest包
		if (DecodeIcmpResponse(IcmpRecvBuf, nRet, *stDecodeResult))
		{
			if (stDecodeResult->dwIPaddr.s_addr == saDest->sin_addr.s_addr)
			{
				printf("该主机处于活动状态\n");
				return 1;
			}
		}
		else
			return -1;
	}
	else if (WSAGetLastError() == WSAETIMEDOUT) //接收超时,一般情况下,不可达可以收到目的地不可达icmp报文,但如果需要扫描ip与本机处于同一局域网,则主//机在发送icmp之前,回先发送arp请求目的ip物理地址,如果该ip存在,则才会继续发送icmp报文,如果不存在,则不会有后续的,这里就是处理这种情况
	{
		printf("该主机不处于活动状态\n");
	}
	else
	{
		printf("recvfrom函数调用错误,错误号: %d\n", WSAGetLastError());
		return -1;
	}
	return 0;
}


5.以下开始是主函数



<pre name="code" class="cpp">
int main() 

 { 

int iResult; 

SOCKET sockRaw; 

sockaddr_in addrDest, addrSrc; 

DECODE_RESULT stDecodeResult; 

USHORT usSeqNo = 0; 



WSADATA wsaData; 

iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 

if (iResult != 0)  

{ 

//告知用户无法找到合适可用的Winsock DLL 

printf("WSAStartup 函数调用错误,错误号: %d\n", WSAGetLastError()); 

return -1; 

} 

//创建原始套接字 

sockRaw= socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); 


//设置接收超时选项 

int iTimeout = 1000; 

iResult = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeout, sizeof(iTimeout)); 



memset(&stDecodeResult, 0, sizeof(DECODE_RESULT)); 


//需要扫描的ip,根据自己的需要来赋值吧 

addrDest.sin_addr.S_un.S_addr = inet_addr("192.168.218.18"); 

addrDest.sin_family = AF_INET; 



// 发送ICMP Echo请求 

iResult = SendEchoRequest(sockRaw, &addrDest); 

if (iResult == SOCKET_ERROR) 

{ 

if (WSAGetLastError() == WSAEHOSTUNREACH){ 

printf("目的主机不可达,traceroute探测结束!"); 

return -1; 

} 

} 

//接收ICMP的EchoReply数据报 

iResult = RecvEchoReply(sockRaw, &addrSrc, &addrDest, &stDecodeResult); 

return 0; 

 }




好了,这样就ok了