FTP的兩種傳輸模式的坑
- 2020 年 1 月 13 日
- 筆記
上周的系統遷移,進了一個坑,可謂真是坑,從問題的定位,到問題的解決,有很多值得借鑒、學習的,可以算是一次非常有價值的故障排查,

介紹一下背景,這是一套Tuxedo的服務,下圖所示,服務A調用服務B,其中服務A的功能是執行一定的邏輯生成個文件,服務B的功能是將文件推送至客戶FTP(Windows)伺服器的指定路徑中,

這次搬遷的目的,是因為客戶的FTP伺服器需要遷移升級,換了一台機器,但是IP不變,同時更新了FTP帳號的密碼,推送系統(服務A和B)未做任何變更,理論上,對推送端來說,是次很簡單的配合操作。
凡事不能亂立flag,越簡單的操作,可能蘊藏著你所不知的問題。當晚啟動新機器,推送端系統改了配置,執行測試,發現文件推送出現了問題,從服務A的日誌看,文件生成成功,非同步調用服務B,未出現任何錯誤,但是服務B的日誌,未找到這次文件推送的請求,換句話說,從現象上看,服務A生成的文件,在調用下層服務B的過程中消失了?
一開始,沒找著頭緒,以至於嘗試了屢試不爽的重啟大法,還是無效。
其實,從ULOG日誌中,還是看得出一些端倪,他提示了服務B出現過server被kill,自動重啟的現象,

這個476的錯誤,從說明了解,應該就是server被kill,導致重啟,
476 WARN: Server groupid/serverid: client process processid: lost message Description A server died and the specified server (with group groupid and server identifier serverid) is recovering on its behalf. A message from the client with the specified processid has been lost. Additional information will be printed in subsequent messages Action No action required. See Also See Messages 477 and 478 below. 477 WARN: SERVICE=servicename MSG_ID=msgid REASON=server died Description A server died (see message 476) while processing service servicename. The client from which the message was sent is still active so a response message will be sent indicating the failure. Action No action required. 478 WARN: SERVICE=servicename MSG_ID=msgid REASON=server and client died Description A server died (see message 476) while processing service servicename. The client from which the message was sent is no longer active either so that a response message cannot be sent indicating the failure. Action No action required.
那麼現在的問題就是什麼原因導致服務B的server被kill進而重啟?
通過看出現問題的二進位文件,發現卡在了一個叫做putfile的函數上,程式用的FTP底層庫,有這段的邏輯,意思是在執行FTP的put文件指令時,會調用pasv函數,他會執行PASV指令,
int pasv(char *ip){ if(command("PASVrn") <= 0 ... } int putfile(char *name, char *ip){ if(t > 0) close(t); t = pasv(ip); ... }
這時,問題有些豁然開朗了,究其原因,如果了解FTP的朋友,就可能猜到,他可能和FTP的傳輸模式有關。
FTP是一種基於TCP的應用層協議,他不支援UDP協議。FTP工作在一種特殊的服務機制上,他使用兩個埠,一個「數據」埠和一個「命令」埠(也稱為控制埠)。通常情況下,埠21用作命令埠,埠20用作數據埠。然而,數據埠有時候並不是在埠20上時。因此,FTP的傳輸模式,可以分為兩種,主動模式,被動模式。
1. 主動模式
在主動模式的FTP中,客戶端從一個隨機的非系統埠(N>1023)連接到FTP伺服器的命令埠埠21。然後,客戶端開始監聽埠N+1,並將FTP命令埠N+1告訴FTP伺服器,「請把數據發送給我的N+1埠」。然後,伺服器將從本地數據埠(埠20連接回客戶端的數據埠,也就是N+1埠。
因為伺服器防火牆的隔離作用,我們應該確保伺服器FTP到客戶端的一下幾個通道的暢通:
FTP伺服器埠21(接受全部客戶端)
FTP伺服器埠21到>1023的埠(伺服器響應客戶端控制埠)
FTP伺服器埠20到>1023的埠(伺服器發起到客戶端的數據埠的連接)從>1023的埠到FTP伺服器埠20(客戶端發送ack到伺服器的數據埠)
用圖來表示這些通道:

第1步,客戶端的命令埠與伺服器的命令埠連接並發送命令埠1027。然後,伺服器在第2步時將一個ACK發送回客戶端的命令埠。第3步,伺服器在其本地數據埠上啟動連接,連接到前面指定的客戶端的數據埠。最後,客戶端返回ACK,如第4步所示。
主動模式的FTP主要問題實際上落在客戶端。FTP的客戶端並不會主動連接到伺服器的數據埠,而是是告訴伺服器他正在監聽哪個埠,然後伺服器發起連接到客戶端上指定的埠。但是,這樣的連接有時候會被客戶端的防火牆阻止。
2. 被動模式
為了解決伺服器主動發起到客戶端連接會被阻止的問題,另一種更完善的工作模式出現了,他就是FTP的被動模式,縮寫作PASV,他工作的前提是客戶端明確告知FTP伺服器他使用被動模式。
在被動模式的FTP中,客戶端啟動到伺服器的兩個連接,解決了防火牆阻止從伺服器到客戶端的傳入數據埠連接的問題。FTP連接建立後,客戶端在本地打開兩個隨機的非系統埠N和N+1(N>1023)。第一個埠連接伺服器上的21埠,但是客戶端這次將會發出PASV命令,也就是不允許伺服器連接回其數據埠。這樣,伺服器隨後會打開一個隨機的非系統埠P(P>1023),並將P發送給客戶端作為PASV命令的響應。然後客戶端啟動從埠N+1到埠P的連接來傳輸數據。
在被動模式中,要保持一下通道的暢通:
FTP伺服器的21埠(接受所有客戶端)
FTP伺服器的21埠到>1023的遠程埠(伺服器響應客戶端控制埠)
FTP伺服器>1023的埠(接受所有客戶端發起的連接到伺服器指定的隨機埠)
FTP伺服器>1023的埠到>1023的遠程埠(伺服器發送ack和數據到客戶端數據埠)
被動模式用圖表示:

第1步,客戶端在命令埠上與伺服器連接,並發出PASV命令。然後,伺服器在第2步時使用埠2024進行響應,告訴客戶端他正在監聽的數據連接埠。第3步,客戶端啟動從其數據埠到指定伺服器數據埠的數據連接。最後,伺服器在第4步將ACK發送回客戶端的數據埠。
伺服器防火牆需要給FTP的被動模式開放一個埠範圍允許所有客戶端連接,比如5000 – 6000。
因此,從現象以及程式碼,服務B的卡頓,確實可能和被動模式有關,通過服務B進行FTP傳輸,首先設置了PASV,然後hang住,說明可能當前的環境,不支援被動模式。另一個角度,通過命令行驗證,在客戶端伺服器,執行ftp命令,不輸入任何參數的情況下,put文件正常,但是當執行了pasv,此時命令行hang了。
因為服務端的FTP是通過Windows自帶FTP功能搭建的,並不是通過一些常見的FTP工具做的,Windows伺服器的設置,自己不是很熟,網上搜了下,有的說是在伺服器管理器-FTP防火牆支援中,設置數據通道埠範圍,就可以配置被動的模式,

但是,客戶主機工程師通過如下配置,設置被動模式,不覺明歷,
1. sc sidtype ftpsvc unrestricted(將ftp服務的註冊卸載) 2.net stop ftpsvc & net start ftpsvc(重啟ftp服務) 3.netsh advfirewall firewall add rule name="FTP for IIS7" service=ftpsvc action=allow protocol=TCP div=in(開啟所有ftp埠監聽) 4.netsh advfirewall set global Statefulftp disable(使防火牆不攔截所有ftp服務的訪問)
執行測試,此時服務A和B,能配合推送FTP文件,這才算是從坑中跳出來。
對開放系統來說,一個問題的解決,往往蘊涵著很多關聯的知識,一方面能找到問題的突破口(通過日誌、pstack、gdb等指令定位問題),另一方面能由點及面的武裝自己的知識庫(FTP的傳輸模式有何區別、如何設置不同的傳輸模式),這些可能都是需要在日常工作和學習中積累和沉澱的。
其實,對個人來說,碰到問題是好事,但前提是能積累這些問題,無論是誰的經驗,能融會貫通,轉成自己的知識,同一個坑,不要進入兩次,就很有價值了。