透析ICMP协议.docx
《透析ICMP协议.docx》由会员分享,可在线阅读,更多相关《透析ICMP协议.docx(31页珍藏版)》请在冰豆网上搜索。
透析ICMP协议
对于熟悉网络的人来说,ICMP是再熟悉不过了.它同IP协议一样工作在ISO模型的网络层,它的全称是:
InternetControlMessageProtocal.其在网络中的主要作用是:
-主机探测
-路由维护
-路由选择
-流量控制
对于主机探测来说有很多方法,主机某些服务的BANNER,一些使用的应用程序,或者使用工具来检测主机,如NMAP,在WEB上有来简单的估测主机。
?
..飧鯥CMP协议,
首先我来讲一下主机探测用到的ICMP报文我没有一一讲全部报文,详细请参见RFC792协议)
1.回送或回送响应
我们使用一个ICMPECHO数据包来探测主机地址是否存活(当然在主机没有被配置为过滤ICMP形式),通过简单的发送一个ICMPECHO(Type8)数据包到目标主机,如果ICMPECHOReply(ICMPtype0)数据包接受到,说明主机是存活状态。
如果没有就可以初步判断主机没有在线或者使用了某些过滤设备过滤了ICMP的REPLY。
这种机制就是我们通常所用的ping命令来检测目标主机是否可以ping到.
回送消息的源地址是回送响应消息的目的地址。
若要形成一个回送响应消息,应该将源和目的地址交换,将类型代码更改为0,重新计算机校验码。
下面是这个报文的格式:
0123
01234567890123456789012345678901
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Type|Code|Checksum|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Identifier|SequenceNumber|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Data...
+-+-+-+-+-
类型:
8代表回送消息;
0代表回送响应消息。
代码:
0
校验码:
16位数据(从ICMP类型开始)的反码和再取反而得。
为计算校验码,校验码域应该为零。
这些零在以后会被校验码取代。
标识符:
如果代码=0,帮助匹配回送和回送响应的代码可以为0。
序列码:
如果代码=0,帮助匹配回送和回送响应的序列码可以为0。
说明:
回送消息中接收到的消息应该在回送响应消息中返回。
标识符和序列码由回送发送者使用帮助匹配
回送请求的响应。
代码:
从主机或网关接收0
2.超时报文
0123
01234567890123456789012345678901
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Type|Code|Checksum|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|unused|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|InternetHeader+64bitsofOriginalDataDatagram|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
类型:
11
代码:
0=传送超时;
1=分段级装超时。
校验码:
16位数据(从ICMP类型开始)的反码和再取反而得。
为计算校验码,校验码域应该为零。
这些零在以后会被校验码取代。
Internet包头+64位源数据报数据:
Internet包头加上源数据的头64位而得。
此数据用于主机匹配信息到相应的进程。
如果高层协议使用端口号,应该假设其在源数据的头64个字节之中。
说明:
如果网关在处理数据报时发现生存周期域为零,此数据报必须抛弃。
网关同时必须通过超
时信息通知源主机。
如果主机在组装分段的数据报时因为丢失段未能在规定时间内组装数据,
此数据报必须抛弃。
网关发送超时信息。
如果段零不可用则不用发送超时信息。
代码0由网关发送,代码1由主机发送。
3.目标主机不可达报文
0123
01234567890123456789012345678901
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Type|Code|Checksum|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|unused|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|InternetHeader+64bitsofOriginalDataDatagram|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
类型:
3
代码:
0=网络不可达;
1=主机不可达;
2=协议不可用;
3=端口不可达;
4=需要段和DF设置;
5=源路由失败;
校验码:
16位数据(从ICMP类型开始)的反码和再取反而得。
为计算校验码,校验码域应该为零。
这些零在以后会被校验码取代。
Internet包头+源数据报:
Internet包头加上源数据的头64位而得。
此数据用于主机匹配信息到相应的进程。
如果高层协议使用端口号,应该假设其在源数据的头64个字节之中。
说明:
相应于网关的路由表,如果在目的域中指定的网络不可达,如网络距离为无限远,网关会向发送
源数据的主机发送目的不可达消息。
而且,在一些网络中,网关有能力决定目的主机是否可达。
如果目的地不可达,它将向发送源数据的主机发送不可达信息。
在目的主机,如果IP模块因为指定的协议模块和进程端口不可用而不能提交数据报,目的主机将
向发送源数据的主机发送不可达信息。
另外一种情况是当数据报必须被分段传送,而“不可分段”位打开,在这种情况下,网关必须抛弃
此数据报,并向向发送源数据的主机发送不可达信息。
代码0,1,4和5由网关发送,而代码2和3由主机发送。
Windows的Socket函数有许多,我没有做详细介绍,这里的函数都是简要说明其用途,详细用法请参考MSDN.
这里的主要目的是为了后面的三个应用服务.
函数说明:
---------
WSAStartup函数
初始化Winsock
[声明]
intWSAStarup(WORDwVersionRequested,LPWSADATAlpWSAData);
[参数]
wVersionRequested-要求使用Winsock的最低版本号
lpWSAData-Winsock的详细资料
[返回值]
当函数成功调用时返回0
失败时返回非0的值
---
socket函数
用于生成socket(soketDescriptor)
[声明]
SOCKETsocket(intaf,inttype,intprotocol);
[参数]
af-地址家族(通常使用:
AF_INET)
type-socket的种类
SOCK_STREAM:
用于TCP协议
SOCK_DGRAM:
用于UDP协议
protocol-所使用的协议
[返回值]
当函数成功调用时返回一个新的SOCKET(SocketDescriptor)
失败时返回INVALID_SOCKET.
---
inet_addr函数
地址转换,把"A.B.C.D"的IP地址转换为32位长整数
[声明]
unsignedlonginet_addr(constcharFAR*cp);
[参数]
cp-指向IP地址字符串的指针
[返回值]
当函数成功调用时返回用32位整数表示的IP地址
失败时返回INADDR_NONE.
---
gethostbyname函数
从主机名获取主机信息.
[声明]
structhostentFAR*gethostbyname(constcharFAR*name);
[参数]
name-指向主机名字符串的指针
[返回值]
当函数成功调用时返回主机信息
失败时返回NULL(空值)
recv函数
利用Socket进行接受数据.
[声明]
intrecv(SOCKETs,charFAR*buf,intlen,intflags);
[参数]
s-指向用Socket函数生成的SocketDescriptor
buf-接受数据的缓冲区(数组)的指针
len-缓冲区的大小
flag-调用方式(MSG_PEEK或MSG_OOB)
[返回值]
成功时返回收到的字节数.
如果连接被中断则返回0
失败时返回SOCKET_ERROR
---
sendto函数
发送数据.
[声明]
intsendto(SOCKETs,constcharFAR*buf,intlen,intflags,conststructsockaddrFAR*to,inttoken);
[参数]
s-指向用Socket函数生成的SocketDescriptor
buf-接受数据的缓冲区(数组)的指针
len-缓冲区的大小
flag-调用方式(MSG_DONTROUTE,MSG_OOB)
to-指向发送方SOCKET地址的指针
token-发送方SOCKET地址的大小
[返回值]
成功时返回已经发送的字节数.
失败时返回SOCKET_ERROR
原理简介:
--------
这个例子演示了应用微软的ICMP.DLL怎样"ping"另一台机器.这个DLL是没有文档话的发送ICMP回送包API接口,也称为"pings,"就像潜水员对声纳信号的术语一样.这段代码出自一个被一个名叫MarkG的家伙的GUI程序,他的网页已经消失了.
ICMP.DLLAPI现在在Windows平台上与微软的Winsocks工作的很好,但是微软说更好的产品一出来他们将替换它.微软说这个自从Windows95时代就在用,这些功能在在Windows2000上仍然存在.
FormoreinformationontheICMP.DLLAPI,checkout'sICMPAPIpage.
更详细的ICMP.DLLAPI的信息到的ICMPAPI网页获取.
具体实现:
--------
//BorlandC++5.0:
bcc32.cppping.cpp
//VisualC++5.0:
clping.cppwsock32.lib
//
//Thissampleprogramisherebyplacedinthepublicdomain.
#include
#include
#include
#include"icmpdefs.h"
==================ping的实现部分==================
intdoit(intargc,char*argv[])
{//[bugfree]建议将这个argc和argv的处理拿到main函数中
//检查命令行参数
if(argc<2){
cerr<<"usage:
ping"<return1;
}
//装载ICMP.DLL连接库
HINSTANCEhIcmp=LoadLibrary("ICMP.DLL");
if(hIcmp==0){
cerr<<"UnabletolocateICMP.DLL!
"<return2;
}
//查找给定机器的IP地址信息
structhostent*phe;
if((phe=gethostbyname(argv[1]))==0){
cerr<<"CouldnotfindIPaddressfor"<return3;
}
//定义函数三个指针类型
typedefHANDLE(WINAPI*pfnHV)(VOID);
typedefBOOL(WINAPI*pfnBH)(HANDLE);
typedefDWORD(WINAPI*pfnDHDPWPipPDD)(HANDLE,DWORD,LPVOID,WORD,
PIP_OPTION_INFORMATION,LPVOID,DWORD,DWORD);//evil,no?
//定义三个指针函数
pfnHVpIcmpCreateFile;
pfnBHpIcmpCloseHandle;
pfnDHDPWPipPDDpIcmpSendEcho;
//从ICMP.DLL中得到函数入口地址
pIcmpCreateFile=(pfnHV)GetProcAddress(hIcmp,"IcmpCreateFile");
pIcmpCloseHandle=(pfnBH)GetProcAddress(hIcmp,"IcmpCloseHandle");
pIcmpSendEcho=(pfnDHDPWPipPDD)GetProcAddress(hIcmp,"IcmpSendEcho");
if((pIcmpCreateFile==0)||(pIcmpCloseHandle==0)||
(pIcmpSendEcho==0)){
cerr<<"Failedtogetprocaddrforfunction."<return4;
}
//打开ping服务
HANDLEhIP=pIcmpCreateFile();
if(hIP==INVALID_HANDLE_VALUE){
cerr<<"Unabletoopenpingservice."<return5;
}
//构造ping数据包
characPingBuffer[64];
memset(acPingBuffer,'\xAA',sizeof(acPingBuffer));
PIP_ECHO_REPLYpIpe=(PIP_ECHO_REPLY)GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,
sizeof(IP_ECHO_REPLY)+sizeof(acPingBuffer));
if(pIpe==0){
cerr<<"Failedtoallocateglobalpingpacketbuffer."<return6;
}
pIpe->Data=acPingBuffer;
pIpe->DataSize=sizeof(acPingBuffer);
//发送ping数据包
DWORDdwStatus=pIcmpSendEcho(hIP,*((DWORD*)phe->h_addr_list[0]),
acPingBuffer,sizeof(acPingBuffer),NULL,pIpe,
sizeof(IP_ECHO_REPLY)+sizeof(acPingBuffer),5000);
if(dwStatus!
=0){
cout<<"Addr:
"<<
int(LOBYTE(LOWORD(pIpe->Address)))<<"."<<
int(HIBYTE(LOWORD(pIpe->Address)))<<"."<<
int(LOBYTE(HIWORD(pIpe->Address)))<<"."<<
int(HIBYTE(HIWORD(pIpe->Address)))<<","<<
"RTT:
"<RoundTripTime)<<"ms,"<<
"TTL:
"<Options.Ttl)<}
else{
cerr<<"Errorobtaininginfofrompingpacket."<}
//关闭,回收资源
GlobalFree(pIpe);
FreeLibrary(hIcmp);
return0;
}
==================主函数==================
intmain(intargc,char*argv[])
{
WSADatawsaData;
if(WSAStartup(MAKEWORD(1,1),&wsaData)!
=0){
return255;
}
intretval=doit(argc,argv);
WSACleanup();
returnretval;
}
==================头文件==================
icmpdefs.h
//ICMP.DLL函数中需要的结构
typedefstruct{
unsignedcharTtl;//TimeToLive
unsignedcharTos;//TypeOfService
unsignedcharFlags;//IPheaderflags
unsignedcharOptionsSize;//Sizeinbytesofoptionsdata
unsignedchar*OptionsData;//Pointertooptionsdata
}IP_OPTION_INFORMATION,*PIP_OPTION_INFORMATION;
typedefstruct{
DWORDAddress;//Replyingaddress
unsignedlongStatus;//Replystatus
unsignedlongRoundTripTime;//RTTinmilliseconds
unsignedshortDataSize;//Echodatasize
unsignedshortReserved;//Reservedforsystemuse
void*Data;//Pointertotheechodata
IP_OPTION_INFORMATIONOptions;//Replyoptions
}IP_ECHO_REPLY,*PIP_ECHO_REPLY;
原理简介:
--------
用RAWSocket实现的ping可能比上一节的应用ICMP.DLL的程序庞大些,但是这才是我们需要关注的东西,我的观点真正想做网络开发的程序员应该静下心来读读这篇文章,相信你会从中获益颇多.中间我也会讲解一些东西为后一章的路由追踪做一些铺垫.
另一个重要的要讲的东西,微软宣布随时不支持上节讲的ping用到的开发接口,但是本节的讲的是更一般的东西.所以它不会过时,甚至做很小的改动就可以移植到别的系统上去.系统移植不是我们的讲的重点.但是微软的长期支持足以引起我们充分的重视.
如何少作变动来使的这个程序实现追踪路由的功能,这里只是抛砖引玉.将ICMP包中IP包的包头该为特定的值就能得到那个路由器的IP(要求到达目的地的跳数大于你设的特定值).
这个程序需要windows2k/WindowsXP/WindowsNT平台和系统管理员的权限.
具体实现:
--------
这段源代码大部分来自:
[bugfree]只做了少量修改,给出了大量的注释,最后结合经验给出了自己的建议.
----------
/*
*程序名:
rawping_driver.cpp
*说明:
*驱动程序,也是主函数
*/
#include
#include
#include"rawping.h"
#defineDEFAULT_PACKET_SIZE32//默认ICMP包字节数
#defineDEFAULT_TTL30//默认TTL值
#defineMAX_PING_DATA_SIZE1024//最大数据块
#defineMAX_PING_PACKET_SIZE(MAX_PING_DATA_SIZE+sizeof(IPHeader))//最大ICMP包长度
/*为send_buf和recv_buf分配内存
*send_buf大小为packet_size
*recv_buf大小为MAX_PING_PACKET_SIZE,保证大于send_buf
*/
intallocate_buffers(ICMPHeader*&send_buf,IPHeader*&recv_buf,
intpacket_size);
///////////////////////////////////////////////////////////////////////
//Programentrypoint
intmain(intargc,char*argv[])
{
intseq_no=0;//用在发送和接受的ICMP包头中
ICMPHeader*send_buf=0;
IPHeader*recv_buf=0;
//判断命令行是否合法
if(argc<2){
cerr<<"usage:
"<[data_size][ttl]"<<
endl;
cerr<<"\tdata_sizecanbeupto"<"byt