ICMP 协议及 Ping程序的实现

来源:互联网 发布:管家婆软件是作用 编辑:程序博客网 时间:2024/04/28 11:54

ICMP协议:
 ICMP(Internet Control Message Protocl,网际控制报文协议)是和IP协议同一层次的协议,对Internet以及IP网络的正常运转起着至关重要的作用.ICMP是IP层的一个IP的一个组成部分,它在IP系统间传递差错和其他需要注意的信息.ICMP报文通常被IP层或更高层协议(TCP/UDP)使用.一些ICMP报文把差错报文返回给用户进程
ICMP报文格式
  ICMP报文是在IP数据报内部传输的
ICMP报文格式:

------------------------------------------ 
 |8位ICMP类型|8位ICMP代码|16位ICMP检验和|
 |  ICMP具体内容(取决于类型和代码)      |
------------------------------------------

所有报文的前4个字节都是一样的 
第一个字段指定的是ICMP报文类型
"代码"字段进一步定义了查询或消息的类型
"效验和"字段的长度为16位是对ICMP头内容的一个补余求和
最后ICMP的实际内容要依赖于前面设定的ICMP类型及代码

几种重要的报文类型:
ICMP时间戳请求与应答
   ICMP时间戳请求允许系统向另一个系统查询当前的时间。返回的建议值是自午夜开始计算的毫秒,协调的同一时间(UTC).这种ICMP报文的好处是它提供了毫秒级的分辨率,而利用其他方法从别的主机获取的时间只能提供秒级的分辨率

  ICMP时间戳请求和应答报文格式
  ----------------------------
  |类型(13或14)|代码(0)|检验和
  ----------------------------
  |  标识符    |  序列号     |
  |   发起时间戳             |
  |   接受时间戳             |
  |   传送时间戳             |
  
   请求端填写发起时间戳,然后发送报文。应答系统收到请求报文时填写接收时间戳,在发送应答时填写发送时间戳.但是,实际上,大多数的实现把后面两个字段都设成相同的值(提供3个字段的原因是可以让发送方分别分别计算发送请求的时间和发送应答的时间)
  1回显请求和回显应答报文
   回显请求和回显应答报文类型是一个常用的ICMP报文类型,它经常用来判断一个特定的主机是否处于   活动状态,并且是否可以通过网络访问到。

回显请求和回显应答报文的格式如图:
   ---------------------------
   |类型(0或8)|代码(0)|检验和|
   ---------------------------
   |   序号   |    标识符    |
   ---------------------------
   |         选项值          |
   ---------------------------

2 ICMP地址掩码请求与应答
  ICMP地址掩码请求用于无盘系统在引导过程中获取自己的子网掩码。系统广播它的ICMP请求报文
 
  ICMP地址掩码请求和应答报文的格式
  -----------------------------
  |类型(17或18)|代码(0)|检验和|
  -----------------------------
  |   标识符   |     序列号   |
  -----------------------------
  |       32子网掩码          |
  -----------------------------
  ICMP 报文中的标识符和序列号字段有发送端任意选择设定,这些值在应答中将被返回。这样,发送端  就可以把应答与请求进行匹配
3 ICMP端口不可达差错
  ICMP不可达报文的一般格式
   -----------------------------------------------
   |         类型(3)|代码(0-15)|效验和           |
   ----------------------------------------------
   |           暂未使用(必须添加0)               |
   ----------------------------------------------
   |IP首部(包过选项)+原始IP数据报中数据的前8字节 |
   -----------------------------------------------
有16中不同类型的ICMP不可达报文,代码分别从0-15.ICMP端口不可达差错代码是3.
尽管ICMP报文中的第二个32bit必须为0,但是当代码为4时(需要分片但设置了不分片比特),路径MTU发现机制却允许路由器把外出接口的MTU填在这32bit的低16bit中。
 ICMP协议在网络中有两个基本的应用:ping和Trace Route
使用Ping程序可以检测到另一台主机是否可达.该程序发送一份 ICMP回显请求报文给主机,并等待返回ICMP回显应答.一般来说,如果不能Ping到幕台主机,那么就不能Telnet或者FTP到那台主机。反过来,如果不能Telnet到幕台主机,那么通常可以用Ping程序来确定问题出在哪里。Ping程序还能测出到这台主机的往返时间,以表明该主机离我们有"多远"
 不过随着Internet的发展,出现了限制访问的路由器和防火墙。防火墙会截断ICMP数据包,使Ping程序 无效
 另外一个非常有用的工具是TraceRoute,即路由追踪器。在Windows操作系统中,一般可以直接在控制台下运行TraceRoute.exe调用这个工具.TraceRoute,负责追踪到幕个IP节点所需要经过的路由
 
