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选项:
![](http://rogerfd.cn/wp-content/uploads/2008/06/11-300x195.jpg)
不带-a选项:
![](http://rogerfd.cn/wp-content/uploads/2008/06/2-300x195.jpg)
用UDP的Ping:
![](http://rogerfd.cn/wp-content/uploads/2008/06/3-300x195.jpg)
默认用ICMP来ping
![](http://rogerfd.cn/wp-content/uploads/2008/06/4-300x195.jpg)
完整源代码和可执行程序下载:superping
————————————————————————
作者: roger
Blog: http://rogerfd.cn
Email:roger99707@163.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————
本文转自
http://rogerfd.cn/?p=108
传统的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选项:
![](http://rogerfd.cn/wp-content/uploads/2008/06/11-300x195.jpg)
不带-a选项:
![](http://rogerfd.cn/wp-content/uploads/2008/06/2-300x195.jpg)
用UDP的Ping:
![](http://rogerfd.cn/wp-content/uploads/2008/06/3-300x195.jpg)
默认用ICMP来ping
![](http://rogerfd.cn/wp-content/uploads/2008/06/4-300x195.jpg)
完整源代码和可执行程序下载:superping
————————————————————————
作者: roger
Blog: http://rogerfd.cn
Email:roger99707@163.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————
本文转自
http://rogerfd.cn/?p=108
相关文章推荐
- 在C++ Builder中用socket api来写网络通讯程序(同时支持TCP和UDP协议)
- 在C++ Builder中用socket api来写网络通讯程序(同时支持TCP和UDP协议)
- 在C++ Builder中用socket api来写网络通讯程序(同时支持TCP和UDP协议)(二)
- 在C++ Builder中用socket api来写网络通讯程序(同时支持TCP和UDP协议)
- TCP/IP之ICMP(Internet控制报文协议),Ping程序和Traceroute程序
- 为什么ICMP的ping和tracert不经过tcp或udp
- hping - 使用 TCP/UDP ping 解决防火墙拦截 ICMP ping
- 【tcp-ip学习总结】基础UDP实现的简单通信功能小程序
- TCP/IP, UDP, ICMP, ARP协议族简介--纯图慎点
- linux下tcp,udp,icmp小例子
- IP、ICMP、UDP、TCP 校验和算法
- linux网络编程——UDP聊天程序(支持群聊与私聊)
- 微信小程序的百度地图获取地理位置 —— 微信小程序教程系列(15)
- TCP、UDP、ICMP、IP header
- ip, tcp, udp, icmp header
- TCP/IP 详解卷一 - 第6、7、8章 ICMP协议和ping、traceroute程序
- JAVA系列课程讲座二:使用Socket通信实现简单聊天通信程序(UDP方式)
- 支持MSNP15的MSN登录程序PYTHON版代码
- tcp/ip ---------- ping程序
- IP、TCP、UDP、ICMP头详解