linux開啟tcp_timestamps和tcp_tw_recycle引發的問題研究


環境:centos7.4 內核版本3.10

最近看內核參數tcp_tw_recycle(該參數在內核 4.12 之后被移除),它用於快速回收處理TIME_WAIT狀態的socket。搜索該參數相關的資料,發現同時啟用該參數和tcp_timestamps后有可能在NAT環境下導致客戶端始連接失敗,抓包表現為:客戶端一直發送SYN報文,但服務端不響應。但這些文章中只給出了如何解決問題,並沒有給出如何復現問題。特別怪異的是,服務端是被動關閉的,並不會進入TIME_WAIT狀態,到底怎么產生的呢?

先使用如下拓撲復現該場景,其中10.85.3.51機器為NAT服務器,10.85.1.2和10.85.3.52通過NAT服務器訪問server 10.85.3.111:19090

+-------------+
|  10.85.1.2  +------------+
+-------------+            |
                     +-----+-------+         +---------------------+
                     |  10.85.3.51 +---------+  10.85.3.111:19090  +
                     +-----+-------+         +---------------------+
+-------------+            |
|  10.85.3.52 +------------+
+-------------+

在10.85.3.51機器上配置如下iptables表項,用於轉發client和server之間的TCP報文。(10.85.3.51需要開啟net.ipv4.ip_forward功能)

# iptables -t nat -I PREROUTING -d 10.85.3.51 -p tcp -m tcp --dport 29090 -j DNAT --to 10.85.3.111:19090
# iptables -t nat -I POSTROUTING -d 10.85.3.111 -p tcp -m tcp --dport 19090 -j SNAT --to 10.85.3.51
  • 首先開啟tcp_timestamps,關閉tcp_tw_recycle

在10.85.3.111上進行抓包並且啟動10.85.1.2和10.85.3.52進行連接。報文如下,其中第4和第7條為兩個連接的TCP SYN報文,后續server都進行了回復,兩條連接正常建鏈

1 # tcpdump -i eth0 src port 19090 or dst port 19090
2 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
3 listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
4 17:39:27.970358 IP 10.85.3.51.57104 > 10.85.3.111.19090: Flags [S], seq 2466985868, win 25200, options [mss 1260,sackOK,TS val 3075335984 ecr 0,nop,wscale 7], length 0
5 17:39:27.970417 IP 10.85.3.111.19090 > 10.85.3.51.57104: Flags [S.], seq 2846609535, ack 2466985869, win 24960, options [mss 1260,sackOK,TS val 2612548200 ecr 3075335984,nop,wscale 7], length 0
6 17:39:27.970783 IP 10.85.3.51.57104 > 10.85.3.111.19090: Flags [.], ack 1, win 197, options [nop,nop,TS val 3075335985 ecr 2612548200], length 0

7 17:39:29.059890 IP 10.85.3.51.34230 > 10.85.3.111.19090: Flags [S], seq 2892210420, win 25200, options [mss 1260,sackOK,TS val 1740811766 ecr 0,nop,wscale 7], length 0 8 17:39:29.059949 IP 10.85.3.111.19090 > 10.85.3.51.34230: Flags [S.], seq 3434079625, ack 2892210421, win 24960, options [mss 1260,sackOK,TS val 2612549289 ecr 1740811766,nop,wscale 7], length 0 9 17:39:29.060623 IP 10.85.3.51.34230 > 10.85.3.111.19090: Flags [.], ack 1, win 197, options [nop,nop,TS val 1740811767 ecr 2612549289], length 0

啟用tcp_tw_recycle,重復上面操作。發現即使后面一個連接的SYN報文的時間戳小於前面一個連接的SYN報文中的時間戳,也能夠正常建鏈,並沒有出現連接異常。

1 # tcpdump -i eth0 src port 19090 or dst port 19090
2 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
3 listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
4 17:49:12.111152 IP 10.85.3.51.58164 > 10.85.3.111.19090: Flags [S], seq 2599215624, win 25200, options [mss 1260,sackOK,TS val 3075920126 ecr 0,nop,wscale 7], length 0
5 17:49:12.111221 IP 10.85.3.111.19090 > 10.85.3.51.58164: Flags [S.], seq 795235982, ack 2599215625, win 24960, options [mss 1260,sackOK,TS val 2613132341 ecr 3075920126,nop,wscale 7], length 0
6 17:49:12.111766 IP 10.85.3.51.58164 > 10.85.3.111.19090: Flags [.], ack 1, win 197, options [nop,nop,TS val 3075920127 ecr 2613132341], length 0

7 17:49:12.871092 IP 10.85.3.51.34234 > 10.85.3.111.19090: Flags [S], seq 3696139072, win 25200, options [mss 1260,sackOK,TS val 1741395578 ecr 0,nop,wscale 7], length 0 8 17:49:12.871149 IP 10.85.3.111.19090 > 10.85.3.51.34234: Flags [S.], seq 3928136503, ack 3696139073, win 24960, options [mss 1260,sackOK,TS val 2613133101 ecr 1741395578,nop,wscale 7], length 0 9 17:49:12.871697 IP 10.85.3.51.34234 > 10.85.3.111.19090: Flags [.], ack 1, win 197, options [nop,nop,TS val 1741395579 ecr 2613133101], length 0
  • 后來在這篇文章中找到靈感。正常TCP TIME_WATI時長為2MSL,用於揮手階段最后一個ACK報文的重傳,以及防止當前連接上滯留的報文影響到下一個連接。當啟用tcp_tw_recycle后,系統會在一個RTO的極短時間內回收處於TIME_WAIT狀態的socket,但仍然無法杜絕接收到上一個連接在鏈路上滯留的報文。為了防止這種情況的發生,在啟用tcp_tw_recycle的情況下,由於已經釋放了socket,系統無法使用socket來標記一條連接,只能退而求其次,通過判斷對端IP發過來的報文的時間戳來判斷該報文是新產生的還是老的報文,如果是老報文,則丟棄且不回復。

