昨天為了一個ftp問題折騰了一天。問題背景:原來有個接口涉及到上傳文件,服務端更換了ftp服務器,我們這邊需要刷新連接服務端的ip和端口配置,代碼沒動。聯調環境和驗收環境都測試通過,一到生產環境就歇菜了。我們手工連接ftp並上傳文件正常,就是跑接口由程序上傳不行。根據日志信息定位發現在登錄ftp后程序做了兩個事情,一個是把傳輸模式設置為二進制,一個是設置被動模式,用apache的Ftpclient實現:
ftpClient.enterLocalPassiveMode();
從代碼層面看不出問題,因為我們根本就沒動,因此開始懷疑服務端配置問題,找他們確認后,他們的ftp服務器設置的就是被動模式。這里需要區別下主被動模式:主動模式下客戶端通過命令端口(通常是21)連服務端后,服務端通過數據端口(通常是20)“主動”連接客戶端;被動模式下客戶端通過命令端口連接服務端后,服務端還是通過命令端口告訴客戶端“我的XX數據端口可以連接,你過來吧”,於是客戶端按該指定數據端口連接過去,服務端“被動”連接。命令端口就是傳輸ftp命令用的,數據端口就是上傳下載文件用的。XX端口一般不固定且大於1024。既然服務端是被動模式,那么它會告知我們一個隨機端口給我們去上傳文件。服務端的命令端口是10000,開通了10001-10100作為數據端口。但現在不通,兩邊都說沒問題,沒辦法,只能抓包:
220 "welcome to FOOBAR FTP service."
USER wlf
331 Please specify the password.
PASS 123
230 Login successful.
TYPE I
200 Switching to Binary mode.
PASV
227 Entering Passive Mode (222,111,8,111,10,40)
500 OOPS:
vsf_sysutil_recv_peek: no data
讓服務端改為主動模式重試,抓包結果一樣。這說明只要我們程序里設置了客戶端為被動模式,服務端無論初始設置是主動還是被動,都會按被動模式來進行,而問題很可能就出現在服務端再次發送過來的連接通道上,如果該通道對客戶端不可達,那么必將導致客戶端再次連接服務端上傳文件失敗。經確認,我們跟服務端之間是用內網連接的,登錄時也是根據內網ip來的,從抓包看登錄成功。而從報錯的信息看,客戶端設置了被動模式后服務端新建到客戶端的連接使用的是經過NAT映射后的外網ip,就是上面那個222.111.8.111。客戶端內網跟服務端外網網絡是不通的,導致客戶端到服務端新建的數據傳輸管道無法連通。
綜上所述,問題的解決方案有三個:要么服務端把外網ip改為內網ip,要么客戶端使用主動模式上傳文件,要么開通客戶端內網到服務端外網的網絡策略。第一個方案服務端不同意,理由是他們還有其他客戶端,需要統一提供外網;第二個方案我不知道能否開通內網到內網映射的外網的網絡策略,有待確認;所以最終只能我們改代碼,客戶端不再設置被動模式,把上面那一行設置被動模式的代碼注釋掉就可以了,默認就是按主動模式來的。