您的位置:首页 > 理论基础 > 计算机网络

Roger的小程序系列(15)SuperPing 支持TCP、UDP和ICMP的ping

2008-08-09 10:51 603 查看
导读:
   传统的ping通过发送ICMP ECHO包来请求远程主机的回显,以此来观察网络的延迟、跳转数目、丢包情况等等。但是出于安全的考虑,现在很多防火墙都默认阻止传输ICMP数据报文。就如Roger的博客所在的主机,明明可以访问却ping不通,于是写了这个SuperPing,它除了实现跟系统的ping.exe类似的ICMP请求回显,还支持TCP和UDP的探测。下面是Roger在公司用SuperPing来ping rogerfd.cn的结果:
  Microsoft Windows XP [版本 5.1.2600]
  (C) 版权所有 1985-2001 Microsoft Corp.
  D:/Documents/Work/C/SuperPing/Release>SuperPing.exe -p TCP -a rogerfd.cn
  Roger’s Super Ping (ICMP,TCP,UDP Supported)
  Version: 0.1
  Mailto:? roger99707@163.com
  Blog:??? http://rogerfd.cn
  Local Host Name : roger
  Local IP Address: 192.168.1.25
  Local Position? : 局域网–对方和您在同一内部网
  Ping rogerfd.cn [222.73.44.108] (上海市 电信ADSL) using TCP …
  Reply from 222.73.44.108: time=641 ms TTL=106
  Reply from 222.73.44.108: time=1922 ms TTL=106
  Reply from 222.73.44.108: time=719 ms TTL=106
  Reply from 222.73.44.108: time=609 ms TTL=106
  Ping statistics for 222.73.44.108:
   Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
  Approximate round trip times in milli-seconds:
   Minimum = 609ms, Maximum = 1922ms, Average = 972ms
  D:/Documents/Work/C/SuperPing/Release>
   很明显看得出来网络连接的状态不太好,延迟挺大。下面Roger来讲讲这个SuperPing的原理和实现。传统的ping依赖于TCP/IP协议栈中ICMP ECHO服务。任何主机收到ICMP ECHO报文都应该做出回复。Ping报文是这样的一个结构(省去前面的IP头部):
   typedef struct icmp_ping_hdr
   {
   unsigned char? ih_type;
   unsigned char? ih_code;
   unsigned short ih_sum;
   unsigned short ih_id;
   unsigned short ih_seq;
   }ICMP_PING_HDR;
  对于Ping请求来说,type就是8,code是0,sum是校验和,id是一个和应用程序相关的数,seq是一个应用程序指定的序列号,用来标识不同的请求。而对Ping回复来说,type和code都是0,id和seq必须和ping请求的一致;如果ping请求中有额外的数据,也必须把这些数据原样返回。关于这个的代码在《Roger的小程序系列(6)带地理位置的trace route》中也有类似的介绍,就不详述了,下面主要是讲讲如何用TCP和UDP来ping.
  首先要明确我们需要从“ping”里边得到什么信息。一个是往返的延迟,包括超时;另一个是TTL,表示网络上的距离。至于ping还可以获取路由表,这个功能太鸡肋了,完全不如trace route,估计现在也没人用。那要获取TCP的一个响应,最简单的办法自然是发送一个TCP的连接请求,然后检测有没有回应。如果收到一个回应,不管是 SYN+ACK标志的表示可以建立连接,还是RST标志的端口不开放拒绝连接,我们都可以从这个回应的IP包中提取TTL以及计算延迟。
   为了更好的处理接收的报文,Roger决定采用Raw socket来接收。但是在Windows XP下面是禁止用Raw socket来发送TCP报文的,我们当然可以通过winpcap来发(具体见《Roger的小程序系列(13) 快速TCP端口扫描》一文),但是太复杂了;于是Roger想了个办法,用异步的普通的TCP socket去connect;然后用raw socket来接收,接收成功或者超时之后再把那个普通的TCP socket关闭。
   这个是发送TCP请求的函数
   int send_tcp_packet (unsigned long dstIP,
  unsigned short port,
  unsigned char sendTTL,
  SOCKET* pSocket
   )
   {
   SOCKET???????????? sendSocket;
   unsigned int?????? flag = 1;
   //int??????????????? ttl;
   struct sockaddr_in addr;
   int??????????????? ret, err;
   if ((sendSocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {
   return WSAGetLastError ();
   }
   // set non-block mode
   if (SOCKET_ERROR == ioctlsocket (sendSocket, FIONBIO, &flag))
   {
   closesocket (sendSocket);
   return WSAGetLastError ();
   }
   //but it does not work …TTL is always 128 on windows xp
   /*ttl = sendTTL;
   if (SOCKET_ERROR == setsockopt (sendSocket, IPPROTO_IP, IP_TTL, (char*)&ttl, sizeof(ttl)))
   {
   closesocket (sendSocket);
   return WSAGetLastError ();
   }*/
   addr.sin_family?????????? = AF_INET;
   addr.sin_port???????????? = htons(port);
   addr.sin_addr.S_un.S_addr = dstIP;
   ret = connect (sendSocket, (struct sockaddr*)&addr, sizeof (addr));
   err = WSAGetLastError ();
   if (ret == 0 || WSAEWOULDBLOCK == err){
   *pSocket = sendSocket;
   return 0;
   }
   return err;
   }
   结构有点不雅因为返回了一个socket。接下来是接收的代码:
   int send_recv_tcp_packet (unsigned long? localIP,
  unsigned long? dstIP,
  unsigned short port,
  unsigned long? maxwait,
  unsigned char sendTTL,
  unsigned char* pTTL
   )
   {
   SOCKET????????? sock;
   SOCKADDR_IN???? addr;
   IP_HEADER????? *ipHeader;?
   TCP_HEADER???? *tcpHeader;
   fd_set????????? fd;
   int???????????? ret;
   char??????????? recvBuffer[2048] = {0};
   unsigned int??? flag = 1;
   struct timeval? timeout = {0, 100000};
   unsigned long?? ulStartTime;
   SOCKET????????? sockSend;
   if ((sock = socket (AF_INET, SOCK_RAW, IPPROTO_IP)) == INVALID_SOCKET){
   return WSAGetLastError ();
   }
   addr.sin_family????? = AF_INET;
   addr.sin_port??????? = htons(0);
   addr.sin_addr.s_addr = localIP;
   if (SOCKET_ERROR == bind (sock, (PSOCKADDR)&addr, sizeof(addr))){
   return WSAGetLastError ();
   }
   ulStartTime = GetTickCount ();
   ret = send_tcp_packet (dstIP, port, sendTTL, &sockSend);
   if (ret){
   return ret;
   }
   if (1)
   {
   //很奇怪不设置混杂模式就收不到
   //百思不得其解
   DWORD dwBufferLen [10] ;
   DWORD dwBufferInLen = 1 ;
   DWORD dwBytesReturned = 0 ;
   WSAIoctl(sock,SIO_RCVALL,&dwBufferInLen,sizeof(dwBufferInLen),
   &dwBufferLen,sizeof(dwBufferLen),&dwBytesReturned,NULL,NULL);
   }
   while(1)
   {
   memset(recvBuffer , 0 , sizeof(recvBuffer));
   FD_ZERO(&fd);
   FD_SET(sock, &fd);
   ret = select (0, &fd, 0, 0, &timeout);
   if (ret == SOCKET_ERROR){
   return WSAGetLastError ();
   }
   if (ret == 0)
   {???
   if (GetTickCount() - ulStartTime destIP != localIP
   || ipHeader->sourceIP != dstIP
   || ipHeader->proto != IPPROTO_TCP){
   continue;
   }
   tcpHeader = (TCP_HEADER *) (recvBuffer + sizeof(IP_HEADER));
   if (tcpHeader->th_sport != htons (port)){
   continue;
   }
   *pTTL = ipHeader->ttl;
   closesocket (sock);
   closesocket (sockSend);
   return 0;
   }
   return 1; // never run to here
   }
   注意这里没有检查收到的TCP回应。尽管我们不需要区分是SYN还是RST,但是还是应该区别一下收到的回应是不是我们所发的请求。如果能检查一下回应的ACK所指的序号是不是我们发送的Seq就可以了,但是获取之前发的Seq有点麻烦,Roger就先偷懒啦。
  UDP的Ping原理又不一样。当发送一个UDP数据包到某一个主机的某个端口。而没有任何一个应用程序在这个端口上面接收数据,主机的TCP/IP协议栈就会默认回复一个ICMP报文指示:端口不可达。我们就专门接收这个回复。那万一这个端口有进程在监听呢?既然那么巧,咱也没办法,换一个端口咯。UDP的发送很简单,和TCP的差别就是socket创建的类型
   sendSocket = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
   以及发送的时候用sendto
   ret = sendto (sendSocket, (char*)&seq, sizeof seq, 0, (struct sockaddr*)&addr, sizeof (addr));
   Roger在这里发送了一个4字节的序列号作为数据,如果目标主机发送了端口不可达的ICMP报文,这个报文的数据段将包含我们发送的UDP报文,这样Roger就可以确认收到的ICMP报文确实是我们发的UDP报文的回应了。
   下面这个是检测UDP回应的代码片断,接收部分和ICMP Ping是几乎一样的
   icmpHeader = (ICMP_HDR *) (recvBuffer + sizeof(IP_HEADER));
   if ( icmpHeader->ih_type == 3 /* dest unreachable */
   &&icmpHeader->ih_code == 3 /* port unreachable */)
   {
   // simply check seq
   unsigned long seqRecvd = *(unsigned long*)(recvBuffer+ret-4);
   if (seq == seqRecvd)
   {
   // that is what we need
   *pTTL = ipHeader->ttl;
   return 0;
   }
   }
   其余细节看完整代码吧,下面是这个SuperPing的用法
  Roger’s Super Ping (ICMP,TCP,UDP Supported)
   Version: 0.1
   Mailto:? roger99707@163.com
   Blog:??? http://rogerfd.cn
   Usage: SuperPing.exe [-p proto] [-a] [-n count] [-i TTL][-w timeout] target
   Options:
   -p proto?????? ICMP,TCP or UDP to use, ICMP is default
   -a???????????? Resolve addresses to position.
   -n count?????? Number of requests to send,default 4, max 1024.
   -i TTL???????? Time To Live, default is 64
   -w timeout???? Timeout in milliseconds to wait for, default 3000
   -P port??????? Port of packet, for TCP and UDP, default 80
   target???????? The target could be ip or domain name???
  有一个已知的问题是,TTL的设置在TCP中无效,原因未明,有知道的跟Roger说一声啊,嘿嘿。-a 选项将查询本机IP和目标IP的地理位置,需要有ipsearcher.dll和QQWry.Dat的支持。
  运行的样子,带-a选项:
  
  


  
  不带-a选项:
  
  


  
  用UDP的Ping:
  
  


  
  默认用ICMP来ping
  
  


  
  完整源代码和可执行程序下载:superping
  ————————————————————————
  作者: roger
  Blog: http://rogerfd.cn
  Email:roger99707@163.com
  本文欢迎转载和引用,请保留本说明并注明出处
  ————————————————————————

本文转自
http://rogerfd.cn/?p=108
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: