1. 前言
在使用FTP/FTPS协议进行文件传输时,有一些特性可能会让人疑惑而导致问题,本文进行了总结。
2. FTP说明
FTP协议的原始规范由Abhay Bhushan撰写,并于1971年4月16日发布为RFC 114。
2.1 FTP传输模式
wiki“File Transfer Protocol”关于ftp的主动模式与被动模式说明如下:
FTP可以以主动或被动模式运行,从而确定数据连接的建立方式。在这两种情况下,客户端从一个随机的,通常是非特权的端口N创建一个TCP控制连接到FTP服务器命令端口21。 在主动模式下,客户端开始侦听来自端口M上的服务器的传入数据连接。它发送FTP命令PORTM,通知服务器它正在侦听哪个端口。然后,服务器从其端口20(FTP服务器数据端口)向客户端发起数据通道。 在客户端位于防火墙后并且无法接受传入TCP连接的情况下,可能会使用被动模式。在此模式下,客户端使用控制连接向服务器发送PASV命令,然后从服务器接收服务器IP地址和服务器端口号,客户端然后使用它来打开数据连接一个任意客户端口到服务器的IP地址和服务器端口号。 |
被动模式又称为防火墙友好FTP(Firewall-Friendly FTP)。
使用主动模式FTP时,客户端需要监听端口供服务端访问,不便于使用;使用被动模式FTP时,只需要客户端访问服务器的端口,服务器不用访问客户端的端口,更加适合实际使用场景。
以下仅讨论FTP被动模式。
2.2 FTP端口
FTP使用的端口分为命令端口与数据端口,默认命令端口为21,默认数据端口为20。
通过FTP进行文件传输时,文件内容通过数据端口进行传输。
当FTP客户端与服务器之间存在防火墙时,命令端口与数据端口均需要允许访问,才能进行FTP文件传输。
命令端口由FTP服务器监听,可以telnet连接成功;数据端口无法通过telnet连接成功。
在某些型号的防火墙配置允许21端口通信后,会自动允许对应连接的20端口通信。
2.3 FTP数据表示方法
wiki“File Transfer Protocol”关于FTP数据表示方法说明如下:
FTP存在四种数据表示方法: ASCII模式:用于文本。 如果需要,将数据从发送主机的字符表示转换为传输前的“8位ASCII”,并且(如有必要)将数据转换为接收主机的字符表示。 因此,此模式不适用于包含纯文本以外的数据的文件。 图像模式(通常称为二进制模式):发送机逐字节发送每个文件, 收件人收到字节流时收到它。(对于FTP的所有实现,建议使用图像模式支持)。 EBCDIC模式:用于使用EBCDIC字符集的主机之间的纯文本。 本地模式:允许具有相同设置的两台计算机以专有格式发送数据,而无需将其转换为ASCII。 |
通常使用二进制模式,不会对传输的文件内容进行修改。
ASCII模式会对传输文件内容中的回车换行符进行替换,导致文件内容被修改。
2.4 FTP被动模式
2.4.1 传输过程
FTP被动模式文件传输过程如下:
l FTP客户端连接FTP服务器的命令端口,FTP服务器向FTP客户端返回数据端口及数据传输IP;
l FTP客户端连接上一步获取的数据传输IP的数据端口,进行数据传输。
FTP客户端可以通过向FTP服务器发送PASV命令进入被动模式;FTP服务器可以决定FTP客户端进行数据传输时连接的IP与端口。
2.4.2 访问示例
2.4.2.1 Linux ftp命令
使用Linux ftp命令连接ftp服务器时,访问示例如下:
ftp> open xx.xx.xx.xx 9501 Connected to xx.xx.xx.xx (xx.xx.xx.xx). 220 Serv-U FTP Server v15.0 ready... Name (xx.xx.xx.xx:app): user ---> USER user 331 User name okay, need password. Password: ---> PASS XXXX 230 User logged in, proceed. ---> SYST 215 UNIX Type: L8 Remote system type is UNIX. Using binary mode to transfer files. ftp> ls ftp: setsockopt (ignored): Permission denied ---> PASV 227 Entering Passive Mode (yy,yy,yy,yy,50,202) |
2.4.2.2 Filezilla Client
使用Filezilla Client连接ftp服务器时,访问示例如下:
状态: 正在连接 127.0.0.1:21... 状态: 连接建立,等待欢迎消息... 响应: 220-FileZilla Server 0.9.60 beta 响应: 220-written by Tim Kosse (tim.kosse@filezilla-project.org) 响应: 220 Please visit https://filezilla-project.org/ 命令: USER test 响应: 331 Password required for test 命令: PASS **** 响应: 230 Logged on 状态: 已登录 状态: 读取目录列表... 命令: PWD 响应: 257 "/" is current directory. 状态: 列出“/”的目录成功 状态: 读取“/”的目录列表... 命令: TYPE I 响应: 200 Type set to I 命令: PASV 响应: 227 Entering Passive Mode (127,0,0,1,48,57) 命令: MLSD 响应: 150 Opening data channel for directory listing of "/" 响应: 226 Successfully transferred "/" 状态: 列出“/”的目录成功 响应: 421 Connection timed out. 状态: 连接被服务器关闭 |
2.4.3 PASV命令回应
FTP客户端通过被动模式连接FTP服务器后,会向服务器发送PASV命令。
FTP服务器对于PASV命令的回应为“A1,A2,A3,A4,a1,a2”格式,其中包含了服务器返回的数据传输IP及数据端口,如“访问示例”部分黄色背景的内容。
“A1,A2,A3,A4”为服务器向客户端返回的数据传输IP,对应IP为“A1.A2.A3.A4”。如返回“127,0,0,1”,则对应的数据传输IP为“127.0.0.1”。
“a1,a2”为服务器向客户端返回的数据端口,将a1的十六进制作为高位与a2的十六进制作为低位进行拼接,拼接结果的十进制数值,为数据端口。如返回“48,57”,48=0x30,57=0x39,0x3039=12345,则数据端口为12345。
2.4.4 FTP跨局域网配置
当FTP客户端与服务器不在同一个局域网时,使用被动模式传输时配置需要进行调整,否则无法传输。
2.4.4.1 客户端不配置NAT
当FTP客户端不配置NAT时,FTP客户端直接访问FTP服务器的外网IP。此时FTP服务器需要将数据传输IP设置为FTP服务器的外网IP,不能设置为FTP服务器的局域网IP。
网络环境如下所示:
访问过程如下所示:
2.4.4.2 客户端配置NAT
当FTP客户端配置NAT时,FTP客户端只能访问在客户端NAT后的IP,无法直接访问FTP服务器的外网IP。此时FTP服务器需要将数据传输IP设置为客户端NAT后的IP,不能设置为FTP服务器的外网IP,也不能设置为FTP服务器的局域网IP。
网络环境如下所示:
访问过程如下所示:
2.4.5 FTP服务器被动模式配置
以FileZilla Server为例,对FTP服务器被动模式配置进行说明。
FileZilla Server的“Passive mode settings”配置中,可设置被动模式数据端口范围,数据传输IP,如下图所示,设置数据端口为2121,数据传输IP为“192.168.201.25”。
“Don't use external IP for local connections”选项是否钩选视情况而定。如果在通信时FTP服务器获取的FTP客户端IP为外网IP,则上述选项可以钩选;如果在通信时FTP服务器获取的FTP客户端IP为服务器局域网IP,则上述选项不能钩选。
3. FTPS说明
3.1 FTPS与FTP对比
FTPS协议为FTP协议的扩展,通信数据通过SSL/TLS协议进行加密,其他特性与FTP协议一致。
FTPS服务器需要配置SSL密钥与证书。
FTPS协议与SFTP协议为不同的协议。
3.2 隐式ftps与显式ftps
wiki“FTPS”关于隐式ftps与显式ftps说明如下:
隐式方法要求从连接开始建立传输层安全性,从而破坏与非FTPS感知客户端和服务器的兼容性,但显式方法使用标准FTP协议命令和回复来升级纯文本连接到加密的一个,允许单个控制端口用于为FTPS感知和非FTPS感知的客户端提供服务。 隐式FTPS配置不支持协商。客户端将立即使用TLSClientHello消息来挑战FTPS服务器。如果FTPS服务器未收到这样的消息,服务器应该删除连接。 在显式模式(也称为FTPES)中,FTPS客户端必须“明确地请求”FTPS服务器的安全性,然后加强为双方同意的加密方式。如果客户端不要求安全性,则FTPS服务器可以允许客户端以不安全的方式继续或拒绝连接。 |
如果FTPS服务器仅允许FTPS连接,则FTP客户端在连接时需要选择隐式FTPS;如果FTP服务器允许FTP与FTPS连接,则FTP客户端在连接时可选择显式FTPS。
4. FTP Java实现说明
4.1 被动模式设置
当使用org.apache.commons.net提供的FTP功能时,若需要使用被动模式,则需要在进行文件传输操作之前调用FTPClient类的enterLocalPassiveMode方法,使FTP客户端使用被动模式。
在以上方法中,会将__dataConnectionMode变量设置为PASSIVE_LOCAL_DATA_CONNECTION_MODE。
以上方法的注释如下:
Use this method only for data transfers between the client and server. This method causes a PASV (or EPSV) command to be issued to the server before the opening of every data connection, telling the server to open a data port to which the client will connect to conduct data transfers. |
5. 常见异常分析
5.1 生产环境未添加数据端口策略
假如需要以客户端身份访问合作方的FTP服务器,由于测试环境的DMZ服务器访问公网端口无限制,因此可能导致漏掉对生产环境ftp服务器数据端口增加防火墙策略,需要进行检查。
5.2 FTP服务返回局域网IP
FTP客户端与FTP服务器不在同一个局域网,使用Linux的ftp命令登录FTP服务器,登录成功,但执行ls命令显示“Connection refused”,如下所示:
ftp> ls 227 Entering Passive Mode (192,168,0,16,193,82). ftp: connect: Connection refused ftp> ls 227 Entering Passive Mode (192,168,0,16,193,83). ftp: connect: Connection refused ftp> ls 227 Entering Passive Mode (192,168,0,16,193,84). ftp: connect: Connection refused ftp> ls 227 Entering Passive Mode (192,168,0,16,193,85). ftp: connect: Connection refused |
可以看到服务器返回的数据传输IP为“192.168.0.16”,为FTP服务器的局域网IP,说明FTP服务器配置的数据传输IP错误,需要进行修改。
5.3 FTP服务器未限制数据端口范围
使用Linux的ftp命令登录FTP服务器,登录成功,但服务器每次返回的数据端口不相同,如下所示:
ftp> ls 227 Entering Passive Mode (xx,xx,xx,xx,50,201) |
ftp> ls 227 Entering Passive Mode (xx,xx,xx,xx,50,202) |
之后返回的数据端口都次都加1 |
可以看到服务器返回的数据端口分别为“50,201”“50,202”等。
50=0x32,201=0xC9,0x32C9=13001。
50=0x32,202=0xCA,0x32CA=13002。
即服务器返回的数据端口分别为13001、13002等。
对以上操作进行抓包,结果分别如下,可以看到FTP客户端分别访问了FTP服务器的13001、13002等端口。
14:31:41.056265 IP [client ip].36918 > [server ip].9501: Flags [P.], seq 17:23, ack 50, win 115, length 6 14:31:41.090168 IP [server ip].9501 > [client ip].36918: Flags [P.], seq 50:102, ack 23, win 64208, length 52 14:31:41.090260 IP [client ip].36918 > [server ip].9501: Flags [.], ack 102, win 115, length 0 14:31:41.090315 IP [client ip].56974 > [server ip].13001: Flags [S], seq 1440044607, win 14600, options [mss 1460,sackOK,TS val 136313463 ecr 0,nop,wscale 7], length 0 |
14:31:54.087262 IP [client ip].36918 > [server ip].9501: Flags [P.], seq 29:35, ack 217, win 115, length 6 14:31:54.122054 IP [server ip].9501 > [client ip].36918: Flags [P.], seq 217:269, ack 35, win 64196, length 52 14:31:54.122122 IP [client ip].36918 > [server ip].9501: Flags [.], ack 269, win 115, length 0 14:31:54.122163 IP [client ip].44644 > [server ip].13002: Flags [S], seq 1338001892, win 14600, options [mss 1460,sackOK,TS val 136326495 ecr 0,nop,wscale 7], length 0 |
以上现象说明FTP服务器未限制数据端口范围,需要进行修改。
5.4 FTPClient类未设置使用被动模式
在Java代码中使用org.apache.commons.net的FTP功能进行文件上传,调用FTPClient类的storeFile方法进行文件上传后一直不返回。
调试发现依次进入storeFile、__storeFile、_storeFile、_openDataConnection_方法。
在_openDataConnection_方法中,执行了以下方法:
if (__dataConnectionMode == ACTIVE_LOCAL_DATA_CONNECTION_MODE) ... ServerSocket server = _serverSocketFactory_.createServerSocket(getActivePort(), 1, getHostAddress()); ... socket = server.accept(); |
客户端启动了Socket监听,说明FTP客户端以主动模式运行。
ACTIVE_LOCAL_DATA_CONNECTION_MODE从变量名看是主动模式,根据该变量找到PASSIVE_LOCAL_DATA_CONNECTION_MODE变量,从变量名看是被动模式,根据“__dataConnectionMode = PASSIVE_LOCAL_DATA_CONNECTION_MODE”找到enterLocalPassiveMode方法,该方法为进入被动模式。当__dataConnectionMode变量为PASSIVE_LOCAL_DATA_CONNECTION_MODE时,在_openDataConnection_方法中,会进入被动模式的处理流程,代码略。
因此当需要使用被动模式时,在进行文件传输操作之前需要调用FTPClient类的enterLocalPassiveMode方法。