FTP 原理与报文 — 双连接模型、命令与主动被动模式

FTP 控制/数据双连接、命令集与应答码、主动与被动模式时序、传输类型与断点续传 / 应用层 / TCP 21/20 / RFC 959

先建立直觉

先别管命令和端口号,用一个画面记住 FTP 的"怪":

FTP 同时开着两条线——一条"对讲机"、一条"搬运道"。 对讲机(控制连接)从你登录到退出一直开着,只用来喊口令:“我要下载 xxx"“切到二进制模式”。真正搬运文件字节的是另一条临时拉的"搬运道”(数据连接),货搬完这条道立刻撤掉。正因为两条线分开,你才能在搬运途中对着对讲机大喊"停!"(发 ABOR)让搬运工立刻住手——如果命令和数据混在一条线上,就做不到这么干脆。

记住这个"双线模型",后面所有的复杂(主动/被动、防火墙难过)都是因为这第二条线的"门牌号"是口头报出来的、隔着墙就找不到了。

它解决什么问题

FTP 要解决的不是"怎么传文件"一个点,而是一整套"异构机器之间怎么可靠地传文件":

  • 机器不一样:换行符(CRLF vs LF)、字符编码(ASCII vs EBCDIC)、文件结构(流式 vs 记录式)都不同 → 用 TYPE / STRU / MODE 协商。
  • 要能浏览和操作远程文件系统:列目录、建删目录、改名、删文件 → 一整套文件系统命令(LIST/CWD/MKD/DELE/RNFR/RNTO)。
  • 大文件不能一断就重来REST 断点续传(RFC 3659)。
  • 命令不能和字节流搅在一起带外控制:控制连接与数据连接物理分离,传输中仍可发 ABOR 中止。
  • 公开分发不想给每人开账号匿名 FTP(用户名 anonymous,密码填邮箱,RFC 1635)。

一句话:FTP 把"传文件"做成了带目录操作、可续传、可中止的完整文件系统会话,而不只是"发一串字节"。

工作流程(简化版)

把一次下载拆开,核心就是"先对讲、再开搬运道、搬完撤道":

双连接模型

FTP 把"说什么"和"传什么"彻底分开:

连接端口生命周期承载内容
控制连接(Control Connection)服务器 TCP 21整个会话期间保持Telnet NVT 格式的 ASCII 命令与三位数字应答,以 \r\n 结尾
数据连接(Data Connection)主动模式:服务器 TCP 20;被动模式:服务器动态端口每次传输单独建立,传完即关纯字节流:文件内容或目录列表,无任何协议头

这样设计的好处是:文件正在传输时,控制连接依然畅通,客户端可以随时发 ABOR 中止;而数据连接上不需要任何分隔符或长度字段——连接关闭本身就是"传输结束"的信号

代价是:需要动态协商第二条连接的端口,而这个端口号写在应用层数据里,导致 FTP 与 NAT、防火墙、加密三者天然冲突。

一次下载的完整流程

  1. 客户端连服务器 21 端口 → 服务器回 220 服务就绪
  2. USER alice331 需要密码PASS ***230 登录成功
  3. TYPE I 切到二进制模式 → 200 OK忘了这步会导致二进制文件损坏)。
  4. PASV → 服务器回 227 Entering Passive Mode (192,168,1,10,195,80),客户端算出端口 = 195×256+80 = 49936。
  5. 客户端向服务器 49936 端口建立数据连接。
  6. RETR bigfile.iso → 服务器回 150 打开数据连接,随即在数据连接上推送文件字节。
  7. 传完服务器关闭数据连接,并在控制连接上回 226 传输完成
  8. QUIT221 再见,关闭控制连接。

报文/头部长什么样

先记住一句话:FTP 没有二进制头部,命令和应答都是文本行。 所谓"报文结构",就是"一行命令 + 一行三位数字应答",外加第二条连接上纯粹的字节流。

常用命令(RFC 959 及扩展)

这张表是 FTP 的"字典"。先记住:USER/PASS 登录、PASV/PORT 协商数据连接、RETR/STOR 收发文件、TYPE I 切二进制——这四组用得最多。

命令参数作用典型应答
USER用户名提交用户名331(需密码)/ 230(无需密码直接登录)
PASS密码提交密码(明文230 / 530
ACCT账户提交账户信息(极少用)230 / 202
SYST查询服务器系统类型215 UNIX Type: L8
FEAT查询服务器支持的扩展(RFC 2389)211-Features: …
PWD显示当前工作目录257 “/home/alice”
CWD路径切换工作目录250
CDUP切换到上级目录200 / 250
MKD / RMD目录名创建 / 删除目录257 / 250
DELE文件名删除文件250
RNFRRNTO旧名 → 新名两步重命名350 → 250
TYPEA / IASCII 模式 / 二进制(Image)模式200
MODES/B/C流 / 块 / 压缩传输模式(几乎只用 S)200
STRUF/R/P文件 / 记录 / 页结构(几乎只用 F)200
PORTh1,h2,h3,h4,p1,p2主动模式:告知服务器连接客户端的哪个地址端口200
PASV被动模式:请求服务器开监听端口227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)
EPRT / EPSVRFC 2428 扩展支持 IPv6 且格式对 NAT 更友好的主动/被动200 / 229 Entering Extended Passive Mode (|||端口|)
LIST[路径]详细目录列表(类 ls -l,格式不标准)150 → 226
NLST[路径]仅文件名列表(便于程序解析)150 → 226
MLSD / MLST[路径]机器可读的标准化列表(RFC 3659,推荐)150 → 226 / 250
RETR文件名下载(Retrieve)150 → 226
STOR文件名上传(Store,同名覆盖)150 → 226
STOU上传并由服务器生成唯一文件名150 → 226
APPE文件名追加上传150 → 226
REST偏移量从指定字节偏移续传(RFC 3659)350
SIZE文件名查询文件字节大小(RFC 3659)213 1048576
MDTM文件名查询最后修改时间213 20260803021500
ABOR中止当前数据传输426 + 226
NOOP空操作,用于保活200
QUIT退出并关闭控制连接221
AUTHTLS显式升级为 FTPS(RFC 4217)234
PBSZ / PROT0 / P设置保护缓冲区大小 / 数据连接也加密200

应答码结构(三位数字,每位都有含义)

先看"三位数字怎么读":第一位定性质(成功/失败/需补充),第二位定类别(语法/连接/认证/文件系统等)。

第一位 —— 结果类别

首位含义
1yz预备应答:命令已接受,正在处理,还会有后续应答
2yz完成应答:命令成功执行
3yz中间应答:命令被接受,但需要更多信息才能继续
4yz暂时性否定:本次失败,但稍后重试可能成功
5yz永久性否定:命令失败,重试同样会失败,需修改后再试

第二位 —— 功能分类

次位含义
x0z语法错误类
x1z信息类(状态、帮助)
x2z连接类(控制/数据连接管理)
x3z认证与账户类
x5z文件系统类

高频应答码速查

含义
110重启标记应答
150文件状态正常,即将打开数据连接
200命令执行成功
211系统状态 / 特性列表(FEAT 响应)
213文件状态(SIZE / MDTM 的结果)
215系统类型(SYST 的结果)
220服务就绪(连接后服务器的第一句欢迎语)
221服务关闭控制连接(QUIT 的响应)
226关闭数据连接,请求的文件操作成功
227进入被动模式(含 IP 与端口)
229进入扩展被动模式(EPSV,仅含端口)
230用户登录成功
234AUTH TLS 被接受,开始 TLS 握手
250请求的文件操作完成(CWD/DELE/RNTO 等)
257创建了 “PATHNAME”(MKD / PWD 的响应)
331用户名正确,需要密码
350请求的操作待进一步信息(RNFR / REST 之后)
421服务不可用,正在关闭控制连接(超时/连接数满)
425无法打开数据连接(主动/被动模式或防火墙问题的典型症状)
426连接关闭,传输被中止(ABOR 或网络中断)
500语法错误,命令无法识别
501参数语法错误
502命令未实现
504该参数下命令未实现
530未登录 / 登录失败(密码错误、匿名被禁用)
532存储文件需要账户信息
550请求的操作未执行:文件不存在或无权限
552超出存储配额
553文件名不允许

