connect超時時間的一點探討

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:[email protected].相關連接如下:
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 (DF)
12:37:36.926670 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:37:39.924937 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:37:39.924937 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:37:45.924934 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:37:45.924934 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:37:57.924934 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:37:57.924934 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:38:21.924940 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:38:21.924940 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:39:09.924941 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:39:09.924941 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:40:45.924939 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:40:45.924939 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:42:45.924938 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:42:45.924938 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:44:45.924940 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:44:45.924940 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:46:45.924941 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:46:45.924941 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:48:45.924939 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:48:45.924939 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:50:45.924942 lo > redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (DF)
12:50:45.924942 lo < redhat-6.1.nsfocus.com.3389 > redhat-6.1.nsfocus.com.50001: S 2996115497:2996115497(0) win 31072 (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秒。

其他系統的測試我沒有時間做了,如果有有感興趣的測過了請把結果給我發一份[email protected].

測試程序:
[yawl@redhat-6 tmp]$ cat connect.c
#include
#include
#include
#include
#include

int main(int argc, char* argv[])
{
int fd;
struct sockaddr_in sa;
time_t cur;

if(argc!=3){
printf("%s IP PORT\n",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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章