大胖的部落格

Just a note

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  112 随笔 :: 0 文章 :: 3 评论 :: 0 Trackbacks
涉及内容:
原始套接字,套接字选项中的超时设置,无连接通信,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, 0x00sizeof(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);
}
posted on 2009-07-08 15:19 大胖 阅读(377) 评论(0)  编辑 收藏 引用 所属分类: Win32

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理