資料庫連線池長時間不用,乍一用還用不了,結果是防火牆的鍋

三國夢迴發表於2023-09-26

前言

我們的程式,在實際的網路部署時,一般比較複雜,會經過很多的網路裝置,防火牆就是其中的一種。做開發的同事,一般對這塊瞭解不多,也很可能被防火牆坑到。比如,應用一般需要訪問資料庫,為了避免頻繁建立連線,一般是會提前建立一個連線池,每次來一個請求,就從連線池取一個連線來用,用完再歸還到池子裡。

連線池中的連線是啥呢,其實就是和資料庫之間的完成了三次握手後的socket,這個socket在白天時,一般經常有資料傳輸,而到了凌晨這種,可能就很少資料傳輸,等到了第二天,某個請求來了,從池子裡取了某個socket,就直接傳送資料,此時,很可能就會出現一個read timeout的情況。

為啥呢,是資料庫不返回資料嗎?不一定,如果應用伺服器和db伺服器之間,經過了防火牆的話,很可能,你這個socket發出去的包,直接就防火牆給丟棄了,根本沒有到達資料庫。如果想搜尋這塊相關的案例,可以搜尋“防火牆 長連線”關鍵字。

狀態防火牆(stateful firewall)

現在的防火牆,基本都是有狀態的,什麼叫做有狀態呢?以tcp為例,當服務端收到第一次握手(syn)時,此時,會認為這是要新建立連線,當三次握手完成後,再收到客戶端在該socket上發來的請求報文時,此時,就知道這個報文不再是要新建立連線了,socket的狀態此時也是established。說白了,握手時候的報文和握手完成後的報文,是不一樣的,我們是可以區別對待的,這就是通俗意義上的“有狀態”。

而在此之前,都是無狀態防火牆(stateless firewall),不管是什麼報文,都是一視同仁,沒法根據狀態來區分處理。

接下來,我們可以以iptables為例,理解下狀態防火牆(iptables就是典型的狀態防火牆)。

iptables中的狀態

該部分參考官網:https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html#STATEMACHINE

iptables是使用者態中的命令,在核心中由netfilter實現,netfilter中實現連線的狀態跟蹤的機制,叫做conntrack(connection track)。在這個機制中,一共定義了4種狀態:NEW, ESTABLISHED, RELATED,INVALID,UNTRACKED。

注意這裡的conntrack,中文就是連線追蹤,既然是連線,那麼,我們怎麼區分一個連線呢,比如,在tcp中,就是依靠四元組,這個四元組就可以唯一標識一個連線。

我們可以定義一個hash表,在收到一個tcp報文時,檢查下這個四元組在hash表中是否存在(key就是四元組),如果不存在,我們就可以認為這個報文此時是新來的,也就是狀態是new;當我們收到服務端返回的報文時,我們又可以檢測下四元組在本地hash表是否存在,發現已經存在了,此時就可以認為狀態是established。

這裡大家如果要看這個狀態的詳細解釋,可以檢視官網文件;另外,我發現一篇文章也不錯:

https://mp.weixin.qq.com/s/kv32lyWak4dCaPjMI4_6jw

NEW:
新建連線請求的資料包,且該資料包沒有和任何已有連線相關聯。 判斷的依據是conntrack當前“只看到一個方向資料包(UNREPLIED)”,沒有回包。
ESTABLISHED:
該連線是某NEW狀態連線的回包,也就是完成了連線的雙向關聯。

其他幾種狀態,RELATEDINVALID我感覺一般比較少用,至於UNTRACKED ,可以等後續再慢慢領悟。

實際感受狀態變化

客戶端首次握手(syn)

可以透過cat /proc/net/nf_conntrack檢視連線狀態。

如收到首次握手請求後,會看到如下內容:

tcp      6 117 SYN_SENT src=192.168.1.5 dst=192.168.1.35 sport=1031 \
     dport=23 [UNREPLIED] src=192.168.1.35 dst=192.168.1.5 sport=23 \
     dport=1031 use=1

各個欄位的意思:

  • tcp,協議名

  • 6,傳輸層協議代號,其中tcp是6,udp是17,

  • 117,ttl,表示這個conntrack的過期時間,類似於redis裡的key的ttl

  • SYN_SENT,socket此時的狀態。官方:The value SYN_SENT tells us that we are looking at a connection that has only seen a TCP SYN packet in one direction

  • src=192.168.1.5 dst=192.168.1.35 sport=1031 dport=23部分:

    the source IP address, destination IP address, source port and destination port

  • UNREPLIED(未回覆)

    we have seen no return traffic for this connection,表示我們還沒看到回包,現在只有單方向的包

  • src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031

    這是我們期待的回包的樣子

服務端返回syn+ack

tcp      6 57 SYN_RECV src=192.168.1.5 dst=192.168.1.35 sport=1031 \
     dport=23 src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031 \
     use=1

收到syn+ack後,此時,我們的狀態由SYN_SENT變成SYN_RECV,此時,也移除了之前的UNREPLIED關鍵字

客戶端第三次握手 ack

tcp      6 431999 ESTABLISHED src=192.168.1.5 dst=192.168.1.35 \
     sport=1031 dport=23 src=192.168.1.35 dst=192.168.1.5 \
     sport=23 dport=1031 [ASSURED] use=1

狀態變成ESTABLISHED,增加了關鍵字:ASSURED

其他

涉及到socket關閉的部分,導致的狀態變化請參考官方文件:https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html#STATEMACHINE

image-20230926220825759

簡單來說,就是兩邊都完成關閉,狀態才變成closed。

狀態標記發生的階段

我們上面可以看到在一個connection上狀態的變化,說白了,這部分就是給一個報文打上標記,關於狀態的標記,後續我們才能基於這個狀態label進行匹配。

所以,我們對於這個標記發生的時間,需要特別注意,可以看下,下圖的左上角的綠色方塊(connection tracking)就是狀態標記發生的位置,基本上是最前面了(最上面是網路卡,再下來是raw,再下來就是connection tracking了)。

在這個綠色方塊執行完成後,狀態就已經標記好了,我們就可以根據狀態來匹配這些報文來進行accept或者drop了,具體看下文。

4515962123a9190c28252a1d8dbedc4

iptables模擬長連線超時後繼續使用該連線的場景

部署圖

我們的兩臺機器如下:

image-20230926221456416

伺服器:10.0.2.15:2222,監聽2222埠

客戶端:10.0.2.4

iptables配置

放行第一次握手請求

三次握手的報文都需要放行,第一次握手請求的特徵是,此時,根據前面我們講的狀態,第一次握手到來時,本地是找不到該四元組的,所以state是new,再加上目的埠是2222,因此,我們的匹配條件是:

--dport 2222 -m state --state NEW,因此,命令如下:

iptables -I INPUT 4 -p tcp --dport 2222 -m state --state NEW -j ACCEPT

放行第二次、第三次握手請求

此時,馬上會收到2222埠返回的syn + ack,此時,狀態就會變成established。

這種報文的特徵是,state為established,這種怎麼放心呢,這種其實預設就放心了,不需要我們再幹啥。

image-20230926222413959

第三次請求,也是同理,狀態會被標記為established。

等待conntrack記錄過期或手動刪除、清空

我們這邊就需要模擬連線建立後,等conntrack記錄過期後,客戶端再使用這個socket會發生什麼。

那麼,我們怎麼來檢視這些conntrack呢,需要安裝工具:yum install conntrack-tools

檢視hash表
[root@node1 ~]# conntrack -L
tcp      6 431997 ESTABLISHED src=10.0.2.2 dst=10.0.2.15 sport=9526 dport=22 src=10.0.2.15 dst=10.0.2.2 sport=22 dport=9526 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1