因此復現場景為:服務端主動斷開與客戶端的一條連接,在后續的TCP_PAWS_MSL(60s)時間內,如果客戶端發過來的SYN報文的TSVal時間戳小於系統保留的上一個連接的時間戳,則該SYN報文會被丟棄,實際表現為客戶端連接超時或很慢(60s之后可正常連接)

  1.  首先server使用命令 telnet 10.85.3.51 22 連接NAT機器,並立即斷開連接,此時server會很快回收一個TIME_WAIT的socket。在server端抓包,可以看到保存的該連接上對端發來的最后一個時間戳為3035582641
 1 # tcpdump -i eth0 src host 10.85.3.51 or dst host 10.85.3.51
 2 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 3 listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
 4 22:45:10.015335 IP 10.85.3.111.49416 > 10.85.3.51.ssh: Flags [S], seq 1039611753, win 25200, options [mss 1260,sackOK,TS val 2630890245 ecr 0,nop,wscale 7], length 0
 5 22:45:10.016055 IP 10.85.3.51.ssh > 10.85.3.111.49416: Flags [S.], seq 489573340, ack 1039611754, win 24960, options [mss 1260,sackOK,TS val 3035577005 ecr 2630890245,nop,wscale 7], length 0
 6 22:45:10.016074 IP 10.85.3.111.49416 > 10.85.3.51.ssh: Flags [.], ack 1, win 197, options [nop,nop,TS val 2630890246 ecr 3035577005], length 0
 7 22:45:10.023482 IP 10.85.3.51.ssh > 10.85.3.111.49416: Flags [P.], seq 1:22, ack 1, win 195, options [nop,nop,TS val 3035577013 ecr 2630890246], length 21
 8 22:45:10.023507 IP 10.85.3.111.49416 > 10.85.3.51.ssh: Flags [.], ack 22, win 197, options [nop,nop,TS val 2630890253 ecr 3035577013], length 0
 9 
10 22:45:15.648562 IP 10.85.3.111.49416 > 10.85.3.51.ssh: Flags [F.], seq 1, ack 22, win 197, options [nop,nop,TS val 2630895878 ecr 3035577013], length 0
11 22:45:15.649128 IP 10.85.3.51.ssh > 10.85.3.111.49416: Flags [.], ack 2, win 195, options [nop,nop,TS val 3035582639 ecr 2630895878], length 0
12 22:45:15.651394 IP 10.85.3.51.ssh > 10.85.3.111.49416: Flags [F.], seq 22, ack 2, win 195, options [nop,nop,TS val 3035582641 ecr 2630895878], length 0
13 22:45:15.651411 IP 10.85.3.111.49416 > 10.85.3.51.ssh: Flags [.], ack 23, win 197, options [nop,nop,TS val 2630895881 ecr 3035582641], length 0

在斷開連接的TCP_PAWS_MSL時間內啟動10.85.1.2通過NAT連接到server,server端抓包可以看到該連接的SYN報文的時間戳1759176699遠小於保存的時間戳3035582641,此時server端丟棄接收到的所有SYN報文,客戶端連接超時。

1 22:45:33.942378 IP 10.85.3.51.34264 > 10.85.3.111.19090: Flags [S], seq 668096838, win 25200, options [mss 1260,sackOK,TS val 1759176699 ecr 0,nop,wscale 7], length 0
2 22:45:34.942300 IP 10.85.3.51.34264 > 10.85.3.111.19090: Flags [S], seq 668096838, win 25200, options [mss 1260,sackOK,TS val 1759177700 ecr 0,nop,wscale 7], length 0
3 22:45:36.946320 IP 10.85.3.51.34264 > 10.85.3.111.19090: Flags [S], seq 668096838, win 25200, options [mss 1260,sackOK,TS val 1759179704 ecr 0,nop,wscale 7], length 0
  • 結合上述測試可以得出結論:同時啟動tcp_timestamps和tcp_tw_recycle可能會導致客戶端連接不上前提條件是server主動斷開過與客戶端的連接(可能是服務重啟等原因),導致server處於TIME_WAIT狀態的socket被快速回收,如果在TCP_PAWS_MSL時間內接收到客戶端經NAT發過來的報文的時間戳小於前一個連接保存的時間戳,該報文會被認為是老鏈路殘留的報文而丟棄。進而可以得出:
    1. 在NAT場景下一定不能啟用tcp_tw_recycle;
    2. NAT場景下單獨啟動tcp_timestamps不會影響正常使用,連接斷鏈后會在2MSL過后回收socket;
    3. 生產中不要使用tcp_tw_recycle,即使沒有使用到NAT設備,但當前虛擬化環境下用到NAT的地方很多,如kubernetes的service等

TIPS

  • 為了復現如上問題,曾嘗試過使用1.17.0版本的nginx作為NAT服務。但發現經過nginx的所有連接的SYN報文的時間戳都會被nginx修改,且后面連接SYN報文的時間戳一定大於前面連接的SYN報文中的時間戳,因此nginx下面不會出現客戶端方式失敗的場景

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM