概述
本文系原创,同时发布于F5社区。
Nginx从1.9.0开始加入了stream模块支持四层的代理,转发和负载均衡。但是,stream模块的功能相对简单。对需要ALG处理的协议比如FTP的支持也远远不够。
我试着去修改了Nginx 的源代码,使之支持了FTP Passive模式的ALG功能。 Github的源码地址为:源码 。
可能大家会说,Passive模式不需要ALG。准确的说,Passive模式下,如果ftp client到server路由可达,可以不需要ALG。但是在client到server路由不可达的情况下,Passive模式也必须有ALG的支持。
代码修改更多是为了功能实验,代码写得比较粗糙,要想和Nginx本身的代码完全融合起来,还需要一些时间去做修改和打磨。
FTP和ALG
FTP协议
FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一,也属于典型的客户端服务器结构。
FTP客户端和服务器之间需要建立两条TCP连接,一条是控制连接,用来发送控制指令,另外一条是数据连接,真正的文件传输是通过数据连接来完成的。比如在ftp客户端输入ls命令,ls命令本身是通过控制连接发送的,而ls得到的内容是通过数据连接发送的。
图1: FTP主动连接
图2:FTP被动连接
如上图对于两种传输模式来说,控制连接的建立过程都是一样,均为服务器监听21号端口,客户端向服务器的该端口发起TCP连接。
对于数据连接,FTP有两种工作方式,一种是主动方式(Port方式),另外一种是被动方式(Passive方式)。 这两种工作方式的命名是以服务器的视角来区分的。主动方式就是数据连接是服务器主动来连接客户端。被动方式是服务器被动地等待客户端来连接。主动模式服务器通过控制连接知道客户端监听的端口后,使用自己的20号端口作为源端口,“主动”发起TCP数据连接。
而被动模式服务器监听1024-65535的一个随机端口,并通过控制连接将该端口告诉客户端,客户端向服务器的该端口发起TCP数据连接,这种情况下数据连接的建立相当于服务器是“被动”的。
FTP和NAT
FTP协议出现时,Internet规模还没有如此庞大。IPV4的地址足够每一个客户端使用。所以,FTP在那时工作的很好。
随着Internet的飞速发展,IPV4的地址已经远远不够。为了解决这一问题,NAT技术就出现了。NAT技术使得多个client可以使用同一个IP进行Internet访问。但是NAT技术只会改变报文头部的IP地址,并不会改变数据报文里面的内容。这使得FTP协议在NAT环境下,数据连接会出现问题。
图3: FTP主动模式经过NAT
图4:FTP被动模式经过NAT
如上图,常见的FTP使用场景,客户端在内网,服务器在外网。客户端访问服务需要经过NAT。
客户端的报文经过了路由器NAT之后,服务器端看到的报文的客户端的报文的IP源地址和端口都已经被改变了,当然对于控制连 接来说这没有影响,因为有网关或者路由器的NAT模块在中间做管理。
对于正要通过控制连接建立的数据连接就有问题了,因为NAT改变的仅仅是IP报文 头,而对于因为报文中PORT和PASV命令中包含的地址和端口信息没有做任何改动,这样对与PORT模式服务器是无法和一个内部地址建立连接的。对于PASV模式,而且客户端和服务路由不可达的情况下,客户端无法直接和Server建立连接。所以这个时候就出现了ALG这个功 能。
ALG
ALG是Application Layer Gateway的简称。它的功能就是在发现如果报文头做了NAT,在这个时候如果发现这个是一个FTP的连接的时候,就需要同时改变PORT和PASV命令中的地址和端口。从而让client和server之间可以正确连接。在这个过程中,ALG不仅需要监听,修改控制连接的报文,还需要代理转发数据连接的报文。
代码改动的原理
Nginx Stream模块对于协议只是进行了简单的两端数据交换。本身不会对协议包内容进行任何的监听和修改。
图5:ALG对ftp的支持
代码的修改原理是,
- 监听ftp控制连接上的数据,如果发现server给client方法PASV <IP> <PORT>命令。修改报文中的<IP>和<PORT>。把IP改写成自己的IP地址,同时用一个新port N代替原来报文里面的PORT。
- 在port N上启一个监听socket.同时把修改后的报文传给client.
- Client收到新的报文以后就会往Nginx的port N上进行连接。
- 建立连接以后,Nginx再向server原来的地址和port发起连接。
等两端连接建立起来以后,client和server的数据通道就可以打通了。
结语
通过修改Nginx代码支持FTP的passive模式的ALG,可以进一步学习和理解Nginx的原理和代码,从而进一步掌握Nginx的内部运行机理。