bigywater.tech
网络协议 · 99

FTP:控制连接与数据连接,主动与被动模式

FTP 是最古老的文件传输协议之一,也是最“怪”的:一条控制连接发命令,每次传输再另开一条数据连接,还分主动和被动两种方向。这篇在本机起一个 FTP 服务器,逐个把这些怪处看清楚,并量出它为什么不适合传一大堆小文件。

37 × 256 + 8 = 9480
PASV 答复 (127,0,0,1,37,8) 里端口的计算
241 字节
控制连接上能直接读到密码 pa55w0rd 的明文流量
4.1 个往返
100 ms 往返下被动模式取一个小文件的平均代价

控制连接

实测 · 本机执行输出
    S: 220 w99 test ftp ready
    C: USER alice
    S: 331 Username ok, send password.
    C: PASS pa55w0rd
    S: 230 Login successful.
    C: SYST
    S: 215 UNIX Type: L8
    C: FEAT
    S: 211-Features supported:
    S:  EPRT
    S:  EPSV
    S:  MDTM
    S:  MFMT
    S:  MLST type*;perm*;size*;modify*;unique*;unix.mode;unix.uid;unix.gid;
    S:  REST STREAM
    S:  SIZE
    S:  TVFS
    S:  UTF8
    S: 211 End FEAT.
    C: PWD
    S: 257 "/" is the current directory.
    C: CWD pub
    S: 250 "/pub" is the current directory.
    C: PWD
    S: 257 "/pub" is the current directory.
    C: TYPE I
    S: 200 Type set to: Binary.
    C: SIZE f0.txt
    S: 213 700
    C: QUIT
    S: 221 Goodbye.
  注意 PASS 命令把密码 pa55w0rd 明文写在控制连接上(下面第四节抓包验证);FEAT 的多行答复用 “211-” 开头、“211 ” 结尾,和 SMTP 的 250- 一样

文本命令与三位数字答复,多行答复用 “211-” 到 “211 ”,和 SMTP 同一种风格。

被动模式

实测 · 本机执行输出
    S: 230 Login successful.
    C: CWD pub
    S: 250 "/pub" is the current directory.
    C: TYPE I
    S: 200 Type set to: Binary.
    C: PASV
    S: 227 Entering passive mode (127,0,0,1,37,8).
    *: 227 里的 (127,0,0,1,37,8) → 端口 = 37 × 256 + 8 = 9480
    *: 客户端另开一条 TCP 连接 127.0.0.1:9480 → 127.0.0.1:9480(数据连接)
    C: LIST
    S: 125 Data connection already open. Transfer starting.
    *: 数据连接上收到 694 字节(目录列表),服务器发完就关闭这条连接:'-rw-r--r--   1 w        staff      200000 Oct 15 11:10 big.bin' …
    S: 226 Transfer complete.
    C: QUIT
    S: 221 Goodbye.

PASV 报告端口,客户端再连;数据连接用完即关。

主动与 EPSV

实测 · 本机执行输出
    C: EPSV
    S: 229 Entering extended passive mode (|||9466|).
    C: QUIT
    S: 221 Goodbye.
  PORT 127,0,0,1,228,126(= 端口 228 × 256 + 126 = 58494)→ 服务器答复 '200 Active data connection established.';随后 LIST 时,**服务器**主动连客户端的 58494 端口:数据连接的服务器一侧端口是 58496,方向与被动模式相反;收到 694 字节的目录列表,控制连接答复 '125 Data connection already open. Transfer starting.'
  被动模式里数据连接由客户端发起(穿 NAT 友好);主动模式由服务器发起,客户端在 NAT 后面就收不到——这是 FTP 在现代网络里首选被动模式的原因

主动模式 PORT 让服务器反过来连客户端;EPSV 只给端口,支持 IPv6。

明文

实测 · 本机执行输出
  控制连接的全部字节(241 字节)里能直接读到:USER alice; PASS pa55w0rd; CWD pub; RETR f0.txt
  所以 FTP 的密码在路径上任何一个能抓包的地方都能被读走;FTPS(FTP over TLS)和 SFTP(基于 SSH,不是 FTP)是后来补的

用户名、密码、文件名全是明文。

代价

实测 · 本机执行输出
  登录(TCP 握手 + 220 + USER + PASS + CWD + TYPE)中位 608 ms ≈ 6.1 个往返;取 10 个小文件 中位 4051 ms,平均每个 405 ms ≈ 4.1 个往返(PASV 1 个、连数据端口 1 个、RETR 1 个,外加传输收尾)
  取一个 200000 字节的大文件:505 ms
  对比 HTTP/1.1 keep-alive:取一个小文件只要 1 个往返;FTP 小文件每个要约 4 个往返,所以 FTP 不适合大量小文件

每个文件一条新数据连接,小文件约 4.1 个往返;登录约 6.1 个往返。

分步演示

FTP
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。全部在本机,服务器是 pyftpdlib(临时目录,临时用户,用后删除);被动端口范围我限定在 9460~9489 以便 netlab 作用;第五节每组 2 次取中位,只取了 10 个 700 字节的小文件与一个 200000 字节的大文件;主动模式的数据连接服务器端口(58496)是 pyftpdlib 的行为
    • 没有验证:FTPS(显式 AUTH TLS / 隐式 990);真实公共 FTP 服务器的行为;ASCII 模式的换行转换;REST 断点续传;MLSD / MLST 的机器可读列表;NAT 设备的 FTP ALG(应用层网关)改写 PORT / PASV 地址;防火墙对随机被动端口的处理
    京ICP备2026067502号-1京公网安备11010502063487号