前言
我們的程式,在實際的網路部署時,一般比較複雜,會經過很多的網路裝置,防火牆就是其中的一種。做開發的同事,一般對這塊瞭解不多,也很可能被防火牆坑到。比如,應用一般需要訪問資料庫,為了避免頻繁建立連線,一般是會提前建立一個連線池,每次來一個請求,就從連線池取一個連線來用,用完再歸還到池子裡。
連線池中的連線是啥呢,其實就是和資料庫之間的完成了三次握手後的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狀態連線的回包,也就是完成了連線的雙向關聯。
其他幾種狀態,RELATED和INVALID我感覺一般比較少用,至於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
簡單來說,就是兩邊都完成關閉,狀態才變成closed。
狀態標記發生的階段
我們上面可以看到在一個connection上狀態的變化,說白了,這部分就是給一個報文打上標記,關於狀態的標記,後續我們才能基於這個狀態label進行匹配。
所以,我們對於這個標記發生的時間,需要特別注意,可以看下,下圖的左上角的綠色方塊(connection tracking)就是狀態標記發生的位置,基本上是最前面了(最上面是網路卡,再下來是raw,再下來就是connection tracking了)。
在這個綠色方塊執行完成後,狀態就已經標記好了,我們就可以根據狀態來匹配這些報文來進行accept或者drop了,具體看下文。
iptables模擬長連線超時後繼續使用該連線的場景
部署圖
我們的兩臺機器如下:
伺服器: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,這種怎麼放心呢,這種其實預設就放心了,不需要我們再幹啥。
第三次請求,也是同理,狀態會被標記為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效果
實際測試
用到了該部落格中的客戶端、服務端程式:
https://blog.csdn.net/fengcai_ke/article/details/125717134
server.c,主要是監聽2222埠;client.c,就是連線併傳送請求。
服務端發起監聽,客戶端發起連線,完成三次握手:
[root@node1 ~]# ./server
[root@node2 ~]# ./client
please input:
此時,可以看到三次握手:
服務端的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,沒回復,客戶端一直重傳:
觀察iptables,可以發現,確實匹配上了drop那條:
參考
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