21
FTP 的控制連線——指令和三位數的回應碼走的是這個連接埠,檔案本身則走另一條連線,而那條用的連接埠沒有人告訴過你的防火牆。
照你的系統挑一行貼上去執行。它會直接印出占著這個連接埠的行程,以及它綁在哪個位址上——通常看到這兩件事答案就出來了。
sudo lsof -nP -iTCP:21 -sTCP:LISTEN每一個監聽中的 socket 一行。COMMAND 是程式,PID 是你真的會動手的那個號碼,USER 是它跑在哪個帳號下,NAME 則是它綁的位址。`127.0.0.1:21` 只有回送位址,所以不管防火牆怎麼開,別台機器永遠連不進來;`*:21` 才是每一張介面。`-n` 讓 lsof 不把位址解析成主機名稱,`-P` 讓它不要把 `21` 印成 `ftp`,NAME 那一欄好讀就是靠這兩個。`-sTCP:LISTEN` 會濾掉已建立的工作階段;想看現在誰在傳檔就把它拿掉。完全沒有輸出、lsof 以 1 結束,代表沒有東西占著它。不加 `sudo` 只看得到自己的行程,所以以 root 執行的 daemon 根本不會出現。
sudo ss -tlnp 'sport = :21'`-t` 只看 TCP,`-l` 只看監聽中的 socket,`-n` 讓連接埠維持數字,`-p` 補上所屬行程;`sport = :21` 是 ss 對來源埠的過濾式。先讀 Local Address:Port 這一欄——`0.0.0.0:21` 是每一張 IPv4 介面,`127.0.0.1:21` 只有回送位址,遠端用戶端一直不會來多半就是這個原因,`[::]:21` 是 IPv6 萬用位址,多數 Linux 主機連 IPv4 也一起收。持有者在最後一欄的 `users:(("vsftpd",pid=1102,fd=3))`:引號裡是程式名,後面是 PID。機器上沒有 `ss` 的話,net-tools 的老寫法是 `sudo netstat -tlnp | grep ':21 '`,`-p` 會印出以斜線分隔的 pid/program——樣式裡最後那個空格要留著,不然 `:2121` 和 `:21000` 也會一起命中。兩種寫法都一樣:沒有超級使用者權限時,socket 照列,行程那一欄卻是空的,看起來像「沒東西」,其實不是。
netstat -ano | findstr ":21"`-a` 會把連線和電腦正在監聽的連接埠都列出來,`-n` 讓位址和連接埠維持數字,`-o` 在最後一欄補上所屬行程 ID。那一行要是監聽者,State 欄必須是 LISTENING,其他都是用戶端的工作階段。Local Address 就是綁定位址:`127.0.0.1:21` 只有回送位址,`0.0.0.0:21` 是每一張介面。用 `tasklist /FI "PID eq 1102"` 把 PID 換成程式名,這個 PID 過濾器吃 eq、ne、gt、lt、ge、le。小心子字串:`findstr` 比對的是整行任何位置,所以 `":21"` 也會命中 `:2121`、`:21000`,還有任何含 21 的臨時來源埠——動手砍之前,把號碼整個讀完。
Get-NetTCPConnection -LocalPort 21 | Select-Object LocalAddress,LocalPort,State,OwningProcessPowerShell 這版是把連接埠當數字比對而不是子字串,這就是它值得擺在 `netstat` 旁邊的理由。`State` 是 `Listen` 而 `LocalAddress` 是 `127.0.0.1`,就正好是那種只回應本機、其他人一律拒絕的設定。把 `OwningProcess` 餵給 `Get-Process -Id` 就能拿到執行檔名稱;在 Windows 上,監聽者通常是開機就起來的服務,而不是你在 shell 裡啟動的東西。結果是空的但綁定還是失敗,代表這個連接埠是被保留而不是被占用,`netsh interface ipv4 show excludedportrange protocol=tcp` 會列出 Hyper-V、WSL2 和 Docker Desktop 開機時搶走的區段。
`nmap -Pn -p 21 files.example.com` 從主機外面問了唯一重要的那個問題,而 `-Pn` 會跳過主機探索,所以不回 ping 的機器照樣掃得到。答案的意思由 nmap 自己的定義決定:`open` 是那個連接埠上真的有應用程式在接受連線;`closed` 是連接埠到得了、也回了話,但沒有應用程式在監聽;`filtered` 是封包過濾讓探測封包根本到不了連接埠,所以 nmap 分不出來它其實是前面哪一種。加 `--reason` 可以看到是什麼決定了結果——closed 回的是 reset,filtered 是一片安靜;加 `-sV` 則會讓 nmap 讀 `220` 那句招呼,通常直接報出 daemon 名稱和版本。這個連接埠有一點要特別注意:這次掃描完全沒有講到資料連線,因為被動模式傳檔用的是掃描根本沒碰的高位連接埠。21 顯示 `open`、下載卻卡在零位元組,這兩件事完全可以同時成立。只掃你自己負責的主機。
| 你看到的訊息 | 代表什麼 |
|---|---|
ftp: connect: Connection refused | 封包送到了主機,而它的核心回了一個 reset,因為沒有任何行程占著連接埠 21。可能是 daemon 停了、啟動失敗,或綁在一個不含你撥過去那個位址的地方。上面那些探測指令要在伺服器上跑,不是在你的筆電上,而且不要只確認有那一行,要把綁定位址讀出來。 |
The client sits silent for tens of seconds, then reports Connection timed out | 完全沒有回應。有個封包過濾器正安靜地丟掉你的 SYN——雲端的安全群組、主機上的防火牆,或路徑中間的網路 ACL。被拒絕會立刻回來,被丟掉要等,這段等待本身就是診斷。從外面用 `nmap --reason` 確認:`filtered` 是丟掉,`closed` 是拒絕。 |
425 Can't open data connection | 連接埠 21 沒問題。你已經登入、伺服器也收下了指令,不然不會回你一個帶號碼的回應。壞掉的是第二條連線。被動模式下,可能是伺服器給了你一個高位連接埠而你的防火牆不放你出去,也可能是伺服器自己那段被動範圍沒有對外開。主動模式下,則是伺服器要回撥給你的用戶端,被中間某一段擋住了。切換模式看哪一邊會通:多數用戶端用 `passive` 切換,而 `curl` 除非你加 `-P -`,否則一律走被動。 |
500 Illegal PORT command | 伺服器直接拒絕主動模式,現在很多都這樣做,因為那個回撥是眾所周知的濫用途徑。改用被動模式。另外還有一種長得一樣的情況:NAT 改寫了 `PORT` 指令裡的位址,伺服器看到的位址跟它正在講話的那條控制連線對不起來,於是刻意擋掉。 |
227 Entering Passive Mode reports an address you cannot route to | 在 NAT 後面的伺服器如果沒被告知自己的對外位址,就會把私有位址原封不動廣播出去,於是用戶端很老實地去連 10.0.0.5 這種地方然後卡死。要修的是伺服器端:設定它該廣播的外部位址,或改用擴充的 `EPSV`,那個只回連接埠,位址沿用控制連線的。 |
530 Login incorrect | 這裡沒有一件事跟連接埠有關。控制連線已經建立,伺服器是讀完帳密之後才拒絕你的。不是帳號或密碼錯,就是帳號存在但被單獨擋掉 FTP——shell 設成 `/usr/sbin/nologin`、名字被列在伺服器的拒絕清單裡,或設定只允許被 chroot 的虛擬使用者。 |
500 OOPS: could not bind listening IPv4 socket / bind: Address already in use | `EADDRINUSE`:已經有東西占著這個 socket,而 daemon 是在啟動途中就結束,不是跑一跑才掛。通常是另一個套件裝進來的第二套 FTP 伺服器,或是把 21 發布到主機上的容器。動手砍之前先用上面的指令找出持有者;如果一兩分鐘後連接埠自己空出來,那是前一個行程留下的 `TIME_WAIT` socket,本來就不需要砍任何東西。 |
bind: Permission denied while starting the server on 21 | 這一個是真的在講特權連接埠:類 Unix 系統上 1024 以下需要 root,而 21 就在裡面。用 init 系統以 root 啟動 daemon,或在 Linux 上給那支執行檔 `CAP_NET_BIND_SERVICE`,再不然監聽高位連接埠再轉導。不過在 SELinux 或 AppArmor 的主機上,政策拒絕那支執行檔綁定時也會吐同一句話,所以先看稽核紀錄再認定是 1024 的規則。 |
IANA 依 RFC 959 把 21 指派給 `ftp`,也就是 File Transfer Protocol 的控制連線,隔壁的 20 則登記成 `ftp-data`。註冊表上 TCP 和 UDP 都有,實際在用的只有 TCP。關於這個連接埠,最值得先知道的就是「連線有兩條」這件事。工作階段開在 21 上,你打的每一個 `USER`、`PASS`、`LIST`、`RETR`,以及伺服器回的數字碼,全都以看得懂的 ASCII 在上面跑——但檔案內容一個位元組都不會走這裡。每次傳檔都會另外開一條 TCP 連線,而由哪一邊撥出去,決定了它過不過得了防火牆。主動模式是用戶端送 `PORT`(RFC 2428 則是 `EPRT`),由伺服器反過來連回用戶端,這在現在幾乎所有 NAT 和用戶端防火牆都會被擋掉。被動模式是用戶端送 `PASV`,伺服器回 `227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)`,最後兩個數字就是連接埠,實際值是 `p1 * 256 + p2`;或是回 `229 Entering Extended Passive Mode (|||port|)`,然後由用戶端撥過去。現在大家預設都走被動,這也是為什麼 FTP 伺服器不能只開 21,還得把一整段資料用的連接埠範圍一起開出來。再來就是把多數人帶到這一頁的命名陷阱:FTPS 和 SFTP 不是同一個東西的兩種寫法。FTPS 是 RFC 4217 幫 FTP 加上 TLS——用戶端在同一個 21 上送 `AUTH TLS`,之後整段就是加密的;至於從第一個位元組就加密、完全不需要 `AUTH` 的舊式做法,IANA 另外把 990 指派成 `ftps`。而 SFTP 怎麼看都不是 FTP:它是連接埠 22 上 SSH 的一個子系統,跟 FTP 沒有共用任何指令、任何回應碼,也沒有第二條資料連線,你只需要開 22 這一個連接埠。
不要把明文 FTP 放到網際網路上。密碼在 21 上是明文過去的,跟 telnet 一模一樣,而公開的監聽者幾小時內就會被全網掃描器找到,接著被拿匿名存取和廠商預設值一個個試。FTP 伺服器比別人更會漏,通常是兩個習慣造成的:為了給某一個檔案而打開的匿名讀取,之後就再也沒關掉;還有沒有做每位使用者的 chroot,於是通過認證的帳號可以一路走出自己的目錄。如果你只是要把檔案交給某個人,443 的 HTTPS 做同一件事,暴露面小得多。如果你要的是互動式的檔案操作,走 SSH 的 SFTP 只要開一個連接埠,還附帶主機金鑰驗證,指令和內容一起加密。當 FTP 真的非留不可——某台設備、某具實驗儀器、某個不肯改的合作對象——就強制使用帶 `AUTH TLS` 的明確式 FTPS,同時要知道:保護控制連線並不等於保護傳輸。資料連線只有在用戶端另外要求 `PROT P` 時才會加密,所以完全可能出現登入走 TLS、檔案卻是明文送出去的情況。把被動模式的連接埠範圍在伺服器設定裡釘死,讓防火牆規則是一扇窄窗而不是整段高位範圍;再用來源位址收一次;還有,與其相信 NAT 幫你擋著,不如去看路由器實際轉發了什麼。
這些全都在你的瀏覽器裡跑,不會上傳任何東西。
查 21 的時候,通常也會順手看一下這幾個。
幾乎都是權限問題。lsof 和 ss 會列出 socket,但別人的行程它不會顯示所有者,所以由系統帳號、容器執行環境或 launchd 啟動的服務,在你補上 sudo 之前就只是一個沒有主人的監聽項。Windows 則是反過來:netstat 什麼都沒有,綁定卻還是失敗,那代表這個號碼落在 Hyper-V、WSL2 或 Docker Desktop 開機時預留的範圍裡。
先看清楚它是什麼。沒收乾淨的開發伺服器砍掉沒事;正在寫入的資料庫不行,其他東西依賴的系統服務也不行。如果持有者是容器,請停容器,不要砍主機看到的那個行程——砍了執行環境只會把它拉起來。遇到真的不能動的,把自己的服務換一個連接埠反而更快。
因為它綁在 127.0.0.1 而不是 0.0.0.0。每一頁的指令之所以會把綁定位址跟行程名一起印出來,就是為了這件事:綁在回送位址上的服務,不管防火牆怎麼設,都只有這台機器自己連得到。現在不少框架是刻意這樣預設的,所以通常是一個參數的問題,不是 bug。如果已經是 0.0.0.0 還是不通,下一個要懷疑的就是主機防火牆或雲端的安全群組。
幾乎沒有幫助。掃全網的機器持續在掃整段範圍,換號碼買到的是幾個小時,不是安全,代價卻是每個同事都得記住它。它確實能讓針對預設埠的無差別掃描少污染一點日誌,這算是實際但有限的好處。真正會改變結果的是認證、限制來源位址的防火牆,以及一開始就不要讓它對外。
連接埠 21 還是卡住嗎?看完整的連接埠對照表,或是回到上面,從你實際看到的那行錯誤往回追。