FTP被动模式防火墙配置:目录列表卡住怎么办
FTP可正常登录但目录列表卡住,通常是数据通道端口未放行。为被动模式设置固定端口范围,并在防火墙中放行相同范围;NAT后还需配置正确地址。
FTP 可以登录,但目录列表一直卡住的原因
FTP 被动模式会因防火墙而失效,因为 FTP 使用两条 TCP 连接,而不是一条。连接到 21 端口的连接承载登录信息和命令,因此用户名和密码可以通过,防火墙看起来也配置正确。随后,第一次 ls 需要在另一个端口上建立第二条连接。防火墙没有放行这条连接,因此客户端会一直等待,直到超时。
解决方法是为这些数据连接指定固定端口范围,并在防火墙中放行相同的范围。位于 NAT(网络地址转换)之后的服务器还需要额外设置,以便通告正确的地址。过去,连接跟踪辅助模块会自动完成这项工作。现在已经不会了。在照搬旧指南之前,应先了解其中的原因。
控制通道和数据通道
FTP(文件传输协议)在 RFC 959 中定义,早于 NAT 和有状态防火墙。一次会话会建立一个到 TCP 端口 21 的控制连接,并在整个会话期间保持连接。命令以纯文本形式发送。响应由三位数字代码和一行文本组成。该连接不会传输文件内容。
每项数据都使用单独的 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(扩展被动模式,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 中端口和监听套接字的工作方式,了解相关背景。
有状态防火墙确实会跟踪连接,内核也可以将新连接识别为现有连接的 RELATED。但对于 FTP,必须有程序读取控制连接的数据流,并从 227 或 PORT 行中提取端口号。默认情况下没有程序会执行此操作。
为什么 FTP 连接跟踪辅助程序已经不再适用
旧版指南通常会让您使用内核模块 nf_conntrack_ftp。它读取明文控制通道,找到通告的端口并注册预期连接,因此无需为该端口单独编写规则即可允许数据连接。自这些指南发布以来,情况发生了4项变化。
自动辅助程序分配已关闭。内核将 nf_conntrack_helper sysctl 的值记录为“0 - disabled (default)”,并补充说明:“如果禁用,则必须设置 iptables 规则,为连接分配辅助程序。”仅加载该模块不会产生任何作用。
在当前内核中,这个开关已经移除。运行 sysctl net.netfilter.nf_conntrack_helper。如果返回 sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_helper: No such file or directory,表示内核中已没有可重新启用的自动辅助程序分配功能。如果返回一个数字,则表示该开关仍然存在,其默认值为 0。
防火墙前端也已弃用该功能。Ubuntu 24.04 中的 man ufw-framework 对 /etc/default/ufw 的 IPT_MODULES 行说明:“以这种方式无条件加载连接跟踪模块(nf_conntrack_*)已弃用”,并补充说明,辅助程序规则“必须通过 RULES FILES 进行管理”。firewalld 在 firewalld.conf 中将 AutomaticHelpers 记录为:“已弃用。此选项会被忽略且不再使用。”现在附加辅助程序意味着手动编写带有 CT target 的显式规则。这比下面的修复方法更复杂,并且启用 TLS 后会立即失效。Ubuntu 上的 iptables 和 nftables介绍了这些规则的实际存放位置。
TLS 会使这一方案失效。辅助程序依靠读取文本形式的控制通道工作。该通道加密后,辅助程序看到的是密文,因此无法找到端口。这个问题无法修复,也不应修复:能够读取控制通道的中间设备,也就能够读取您的密码。
在服务器上声明被动端口范围
每个 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 与数据包过滤器的兼容性”。软件包通常会在配置文件中封装此标志,因此请查阅发行版自身的文档以确定文件名,不要自行猜测。
需要多少个端口?每个正在进行的数据连接需要一个端口。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 的相同命令会被拒绝,并提示指定 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 命名为 ftp。它不会开放被动模式端口范围,因此单独执行该命令后,问题仍未解决。VPS 上的 firewalld 区域和服务介绍了更完整的配置。
直接使用 nftables,在 input 链中添加:
tcp dport { 21, 30000-30099 } accept还要注意另一层防火墙:大多数服务商会在控制面板中运行网络防火墙,该防火墙位于操作系统之外。如果服务器上的规则看起来正确,但数据包仍然无法到达,请同时在该网络防火墙中开放相同的端口范围。
服务器位于 NAT 后时告知服务器其公网地址
在服务器上运行 ip -4 addr show。如果接口上的地址就是客户端连接使用的地址,请跳过本节。如果接口使用私有地址(10.x、172.16 到 172.31.x、192.168.x),且平台将公网地址映射到该地址,服务器就不知道自己的公网地址。vsftpd 文档说明,pasv_address 的默认值是“从传入连接的套接字获取地址”,因此 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,其文档说明该选项适用于“服务器位于伪装(NAT)设备之后”的情况。EPSV 的响应不包含地址字段,因此可以避免整个问题;但不能依赖这一点,因为要发送哪个命令由客户端决定。
TLS 的变化
FTPS 是基于 TLS(传输层安全)的 FTP。客户端照常连接到 21 端口,然后发送 AUTH TLS 以保护控制通道,再发送 PROT P,同时加密数据通道。普通 FTP 会以可读文本形式在网络中传输密码,因此如果必须继续使用 FTP,应运行 FTPS。vsftpd 发布时将 ssl_enable 的默认值设为 NO。
这会带来两个结果。连接跟踪辅助程序将无法工作,这正是上一节从另一侧看到的情况。此外,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 正在监听端口 21。服务器空闲时,被动端口范围内不会有任何监听进程,因为这些套接字会在传输开始时创建,并在传输结束后关闭。
--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 吗?
对于新项目,不应使用 FTP。SFTP(SSH 文件传输协议)通过 22 端口,在单个 SSH 连接中运行。它不需要第二个通道、被动端口范围、NAT 设置或额外守护进程,因为 OpenSSH 已经提供了这些功能。sftp user@example.com 可在完全没有配置文件传输服务的服务器上运行。如果只想向他人提供文件而不授予其他权限,sshd_config 会将 ForceCommand internal-sftp 与 ChrootDirectory 配合使用。该目录必须归 root 所有,并且用户不得拥有写入权限,否则 sshd 会拒绝会话,并记录一条 bad ownership or modes for chroot directory 日志。
当对端无法更换协议时,FTP 仍有使用场景。扫描仪和多功能打印机出厂固件通常只支持 FTP。实验室和工业设备也常使用固定镜像,无法重新认证。业务合作伙伴可能会提供 FTPS 文件投递服务,并且不愿为单个供应商增加其他协议。在这些情况下,被动端口范围和匹配的防火墙规则就是全部配置工作,并且应运行 FTPS,而不是明文 FTP。双通道设计源自 1985 年,如今仍运行在它从未预见的环境中,详见文件传输协议的历史。
FAQ
为什么 FTP 可以登录,但目录列表会挂起?
登录只使用端口 21 上的控制连接,防火墙允许该连接。目录列表还需要在另一个端口上建立第二个 TCP 连接,但该连接被阻止了。在 FTP 服务器上声明被动端口范围,并在防火墙中开放相同范围,目录列表即可完成。目录列表挂起是数据通道问题,不是密码问题。
FTP 被动模式需要开放哪些端口?
控制通道使用端口 21,此外还需要开放为被动数据连接配置的端口范围。没有标准范围,因为范围由您选择。例如,30000 到 30099 通常可用:应根据同时传输的峰值数量设置范围大小,并避开内核的出站端口范围。您可以使用 sysctl net.ipv4.ip_local_port_range 查看该范围。如果您的服务商在控制面板中提供网络防火墙,也应在那里开放相同范围。
还需要 nf_conntrack_ftp 吗?
不需要。在当前内核上也不能依赖它。自动分配 helper 默认已禁用;在较新的内核中,net.netfilter.nf_conntrack_helper 开关已被移除,因此 sysctl 会报告文件不存在。ufw 手册将无条件加载这些模块标记为已弃用,firewalld 则完全忽略 AutomaticHelpers。helper 还必须将控制通道作为纯文本读取,因此启用 FTPS 后它会立即停止工作。请改为声明被动端口范围。
为什么 FTP 客户端提示被动响应中的地址无法路由?
服务器通过 PASV 返回了它在自身网卡上看到的地址,而该地址是私有地址。当平台将公网地址映射到私有地址时,就会发生这种情况。请显式设置公网地址:在 vsftpd 中使用 pasv_address,在 ProFTPD 中使用 MasqueradeAddress,或在 Pure-FTPd 中使用 -P。FileZilla 会通过重新使用已连接的地址来规避该问题,并记录“改用服务器地址”,因此有些客户端可以继续工作,而其他客户端会挂起。
应该使用 FTPS 还是 SFTP?
对于两端都由您控制的场景,应使用 SFTP:它通过 SSH 在端口 22 上建立一个连接,不需要开放数据通道,而且通常已经在运行。FTPS 是基于 TLS 的 FTP,因此仍然采用双通道设计,并保留由此产生的所有防火墙问题。只有在对端不支持其他协议时,才选择 FTPS。不要在互联网中使用纯 FTP,因为密码会以可读文本形式通过网络传输。