如何使用iptables防火牆模擬遠程服務超時

前言

超時,應該是程序員很不愛處理的一種狀態。當我們調用某服務、某個中間件、db時,希望對方能快速回復,正確就正常,錯誤就錯誤,而不是一直不回覆。目前在後端領域來說,如java領域,調用服務時以同步阻塞調用爲主,此時一般會阻塞當前線程,等待結果。如果我們設置了超時時間還好,一段時間等不到就報錯了,要是超時時間沒設置或者過長,會導致線程動不了,即hang住了,多來幾個這種線程,線程池也就全都hang住了,此時,我們也就沒辦法響應前端了。

由於業務代碼或者底層框架編碼時不注意超時問題,這個問題經常會在線上纔出現(比如依賴的某個服務A,長時間運行的情況下,會出現響應慢問題,但是在平時開發環境服務A經常重啓,把問題掩蓋了,我們依賴方在開發環境測,當然也就沒注意A可能超時,等已上線,A一超時,我們就完蛋了)。

我前面幾篇文章的起源,也就是研究線上一個問題,就是懷疑我們服務中的數據庫連接池的連接被db或者防火牆幹掉了,導致我們這邊因爲也沒設置超時時間,進而卡死。

當時我就想模擬oracle數據庫不響應的情況,發現還是很不好模擬,後面經過各種查資料,才發現現在使用的這種iptables防火牆丟棄oracle返回的數據包的方式。

實驗環境

我們要模擬的事情如下:

image-20230729212918357

oracle我就不模擬了,原理一樣的,那個網絡包要複雜一些,講起來就重點發散了。

這個圖裏,服務A就是相當於oracle,我們這裏是簡單啓動一個http服務:

python -m SimpleHTTPServer 8000

我們直接請求看下結果:

image-20230729213214486

然後,我們後臺服務部署在10.80.121.46服務器的8084端口,我們正常請求該端口的接口,它就會去請求服務A,將服務A的響應返回給我們。

@PostMapping("/")
@ApiOperation(value = "訪問遠程服務")
public String test() {
    String s = HttpClientUtil.doGet("http://10.80.121.115:8000/");
    log.info("resp:{}",s);
    return s;
}

image-20230729213440371

實驗步驟

iptables安裝啓動

yum install -y iptables-services

systemctl start iptables
systemctl stop iptables
systemctl status iptables
如果提示綠色的“active (exited)”,則iptables已經啓動成功。

允許瀏覽器訪問8084端口

允許訪問服務端口,否則沒法測試,另外,如果對iptables完全不懂的,先去看下我前一篇再回來看。

iptables -I INPUT -p tcp -m tcp --dport 8084 -j ACCEPT

思路

要模擬出服務A不返回數據的效果,其實有兩種思路,一種是,把我們程序發給服務A的包丟掉;一種是,不丟棄我們發出去的包,但是,把服務A回給我們的包丟掉。

思路2:丟棄服務A返回的數據包

我是一開始就用的這個思路,所以先講這種。

這種的話,有的人可能覺得很簡單,實際上不是那麼簡單。我們不能簡單地把服務A返回給我們的包,全丟,因爲這樣的話,tcp連接都沒法建立。所以,我們要丟服務A返回的數據包,但是,tcp三次握手的包不能丟。

我們來分析下,這兩種包的不同之處。

三次握手時,對方返回的包長這樣:

image-20230729214357116

即,tcp標誌位設置了SYN/ACK。

再來看看返回數據時的報文:

image-20230729214524226

可以看到,標誌不一樣,這次是PSH/ACK。

所以,我們可以根據標誌來進行區分。

所以,最終我們的丟包策略是:

iptables -I INPUT 1 -p tcp -m tcp  --tcp-flags PSH,ACK PSH,ACK --sport 8000 -j DROP

上面的意思是,對於服務A返回的包(源端口--sport爲8000),如果tcp標誌爲--tcp-flags PSH,ACK PSH,ACK,就drop

這個tcp-flags的語法,詳細如下:

[!] --tcp-flags mask comp
              Match when the TCP flags are as specified.  The first argument mask is the flags which we should examine, written as a comma-separated  list,  and  the
              second argument comp is a comma-separated list of flags which must be set.  Flags are: SYN ACK FIN RST URG PSH ALL NONE.  Hence the command
               iptables -A FORWARD -p tcp --tcp-flags SYN,ACK,FIN,RST SYN
              will only match packets with the SYN flag set, and the ACK, FIN and RST flags unset.

後面接了兩端,第一段是掩碼,表示要檢查哪些標誌位(這裏指定n個要檢查的標誌位,別的沒說要檢查的,咱們就不管了),第二段是我們預期應該要設置的標誌,比如,我們這裏預期是要設置了PSH和ACK的纔算匹配。

ok,加完這個,我們再請求一次,看看效果,我們看前臺是轉圈,後臺日誌呢,是過了很久之後,顯示超時:

image-20230729215400867

然後,我在後臺服務機器抓了包,可以看到,下面全是超時重傳,因爲服務A的數據發過來,我們丟棄了,服務A以爲我們沒收到,一直重發:

image-20230729215553769

而我們服務這邊,代碼也是有點問題的,超時時間用的默認的,沒設置:

image-20230729215733831

過了很久,都快3分鐘了,一直等不到迴應,纔去斷開連接,這要是在線上,不是又悲劇了嗎?所以,這個模擬超時,還是可以找出一些我們代碼問題的,有點用。

另外,我們看到,對方還給我們回覆了RST,我之前遇到過一種情況,對方回覆RST後,我們這邊連接就斷開了,報錯是:broken pipe,而不是read time out,如果,我們必須要模擬出read time out這種異常的話,可以把對方的RST也給丟了。

iptables -I INPUT 1 -p tcp -m tcp  --tcp-flags RST RST --sport 8000 -j DROP

思路1:丟棄我們發出去的包

這個思路呢,和上面的原理類似,也是看看我們要發出去的包有什麼特徵,然後識別出來丟棄。

我最終寫出來的規則如下:

iptables -I OUTPUT 1 -p tcp -m tcp  --tcp-flags PSH,ACK PSH,ACK --dport 8000 -j DROP

首先,這個是出去的包,所以,-I OUTPUT表示把規則寫入OUTPUT鏈,然後tcp標誌和上面是一樣的(這個需要自己結合tcpdump去觀察這些標誌),然後目的端口是8000,這樣的包,就是我們程序發出去的包,丟棄。

另外,我也觀察了tcpdump抓包,這次,意外的是:

image-20230729220937910

整個過程,在tcpdump看來,只發現有三次握手的包,而程序發出去的包,被iptables丟棄後直接沒進協議棧,壓根就沒再繼續了,所以tcpdump也就看不到。

這個思路看起來更簡單暴力一些。

最終的效果還是一樣的:

image-20230729221133848

結語

整個過程就講完了,iptables暫時告一段落,最近要忙點其他的事情了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章