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)