TCP KeepAlive機制理解與實踐小結

huey_x發表於2022-01-03

0 前言

本文將主要通過抓包並檢視報文的方式學習TCP KeepAlive機制,以此加深理解。

 

1 TCP KeepAlive機制簡介

TCP長連線下,客戶端和伺服器若長時間無資料互動情況下,若一方出現異常情況關閉連線,抑或是連線中間路由出於某種機制斷開連線,而此時另一方不知道對方狀態而一直維護連線,浪費系統資源的同時,也會引起下次資料互動時出錯。

為了解決此問題,引入了TCP KeepAlive機制(並非標準規範,但作業系統一旦實現,預設情況下須為關閉,可以被上層應用開啟和關閉)。其基本原理是在此機制開啟時,當長連線無資料互動一定時間間隔時,連線的一方會向對方傳送保活探測包,如連線仍正常,對方將對此確認回應。

關於TCP KeepAlive機制的詳細背景可以參考《TCP/IP詳解 卷1:協議》一書,在此不詳細贅述。

 

2 TCP KeepAlive設定引數和報文格式簡介

2.1 TCP KeepAlive引數

TCP KeepAlive機制主要涉及3個引數:

  • tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
    The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-alives are sent only when the SO_KEEPALIVE socket option is enabled. The default value is 7200 seconds (2 hours). An idle connection is terminated after approximately an additional 11 minutes (9 probes an interval of 75 seconds apart) when keep-alive is enabled.
    在TCP保活開啟的情況下,最後一次資料交換到TCP傳送第一個保活探測包的間隔,即允許的持續空閒時長,或者說每次正常傳送心跳的週期,預設值為7200s(2h)。

  • tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
    The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end.
    在tcp_keepalive_time之後,最大允許傳送保活探測包的次數,到達此次數後直接放棄嘗試,並關閉連線,預設值為9(次)。

  • tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
    The number of seconds between TCP keep-alive probes.
    在tcp_keepalive_time之後,沒有接收到對方確認,繼續傳送保活探測包的傳送頻率,預設值為75s。

 

2.2 TCP KeepAlive報文格式

TCP KeepAlive探測報文是一種沒有任何資料,同時ACK標誌被置上的報文,報文中的序列號為上次發生資料互動時TCP報文序列號減1。比如上次本端和對端資料互動的最後時刻,對端回應給本端的ACK報文序列號為 N(即下次本端向對端傳送資料,序列號應該為N),則本端向對端傳送的保活探測報文序列號應該為 N-1。 在本文第四節,將通過抓包的方式再次介紹TCP KeepAlive報文。

 

3 TCP KeepAlive的配置

3.1 系統核心引數配置

Linux核心提供了通過sysctl命令檢視和配置TCP KeepAlive引數的方法。

  • 檢視當前核心TCP KeepAlive引數
    sysctl net.ipv4.tcp_keepalive_time
    sysctl net.ipv4.tcp_keepalive_probes
    sysctl net.ipv4.tcp_keepalive_intvl

     

  • 修改TCP KeepAlive引數
    sysctl net.ipv4.tcp_keepalive_time=3600

     

3.2 C語言socket設定

對於Socket而言,可以在程式中通過socket選項開啟TCP KeepAlive功能,並配置引數。對應的Socket選項分別為SO_KEEPALIVETCP_KEEPIDLETCP_KEEPCNTTCP_KEEPINTVL

int keepalive = 1;          // 開啟TCP KeepAlive功能
int keepidle = 7200;        // tcp_keepalive_time
int keepcnt = 9;            // tcp_keepalive_probes
int keepintvl = 75;         // tcp_keepalive_intvl

setsockopt(socketfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive));
setsockopt(socketfd, SOL_TCP, TCP_KEEPIDLE, (void *) &keepidle, sizeof (keepidle));
setsockopt(socketfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof (keepcnt));
setsockopt(socketfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof (keepintvl));

 

4 抓包實踐分析

4.1 實踐環境設定

