FTP 被動模式為何會被防火牆阻擋?
FTP 可登入但目錄清單卡住?資料通道使用獨立連接埠。了解如何設定被動模式連接埠範圍、防火牆規則,以及 NAT 後方伺服器的必要設定。
FTP 可以登入,但目錄清單會卡住的原因
FTP 被防火牆阻擋時,被動模式會失效,因為 FTP 使用 2 條 TCP 連線,而不是 1 條。連往 21 埠的連線會傳送登入資訊與指令,因此使用者名稱和密碼能通過驗證,防火牆看起來也沒有問題。接著,第一個 ls 還需要在另一個連接埠上建立第 2 條連線。防火牆沒有放行該連線,因此用戶端會一直等待,直到逾時。
解決方法是為這些資料連線指定固定的連接埠範圍,並在防火牆上允許相同的範圍。位於 NAT(network address translation)後方的伺服器還需要多設定一項,讓它公告正確的位址。過去可由 connection tracking helpers 自動處理這項工作。現在已不再如此;在直接套用舊版指南前,應先了解其中原因。
控制通道與資料通道
FTP(檔案傳輸協定)定義於 RFC 959,早於 NAT 與有狀態防火牆。工作階段會建立一個連往 TCP port 21 的控制連線,並在整個工作階段期間保持連線。命令以純文字傳送。回覆則由 3 位數代碼與一行文字組成。這個連線不會傳送檔案內容。
每筆資料都會使用自己的 TCP 連線:目錄清單使用一條(LIST),每次下載使用一條(RETR),每次上傳也使用一條(STOR)。連線建立後使用一次,隨即關閉。驗證完全在控制通道上進行,因此資料路徑中斷時,表現一律相同:登入成功,接著卡住。如果用戶端顯示 230 回覆後,在取得清單時停止,問題出在資料通道,而不是認證資訊。
主動模式:伺服器回連用戶端
在主動模式中,用戶端選擇一個連接埠,在該連接埠上監聽,並告知伺服器連線目標:
PORT 192,168,1,50,195,80前 4 個數字是用戶端的 IP 位址。最後 2 個數字是連接埠,編碼為 2 個位元組:195 * 256 + 80 = 50000。接著,伺服器會從自己的連接埠 20 建立資料連線,連至用戶端的連接埠 50000。
從用戶端的角度來看,這是進入且未經請求的連線,因此用戶端防火牆會將其丟棄。如果用戶端位於家用路由器後方,`PORT` 命令中的位址是私有位址,伺服器根本無法連線到該位址。主動模式正是 FTP 被認為經常無法運作的原因。
被動模式:用戶端開啟兩條連線
被動模式反轉資料連線的建立方向。用戶端傳送 PASV,伺服器則回應自己的位址與連接埠:
227 Entering Passive Mode (203,0,113,10,195,80)編碼方式相同,因此用戶端會連線至 203.0.113.10 的 50000 埠。此時兩條連線都由用戶端開啟,這也是被動模式能通過用戶端 NAT,且目前所有用戶端都會優先要求使用被動模式的原因。
問題只是轉移,並未消失。未經請求的輸入連線現在會抵達你的伺服器,且使用每次傳輸都會變更的高號連接埠。該防火牆由你管理,因此問題也落在你身上。
EPSV(extended passive mode,RFC 2428)採用相同概念,但回應格式更簡潔:
229 Entering Extended Passive Mode (|||50000|)回應中不包含位址。用戶端會重用控制連線已使用的位址,因此可在 IPv6 上運作,也排除了一整類 NAT 錯誤。curl 的手冊指出,curl 通常會先嘗試 EPSV,再嘗試 PASV。連接埠仍會在執行時選取,因此 EPSV 不會改變你的防火牆規則。
為何一般防火牆規則無法允許資料通道
因為編寫規則時,該連接埠號碼尚不存在。伺服器會為每次傳輸選擇連接埠。使用預設值時,vsftpd 將 pasv_min_port 與 pasv_max_port 記錄為 0,也就是「使用任意連接埠」,因此資料連線可能從任何大於 1023 的連接埠進入。sudo ufw allow 21/tcp 只允許控制通道,沒有允許其他內容;這正是登入成功但目錄列表無法載入的配置。如果對服務監聽單一固定連接埠的概念仍不清楚,Linux 中的連接埠與監聽 socket 如何運作可提供相關背景。
有狀態防火牆確實會追蹤連線,核心也能將新連線判定為現有連線的 RELATED。但對 FTP 而言,必須有元件讀取控制資料流,並從 227 或 PORT 行中擷取連接埠。預設沒有任何元件會執行這項工作。
為什麼 FTP 連線追蹤 helper 不再是解決方案
較早期的指南會指示你使用核心模組 nf_conntrack_ftp。它會讀取明文控制通道,找出所宣告的埠,並建立 expectation,因此資料連線不必透過明確指定埠號的規則即可通過。自那些指南撰寫後,已有 4 項變更。
自動指派 helper 已停用。核心文件將 nf_conntrack_helper sysctl 說明為「0 - disabled (default)」,並補充:「若停用,必須設定 iptables 規則,將 helper 指派給連線。」單獨載入模組不會產生任何作用。
在目前的核心中,這個開關已移除。執行 sysctl net.netfilter.nf_conntrack_helper。若回覆為 sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_helper: No such file or directory,表示核心已沒有可重新啟用的自動 helper 指派功能。若回傳數值,表示該開關仍存在,其預設值為 0。
防火牆前端也已棄用這項功能。Ubuntu 24.04 的 man ufw-framework 對 /etc/default/ufw 中的 IPT_MODULES 行說明:「以這種方式無條件載入連線追蹤模組(nf_conntrack_*)已棄用」,並補充 helper 規則「必須透過 RULES FILES 管理」。firewalld 在 firewalld.conf 中將 AutomaticHelpers 說明為「Deprecated. This option is ignored and no longer used.」現在附加 helper 必須手動撰寫使用 CT target 的明確規則。這比下方的修正方式更繁瑣,而且一旦啟用 TLS 就會失效。Ubuntu 上的 iptables 與 nftables 說明這些規則實際存放的位置。
TLS 會使這項做法失效。helper 必須讀取文字形式的控制通道才能運作。控制通道加密後,helper 看到的是密文,因此無法找出埠號。這沒有修正方法,而且也不應該有:能讀取控制通道的中間設備,也就能讀取你的密碼。
在伺服器上指定被動連接埠範圍
每個 FTP 伺服器都能設定從指定範圍選擇被動連接埠。選項名稱各不相同,因此請查閱實際執行之伺服器的文件。
vsftpd 的設定檔位於 /etc/vsftpd.conf:
pasv_enable=YES
pasv_min_port=30000
pasv_max_port=30099pasv_enable 預設值已是 YES。兩個連接埠選項的預設值為 0,也就是上述「使用任意連接埠」的行為。執行 sudo systemctl restart vsftpd 套用設定,然後以 systemctl status vsftpd 確認服務已重新啟動。vsftpd 遇到無法解析的設定行時會拒絕啟動,不會忽略該設定。因此,如果重新啟動失敗,請檢查 journalctl -u vsftpd -n 20,尋找指出剛輸入選項的 500 OOPS: 行。
ProFTPD 的設定檔位於 proftpd.conf:
PassivePorts 30000 30099ProFTPD 未在此處記載預設值:未設定此指令時,由核心選擇連接埠。其文件也說明,當指定範圍內沒有可用連接埠時,伺服器會改用核心指派的連接埠,並寫入日誌。範圍過小時,服務會偶爾發生問題,而不是明確失敗,因此更難診斷。請使用非特權連接埠,也就是 1024 以上的連接埠。
Pure-FTPd 使用旗標 -p first:last。man pure-ftpd 將其記載為「被動模式下載只使用 first 到 last 範圍內的連接埠」,並表示此設定「能讓 pure-ftpd 與封包過濾器更相容」。套件版本通常會在設定檔中包裝此旗標,因此請查閱發行版本身的文件確認檔名,不要自行猜測。
需要多少連接埠?每個同時進行的資料連線需要 1 個。TCP 連接埠關閉後,會在 TIME_WAIT 中維持幾分鐘,之後才能重複使用,因此請配置預期尖峰用量數倍的範圍。100 個連接埠足以應付少數使用者;繁忙的公開伺服器則需要多得多。
範圍應位於哪裡?先執行 sysctl net.ipv4.ip_local_port_range。在標準 Ubuntu 主機上,其輸出為 32768 60999,也就是核心分配給對外連線的連接埠範圍。若被動連接埠範圍位於此視窗內,可能與已由對外連線占用的連接埠衝突,因此請將範圍放在此視窗以下。預設主機上的 30000 至 30099 沒有此問題。請檢查自己的主機,不要直接採信這個數值。
在防火牆中開放相同的連接埠範圍
ufw 使用冒號表示範圍,其手冊指出,範圍或清單「也可用來指定多個連接埠;在此情況下必須指定通訊協定」:
sudo ufw allow 21/tcp
sudo ufw allow 30000:30099/tcp
sudo ufw status verbose現在執行 ufw status verbose 應會列出兩筆規則。若執行不含 /tcp 的相同命令,ufw 會拒絕該命令,並要求指定 tcp 或 udp,因為 ufw 不會自行猜測。VPS 上的 ufw 規則語法涵蓋其餘內容。
firewalld 使用連字號表示範圍,並且需要重新載入:
sudo firewall-cmd --permanent --add-service=ftp
sudo firewall-cmd --permanent --add-port=30000-30099/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all--add-service=ftp 會開放 21/tcp,並要求使用 ftp helper;這是隨附服務定義所指定的 helper。它不會開放你的被動模式範圍,因此單獨執行時,結果與一開始完全相同。VPS 上的 firewalld 區域與服務提供更完整的說明。
直接在 input chain 中使用 nftables:
tcp dport { 21, 30000-30099 } accept還有一個需要記住的防火牆:大多數供應商會在控制面板中執行網路防火牆,位於作業系統之外。如果伺服器上的規則看起來正確,但封包仍完全無法抵達,也要在該處開放相同的範圍。
伺服器位於 NAT 後方時,告知其公開位址
在伺服器上執行 ip -4 addr show。如果介面上的位址就是用戶端連線的位址,請略過本節。如果介面使用私有位址(10.x、172.16 到 172.31.x、192.168.x),而平台將公開位址映射到該私有位址,伺服器便不知道自己的公開位址。vsftpd 將 pasv_address 的預設值說明為「從傳入的已連線 socket 取得位址」,因此 227 回應會帶有私有位址,並將用戶端導向無法連線的位置。
FileZilla 對這個問題有明確名稱:
Server sent passive reply with unroutable address. Using server address instead.FileZilla 會修正這個問題並繼續傳輸。許多其他用戶端則不會。它們會連線到 10.0.0.5,然後停止回應。
curl 也會隱藏這個問題;如果你使用 curl 進行測試,這點很重要。其手冊指出,--ftp-skip-pasv-ip「預設已啟用(於 7.74.0 加入)」,因此 curl 會忽略 227 回應中的位址,改用控制連線的位址。使用 curl 可以成功的傳輸,在圖形化用戶端中仍可能因為這個原因失敗。
請明確設定位址。vsftpd 使用 pasv_address=203.0.113.10;如果偏好使用主機名稱,也可以使用 pasv_addr_resolve=YES(預設為 NO)。ProFTPD 使用 MasqueradeAddress,可接受位址、DNS 名稱或介面名稱。Pure-FTPd 使用 -P;文件說明此選項適用於「伺服器位於 masquerading (NAT) box 後方」的情況。EPSV 可避免整個問題,因為其回應不含位址欄位;但不能依賴這項特性,因為要傳送哪個命令是由用戶端決定的。
TLS 的變更
FTPS 是透過 TLS(傳輸層安全性)的 FTP。用戶端照常連線至 21 埠,傳送 AUTH TLS 來保護控制通道,接著傳送 PROT P,同時加密資料通道。純 FTP 會將密碼以可讀文字傳送到網路上,因此如果必須繼續使用 FTP,請改用 FTPS。vsftpd 提供的 ssl_enable 預設為 NO。
這會帶來兩項結果。任何連線追蹤 helper 都無法運作;這正是上一節從另一個角度看到的問題。此外,sudo tcpdump -nAi any 'tcp port 21' 不再顯示 227 回覆。因此,當你需要知道伺服器公告了哪個位址與埠時,請查看伺服器自己的日誌,不要查看網路封包。
從外部測試變更
請從另一台機器執行以下指令。從伺服器本機測試會繞過你要修正的防火牆。
sudo ss -ltnp | grep :21
curl -v --disable-epsv --user ftpuser:secret ftp://example.com/
nc -vz example.com 30000ss 應顯示 FTP daemon 正在監聽 port 21。伺服器閒置時,被動連接埠範圍內不會有任何監聽中的連接埠,因為這些 socket 會在傳輸時建立,並於傳輸結束後關閉。
--disable-epsv 會強制 curl 使用 PASV 路徑。這是會暴露位址問題的路徑。追蹤輸出會先顯示伺服器的回應,再顯示 curl 要連線的位址與連接埠:
< 227 Entering Passive Mode (203,0,113,10,117,52)117 * 256 + 52 = 30004,位於宣告的範圍內。若該行顯示私有位址,表示未設定 pasv_address。若連接埠超出你的範圍,表示伺服器從未讀取設定變更,因此請確認你編輯的是執行中服務所使用的檔案。
nc 可單獨回答防火牆問題。立即出現 Connection refused 表示封包已抵達伺服器,但找不到任何監聽中的服務。對閒置的被動連接埠而言,這是正確結果,表示你的規則有效。若連線持續等待,直到 nc 放棄,表示封包遭到靜默丟棄。這是防火牆問題,可能是該主機上的防火牆,也可能是供應商控制面板中的防火牆。這項區別與 SSH 被拒絕與逾時的差異 中的說明相同,並適用於所有連接埠。
仍然應該使用 FTP 嗎?
對於新的工作,不應使用。SFTP(SSH file transfer protocol)會在單一 SSH 連線中透過 22 埠運作。不需要第二個通道、被動模式連接埠範圍、NAT 設定或額外的 daemon,因為 OpenSSH 已經提供這項功能。sftp user@example.com 可在完全未設定檔案傳輸服務的主機上運作。若只要提供檔案而不開放其他功能,sshd_config 會將 ForceCommand internal-sftp 與 ChrootDirectory 搭配使用。該目錄必須由 root 擁有,且使用者不得具備寫入權限,否則 sshd 會拒絕工作階段,並記錄一行 bad ownership or modes for chroot directory。
當對端無法變更時,FTP 仍有使用情境。掃描器與多功能事務機出廠時搭載的韌體只支援 FTP。實驗室與工業設備通常執行固定映像檔,沒有人會重新進行認證。業務合作夥伴可能提供 FTPS drop,且不會為單一供應商新增其他通訊協定。在這些情況下,被動模式連接埠範圍與相符的防火牆規則就是全部設定,而應執行 FTPS,不是未加密的 FTP。雙通道設計源自 1985 年的決策,現在卻運作在當時未預期的環境中;檔案傳輸協定的歷史介紹了這段背景。
FAQ
為什麼 FTP 可以登入,但目錄清單會卡住?
登入只使用 port 21 上的控制連線,而防火牆允許這個連線。目錄清單需要在其他連接埠上建立第二個 TCP 連線,但該連線遭到封鎖。在 FTP 伺服器上指定被動模式連接埠範圍,並在防火牆中開放相同範圍,目錄清單就能正常完成。目錄清單卡住是資料通道問題,不是密碼問題。
FTP 被動模式需要開放哪些連接埠?
控制通道需要 port 21,此外還需要開放你為被動資料連線設定的連接埠範圍。這沒有標準範圍,因為範圍由你自行選擇。例如 30000 到 30099 即可:範圍大小應足以應付同時傳輸的尖峰數量,並避開 kernel 的傳出連接埠範圍。你可以使用 sysctl net.ipv4.ip_local_port_range 查看該範圍。如果你的 provider 在控制面板中提供網路防火牆,也要在該處開放相同範圍。
我還需要 nf_conntrack_ftp 嗎?
不需要,而且在目前的 kernel 上不能依賴它。自動 helper 指派預設已停用;在近期 kernel 中,net.netfilter.nf_conntrack_helper switch 已移除,因此 sysctl 會回報檔案不存在。ufw 的手冊將無條件載入這些 module 標示為已淘汰,而 firewalld 完全忽略 AutomaticHelpers。helper 還必須以純文字讀取控制通道,因此啟用 FTPS 後就會立即失效。請改為指定被動模式連接埠範圍。
為什麼 FTP client 表示被動模式回應包含無法路由的位址?
伺服器使用 PASV 回應時,提供了它在自身介面上看到的位址,而該位址是 private 位址。當平台將 public 位址映射到 private 位址時,就會發生這個問題。請明確設定 public 位址:在 vsftpd 中使用 pasv_address,在 ProFTPD 中使用 MasqueradeAddress,或在 Pure-FTPd 中使用 -P。FileZilla 會改用原先連線時使用的位址,並記錄「Using server address instead」,因此部分 client 能避開這項設定錯誤,其他 client 則會卡住。
我應該使用 FTPS 還是 SFTP?
凡是兩端都由你控制,請使用 SFTP:它在 port 22 上透過 SSH 建立單一連線,不需要開放資料通道,而且通常已在執行。FTPS 是透過 TLS 傳輸的 FTP,因此仍保留雙通道設計,以及由此產生的所有防火牆問題。只有在對方不支援其他協定時,才選擇 FTPS。不要在網際網路上使用純 FTP,因為密碼會以可讀文字在網路上傳輸。