登录成功,PWD 成功,然后 LIST 一直挂起,直到客户端放弃或切换到另一种数据模式。这种割裂就是特征:到 21 端口的控制连接是健康的,而数据连接却是用对端无法到达的地址或端口建立的。在同时做 NAT 的 VPN 网关后面,常见的元凶是一条 FTP 命令,其载荷里写着一个隧道地址,例如 10.8.0.6。报头转换从不触碰这个值。

接下来要说的是这个值从哪来,以及哪台设备有权改动它。

坏掉的是载荷,不是隧道

FTP 把数据端点放进控制流里。主动模式下客户端发送 PORT,带上自己的地址和端口,服务器再回拨那个二元组。被动模式下服务器用 227 回答一个二元组,客户端转而呼叫它。

两种形式承载的是同一类陈述:连到这个地址的这个端口。只改写 IP 与 TCP 报头的设备会原样保留这条陈述,于是一端不断尝试到达只存在于隧道另一侧的地址。控制连接已被判定为健康之后,数据连接才停滞下来。

三种模式,三种载荷

下表说明命令要求哪一侧去拨号,以及只改写报头的 NAT 对此能做些什么。

把最后一列读作最先失败的环节。主动模式是服务器拨向私有地址,被动模式是客户端拨号,而 FTPS 的载荷是密文,中间设备可用的线索更少。

ModeValue in the control streamWho dials whomHeader-only NAT leaves
Active (PORT)client address and portserver dials the clienta server dialing an unreachable tunnel address
Passive (227)server address and portclient dials the servera client dialing an address it cannot route to
FTPSthe same values, inside TLSas aboveciphertext, 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 载荷里的地址,并问清是哪台设备在转换这个地址。在那里挂接助手,在一次传输中观察期望表,然后在服务器上修正被动模式,并把范围反映到网关规则里。

把数据路径的故障与批量传输的故障分开,因为二者会把你引向不同的设置。

相关文章