涉及内容:
原始套接字,套接字选项中的超时设置,无连接通信,IP报文头格式,ICMP报文头格式,ICMP报文中的“回显请求”和“回显应答”。
1、创建基于ICMP协议的原始套接字。
2、封装一个ICMP头的数据包,指定其为回响请求(type=8,code=0),并发送。
3、接收目标主机的回响应答,对收到回响应答包(type=0,code=0)进行解析,输出结果。
4、重复步骤2-3,统计最后的结果并输出。
#include <winsock2.h>
#include <Ws2tcpip.h>
#include<process.h>
#pragma comment (lib, "Ws2_32.lib")
const char* DEST_ADDR = "localhost"; //目标主机名或IP地址字符串
const unsigned int SEND_PACK_SIZE = 12; //发送的ICMP报文大小
const unsigned int RECV_BUF_SIZE = 40; //收目标host返回的报文的buffer大小
struct icmp_head //自定义ICMP报文头,12字节
{
unsigned char icmp_type; //8位类型(回显请求时=8,回显应答时=0)
unsigned char icmp_code; //8位代码(回显请求时=0,回显应答时=0)
unsigned short icmp_cksum; //16位校验和
unsigned short icmp_id; //标识符(一般用进程ID)
unsigned short icmp_seq; //报文序列号(标识不同的packet)
unsigned long icmp_time; //时间戳(发包时记录一个时间,收包时就知道收发的时间差)
};
struct ip_head //IP报文头,20字节
{
unsigned char ip_headlenth:4; //head长度,单位是DWORD(右四位,little endian)
unsigned char ip_version:4; //版本(左四位)
unsigned char ip_tos; //8位服务类型
unsigned short ip_len; //IP数据报总长度,单位是byte,网络字节顺序
unsigned short ip_sequence; //标识字段
unsigned short ip_offset; //标识偏移
unsigned char ip_ttl; //8位生存字段,可以经过的路由数
unsigned char ip_protocol; //8位协议字段
unsigned short ip_cksum; //16位校验和
unsigned long ip_src; //源IP
unsigned long ip_dest; //目的IP
};
class MyICMP
{
public:
MyICMP();
~MyICMP();
BOOL Initialize(); //初始化SOCKET,分配堆内存
void Start(const int times); //开始,发送指定数目的包
private:
int SendPack(); //发送ICMP报文包
int RecvPack(); //收目的主机反馈的包(IP_HEAD + ICMP_HEAD)
void Pack(); //发送的ICMP报文头打包
void Unpack(const int iRecvByte); //对接收的报文进行解包
unsigned short GetCheckSum(unsigned short *addr,int len); //校验和计算
SOCKET m_Sock; //可以访问ICMP,IP协议包的原始套接字
sockaddr_in m_Addr; //目标主机地址
char *m_HostName; //目标主机名
char *m_SendPack; //发送包的buffer指针
char *m_RecvBuffer; //接受包的buffer指针
unsigned int m_Sequence; //记录packet的序号
};
MyICMP::MyICMP()
:m_Sock(INVALID_SOCKET),
m_HostName(NULL),
m_SendPack(NULL),
m_RecvBuffer(NULL),
m_Sequence(0)
{
memset(&m_Addr, 0x00, sizeof(sockaddr_in));
WSADATA wd;
::WSAStartup(MAKEWORD(2,2), &wd);
}
MyICMP::~MyICMP()
{
if(INVALID_SOCKET != m_Sock)
::closesocket(m_Sock);
if(NULL != m_SendPack)
{
delete[] m_SendPack;
m_SendPack = NULL;
}
if(NULL != m_RecvBuffer)
{
delete[] m_RecvBuffer;
m_RecvBuffer = NULL;
}
if(NULL != m_HostName)
{
delete[] m_HostName;
m_HostName = NULL;
}
::WSACleanup();
}
BOOL
MyICMP::Initialize()
{
if((INVALID_SOCKET != m_Sock) || (NULL != m_SendPack) || (NULL != m_RecvBuffer))
{
//如果已经初始化,返回
return FALSE;
}
//创建原始套接字
m_Sock = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if(INVALID_SOCKET == m_Sock)
return FALSE;
//设置发送和接受数据超时为2秒
int time_out = 2000;
if(SOCKET_ERROR == ::setsockopt(m_Sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&time_out, sizeof(time_out)))
return FALSE;
if(SOCKET_ERROR == ::setsockopt(m_Sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&time_out, sizeof(time_out)))
return FALSE;
//根据目标地址名称获得地址信息(IP,主机名)
hostent* host_info = ::gethostbyname(DEST_ADDR);
if(NULL == host_info)
{
printf("Get host infomation failed!\n");
return FALSE;
}
m_Addr.sin_family = AF_INET;
m_Addr.sin_port = INADDR_ANY;
m_Addr.sin_addr.s_addr = *((unsigned long*)host_info->h_addr);
//如果主机名还是IP的话,继续获取主机名
if((host_info->h_name[0]>='0') && (host_info->h_name[0]<='9'))
{
host_info = ::gethostbyaddr(host_info->h_addr, 4, PF_INET);
}
if((NULL != host_info)&&(NULL != host_info->h_name))
{
//获得主机名
m_HostName = new char[32];
strcpy_s(m_HostName, 32, host_info->h_name);
printf("Ping %s [%s] with %d bytes of data\n\n", m_HostName, inet_ntoa(m_Addr.sin_addr), SEND_PACK_SIZE);
}
else
{
//获取不到主机名
printf("Ping unknown-name host [%s] with %d bytes of data\n\n", inet_ntoa(m_Addr.sin_addr), SEND_PACK_SIZE);
}
m_SendPack = new char[SEND_PACK_SIZE];
if(NULL == m_SendPack)
return FALSE;
m_RecvBuffer = new char[RECV_BUF_SIZE];
if(NULL == m_RecvBuffer)
return FALSE;
return TRUE;
}
void
MyICMP::Pack()
{
icmp_head* icmp = (icmp_head*)m_SendPack;
icmp->icmp_type = 8; //8表示回响请求
icmp->icmp_code = 0; //代码位设为0
icmp->icmp_cksum = 0; //校验和字段必须设为0!
icmp->icmp_seq = ++m_Sequence; //packet序列号
icmp->icmp_id = _getpid(); //进程ID
icmp->icmp_time = GetTickCount(); //记录发包时间
icmp->icmp_cksum = GetCheckSum( (unsigned short *)icmp,SEND_PACK_SIZE); //计算校验和
}
void
MyICMP::Unpack(const int iRecvByte)
{
//从收到的packet中先取前面IP_HEADER中的回响应答主机IP
ip_head* ip = (ip_head*)m_RecvBuffer;
in_addr src;
src.s_addr = ip->ip_src;
//IP_HEADER后面是ICMP_HEADER
char* p = m_RecvBuffer + ((ip->ip_headlenth)*4);
icmp_head* icmp = (icmp_head*)p;
//如果是回响应答的话,输出信息
if((0 == icmp->icmp_type) && (0 == icmp->icmp_code))
{
printf("Reply from %s: byte=%d time=%dms TTL=%u packet_id=%u\n", inet_ntoa(src), iRecvByte, ::GetTickCount()-icmp->icmp_time, ip->ip_ttl, icmp->icmp_seq);
}
}
//此算法不关注,转载
unsigned short
MyICMP::GetCheckSum(unsigned short *addr,int len)
{
int nleft=len;
int sum=0;
unsigned short *w=addr;
unsigned short answer=0;
while(nleft>1)
{ sum+=*w++;
nleft-=2;
}
if( nleft==1)
{ *(unsigned char *)(&answer)=*(unsigned char *)w;
sum+=answer;
}
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
answer=~sum;
return answer;
}
int
MyICMP::SendPack()
{
//打包,发送
this->Pack();
return ::sendto(m_Sock, m_SendPack, SEND_PACK_SIZE, 0, (sockaddr*)&m_Addr, sizeof(sockaddr_in));
}
int
MyICMP::RecvPack()
{
//收包,解包
int iRecv = ::recv(m_Sock, m_RecvBuffer, RECV_BUF_SIZE, 0);
if(iRecv > 0)
Unpack(iRecv);
else
printf("Request time out!\n"); //收包超时
return iRecv;
}
void
MyICMP::Start(const int times)
{
int send_times = 0;
int recv_times = 0;
for(int i=0; i<times; ++i)
{
::Sleep(1000);
if(this->SendPack() == SOCKET_ERROR)
{
//发包超时
continue;
}
else
{
++send_times;
}
if(this->RecvPack() > 0)
{
++recv_times;
}
}
printf("\nPackets: Send = %d, Received = %d, Lost = %d (%d%% loss)\n",
send_times, recv_times, send_times-recv_times, (send_times-recv_times)*100/send_times);
}
void main()
{
//创建对象,并初始化
MyICMP mi;
if(FALSE == mi.Initialize())
return;
//发送6个包,统计结果
mi.Start(6);
}