我們可以看到過期時間為431997,單位是秒,差不多是5天。這個初始值是432000,來自於核心引數:net.netfilter.nf_conntrack_tcp_timeout_established :

[root@node1 ~]# sysctl -a |grep net.netfilter.nf_conntrack_tcp_timeout_established
net.netfilter.nf_conntrack_tcp_timeout_established = 432000

我們測試的話,這個時間就太長了,我們可以修改這個核心引數進行設定。我們可以改成60s過期:

[root@node1 ~]# vim /etc/sysctl.conf
net.netfilter.nf_conntrack_tcp_timeout_established = 60

執行sysctl -p

我們也可以選擇手動刪除、清空這些conntrack記錄:

刪除目標埠為2222的記錄:
conntrack -D -p tcp --dport 2222

清空整個hash表
conntrack -F

實時監控hash表的變動(增刪改)
conntrack -E

丟棄客戶端在長時間空閒的長連線上發過來的包

由於我們上一步執行了conntrack記錄刪除,此時,客戶端發的報文,在iptables看來,又是全新的四元組,也就是會把狀態標記為new,但是,這種請求報文和握手報文明顯不同,這種報文的tcp層一般會設定psh標記,握手請求裡則會設定syn等,所以,我們按照這個進行區分。

匹配條件為:--dport 2222 -m state --state NEW -m tcp --tcp-flags PSH PSH,動作為丟棄:drop

iptables -I INPUT 4 -p tcp --dport 2222 -m state --state NEW -m tcp  --tcp-flags PSH PSH  -j DROP

ok,接下來開始測試。

最終的iptables效果

image-20230926225144983

實際測試

用到了該部落格中的客戶端、服務端程式:

https://blog.csdn.net/fengcai_ke/article/details/125717134

server.c,主要是監聽2222埠;client.c,就是連線併傳送請求。

服務端發起監聽,客戶端發起連線,完成三次握手:

[root@node1 ~]# ./server 

[root@node2 ~]# ./client 
please input:

此時,可以看到三次握手:

image-20230926224609816

服務端的conntrack -E,也可以看到hash表的變化:

[root@node1 ~]# conntrack -E
    [NEW] tcp      6 120 SYN_SENT src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 [UNREPLIED] src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216
 [UPDATE] tcp      6 60 SYN_RECV src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216
 [UPDATE] tcp      6 432000 ESTABLISHED src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216 [ASSURED]

可以看到established的conntrack:

[root@node1 ~]# conntrack -L|grep 2222
conntrack v1.4.4 (conntrack-tools): 5 flow entries have been shown.
tcp      6 431857 ESTABLISHED src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1

接下來,刪除conntrack記錄:

刪除記錄:
[root@node1 ~]# conntrack -D -p tcp --dport 2222
tcp      6 431800 ESTABLISHED src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1
conntrack v1.4.4 (conntrack-tools): 1 flow entries have been deleted.
查詢:
[root@node1 ~]# conntrack -L|grep 2222
conntrack v1.4.4 (conntrack-tools): 4 flow entries have been shown.

客戶端使用該socket發起請求:

[root@node2 ~]# ./client 
please input:11
send result
: Success

可以看到,服務端2222,沒回復,客戶端一直重傳:

image-20230926224959627

觀察iptables,可以發現,確實匹配上了drop那條:

image-20230926225312796

參考

https://blog.csdn.net/fengcai_ke/article/details/125717134

https://blog.csdn.net/weixin_46768610/article/details/109354433

https://www.cnblogs.com/saolv/p/13096965.html

https://mp.weixin.qq.com/s/kv32lyWak4dCaPjMI4_6jw

https://mp.weixin.qq.com/s/bXWAd62R8-g0PyInB9MFIQ

https://mp.weixin.qq.com/s/vTys4GJH_tH5jGrDmR2etw

相關文章