WIN32汇编语言教程:第16章 TCP/IP和网络通信 · 16.5 ICMP协议编程(7)

3. 用原始套接字收发ICMP报文

例子程序中创建了一个原始套接字用于收发ICMP报文:

   invoke socket,AF_INET,SOCK_RAW,IPPROTO_ICMP

在16.2.2小节中已经讲到,系统会为使用IPPROTO_ICMP 协议的SOCK_RAW类型的原始套接字添加IP首部,但是接收的时候会返回包括IP首部的数据包,所以例子程序在构造要发送的数据时只需要填写ICMP报文,而不必去填写IP首部。

例子程序使用下面的代码填写ICMP报文:

   mov    ax,@dwID

   mov    [esi].icmp_id,ax

   mov    ax,@dwSeq

   mov    [esi].icmp_seq,ax

   mov    [esi].icmp_type,ICMP_ECHOREQ   ;构造 ICMP_ECHO_REQ 数据包

   invoke GetTickCount

   mov    dword ptr [esi].icmp_data,eax  ;将当前时间当做数据

   mov    ecx,PACKET_SIZE

   add    ecx,sizeof icmp_hdr-1

   invoke _CalcCheckSum,addr szBigBuffer,ecx

   mov    [esi].icmp_cksum,ax

@dwID和@dwSeq是当做序号使用的,每次发送一个数据包以后,这两个数值会加1,ICMP首部的类型字段被填入“请求回显”,在Windows.inc文件中已经将这个类型预定义为ICMP_ECHOREQ,icmp_code字段保持为0,在程序中就不必显式地去设置了。

为了能够计算出一个Ping动作所花的时间,程序使用GetTickCount函数来获取当前系统的时间值并将它当做报文内容放入icmp_data位置,由于对方主机在处理“请求回显”查询时会将报文内容原封不动地封装在“回显应答”报文中返回,所以到接收到报文的时候再次调用GetTickCount函数并将时间相减就可以得出报文来回在路上所花的时间了。

填写好报文数据后,程序调用_CalcCheckSum子程序计算整个报文的校验和并将结果填入ICMP首部的icmp_cksum字段中,最后通过sendto函数发往目标主机。如果sendto函数返回SOCKET_ERROR,表示ICMP报文无法发送到目标主机,这时程序将显示“Destination host unreachable.”错误。

如果ICMP报文被成功发送,那么程序使用select函数等待一秒,如果在一秒内没有数据可供接收则视为目标主机没有应答,这时将在控制台窗口中显示“Request timed out.”错误。

最理想的结果就是select告诉程序有数据可供接收,这说明可能是目标主机有应答了,这时程序使用recvfrom函数来接收数据,由于ICMP协议并不提供端口复用,所以接收的数据有时并不是接收方所要的,比如有一个Ping程序在探测主机A,而我们又开了另一个命令行窗口并执行Ping.exe去探测主机B,那么主机B返回的“回显应答”报文也会被第一个Ping程序收到,而这显然不是程序希望得到的数据包,为了防止这种情况的发生,程序在收到数据以后首先对IP地址进行验证,只有当数据包是目标主机发回来时才进行下一步动作,否则回到select函数的地方继续等待。

当验证了ICMP报文是来自目标主机以后,就可以对它进行解读了:

   ;带有IP首部的ICMP报文被读到szBigBuffer缓冲区中

   invoke GetTickCount

   sub    eax,dword ptr szBigBuffer+sizeof ip_hdr+icmp_hdr.icmp_data

   movzx  ecx,szBigBuffer + ip_hdr.ip_ttl

   invoke wsprintf,addr szBuffer,addr szReply,\

         addr @szBuffer,PACKET_SIZE,eax,ecx

例子程序取出报文数据中保存的发送时间并和当前时间相减,由此得到报文来回所花的时间,并且取出IP首部TTL字段的数值,将这两个数值连同IP地址显示在屏幕上,这样就得到了我们需要的结果:

Reply from 202.106.185.203: bytes=32 time=30ms TTL=244

代码中的ip_hdr是IP首部的结构定义:

ip_hdr STRUCT

 ip_hlv        BYTE  ?

 ip_tos        BYTE  ?

 ip_len       WORD      ?

 ip_id         WORD  ?

 ip_off        WORD  ?

 ip_ttl        BYTE  ?      ;TTL数值

 ip_p          BYTE  ?

 ip_cksum      WORD  ?      ;校验和

 ip_src        DWORD ?      ;源地址

 ip_dest       DWORD ?      ;目标地址

ip_hdr ENDS

从TTL字段可以看出从本地主机到目标主机经过了多少个路由器。当一个IP数据包被发送时,IP首部的TTL字段往往被设置为2的n次方,如256、128或64等,每当数据包经过一个路由器后,TTL字段被减1,定义TTL字段的初衷是防止数据包在设置错误的几个路由器之间循环打转,如果出现这种情况,在一段时间以后TTL字段终究会减少到0,这时数据包就被丢弃。

当从接收到的IP首部取出TTL字段以后,用大于它的最接近的2的n次方去减它就可以得到经过的路由器数量,如上例中的TTL为244,表示本地主机到目标主机经过了12个路由器(256–244=12),如果TTL为120的话,那么经过的路由器数量不是256–120=136而是8个(128–120=8)。

上页:第16章 TCP/IP和网络通信 · 16.5 ICMP协议编程(6) 下页:第17章 PE文件 · 17.1 PE文件的结构(1)

第16章 TCP/IP和网络通信

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号