Ping程序的实现
   1 实现方法是主机向远程计算机发出ICMP回显请求以后,远程计算机会拦截这个请求,然后生成一条一条回显应答信息,再通过网络传回给主机。
   2 假如某些原因,不能抵达目标主机,就会生成对应的ICMP错误消息("比如 目标主机访问不可达"),由原先打算建立通信的那个路径上某处的一个路由器返回。
   3 假定与主机的物理性连接并不存在问题,但远程主机已经关机或没有设置对网路事件作出相应,便需由自己的程序来执行超时检测,侦测出这样的情况。

步骤:
  1 将Ping服务封装成一个类CPing
  

#include <winsock2.h>#include <ws2tcpip.h>#define IP_RECORD_ROUTE 0x7#define DEF_PACKET_SIZE 32  //缺省包长度#define MAX_PACKET      1024//最大ICMP包长度#define MAX_IP_HDR_SIZE 60  //最大IP头长度#define ICMP_ECHO       8   //ICMP回向次数 #define ICMP_ECHOREPLY  0   #define ICMP_MIN        8   //最小8字节的包头//IP头结构typedef struct _iphdr{ unsigned int   h_len:4;  //头长度 unsigned int   version:4;//IP版本 unsigned char  tos;      //服务类型 unsigned short total_len;//包的总长度 unsigned short ident;    //包标识身份 unsigned short frag_and_flags;//标志 unsigned char  ttl;       //包生命周期 unsigned char  proto;     //协议类型 unsigned short checksum;  //IP校验 unsigned int   sourceIP;  //源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;//IP头选项结构,当socket可选项设置成IP_OPTIONS时使用该结构typedef struct _ipoptionhdr{ unsigned char code;  //可选类型 unsigned char len; unsigned char ptr; unsigned long addr[9];//IP地址}IpOptionHeader;class CPingDlg;//ICMP服务器封装成一个类CPingclass CPing  {public:     // 设置Ping参数     void SetConfigure(char *host,BOOL recordrout=FALSE,int size=DEF_PACKET_SIZE);     //解析IP可选参数     void DecodeIPOptions(char* buf,int bytes);     //清除ICMP包     void Cleanup();     //Ping函数     void Ping(int timeout=1000);     //发送ICMP包的Socket    SOCKET         m_hSocket;    IpOptionHeader m_ipopt;//    SOCKADDR_IN    m_addrDest;//目的地址    SOCKADDR_IN    m_addrFrom;//源地址    char          *icmp_data;    char          *recvbuf;    USHORT         seq_no;    char          *Ipdest;    int            datasize;    BOOL           m_bRecordRout;    CPingDlg      *m_dlg;     CPing(CPingDlg *dlg);    virtual ~CPing();private:    //解析ICMP头结构    void DecodeICMPHeader(char*buf,int bytes,SOCKADDR_IN* from);    //校验数据    USHORT checksum(USHORT *buffer,int size);     //填充ICMP数据    void FillICMPData(char*icmp_data,int datasize);};


  
2 初始化:
  