PASV 应答的端口计算

227 应答把 IP 和端口都写在一行里。端口不是直接给的数字,而是两个字节 p1,p2 拼出来的——端口 = p1 × 256 + p2

1
2
3
4
227 Entering Passive Mode (192,168,1,10,195,80)
                           └─── IP ───┘ └ p1,p2 ┘

数据端口 = p1 × 256 + p2 = 195 × 256 + 80 = 49936

EPSV(RFC 2428)的应答更简洁,且不含 IP(客户端直接复用控制连接的服务器 IP),因此对 NAT 天然友好:

1
229 Entering Extended Passive Mode (|||49936|)

交互时序

一句话看懂这组图:第一张是今天的标准做法(被动模式下载,客户端去连服务器的临时端口);第二张说明为什么主动模式在当今网络几乎必挂(服务器反向连私网客户端被拦);第三张是 FTPS,在 FTP 命令之上套了一层 TLS。

被动模式(PASV)下载文件 —— 现代标准做法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
sequenceDiagram
    participant C as 客户端<br/>203.0.113.8
    participant S as FTP 服务器<br/>198.51.100.5

    Note over C,S: ── 控制连接:客户端 → 服务器:21 ──
    C->>S: TCP 连接到 21 端口
    S->>C: 220 (vsFTPd 3.0.5) 服务就绪
    C->>S: USER alice
    S->>C: 331 Please specify the password.
    C->>S: PASS ******** (明文)
    S->>C: 230 Login successful.
    C->>S: SYST
    S->>C: 215 UNIX Type: L8
    C->>S: TYPE I (切二进制,防止文件损坏)
    S->>C: 200 Switching to Binary mode.
    C->>S: SIZE bigfile.iso
    S->>C: 213 1073741824

    Note over C,S: ── 协商数据连接(被动模式)──
    C->>S: PASV
    S->>C: 227 Entering Passive Mode (198,51,100,5,195,80)
    Note right of C: 计算端口 = 195×256+80 = 49936

    Note over C,S: ── 数据连接:客户端 → 服务器:49936 ──
    C->>S: TCP 连接到 49936 端口
    C->>S: RETR bigfile.iso (控制连接上发命令)
    S->>C: 150 Opening BINARY mode data connection
    S-->>C: 【数据连接】文件字节流……
    S-->>C: 【数据连接】FIN(关闭 = 传输结束)
    S->>C: 226 Transfer complete.

    Note over C,S: ── 断点续传(若中断后重连)──
    C->>S: REST 524288000
    S->>C: 350 Restart position accepted.
    C->>S: RETR bigfile.iso
    S->>C: 150 Opening BINARY mode data connection

    C->>S: QUIT
    S->>C: 221 Goodbye.

主动模式(PORT)—— 理解为什么它在今天几乎不可用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
sequenceDiagram
    participant C as 客户端(NAT 内网)
    participant F as 客户端防火墙/NAT
    participant S as FTP 服务器

    C->>S: TCP 连 21 端口 → 登录成功
    Note left of C: 客户端在本地开监听端口 50100
    C->>S: PORT 192,168,1,100,195,180
    Note right of S: 服务器解析出 192.168.1.100:50100<br/>这是客户端的私网地址!
    S->>C: 200 PORT command successful.
    C->>S: RETR file.txt
    S-->>F: 从自己 20 端口主动连 192.168.1.100:50100
    F--xS: ❌ 目标是私网地址,公网不可路由<br/>❌ 即使可达,防火墙也会拦截入站连接
    S->>C: 425 Failed to establish connection.

