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"
//ip头部,每个变量含义请参考ip首部格式,注意编译器对结构体优化的问题,详细情况,请看http://blog.csdn.net/u012198947/article/details/51078214里面的说明
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)
{
unsigned long cksum = 0;
while (size > 1)
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if (size)
{
cksum += *(UCHAR*)buffer;
}
while (cksum >> 16)
cksum = (cksum >> 16) + (cksum & 0xffff);
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.以下开始是主函数
int main()