TCP--telnet爲什麼在127s後返回?

背景

近期編寫了監控業務服務器的腳本,主要原理是用shell腳本(運行shell的機器稱之爲監控機)調用項目組專用的接口測試工具,對指定的業務服務器進行業務操做,根據接口測試工具的返回結果判斷業務服務器是否運行正常,並使用crontab設置每分鐘執行一次監控腳本。html

在接口測試工具啓動前,先使用telnet ip port判斷了業務服務器的端口是否打開。監控工做如期進行着……linux

然而,當新增對某個業務服務器進行監控時,發現telnet該業務服務器所花費的時間很長,已經超過了1分鐘,這意味着腳本不能在預約的1分鐘的監控間隔內執行完畢,因而我針對該問題進行了探索和研究。算法

探索和研究

從一些網頁中得知可在一些特定文件中設置TMOUT變量來控制telnet的超時時間,也有網頁中提到可使用nc命令代替telnet實現同類效果並能夠設置超時時間——還沒來得及做深刻了解,測試出telnet該業務服務器的所需時長:shell

time telnet 123.59.208.201 62715

 

Trying 123.59.208.201...
telnet: connect to address 123.59.208.201: Connection timed out
telnet 123.59.208.201 62715  0.00s user 0.00s system 0% cpu 2:07.29 total

結果顯示時長爲2:07.29,即127sbash

常見的超時時間,應可能是分鐘的倍數,如60s、3600s等,又或者應是10的倍數,如30s、200s等。但反覆測試了幾回,均是127s。它的特殊性引發了個人好奇,由於127並不是是一個普通的數字,它正是2的7次方-1。因而,我修正了搜索的方向。終於,從搜索返回的結果頁中看到了一個連接,原文連接服務器

http://www.chengweiyang.cn/2017/02/18/linux-connect-timeout/?utm_source=tuicool&utm_medium=referral

這篇文章解決了我此時的疑惑,也開啓了新的探索之旅。app

127的由來

使用tcpdump進行抓包,運行以下命令:electron

sudo tcpdump -i eth0 -nn 'host 123.59.208.201'

 

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes

再新打開一個終端窗口,執行telnet命令:tcp

date +%Y%m%d-%H:%M:%S.%N;time telnet 123.59.208.201 62715;date +%Y%m%d-%H:%M:%S.%N;

 

20170526-18:04:23.764397558
Trying 123.59.208.201...
telnet: connect to address 123.59.208.201: Connection timed out
telnet 123.59.208.201 62715  0.00s user 0.00s system 0% cpu 2:07.22 total
20170526-18:06:30.989480391

這時,tcpdump所在的終端會同步輸出telnet命令所產生的數據包信息:工具

18:04:23.765507 IP 10.253.4.55.34680 > 123.59.208.201.62715: Flags [S], seq 922947731, win 29200, options [mss 1460,sackOK,TS val 13801993 ecr 0,nop,wscale 7], length 0
18:04:24.768182 IP 10.253.4.55.34680 > 123.59.208.201.62715: Flags [S], seq 922947731, win 29200, options [mss 1460,sackOK,TS val 13802996 ecr 0,nop,wscale 7], length 0
18:04:26.772188 IP 10.253.4.55.34680 > 123.59.208.201.62715: Flags [S], seq 922947731, win 29200, options [mss 1460,sackOK,TS val 13805000 ecr 0,nop,wscale 7], length 0
18:04:30.780189 IP 10.253.4.55.34680 > 123.59.208.201.62715: Flags [S], seq 922947731, win 29200, options [mss 1460,sackOK,TS val 13809008 ecr 0,nop,wscale 7], length 0
18:04:38.796205 IP 10.253.4.55.34680 > 123.59.208.201.62715: Flags [S], seq 922947731, win 29200, options [mss 1460,sackOK,TS val 13817024 ecr 0,nop,wscale 7], length 0
18:04:54.828196 IP 10.253.4.55.34680 > 123.59.208.201.62715: Flags [S], seq 922947731, win 29200, options [mss 1460,sackOK,TS val 13833056 ecr 0,nop,wscale 7], length 0
18:05:26.860210 IP 10.253.4.55.34680 > 123.59.208.201.62715: Flags [S], seq 922947731, win 29200, options [mss 1460,sackOK,TS val 13865088 ecr 0,nop,wscale 7], length 0

  

根據telnet命令的起始時間和tcpdump輸出的時間:

04:23.76
04:24.76
04:26.77
04:30.78
04:38.79
04:54.82
05:26.86
06:30.98

不難看出規律:它們的差值(單位s)是雙倍遞增的:一、二、四、八、1六、3二、64

內部原理

tcp超時與重傳

執行telnet命令,實際上是嘗試創建tcp鏈接的過程。而創建tcp鏈接時會涉及到兩個概念:一個是RTT,一個是RTO。
RTT (Round-Trip Time) 即,往返時間
RTO (Retransmission Time Out) 即,重傳超時
上面的時間間隔顯然就是RTO。

創建tcp鏈接的過程和細節這裏再也不贅述,若有須要能夠參閱《TCPIP詳解(卷1)》。

根據該監控機的內核的版本,查看了linux的相關代碼:
http://elixir.free-electrons.com/linux/v3.10/source/include/net/tcp.h

#define TCP_RTO_MAX ((unsigned)(120*HZ))
#define TCP_RTO_MIN ((unsigned)(HZ/5))

獲得:

  1. HZ是1s。因此RTO的最小值是200ms,RTO的最大值是120s,即2分鐘。
  2. tcpdump的結果看,監控機的RTO是1s,緣由未知
  3. 根據代碼中的算法部分可知,RTO重傳間隔是指數增長的,隨着重傳次數的增多,消耗的時間指數增加。當RTO小於TCP_RTO_MAX時,則RTO每次翻倍,當超過TCP_RTO_MAX以後,再也不翻倍,而是固定用TCP_RTO_MAX,即2分鐘。

而,linux內核變量net.ipv4.tcp_syn_retries用來告訴內核,當嘗試新建一個TCP鏈接時,要從新發送多少次初始的SYN報文。能夠經過 sysctl 命令查看。

查看變量tcp_syn_retries的值

sudo sysctl net.ipv4.tcp_syn_retries

  

net.ipv4.tcp_syn_retries = 6

由於tcp_syn_retries控制的是從新發送的次數,因此加上初始的那1次,因此上述tcpdump的輸出中一共有7條。

設置變量tcp_syn_retries的值

設置net.ipv4.tcp_syn_retries並測試telnet的時長:

sudo sysctl net.ipv4.tcp_syn_retries=1

  

net.ipv4.tcp_syn_retries = 1

  

驗證:

time telnet 123.59.208.201 62715

  

Trying 123.59.208.201...
telnet: connect to address 123.59.208.201: Connection timed out
telnet 123.59.208.201 62715  0.00s user 0.00s system 0% cpu 3.008 total

  

可見,net.ipv4.tcp_syn_retries = 1時,telnet的耗時約爲3s。能夠預知的是,若是用tcpdump抓包,應當有兩條,而3s則是RTO分別取1s和2s的結果。

sudo sysctl net.ipv4.tcp_syn_retries=2

  

net.ipv4.tcp_syn_retries = 2

  

time telnet 123.59.208.201 62715

  

Trying 123.59.208.201...
telnet: connect to address 123.59.208.201: Connection timed out
telnet 123.59.208.201 62715  0.00s user 0.00s system 0% cpu 7.010 total

  

net.ipv4.tcp_syn_retries = 2時,telnet的耗時約爲7s,遵照RTO指數遞增規律。

sudo sysctl net.ipv4.tcp_syn_retries=5

  

net.ipv4.tcp_syn_retries = 5

  

time telnet 123.59.208.201 62715

  

Trying 123.59.208.201...
telnet: connect to address 123.59.208.201: Connection timed out
telnet 123.59.208.201 62715  0.00s user 0.00s system 0% cpu 1:03.14 total

  

net.ipv4.tcp_syn_retries = 5時,telnet的耗時約爲63s,遵照RTO指數遞增規律。

那麼,按照RTO指數遞增規律和TCP_RTO_MAX的設定,當net.ipv4.tcp_syn_retries持續增大時,RTO並不會對應地指數遞增下去,把net.ipv4.tcp_syn_retries設置成10看看效果:

sudo sysctl net.ipv4.tcp_syn_retries=10

  

net.ipv4.tcp_syn_retries = 10

  

驗證:

date +%Y%m%d-%H:%M:%S.%N;time telnet 123.59.208.201 62715;date +%Y%m%d-%H:%M:%S.%N;

  

20170526-18:47:25.899074041
Trying 123.59.208.201...
telnet: connect to address 123.59.208.201: Connection timed out
telnet 123.59.208.201 62715  0.00s user 0.00s system 0% cpu 10:08.64 total
20170526-18:57:34.541575652

  

查看tcpdump的結果:

18:47:25.900294 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16384128 ecr 0,nop,wscale 7], length 0
18:47:26.902184 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16385130 ecr 0,nop,wscale 7], length 0
18:47:28.908195 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16387136 ecr 0,nop,wscale 7], length 0
18:47:32.916178 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16391144 ecr 0,nop,wscale 7], length 0
18:47:40.940213 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16399168 ecr 0,nop,wscale 7], length 0
18:47:56.972221 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16415200 ecr 0,nop,wscale 7], length 0
18:48:29.004213 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16447232 ecr 0,nop,wscale 7], length 0
18:49:33.132192 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16511360 ecr 0,nop,wscale 7], length 0
18:51:33.580213 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16631808 ecr 0,nop,wscale 7], length 0
18:53:33.900217 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16752128 ecr 0,nop,wscale 7], length 0
18:55:34.220216 IP 10.253.4.55.36266 > 123.59.208.201.62715: Flags [S], seq 3545463057, win 29200, options [mss 1460,sackOK,TS val 16872448 ecr 0,nop,wscale 7], length 0

  

當RTO18:48:29.00 -> 18:49:33.13到達64s後,下一次按照遞增規則應當變爲128s,而實際上,18:49:33.13 -> 18:51:33.58 -> 18:53:33.90 -> 18:55:34.22,RTO變爲恆定的120s(TCP_RTO_MAX)。

取值範圍

設置成0次會報錯:

sudo sysctl net.ipv4.tcp_syn_retries=0

  

sysctl: setting key "net.ipv4.tcp_syn_retries": Invalid argument
net.ipv4.tcp_syn_retries = 0

  

很多網頁上提到net.ipv4.tcp_syn_retries的最大值可設置爲25五、默認值是5,在這臺監控機上實測發現最大隻能設置爲127:

sudo sysctl net.ipv4.tcp_syn_retries=127

  

net.ipv4.tcp_syn_retries = 127

嘗試設置成128時保錯:

sudo sysctl net.ipv4.tcp_syn_retries=128

  

sysctl: setting key "net.ipv4.tcp_syn_retries": Invalid argument
net.ipv4.tcp_syn_retries = 128

  

在linux內核的相關網頁上有明確記錄——最大不超過12七、默認值爲6
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt

tcp_syn_retries - INTEGER
Number of times initial SYNs for an active TCP connection attempt
will be retransmitted. Should not be higher than 127. Default value
is 6, which corresponds to 63seconds till the last retransmission
with the current initial RTO of 1second. With this the final timeout
for an active TCP connection attempt will happen after 127seconds.

但,指出net.ipv4.tcp_syn_retries的最大值可設置爲255的網頁中,有很多網頁看起來也具備必定的可信度,好比:

https://www.frozentux.net/ipsysctl-tutorial/chunkyhtml/tcpvariables.html

3.3.24. tcp_syn_retries
The tcp_syn_retries variable tells the kernel how many times to try to retransmit the initial SYN packet for an active TCP connection attempt.
This variable takes an integer value, but should not be set higher than 255 since each retransmission will consume huge amounts of time as well as some amounts of bandwidth. Each connection retransmission takes aproximately 30-40 seconds. The default setting is 5, which would lead to an aproximate of 180 seconds delay before the connection times out.

隨着根據實測結果和網站的權威性,我更信賴最大不超過12七、默認值爲6,但同時也很好奇,這些網頁的內容依據出自何處?

永久修改

上述提到的修改net.ipv4.tcp_syn_retries的方式均是臨時修改,重啓系統後便失效。若想永久生效,則須要修改相應的配置文件,請自行搜索。

另外,由於變量net.ipv4.tcp_syn_retries是系統範圍的,而它影響tcp創建鏈接的重傳次數,因此修改前建議與系統管理員確認。

總結

美其名曰探索和研究,實際就是在踩本身由於基礎知識欠缺而埋下的坑

雖然搞清楚了127的由來,但卻帶來了許多新的疑問和不解,做爲下一次踩坑的引子。

參考連接

踩坑的過程,總能發現一些不錯的資源網站,如:

  1. https://www.kernel.org
  2. http://lxr.linux.no/
  3. http://free-electrons.com/docs/

我想,這算得上對困而學之者的最好獎勵了。大概也是爲了讓我可以成爲學而知之者吧。

相關知識

  • tcp協議
  • tcpdump命令
  • sysctl命令
  • telnet命令
相關文章
相關標籤/搜索