OpenBSD PF - Issues with FTP [Contents]

FTP Modes

FTP is a protocol that dates back to when the internet was a small, friendly collection of computers and everyone knew everyone else. At that time, the need for filtering or tight security wasn't necessary. FTP wasn't designed for filtering, for passing through firewalls, or for working with NAT.

FTP can be used in one of two ways: passive or active. Generally, the choice of active or passive is made to determine who has the problem with firewalling.

With active FTP, when a user connects to a remote FTP server and requests information or a file, the FTP server makes a new connection back to the client to transfer the requested data. This is called the data connection. To start, the FTP client chooses a random port to receive the data connection. The client sends the port number it chose to the FTP server and listens for an incoming connection on that port. The FTP server then initiates a connection to the client's address at the chosen port and transfers the data. This is a problem for users attempting to gain access to FTP servers from behind a NAT gateway. Because of how NAT works, the FTP server initiates the data connection by connecting to the external address of the NAT gateway on the chosen port. The NAT machine will receive this, but, because it has no mapping for the packet in its state table, it will drop the packet and won't deliver it to the client.

With passive mode FTP (the default mode with OpenBSD's ftp(1) client), the client requests that the server pick a random port to listen on for the data connection. The server informs the client of the port it has chosen, and the client connects to this port to transfer the data. Unfortunately, this is not always possible or desirable because of the possibility of a firewall in front of the FTP server blocking the incoming data connection. To force active mode FTP, use the -A flag to ftp, or set passive mode to "off" by issuing the command "passive off" at the "ftp>" prompt.

FTP Client Behind the Firewall

As indicated earlier, FTP does not go through NAT and firewalls very well.

PF provides a solution for this situation by diverting FTP traffic through an FTP proxy server. This process acts to "guide" the FTP traffic through the NAT gateway/firewall by actively adding needed rules to PF and removing them when done via the anchor system. The FTP proxy used by PF is ftp-proxy(8).

To activate it, put something like this early in the rules section of pf.conf:

pass in quick on $int_if inet proto tcp to port 21 divert-to port 8021
This diverts FTP from the clients to the ftp-proxy program, which is listening on port 8021 of the server.

An anchor in the rules section is also needed:

anchor "ftp-proxy/*"
The proxy server has to be started and running on the OpenBSD box.
# rcctl enable ftpproxy
# rcctl start  ftpproxy
To support active mode connections from certain (fussy) clients, the -r flag may be needed.

PF "Self-protecting" an FTP Server

In this case, PF is running on the FTP server itself rather than a dedicated firewall computer. When servicing a passive connection, FTP will use a randomly chosen, high TCP port for incoming data. By default, OpenBSD's native ftpd(8) uses the range 49152 to 65535. Obviously, these must be passed through the filter rules, along with port 21 (the FTP control port):
pass in on egress proto tcp to port 21
pass in on egress proto tcp to port > 49151
That range of ports can be tightened up if desired. In the case of ftpd(8), that is done using the sysctl(8) variables net.inet.ip.porthifirst and net.inet.ip.porthilast.

FTP Server Protected by an External PF Firewall Running NAT

In this case, the firewall must redirect traffic to the FTP server in addition to not blocking the required ports.

ftp-proxy can be run in a mode that causes it to forward all FTP connections to a specific FTP server. The proxy will be set up to listen on port 21 of the firewall and forward all connections to the backend server.

# rcctl set ftpproxy flags -R -p 21 -b
Here is the IP address of the actual FTP server, 21 is the port ftp-proxy will listen on, and is the address on the firewall that the proxy will bind to.

Now for the pf.conf rules:

ext_ip = ""
ftp_ip = ""
match out on egress inet from $int_if nat-to (egress)
anchor "ftp-proxy/*"
pass in  on  egress inet proto tcp to $ext_ip port 21
pass out on $int_if inet proto tcp to $ftp_ip port 21 user _ftp_proxy
Here the connection inbound to port 21 is allowed on the external interface, as well as the corresponding outbound connection to the FTP server. The user _ftp_proxy addition to the outbound rule ensures that only connections initiated by ftp-proxy are permitted.

More Information on FTP

More information on filtering FTP and how FTP works in general can be found in this whitepaper.

Proxying TFTP

Trivial File Transfer Protocol (TFTP) suffers from some of the same limitations as FTP does when it comes to passing through a firewall. Luckily, PF has a helper proxy for TFTP called tftp-proxy(8).

tftp-proxy is set up in much the same way as ftp-proxy was in the FTP client behind the firewall section above.

match out on egress inet from $int_if nat-to (egress)
anchor "tftp-proxy/*"
pass in  quick on $int_if inet proto udp from $lan to port tftp \
    divert-to port 6969
pass out quick on $ext_if inet proto udp from $lan to port tftp \
    group _tftp_proxy divert-reply
The rules above allow TFTP outbound from the internal network to TFTP servers on the external network.

The last step is to enable and start tftp-proxy.

# rcctl enable tftpproxy
# rcctl start  tftpproxy