结论:主动模式要求服务器反向连接客户端,这在 NAT 普及和主机防火墙默认拒绝入站的今天几乎必然失败。因此现代客户端(curllftp、浏览器时代的 FileZilla)默认都用被动模式。

显式 FTPS(AUTH TLS,RFC 4217)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
sequenceDiagram
    participant C as 客户端
    participant S as FTPS 服务器 :21
    C->>S: TCP 连 21(明文起步)
    S->>C: 220 Ready
    C->>S: AUTH TLS
    S->>C: 234 Proceed with negotiation
    Note over C,S: 在控制连接上完成 TLS 握手
    C->>S: {USER alice} 加密
    S->>C: {331}
    C->>S: {PASS ****} 加密
    S->>C: {230 Login successful}
    C->>S: {PBSZ 0}
    S->>C: {200}
    C->>S: {PROT P} 声明数据连接也要加密
    S->>C: {200}
    C->>S: {PASV}
    S->>C: {227 …}
    Note over C,S: 数据连接同样做 TLS 握手后传输

关键机制/变体

下面几条是 FTP 真正"绕不开"的知识点,每条先点明它解决什么:

1. 主动模式 vs 被动模式 —— 解决"第二条连接谁来发起"

维度主动模式(Active / PORT)被动模式(Passive / PASV)
谁发起数据连接服务器 → 客户端客户端 → 服务器
服务器数据源端口固定 20动态临时端口(如 49152–65535)
关键命令/应答PORT / EPRT → 200PASV / EPSV227 / 229
客户端在 NAT 后❌ 基本不可用✅ 可用
服务器在 NAT 后✅ 相对容易需配置 pasv_address 与端口范围映射
客户端防火墙压力必须允许入站连接(很难被批准)只需允许出站
服务器防火墙压力只需开 20、21需开放整段被动端口范围
现代默认已基本弃用✅ 默认

一句口诀:主动模式是"服务器主动来找我",被动模式是"服务器被动等我去"。 谁在 NAT 后面,谁就不能被"找"。

2. 传输类型:ASCII 与 Binary —— 解决"不同机器文件格式不同"

  • TYPE A(ASCII):按行传输,自动在不同平台的换行符间转换(网络上统一用 CRLF)。只能用于纯文本
  • TYPE I(Image / Binary):逐字节原样传输,不做任何转换。

经典事故:用 ASCII 模式传 .zip / .exe / .jpg,文件中恰好含 0x0D 0x0A 字节序列时会被"转换",导致文件损坏且大小对不上。规则很简单:除非明确知道是纯文本,一律先发 TYPE I 命令行 ftp 客户端默认可能是 ASCII,必须显式 binary

3. 匿名 FTP(Anonymous FTP,RFC 1635)—— 解决"公开分发不想开账号"

用户名固定为 anonymous(或 ftp),密码约定填写自己的邮箱地址(服务器通常不校验)。用于公开分发软件、镜像站。安全要点:

  • 必须 chroot 到独立目录,防止目录穿越到系统文件;
  • 默认只读,若开放上传必须使用独立的"投递目录"(incoming),且该目录不可读不可列,否则会被滥用为盗版/恶意文件中转站(历史上极常见);
  • 限制并发连接数与带宽,防止被当作免费 CDN。

4. FTP 的安全缺陷 —— 解决"为什么不能在公网裸奔"

缺陷说明
明文凭据USER/PASS 以明文在控制连接上传输,同网段抓包即可获取
明文数据文件内容明文传输,可被窃听与篡改
无完整性保护无法察觉传输过程中数据被修改
FTP Bounce 攻击利用 PORT 命令让服务器向任意第三方主机端口发起连接,可用于绕过防火墙做端口扫描或匿名攻击(CVE 类经典漏洞)。现代服务器默认拒绝 PORT 指向非客户端 IP 的请求
暴力破解友好无内建限速,需靠 fail2ban 等外部手段
NAT ALG 带来的风险防火墙需解析明文载荷改写端口,攻击者可构造伪造应答诱导防火墙打洞

