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字节的包头
-
- typedef struct _iphdr
- {
- unsigned int h_len:4;
- unsigned int version:4;
- 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;
-
- typedef struct _icmphdr
- {
- BYTE i_type;
- BYTE i_code;
- USHORT i_cksum;
- USHORT i_id;
- USHORT i_seq;
- ULONG timestamp;
- }IcmpHeader;
-
- typedef struct _ipoptionhdr
- {
- unsigned char code;
- unsigned char len;
- unsigned char ptr;
- unsigned long addr[9];
- }IpOptionHeader;
- class CPingDlg;
-
- class CPing
- {
- public:
-
- void SetConfigure(char *host,BOOL recordrout=FALSE,int size=DEF_PACKET_SIZE);
-
- void DecodeIPOptions(char* buf,int bytes);
-
- void Cleanup();
-
- void Ping(int timeout=1000);
-
-
- 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:
-
- void DecodeICMPHeader(char*buf,int bytes,SOCKADDR_IN* from);
-
- USHORT checksum(USHORT *buffer,int size);
-
- void FillICMPData(char*icmp_data,int datasize);
- };
2 初始化:
- CPing::CPing(CPingDlg *dlg)
- {
-
- m_dlg=dlg;
-
- icmp_data=NULL;
- seq_no=0;
- recvbuf=NULL;
- m_bRecordRout=FALSE;
- Ipdest=NULL;
- datasize=DEF_PACKET_SIZE;
-
-
- 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)
- {
-
-
-
- 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)
- {
-
- 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 ;
- }
-
-
- 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 ;
- }
- }
-
-
- datasize+=sizeof(IcmpHeader);
-
- icmp_data=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);
- recvbuf=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);
-
- if(!icmp_data)
- {
- AfxMessageBox("对分配错误");
- return ;
- }
- memset(icmp_data,0,MAX_PACKET);
-
-
- FillICMPData(icmp_data,datasize);
-
-
- 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++;
- ((IcmpHeader*)icmp_data)->i_cksum=
- checksum((USHORT*)icmp_data,datasize);
-
- 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);
-
- }
-
- int fromlen=sizeof(m_addrFrom);
- bread=recvfrom(m_hSocket,recvbuf,MAX_PACKET,0,(struct sockaddr*)&m_addrFrom,&fromlen);
- 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 ;
- }
-
-
- 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);
-
-
- m_pinger.SetConfigure(host,state);
-
- m_pinger.Ping();
- m_result+="ping 完成!/r/n----------/r/n";
-
- SetDlgItemText(IDC_EDIT2,m_result);
-
- m_pinger.Cleanup();
- }
-
-
- CPing::~CPing()
- {
-
- }
-
-
- 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_hdr=(IcmpHeader*)icmp_data;
- icmp_hdr->i_type=ICMP_ECHO;
- icmp_hdr->i_code=0;
- icmp_hdr->i_id=(USHORT)GetCurrentProcessId();
- icmp_hdr->i_cksum=0;
- icmp_hdr->i_seq=0;
- }
-
-
- 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();
-
-
-
- if((iphdrlen==MAX_IP_HDR_SIZE) && (!icmpcount))
- DecodeIPOptions(buf,bytes);
-
- CString temp;
- if(bytes < iphdrlen+ICMP_MIN)
- {
- 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);
-
- 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 ;
- }
-
-
- 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 ;
- }
-
-
- 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 ;
- }
-
-
- 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 ;
- }
-
-
- 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);
- }
-