CPing::CPing(CPingDlg *dlg){  m_dlg=dlg; //初始化ICMP包 icmp_data=NULL; seq_no=0; recvbuf=NULL; m_bRecordRout=FALSE; Ipdest=NULL; datasize=DEF_PACKET_SIZE;  //初始化socket WSADATA wsaData; if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0) {  AfxMessageBox("Sorry,you cannot load socket dll!");  return ; } m_hSocket=INVALID_SOCKET;}


3 Ping:
//Ping函数首先创建一个Socket,必须注意的是这个Socket的类型是SOCK_RAW并采用的是IPPROTO_ICMP协议
//把它设置Socket的ICMP包头结构选项,接着设置Socket的接受和发送超时选项,
//如果用户输入 Ping的字符串是一个主机名,还需要将这个主机名解析成IP地址,
//接着,创建一个ICMP包,然后在一个循环中不断的发送和接受ICMP包
//当发送完4个ICMP包的时候主动退出循环,
//最后解析返回的ICMP包头信息并显示Ping的最终结果

void CPing::Ping(int timeout){ //-------------------------------------------------------------------------------------- //创建一个Socket,类型是SOCK_RAW,IPPROTO_ICMP协议 m_hSocket=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED); if(m_hSocket==INVALID_SOCKET) {  AfxMessageBox("socket 创建失败");  return ; }    //--------------------------------------------------------------------------------------- if(m_bRecordRout)//如果记录路由 {  //为每一个ICMP包设置IP头选项  ZeroMemory(&m_ipopt,sizeof(m_ipopt));  m_ipopt.code=IP_RECORD_ROUTE;//记录路由选项  m_ipopt.ptr=4;//指向第一个地址的偏移  m_ipopt.len=39;//可选头的长度  int ret=setsockopt(m_hSocket,IPPROTO_IP,IP_OPTIONS,(char*)&m_ipopt,sizeof(m_ipopt));  if(ret==SOCKET_ERROR)  {   AfxMessageBox("设置Socket协议选项错误");  } } //----------------------------------------------------------------------------------- //设置发送/接受超时选项 int bread=setsockopt(m_hSocket,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)); if(bread==SOCKET_ERROR) {  AfxMessageBox("设置Socket接受超时选项失败");  return ; }  timeout=1000; bread=setsockopt(m_hSocket,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout));  if(bread==SOCKET_ERROR) {  AfxMessageBox("设置Socket发送超时选项失败");  return ; } //----------------------------------------------------------------------------------------    //主机名解析成IP地址 memset(&m_addrDest,0,sizeof(m_addrDest)); //如果可能,需要解析输入的主机名 m_addrDest.sin_family=AF_INET; if((m_addrDest.sin_addr.S_un.S_addr=inet_addr(Ipdest))==INADDR_NONE) {  struct hostent* hp=NULL;  if((hp=gethostbyname(Ipdest))!=NULL)  {   memcpy(&(m_addrDest.sin_addr ),hp->h_addr_list[0],hp->h_length );   m_addrDest.sin_family=hp->h_addrtype;  }  else  {   AfxMessageBox("输入的主机不存在");   return ;  } } //---------------------------------------------------------------------------------------------- //创建ICMP包 datasize+=sizeof(IcmpHeader);  icmp_data=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);//MAX_PACKET 1024 recvbuf=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);//  if(!icmp_data) {  AfxMessageBox("对分配错误");  return ; } memset(icmp_data,0,MAX_PACKET); //------------------------------------------------------------------------------------------------- //往ICMP包中填充数据 FillICMPData(icmp_data,datasize);//44 //-------------------------------------------------------------------------------------------------  //开始发送和接受ICMP数据包  4个ICMP包 int nCount=0; while(1) {  int bwrote;  if(nCount++ ==4)   break;  ((IcmpHeader*)icmp_data)->i_cksum=0;  ((IcmpHeader*)icmp_data)->timestamp=GetTickCount();//时间戳  ((IcmpHeader*)icmp_data)->i_seq=seq_no++;//0 1 2 3  ((IcmpHeader*)icmp_data)->i_cksum=   checksum((USHORT*)icmp_data,datasize);//校验和  //-----------------------------------------------------sendto  bwrote=sendto(m_hSocket,icmp_data,datasize,0,   (struct sockaddr*)&m_addrDest,sizeof(m_addrDest));  if(bwrote==SOCKET_ERROR)  {   if(WSAGetLastError()==WSAETIMEDOUT)   {    m_dlg->m_result+="Timed out ! /r/n";    m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result);    continue;   }   AfxMessageBox("发送数据调用函数错误");   return ;  }    if(bwrote<datasize)  {   CString temp;   temp.Format("Wrote %d bytes /r/n",bwrote);   m_dlg->m_result+=temp;   m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result);     }  //---------------------------------------------------recvfrom  int fromlen=sizeof(m_addrFrom);  bread=recvfrom(m_hSocket,recvbuf,MAX_PACKET,0,(struct sockaddr*)&m_addrFrom,&fromlen);//MAX_PACKET 1024  if(bread==SOCKET_ERROR)  {   if(WSAGetLastError()==WSAETIMEDOUT)   {    m_dlg->m_result+="Timed out ! /r/n";    m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result);    continue;   }   AfxMessageBox("接受数据调用函数错误");   return ;  }  //--------------------------------------------------------------------------  //解析ICMP头,显示Ping结果  DecodeICMPHeader(recvbuf,bread,&m_addrFrom);  //-------------------------------------------------------------------------- } //----------------------------------------------------------------------------------------------------------------}4: void CPingDlg::OnPing() { CButton* checkbox=(CButton*)GetDlgItem(IDC_CHECK_ROUTE); int state=checkbox->GetCheck();//判断是否设置了路由选项  char host[100]; GetDlgItemText(IDC_HOST,host,100);//得到主机名或者IP地址     //设置ping参数  m_pinger.SetConfigure(host,state); //开始ping m_pinger.Ping(); m_result+="ping 完成!/r/n----------/r/n"; //显示ping 结果 SetDlgItemText(IDC_EDIT2,m_result); //清除ping数据 m_pinger.Cleanup();} //其余参考代码CPing::~CPing(){ }//清除Socket,ICMP包头数据以及接受数据缓冲区void CPing::Cleanup(){ if(m_hSocket!=INVALID_SOCKET)  closesocket(m_hSocket); HeapFree(GetProcessHeap(),0,recvbuf); HeapFree(GetProcessHeap(),0,icmp_data); return ;}void CPing::FillICMPData(char *icmp_data,int datasize){ IcmpHeader* icmp_hdr=NULL;  //设置ICMP头结构 icmp_hdr=(IcmpHeader*)icmp_data; icmp_hdr->i_type=ICMP_ECHO;//要求有ICMP回应 icmp_hdr->i_code=0; icmp_hdr->i_id=(USHORT)GetCurrentProcessId();//进程的当前ID icmp_hdr->i_cksum=0;//校验和 icmp_hdr->i_seq=0;//序列号}//负责解析ICMP包头结构信息void CPing::DecodeICMPHeader(char *buf,int bytes,SOCKADDR_IN *from){ IpHeader   *iphdr=NULL; IcmpHeader *icmphdr=NULL; unsigned short  iphdrlen; DWORD           tick; static   int    icmpcount=0; //----------------------------------- iphdr=(IpHeader*)buf; iphdrlen=iphdr->h_len*4; //得到以毫秒为单位的当前时间 tick=GetTickCount(); //---------------------------------------  //第一次解析ICMP头信息时需要解析IP头选项 if((iphdrlen==MAX_IP_HDR_SIZE) && (!icmpcount))//MAX_IP_HDR_SIZE 60  DecodeIPOptions(buf,bytes);  CString temp; if(bytes < iphdrlen+ICMP_MIN)//ICMP_MIN 8 {  temp.Format("Too few bytes from %s /r/n",inet_ntoa(from->sin_addr ));  m_dlg->m_result+=temp;  m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result ); } icmphdr=(IcmpHeader*)(buf+iphdrlen); //-------------------------------------------以上得到ICMP结构 if(icmphdr->i_type!=ICMP_ECHOREPLY) {  temp.Format("nonecho type %d recvd /r/n",icmphdr->i_type );  m_dlg->m_result+=temp;  m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result );  return ; } //---------------------------------------------------------------------- //确保当前的ICMP回应是所发送的ICMP包的回应,有可能其他程序也发送了ICMP包 if(icmphdr->i_id!=(USHORT)GetCurrentProcessId()) {  temp.Format("someone else's packet!/r/n");  m_dlg->m_result+=temp;  m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result );  return ; } //---------------------------------------------------------- //设置Ping结果信息 temp.Format("%d bytes from %s: /r/n", bytes, inet_ntoa(from->sin_addr)); m_dlg->m_result+=temp; temp.Format(" icmp_seq = %d. /r/n", icmphdr->i_seq); m_dlg->m_result+=temp; temp.Format(" time: %d ms /r/n", tick - icmphdr->timestamp); m_dlg->m_result+=temp; m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result); icmpcount++; //-------------------------------------------------- return ;}//用于解析IP头部的可选参数void CPing::DecodeIPOptions(char*buf,int bytes){ IpOptionHeader* ipopt=NULL; IN_ADDR         inaddr; int             i; HOSTENT         *host=NULL; ipopt=(IpOptionHeader*)(buf+19);  m_dlg->m_result+="Ping 结果: /r/n"; m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result); for(i=0;i<(ipopt->ptr/4)-1;i++) {  inaddr.S_un.S_addr=ipopt->addr[i];  if(i!=0)  {   m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result );  }  host=gethostbyaddr((char*)&inaddr.S_un.S_addr,sizeof(inaddr.S_un.S_addr ),AF_INET);  CString temp;  if(host)  {   temp.Format("( %s )/r/n",inet_ntoa(inaddr),host->h_name );   m_dlg->m_result+=temp;   m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result);  }  else  {   temp.Format("( %s )/r/n",inet_ntoa(inaddr));   m_dlg->m_result+=temp;   m_dlg->SetDlgItemText(IDC_EDIT2,m_dlg->m_result );  } }  return ;}//负责对ICMP数据包进行校验USHORT CPing::checksum(USHORT *buffer,int size){ unsigned long cksum=0; while(size > 1) {  cksum+=*buffer++;  size-=sizeof(USHORT); }  if(size) {  cksum+=*(UCHAR*)buffer; } cksum=(cksum>>16)+(cksum & 0xffff); cksum+=(cksum>>16); return (USHORT)(~cksum); }void CPing::SetConfigure(char* host,BOOL recodrout,int size){ if(Ipdest) {  delete []Ipdest;  Ipdest=NULL; }  //设置路由可选项 m_bRecordRout=recodrout; datasize=size; //设置目的地址 Ipdest=new char[strlen(host)+1]; strcpy(Ipdest,host);} 


原创粉丝点击