推荐替代:优先 SFTP(SSH 子系统,单连接、全加密、易穿墙);必须保留 FTP 语义时用 FTPS(显式 AUTH TLS);单纯的文件下载分发直接用 HTTPS

5. 断点续传与传输保活 —— 解决"大文件中断怎么办"

  • SIZE 查询远端文件大小 → 与本地已下载字节比较 → REST <offset>RETR。上传方向用 APPEREST + STOR(服务器需支持)。
  • 大文件传输期间控制连接长时间无数据,容易被 NAT 会话表或防火墙空闲超时踢掉,导致传完后收不到 226。解决办法:开启 TCP keepalive,或客户端周期性发 NOOP

常见误区

  1. 以为 FTP 是"一条连接传文件":它其实是两条——控制连接全程在,数据连接每次传文件/列目录都单独开。所以会出现"登录正常、能 pwd,但 ls 卡住"的典型症状:ls 也需要数据连接,问题出在第二条线上,不是控制线。
  2. 以为主动和被动"差不多":主动模式由服务器从 20 端口反向连客户端,NAT 后客户端是私网地址、防火墙也拦入站,几乎必报 425。现代一律用被动模式(PASV/EPSV)。
  3. 以为 ASCII/Binary 随便选:二进制文件(zip/exe/jpg)用 ASCII 模式传,0x0D 0x0A 会被改写,文件损坏、大小对不上。除明确纯文本外,先 TYPE Ibinary)。
  4. 以为 FTPS 和 SFTP 是一回事:FTPS 是 FTP 套 TLS(仍双连接、21/990 端口、仍有被动模式问题);SFTP 是 SSH 子系统、单连接 22 端口、二进制协议,和 RFC 959 毫无继承关系。选错协议就完全连不上。

速记口诀

  1. 两条连接:21 对讲机指挥,20/临时端口搬货;带外控制,传输中可 ABOR。
  2. 主动=服务器来找我,被动=我去找服务器;谁在 NAT 后,谁就别被找 → 一律 PASV。
  3. 传文件先 TYPE I;被动端口 = p1×256+p2;FTPS 是 FTP 加 TLS,SFTP 是 SSH 子系统。

知识框架

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
mindmap
  root((FTP))
    双连接架构
      控制连接 TCP21 全程保持
      数据连接 传完即关
      带外控制 可随时ABOR
    连接模式
      主动模式 PORT EPRT
        服务器20端口主动连客户端
        NAT后不可用
      被动模式 PASV EPSV
        客户端连服务器临时端口
        227与229应答
        现代默认
    命令集
      认证 USER PASS ACCT
      导航 PWD CWD CDUP
      管理 MKD RMD DELE RNFR RNTO
      参数 TYPE MODE STRU
      传输 RETR STOR STOU APPE
      列表 LIST NLST MLSD
      扩展 FEAT SIZE MDTM REST
      控制 ABOR NOOP QUIT
      安全 AUTH PBSZ PROT
    应答码体系
      1yz 预备 150
      2yz 完成 200 220 226 227 230 250 257
      3yz 中间 331 350
      4yz 暂时否定 421 425 426
      5yz 永久否定 500 530 550
    传输类型
      TYPE A ASCII 换行符转换
      TYPE I Binary 原样传输
      混用导致文件损坏
    匿名FTP RFC1635
      anonymous 加邮箱密码
      chroot隔离
      上传目录不可列
    安全问题
      明文凭据与数据
      FTP Bounce攻击
      无完整性保护
      NAT ALG依赖明文
    安全替代
      FTPS 显式AUTH TLS 21
      FTPS 隐式 990
      SFTP 是SSH子系统 22
      HTTPS 用于分发下载