2009年8月5日星期三

connect超时时间的一点探讨

对阻塞的connect到底会多久超时(返回-1,并且errno被设为ETIMEDOUT)一直也没有搞清楚,今天花时间
看了一下代码并作了一点实验,大致得出了一点结论。没有时间写的太细了,把结果贴出来,感兴趣的人自己去看吧。
背景知识:
各种系统对此都没有一个总时间的限制,而是设置了重连的次数(即如果收不到synack,会重试多少遍),这个缺
省值个个系统不大一样(linux不同版本这个值也有过变化,见后)。每次重连之间的间隔时间会通过算法来调整,
这个算法个个系统的实现也有差别,因此造成了系统之间connect超时时间的千差万别(早期的BSd系统会共耗时75秒
,这点《UNP》 section4.3中提到了,但现代的系统一般更久)。
大多数系统这个重连数可调节(linux可以通过/proc/sys/net/ipv4/tcp_syn_retries或sysctl来修改此值。
TCP/IP V1》的附录E提到了其他系统的)
底下简单分析了linux下的相关代码,并作了个小实验。为了让timeout能够超时,我设了防火墙(通过ipchains)
,将规则设为DENY(REJECT会返回icmp端口不可达,会造成connect函数马上返回),并通过tcpdump检查发包重试
的情况。
自己写程序时应该考虑这种情况,自行设置更短超时(《UNP》中说的很清楚了)。
nmap再扫描这类没有任何返回的端口时,会将其状态标为"filter",这时大多情况就是有防火墙了(如果带宽足够,
对方系统又没有"非常忙"的情况下)。
更细致的探讨请见rfc793,rfc1122,《TCP/IP V1》和论文《Congestion Avoidance
and Control》。
还有一个疑问,为什么alan
cox在两次邮件中一次说总超时大约是15分钟,一次又说是30分钟?是不是以前这个重试
次数设的更大?没时间考古了,知道的请回我的mail:yawl@docshow.net.相关连接如下:
http://www.wcug.wwu.edu/lists/netdev/199808/msg00032.html
http://www.uwsg.indiana.edu/hypermail/linux/kernel/9802.0/0384.html
linux相关的代码(结合后面的结论看):
tcp_v4_init_sock():
tp->rto = TCP_TIMEOUT_INIT; //初值,即3秒
tcp_retransmit_timer():
tp->retransmits++;
tp->rto = min(tp->rto <<1, TCP_RTO_MAX);
//如果小于两分钟(TCP_RTO_MAX),每次乘2
tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
tcp_connect():
tp->rto = TCP_TIMEOUT_INIT; //重复语句???
tp->retransmits = 0;
tcp_clear_retrans(tp);
运行结果:
[yawl@redhat-6 tmp]$ gcc -o connect connect.c
[yawl@redhat-6 tmp]$ ./connect 192.168.10.1 50001
Fri Apr 20 12:37:36 2001
connect fail: Connection timed out
Fri Apr 20 12:50:45 2001
运行环境如下:
[yawl@redhat-6 tmp]$ uname -a
Linux redhat-6 2.2.12-20 #1 Mon Sep 27 10:40:35 EDT 1999 i686
unknown
[yawl@redhat-6 tmp]$ cat /proc/sys/net/ipv4/tcp_syn_retries
10
[root@redhat-6 yawl]# ipchains -A input --proto tcp
--destination-port 50001 -j DENY
[root@redhat-6 yawl]# ipchains -L
Chain input (policy ACCEPT):
target prot opt source destination ports
DENY tcp ------ anywhere anywhere any -> 50001
Chain forward (policy ACCEPT):
Chain output (policy ACCEPT):
[root@redhat-6 yawl]# tcpdump port 50001
Kernel filter, protocol ALL, datagram packet
socket
tcpdump: listening on all devices
12:37:36.926670 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:37:36.926670 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:37:39.924937 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:37:39.924937 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:37:45.924934 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:37:45.924934 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:37:57.924934 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:37:57.924934 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:38:21.924940 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:38:21.924940 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:39:09.924941 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:39:09.924941 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:40:45.924939 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:40:45.924939 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:42:45.924938 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:42:45.924938 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:44:45.924940 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:44:45.924940 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:46:45.924941 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:46:45.924941 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:48:45.924939 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:48:45.924939 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:50:45.924942 lo > redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
12:50:45.924942 lo <redhat-6.1.nsfocus.com.3389 >
redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 <mss
3884> (DF)
结论(请对照前面的代码):
0s 12:37:36.926670 //第一次发起连接
+3s 12:37:39.924937 //未收到回应,开始重试
+6s 12:37:45.924934 //在未超过TCP_RTO_MAX之前,每次重试时间翻倍
+12s 12:37:57.924934
+24s 12:38:21.924940
+48s 12:39:09.924941
+96s 12:40:45.924939
+120S 12:42:45.924938
//超过TCP_RTO_MAX之后,不再翻倍,而是固定用TCP_RTO_MAX,即2分钟
+120s 12:44:45.924940
+120S 12:46:45.924941
+120s 12:48:45.924939
+120s 12:50:45.924942
共用了大约13分钟零9秒,重试了11次(比设置的值多了一次,在maillist中发现有人提到了这个bug,并在新版本
中作了修正,见链接http://www.uwsg.indiana.edu/hypermail/linux/kernel/0001.0/0237.html
重连次数linux不同版本也有变化:
在2.2.16的内核代码中(include/net/tcp.h):
#define TCP_SYN_RETRIES 10 /* number of times to retry opening a
* connection (TCP_RETR2-....) */
而在2.4.1中(include/net/tcp.h):
#define TCP_SYN_RETRIES 5 /* number of times to retry active opening a
* connection: ~180sec is RFC minumum */
我改了一下这个次数:
[root@redhat-6 yawl]# echo "5" >
/proc/sys/net/ipv4/tcp_syn_retries
[root@redhat-6 yawl]# cat /proc/sys/net/ipv4/tcp_syn_retries
5
然后又测了一遍,结论类似,用时3分9秒。
其他系统的测试我没有时间做了,如果有有感兴趣的测过了请把结果给我发一份yawl@docshow.net.
测试程序:
[yawl@redhat-6 tmp]$ cat connect.c
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <time.h>
int main(int argc, char* argv[])
{
int fd;
struct sockaddr_in sa;
time_t cur;
if(argc!=3){
printf("%s IP PORTn",argv[0]);
exit(-1);
}
fd=socket(AF_INET, SOCK_STREAM, 0);
if(fd<0){
perror("socket fail");
exit(-1);
}
sa.sin_family=AF_INET;
sa.sin_addr.s_addr=inet_addr(argv[1]);
sa.sin_port=htons(atoi(argv[2]));
cur=time(NULL);
printf("%s", ctime(&cur));
if(connect(fd, (struct sockaddr*)&sa, sizeof(sa)) <0){
perror("connect fail");
}
cur=time(NULL);
printf("%s", ctime(&cur));
close(fd);
exit(0);
}

没有评论: