为什么 FTP 目录列表会卡在做 NAT 的 VPN 网关后面
NAT 只改写报头时,登录和 PWD 能成功,LIST 与 RETR 却会停滞。了解 FTP 助手必须放在哪里,以及如何修正被动模式。
登录成功,PWD 成功,然后 LIST 一直挂起,直到客户端放弃或切换到另一种数据模式。这种割裂就是特征:到 21 端口的控制连接是健康的,而数据连接却是用对端无法到达的地址或端口建立的。在同时做 NAT 的 VPN 网关后面,常见的元凶是一条 FTP 命令,其载荷里写着一个隧道地址,例如 10.8.0.6。报头转换从不触碰这个值。
接下来要说的是这个值从哪来,以及哪台设备有权改动它。
坏掉的是载荷,不是隧道
FTP 把数据端点放进控制流里。主动模式下客户端发送 PORT,带上自己的地址和端口,服务器再回拨那个二元组。被动模式下服务器用 227 回答一个二元组,客户端转而呼叫它。
两种形式承载的是同一类陈述:连到这个地址的这个端口。只改写 IP 与 TCP 报头的设备会原样保留这条陈述,于是一端不断尝试到达只存在于隧道另一侧的地址。控制连接已被判定为健康之后,数据连接才停滞下来。
三种模式,三种载荷
下表说明命令要求哪一侧去拨号,以及只改写报头的 NAT 对此能做些什么。
把最后一列读作最先失败的环节。主动模式是服务器拨向私有地址,被动模式是客户端拨号,而 FTPS 的载荷是密文,中间设备可用的线索更少。
| Mode | Value in the control stream | Who dials whom | Header-only NAT leaves |
|---|---|---|---|
| Active (PORT) | client address and port | server dials the client | a server dialing an unreachable tunnel address |
| Passive (227) | server address and port | client dials the server | a client dialing an address it cannot route to |
| FTPS | the same values, inside TLS | as above | ciphertext, so no rewrite happens at all |
把助手放在真正发生转换的地方
在 Linux 上,载荷重写由 nf_conntrack_ftp 助手完成。它监视控制连接,学到协商出的端口,为数据流建立期望,并在做 NAT 的设备上改写载荷内的地址和端口。它 只在转换客户端地址的那台设备上起作用。把它加载到客户端,或加载到只转发流量的路由器上,对会话毫无改变。
下面三条命令分别显示模块是否存在、是否已挂接,以及会话打开期间是否出现期望:
modprobe nf_conntrack_ftp
lsmod | grep nf_conntrack_ftp
conntrack -L expect | grep ftp
加载模块并不等于挂接模块。4.7 及以后的内核自带 net.netfilter.nf_conntrack_helper=0,所以助手要等待一条显式规则,例如 iptables -t raw -A PREROUTING -p tcp --dport 21 -j CT --helper ftp。如果在尝试传输时期望表始终为空,要检查的是挂接,而不是模块。
被动模式:告诉服务器该宣告什么
被动模式更容易做到确定性,因为可以直接告诉服务器在 227 回复里放什么。在 vsftpd 里那是 pasv_address,配合 pasv_min_port 和 pasv_max_port;ProFTPD 用的是 MasqueradeAddress 和 PassivePorts。先设置这些,因为正确宣告的地址就不需要在这一侧改写任何东西。
然后让正好这个端口范围通过网关,例如 -A FORWARD -i tun0 -p tcp --dport 30000:30100 -j ACCEPT,使规则与服务器宣告的一致。
两项设置必须彼此吻合。服务器被要求宣告 30000-30100,而网关只放行 40000-40100 时,失败方式最没有帮助:控制对话看起来健康,而每条数据连接都死掉。先比较这些数字,再排查别的东西。
两种故障特征,以及 FTPS 去掉了什么
LIST 挂起指向会话的第一条数据连接,那里地址改写和期望都必须工作。LIST 成功之后 RETR 挂起,指向传输本身,也就是被动范围或隧道的 MTU。第二种特征不是载荷问题,助手的设置修不好它。
TLS 上的 FTP 去掉了助手所读的载荷。中间设备无法改写它解不开的 PORT 或 PASV 值,于是剩下三种选择:终止 TLS 的代理、在有限范围内宣告可达地址的服务器,或者另一种传输协议。
一份简短检查顺序
从症状往外查。确认 PWD 成功而 LIST 失败,抓取 PORT 或 227 载荷里的地址,并问清是哪台设备在转换这个地址。在那里挂接助手,在一次传输中观察期望表,然后在服务器上修正被动模式,并把范围反映到网关规则里。
把数据路径的故障与批量传输的故障分开,因为二者会把你引向不同的设置。