將利用C語言分別編寫伺服器側的程式碼和客戶端的程式碼,在伺服器側開啟TCP KeepAlive功能,伺服器的埠號設定為6699,客戶端的埠號為核心自動分配,並且每次客戶端重啟後核心分配的埠號可能不同,因此只需記住伺服器埠號即可,另外一個相對的就是客戶端。

伺服器側TCP KeepAlive引數具體設定如下:

tcp_keepalive_time = 55s
tcp_keepalive_probes = 2
tcp_keepalive_intvl = 6s

 

客戶端執行於個人MAC電腦上,伺服器執行在另外一臺Linux系統上,後續通過wireshark軟體進行抓包也是在MAC電腦上進行抓包。

4.2 wireshark抓包分析

將伺服器開啟,並通過客戶端與之建立連線,過程中客戶端和伺服器無任何資料互動。通過wireshark抓包分析客戶端和伺服器間的資料互動,如下圖:
tcp_keepalive_1

可以看到,客戶端和伺服器建立連線後,伺服器每隔55s傳送了一次TCP KEEPALIVE的探測報文,也驗證了上面伺服器tcp_keepalive_time的設定,客戶端收到探活報文後,會作出回應。點選具體報文可以檢視報文詳情,如下圖所示:
tcp_keepalive_2

tcp_keepalive_3

本文2.2節中介紹過TCP KEEPALIVE探活報文的序列號為上次發生資料互動時TCP報文序列號減1,實踐中客戶端和伺服器在建立連線三次握手之後,沒有發生任何資料交換,因此伺服器收到客戶端傳送的最後報文的ACK值為1(注意wireshark抓包的報文序列號為相對序列號,Relative Sequence Number),所以伺服器傳送保活探測報文的序列號應該為0,客戶端收到伺服器探活報文後回應的確認報文序列號為1,和wireshark實際抓包相符合。

4.3 進一步測試

上面的測試中驗證了tcp_keepalive_time引數,接下來驗證TCP KeepAlive機制中另外兩個引數。為了模擬由於連線中間路由等異常導致探活報文收不到回應的場景,藉助Linux iptables命令設定防火牆,阻斷TCP探活報文的傳輸。

這裡將來自客戶端的報文丟棄,即模擬伺服器傳送TCP KEEPALIVE探活報文後遲遲收不到回應報文的場景。使用的命令為:sudo iptables -A INPUT -p tcp --sport $YOUR_CLIENT_PORT -j DROP,其中sport為系統為客戶端分配的埠號。

注意伺服器是執行在Linux系統上的,客戶端和wireshark執行在本地MAC電腦上,因此利用上述iptables設定防火牆規則後,wireshark仍能抓到所有確認報文,只是在Linux系統這一側通過軟體防火牆將報文丟棄了。

設定報文過濾規則後,再次通過wireshark抓包,如下圖:
tcp_keepalive_4

從捕捉到的報文可以看到,3024.35s時間戳開始,伺服器傳送TCP KEEPALIVE探活報文後,一直收不到確認報文的返回,便隔6秒重新傳送一個探活報文,即上文提到的伺服器tcp_keepalive_intvl引數的設定。再等待2個tcp_keepalive_intvl時間間隔後,伺服器仍未收到確認報文後,伺服器傳送了RST報文,以釋放網路連線資源,這裡的2次即tcp_keepalive_probes設定的。

注意:在完成上述測試後,藉助sudo iptables -F命令清除所有的防火牆規則。

 

4 總結

本文藉助wireshark軟體,對TCP KEEPALIVE報文進行抓包分析,分析了TCP保活機制中tcp_keepalive_timetcp_keepalive_probestcp_keepalive_intvl三個引數的實際效果,加深了對TCP KEEPALIVE機制的理解。

參考資料

[1] About TCP keepalive: (http://baotiao.github.io/tech/2015/09/25/tcp-keepalive/)
[2] https://www.codenong.com/cs105424711/

相關文章