SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor

FTP passive mode: firewall làm treo danh sách thư mục

FTP đăng nhập được nhưng danh sách thư mục bị treo vì kênh dữ liệu dùng port riêng. Cấu hình dải port passive và firewall rule đúng để hết timeout.

Vì sao FTP đăng nhập được nhưng danh sách thư mục bị treo

FTP passive mode bị lỗi trên firewall vì FTP sử dụng 2 kết nối TCP, không phải 1. Kết nối đến port 21 truyền thông tin đăng nhập và các lệnh, nên username và password được chấp nhận, đồng thời firewall trông có vẻ đã cấu hình đúng. Sau đó, ls đầu tiên cần một kết nối thứ hai trên port khác. Firewall không cho phép kết nối đó, nên client chờ đến khi timeout.

Cách khắc phục là cấu hình một dải port cố định cho các kết nối dữ liệu đó và thêm firewall rule cho phép đúng dải port này. Server nằm sau NAT (network address translation) cần thêm một thiết lập để quảng bá đúng địa chỉ. Trước đây, connection tracking helper tự xử lý việc này. Hiện nay chúng không còn làm vậy nữa. Bạn cần biết lý do trước khi áp dụng lại một hướng dẫn cũ.

Kênh điều khiển và kênh dữ liệu

FTP (file transfer protocol) được quy định trong RFC 959, ra đời trước cả NAT và firewall stateful. Một phiên mở một kết nối điều khiển đến cổng TCP 21 và giữ kết nối này trong suốt phiên. Các lệnh được gửi dưới dạng plain text. Phản hồi gồm một mã gồm 3 chữ số và một dòng văn bản. Kết nối này không truyền nội dung file.

Mỗi phần dữ liệu sử dụng một kết nối TCP riêng: một kết nối cho danh sách thư mục (LIST), một kết nối cho mỗi lần download (RETR), và một kết nối cho mỗi lần upload (STOR). Kết nối được mở, sử dụng một lần rồi đóng. Việc xác thực diễn ra hoàn toàn trên kênh điều khiển, nên lỗi trên đường truyền dữ liệu luôn có cùng biểu hiện: đăng nhập thành công rồi bị treo. Nếu client in ra phản hồi 230 rồi dừng khi lấy danh sách, vấn đề nằm ở kênh dữ liệu, không phải thông tin đăng nhập.

Chế độ active: server kết nối ngược lại đến client

Ở chế độ active, client chọn một port, lắng nghe trên port đó rồi cho server biết cần kết nối đến đâu:

PORT 192,168,1,50,195,80

Bốn số đầu tiên là địa chỉ IP của client. Hai số cuối là port, được mã hóa thành 2 byte: 195 * 256 + 80 = 50000. Sau đó, server mở kết nối dữ liệu từ port 20 của chính nó đến port 50000 trên client.

Theo góc nhìn của client, đây là kết nối inbound không được yêu cầu, nên firewall của client sẽ drop kết nối này. Nếu client nằm sau home router, địa chỉ trong lệnh PORT là địa chỉ private mà server hoàn toàn không thể truy cập. Active mode là nguyên nhân khiến FTP mang tiếng là không hoạt động.

Chế độ passive: client mở cả hai kết nối

Chế độ passive đảo chiều kết nối dữ liệu. Client gửi PASV và server trả về địa chỉ cùng một port của nó:

227 Entering Passive Mode (203,0,113,10,195,80)

Cách mã hóa giống nhau, nên client kết nối đến 203.0.113.10 trên port 50000. Client lúc này mở cả hai kết nối. Vì vậy, chế độ passive hoạt động qua NAT phía client và mọi client hiện nay đều thử chế độ này trước.

Vấn đề chỉ chuyển vị trí chứ không biến mất. Kết nối inbound không được yêu cầu lúc này đi vào server của bạn, trên một port cao thay đổi sau mỗi lần truyền. Firewall đó thuộc quyền quản lý của bạn, nên vấn đề cũng thuộc về bạn.

EPSV (extended passive mode, RFC 2428) hoạt động theo cùng nguyên lý nhưng có phản hồi gọn hơn:

229 Entering Extended Passive Mode (|||50000|)

Phản hồi không chứa địa chỉ. Client dùng lại địa chỉ đã có cho kết nối control. Đây là yếu tố giúp chế độ này hoạt động qua IPv6 và loại bỏ một nhóm lỗi NAT. Manual của curl nêu rằng curl thường thử EPSV trước PASV. Port vẫn được chọn tại runtime, nên EPSV không thay đổi gì trong các rule của firewall.

Vì sao một rule firewall thông thường không thể cho phép data channel

Vì số port chưa tồn tại khi bạn viết rule. Server chọn port cho từng lần truyền. Với giá trị mặc định, tài liệu vsftpd ghi pasv_min_port và pasv_max_port là 0, nghĩa là “dùng bất kỳ port nào”, nên data connection có thể đến trên bất kỳ port nào lớn hơn 1023. sudo ufw allow 21/tcp chỉ cho phép control channel, không cho phép gì khác. Đây chính xác là cấu hình tạo ra tình trạng đăng nhập được nhưng không xem được danh sách file. Nếu khái niệm service listening trên một port cố định vẫn chưa rõ, cách port và listening socket hoạt động trên Linux sẽ cung cấp phần nền cần thiết.

Stateful firewall có theo dõi connection và kernel có thể cho phép một connection mới là RELATED của một connection hiện có. Với FTP, cần có thành phần đọc control stream và lấy port từ dòng 227 hoặc PORT. Mặc định không có thành phần nào làm việc đó.

Vì sao FTP connection tracking helper không còn là cách xử lý phù hợp

Module kernel nf_conntrack_ftp là module mà các hướng dẫn cũ thường yêu cầu bạn dùng. Module này đọc control channel dạng plaintext, tìm cổng được thông báo và đăng ký một expectation. Nhờ đó, data connection được cho phép mà không cần rule chỉ rõ cổng. Từ khi các hướng dẫn đó được viết, 4 điều đã thay đổi.

Tự động gán helper đã bị tắt và trên các kernel hiện tại, bạn không thể bật lại tính năng này. Kernel đã tắt tính năng này theo mặc định từ năm 2016. Sau đó, commit "netfilter: remove nf_conntrack_helper sysctl and modparam toggles" của Pablo Neira Ayuso vào tháng 8 năm 2022 đã xóa hoàn toàn tùy chọn này và nêu rõ rằng "users now have to rely on explicit conntrack helper configuration via ruleset." Sysctl nf_conntrack_helper vẫn có trong Linux 5.19 nhưng không còn từ Linux 6.0 trở đi. Tài liệu conntrack sysctl của kernel cũng không còn liệt kê sysctl này. Chỉ load module không tạo ra tác dụng gì.

Bạn có thể kiểm tra loại kernel đang dùng. Chạy sysctl net.netfilter.nf_conntrack_helper. Nếu kết quả là sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_helper: No such file or directory, kernel không còn cơ chế tự động gán helper để bật. Nếu nhận được một con số, bạn đang dùng kernel cũ hơn 6.0, tùy chọn này vẫn còn và giá trị mặc định là 0.

Các frontend của firewall cũng đã deprecated tính năng này. man ufw-framework trên Ubuntu 24.04 nói về dòng IPT_MODULES trong /etc/default/ufw như sau: "Unconditional loading of connection tracking modules (nf_conntrack_*) in this manner is deprecated", đồng thời cho biết các helper rule "must be managed in via the RULES FILES". firewalld ghi AutomaticHelpers trong firewalld.conf là "Deprecated. This option is ignored and no longer used." Hiện tại, gắn helper có nghĩa là tự viết một rule rõ ràng với target CT. Cách này phức tạp hơn giải pháp bên dưới và sẽ ngừng hoạt động ngay khi bạn bật TLS. iptables và nftables trên Ubuntu giải thích các rule đó thực sự nằm ở đâu.

TLS chấm dứt cách xử lý này. Helper hoạt động bằng cách đọc control channel dưới dạng văn bản. Khi channel đó được mã hóa, helper chỉ thấy ciphertext và không thể tìm được cổng. Không có cách khắc phục vấn đề này, và cũng không nên có: một middlebox có thể đọc control channel của bạn cũng là một middlebox đã đọc được mật khẩu của bạn.

Khai báo dải cổng passive trên server

Mọi FTP server đều cho phép bạn chọn dải cổng dùng cho passive mode. Tên tùy chọn khác nhau, vì vậy hãy xem tài liệu của server bạn thực sự đang chạy.

vsftpd, trong /etc/vsftpd.conf:

pasv_enable=YES
pasv_min_port=30000
pasv_max_port=30099

pasv_enable đã mặc định là YES. Hai tùy chọn cổng mặc định là 0, tức hành vi “dùng bất kỳ cổng nào” được mô tả ở trên. Áp dụng thay đổi bằng sudo systemctl restart vsftpd, rồi xác nhận service đã hoạt động trở lại bằng systemctl status vsftpd. vsftpd từ chối khởi động nếu gặp dòng cấu hình không thể phân tích cú pháp, thay vì bỏ qua dòng đó. Vì vậy, nếu restart thất bại, hãy đọc journalctl -u vsftpd -n 20 để tìm dòng 500 OOPS: chỉ ra tùy chọn bạn vừa nhập.

ProFTPD, trong proftpd.conf:

PassivePorts 30000 30099

Tài liệu ProFTPD không nêu giá trị mặc định cho tùy chọn này: nếu không có directive, kernel sẽ chọn cổng. Tài liệu ProFTPD cũng nói rằng khi không còn cổng nào trong dải khả dụng, server sẽ fallback về cổng do kernel cấp phát và ghi log một thông báo. Vì vậy, dải quá nhỏ sẽ thỉnh thoảng gây lỗi thay vì thất bại rõ ràng, khiến việc chẩn đoán khó hơn nhiều. Hãy dùng các cổng không đặc quyền, từ 1024 trở lên.

Pure-FTPd dùng flag -p first:last. Tài liệu trong man pure-ftpd mô tả flag này là “chỉ dùng các cổng trong dải từ cổng đầu đến cổng cuối, bao gồm cả hai đầu, cho các lượt tải xuống ở passive mode”, và flag này “giúp pure-ftpd tương thích tốt hơn với packet filter”. Các bản build được đóng gói thường đặt flag này trong file cấu hình. Vì vậy, hãy xem tài liệu riêng của distribution để biết tên file thay vì tự đoán.

Bạn cần bao nhiêu cổng? Mỗi data connection đang hoạt động cần một cổng. Một cổng TCP đã đóng sẽ ở trạng thái TIME_WAIT trong vài phút trước khi có thể được dùng lại, vì vậy hãy cấp số cổng gấp vài lần mức cao nhất dự kiến. Một trăm cổng là đủ thoải mái cho vài người dùng, còn public server bận rộn cần nhiều hơn đáng kể. vsftpd ít khoan dung hơn ProFTPD trong trường hợp này: source của nó thử một số cổng ngẫu nhiên trong dải, và nếu tất cả đều đã được sử dụng, nó kết thúc session với 500 OOPS: vsf_sysutil_bind thay vì mượn cổng bên ngoài dải. Với cả hai server, dải quá nhỏ sẽ làm hỏng việc truyền dữ liệu đúng vào lúc server bận nhất.

Nên đặt dải cổng ở đâu? Trước tiên, chạy sysctl net.ipv4.ip_local_port_range. Trên Ubuntu mặc định, lệnh này trả về 32768 60999, tức các cổng kernel cấp cho những kết nối đi ra. Dải passive nằm trong khoảng này có thể xung đột với một kết nối đi ra đang giữ cổng đó, vì vậy hãy đặt dải bên dưới khoảng này. Trên máy mặc định, khoảng 30000 đến 30099 không bị sử dụng. Hãy kiểm tra chính máy của bạn thay vì tin vào con số đó.

Mở cùng dải cổng trên firewall

ufw ghi dải cổng bằng dấu hai chấm. Tài liệu của ufw nêu rằng một dải hoặc danh sách cổng “cũng có thể được dùng để chỉ định nhiều cổng; trong trường hợp đó, bắt buộc phải chỉ rõ protocol”:

sudo ufw allow 21/tcp
sudo ufw allow 30000:30099/tcp
sudo ufw status verbose

ufw status verbose lúc này phải liệt kê cả hai rule. Nếu chạy cùng lệnh nhưng không có /tcp, ufw sẽ từ chối và báo lỗi yêu cầu chỉ rõ tcp hoặc udp, vì ufw không tự đoán protocol. cú pháp rule của ufw trên VPS trình bày phần còn lại.

firewalld ghi dải cổng bằng dấu gạch ngang và cần reload:

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 mở 21/tcp và yêu cầu ftp helper, đúng với tên được định nghĩa trong service definition đi kèm. Lệnh này không mở dải cổng passive, nên nếu chỉ chạy lệnh đó thì vấn đề vẫn còn nguyên. zone và service của firewalld trên VPS giải thích đầy đủ hơn.

nftables thuần, bên trong input chain:

tcp dport { 21, 30000-30099 } accept

Còn một firewall khác cần nhớ: hầu hết nhà cung cấp chạy network firewall trong control panel, nằm bên ngoài operating system. Nếu các rule trên máy chủ đều đúng nhưng packet vẫn không bao giờ đến được máy chủ, hãy mở cùng dải cổng ở đó.

Khai báo địa chỉ public của server khi server nằm sau NAT

Chạy ip -4 addr show trên server. Nếu địa chỉ trên interface là địa chỉ mà client dùng để kết nối, hãy bỏ qua phần này. Nếu interface giữ một địa chỉ private (10.x, 172.16 đến 172.31.x, 192.168.x) và platform ánh xạ một địa chỉ public vào đó, server không biết địa chỉ public của chính nó. Tài liệu vsftpd ghi rằng giá trị mặc định của pasv_address là “địa chỉ được lấy từ socket kết nối đến”, vì vậy phản hồi 227 chứa địa chỉ private và client bị gửi đến một địa chỉ mà nó không thể truy cập.

FileZilla gọi chính xác lỗi này là:

Server sent passive reply with unroutable address. Using server address instead.

FileZilla tự sửa lỗi rồi tiếp tục. Nhiều client khác không làm vậy. Chúng kết nối đến 10.0.0.5 rồi bị treo.

curl cũng ẩn vấn đề này, điều này quan trọng nếu bạn dùng curl để kiểm thử. Tài liệu của curl ghi rằng --ftp-skip-pasv-ip “được bật theo mặc định (được thêm từ 7.74.0)”, vì vậy curl bỏ qua địa chỉ trong phản hồi 227 và dùng lại địa chỉ của control connection. Một transfer hoạt động với curl vẫn có thể thất bại trong client đồ họa chỉ vì lý do này.

Hãy đặt địa chỉ một cách rõ ràng. vsftpd dùng pasv_address=203.0.113.10, cùng với pasv_addr_resolve=YES (mặc định là NO) nếu bạn muốn ghi hostname. ProFTPD dùng MasqueradeAddress; tùy chọn này chấp nhận địa chỉ, tên DNS hoặc tên interface. Pure-FTPd dùng -P, được tài liệu mô tả cho trường hợp “server nằm sau một box masquerading (NAT)”. EPSV tránh hoàn toàn vấn đề này vì phản hồi của nó không có trường địa chỉ, nhưng bạn không thể dựa vào đó vì client quyết định sẽ gửi command nào.

Thay đổi khi dùng TLS

FTPS là FTP chạy trên TLS (bảo mật tầng truyền tải). Client kết nối đến port 21 như bình thường, gửi AUTH TLS để bảo vệ control channel, sau đó gửi PROT P để mã hóa cả data channel. FTP thuần truyền password trên network dưới dạng văn bản có thể đọc được, vì vậy nếu bắt buộc phải dùng FTP, hãy chạy FTPS. vsftpd được phát hành với ssl_enable mặc định là NO.

Có 2 hệ quả. Không connection tracking helper nào có thể hoạt động. Đây chính là vấn đề nêu ở trên nhưng nhìn từ phía ngược lại. Ngoài ra, sudo tcpdump -nAi any 'tcp port 21' sẽ không còn hiển thị phản hồi 227, vì vậy khi cần biết server đã quảng bá address và port nào, hãy đọc log của chính server thay vì đọc dữ liệu trên network.

Kiểm tra thay đổi từ bên ngoài

Chạy các lệnh này từ một máy khác. Kiểm tra ngay trên server sẽ bỏ qua firewall mà bạn đang cố sửa.

sudo ss -ltnp | grep :21
curl -v --disable-epsv --user ftpuser:secret ftp://example.com/
nc -vz example.com 30000

ss phải hiển thị FTP daemon đang listen trên port 21. Khi server không hoạt động, không có tiến trình nào listen trên dải passive, vì các socket đó được tạo cho mỗi lần truyền dữ liệu rồi đóng lại.

--disable-epsv buộc curl đi theo đường dẫn PASV. Đây là đường dẫn làm lộ lỗi địa chỉ. Trace sẽ in phản hồi của server, sau đó là địa chỉ và port mà curl kết nối đến:

< 227 Entering Passive Mode (203,0,113,10,117,52)

117 * 256 + 52 = 30004, nằm trong dải đã khai báo. Nếu dòng đó chứa private address, nghĩa là pasv_address chưa được đặt. Nếu port nằm ngoài dải của bạn, nghĩa là server chưa đọc thay đổi cấu hình. Hãy kiểm tra bạn đã sửa đúng file mà service đang chạy sử dụng.

nc tự trả lời câu hỏi về firewall. Connection refused xuất hiện ngay lập tức nghĩa là packet đã đến server nhưng không tìm thấy tiến trình nào đang listen, đây là kết quả đúng đối với passive port đang idle: rule của bạn hoạt động. Nếu kết nối treo đến khi nc bỏ cuộc, nghĩa là có thành phần đã âm thầm drop packet. Đó có thể là firewall trên máy hoặc firewall trong panel của nhà cung cấp. Phân biệt này cũng giống như phần SSH bị từ chối hay hết thời gian chờ đã mô tả và áp dụng cho mọi port.

Bạn có nên tiếp tục chạy FTP không?

Nếu bắt đầu công việc mới thì không. SFTP (SSH file transfer protocol) chạy trong một kết nối SSH duy nhất trên cổng 22. Không có kênh thứ hai, dải passive, cấu hình NAT hay daemon bổ sung cần bảo mật, vì OpenSSH đã cung cấp sẵn tính năng này. sftp user@example.com hoạt động trên máy chủ mà bạn chưa từng cấu hình dịch vụ truyền tệp. Để cấp cho ai đó quyền nhận tệp mà không cấp quyền nào khác, sshd_config lấy ForceCommand internal-sftp cùng với ChrootDirectory. Thư mục đó phải thuộc sở hữu của root và không được cho user ghi, nếu không sshd sẽ từ chối session và ghi log một dòng bad ownership or modes for chroot directory.

FTP vẫn phù hợp khi phía bên kia không thể thay đổi. Scanner và máy in đa chức năng thường được bán kèm firmware chỉ hỗ trợ FTP. Thiết bị phòng thí nghiệm và thiết bị công nghiệp thường chạy một image cố định mà không ai sẽ cấp lại chứng nhận. Đối tác kinh doanh có thể cung cấp một điểm nhận tệp FTPS và không chấp nhận bổ sung protocol chỉ cho một nhà cung cấp. Trong mỗi trường hợp này, dải passive cùng firewall rule tương ứng là toàn bộ phần cần cấu hình; nên chạy FTPS thay vì FTP thuần túy. Thiết kế hai kênh là quyết định từ năm 1980, đã có trong RFC 765 từ 5 năm trước khi RFC 959 tiếp tục duy trì thiết kế đó. Hiện nay, nó hoạt động trong một môi trường mà ban đầu không được dự tính, như mô tả trong lịch sử các protocol truyền tệp.

FAQ

Tại sao FTP đăng nhập được nhưng bị treo khi liệt kê thư mục?

Đăng nhập chỉ sử dụng kết nối điều khiển trên cổng 21, và firewall của bạn cho phép kết nối này. Việc liệt kê thư mục cần một kết nối TCP thứ hai trên một cổng khác, nhưng kết nối đó bị chặn. Khai báo một dải cổng passive trên FTP server, mở cùng dải cổng đó trong firewall, rồi thao tác liệt kê sẽ hoàn tất. Tình trạng treo khi liệt kê là vấn đề của data channel, không phải vấn đề mật khẩu.

Tôi cần mở những cổng nào cho FTP passive mode?

Mở cổng 21 cho control channel, cùng với dải cổng bạn đã cấu hình cho các kết nối passive data. Không có dải cổng tiêu chuẩn vì bạn tự chọn dải này. Dải từ 30000 đến 30099 là một lựa chọn phù hợp: đặt kích thước dải theo số phiên truyền đồng thời cao nhất, và không trùng với dải cổng outgoing của kernel. Bạn có thể xem dải này bằng sysctl net.ipv4.ip_local_port_range. Nếu nhà cung cấp có network firewall trong control panel, hãy mở cùng dải cổng ở đó.

Tôi còn cần nf_conntrack_ftp không?

Không. Với kernel hiện tại, bạn cũng không thể dựa vào module này. Việc tự động gán helper bị tắt theo mặc định. Từ Linux 6.0, switch net.netfilter.nf_conntrack_helper đã bị loại bỏ, nên sysctl báo rằng file không tồn tại. Manual của ufw đánh dấu việc nạp các module này một cách vô điều kiện là deprecated, còn firewalld hoàn toàn bỏ qua AutomaticHelpers. Helper cũng phải đọc control channel dưới dạng văn bản thuần, nên sẽ ngừng hoạt động ngay khi bạn bật FTPS. Thay vào đó, hãy khai báo một dải cổng passive.

Tại sao FTP client báo rằng phản hồi passive chứa địa chỉ không thể định tuyến?

Server trả lời PASV bằng địa chỉ mà nó nhìn thấy trên interface của chính nó, và địa chỉ đó là địa chỉ private. Điều này xảy ra khi platform ánh xạ một địa chỉ public vào một địa chỉ private. Hãy đặt rõ địa chỉ public: pasv_address trong vsftpd, MasqueradeAddress trong ProFTPD hoặc -P trong Pure-FTPd. FileZilla xử lý tình huống này bằng cách dùng lại địa chỉ mà nó đã kết nối, đồng thời ghi log "Using server address instead". Vì vậy, một số client vẫn hoạt động dù cấu hình sai, còn các client khác bị treo.

Tôi nên dùng FTPS hay SFTP?

Dùng SFTP cho mọi trường hợp bạn kiểm soát được cả hai đầu: chỉ một kết nối qua SSH trên cổng 22, không cần mở data channel, và dịch vụ này thường đã sẵn sàng. FTPS là FTP chạy qua TLS, nên vẫn giữ thiết kế hai channel và mọi vấn đề firewall đi kèm. Chọn FTPS khi phía bên kia không hỗ trợ giao thức nào khác. Không dùng plain FTP qua Internet vì mật khẩu được truyền qua network dưới dạng văn bản có thể đọc được.