2

My Centos 7 firewall is blocking file exchange (import and export) with an external FTP client if I try to connect via script. Everything works fine if I disable the firewall. Do you know if a specific port is needed for file exchange of could something else be the issue here?

# sudo firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: dhcpv6-client ftp http https smtp ssh
  ports: 2222/tcp 587/tcp 25/tcp 465/tcp 21/tcp 80/tcp 40000/tcp 443/tcp 22/tcp 53/udp 53/tcp
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:


# lsmod | grep nf_conntrack_ftp
nf_conntrack_ftp       18478  1 nf_nat_ftp
nf_conntrack          139264  8 nf_nat_ftp,nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_conntrack_ftp,nf_conntrack_ipv4,nf_conntrack_ipv6

# iptables-save -c
# Generated by iptables-save v1.4.21 on Thu Aug 13 19:59:02 2020
*nat
:PREROUTING ACCEPT [81930:11078515]
:INPUT ACCEPT [14712:902333]
:OUTPUT ACCEPT [24878:1693871]
:POSTROUTING ACCEPT [24878:1693871]
:OUTPUT_direct - [0:0]
:POSTROUTING_ZONES - [0:0]
:POSTROUTING_ZONES_SOURCE - [0:0]
:POSTROUTING_direct - [0:0]
:POST_public - [0:0]
:POST_public_allow - [0:0]
:POST_public_deny - [0:0]
:POST_public_log - [0:0]
:PREROUTING_ZONES - [0:0]
:PREROUTING_ZONES_SOURCE - [0:0]
:PREROUTING_direct - [0:0]
:PRE_public - [0:0]
:PRE_public_allow - [0:0]
:PRE_public_deny - [0:0]
:PRE_public_log - [0:0]
[81930:11078515] -A PREROUTING -j PREROUTING_direct
[81930:11078515] -A PREROUTING -j PREROUTING_ZONES_SOURCE
[81930:11078515] -A PREROUTING -j PREROUTING_ZONES
[24878:1693871] -A OUTPUT -j OUTPUT_direct
[24878:1693871] -A POSTROUTING -j POSTROUTING_direct
[24878:1693871] -A POSTROUTING -j POSTROUTING_ZONES_SOURCE
[24878:1693871] -A POSTROUTING -j POSTROUTING_ZONES
[19315:1359674] -A POSTROUTING_ZONES -o eth0 -g POST_public
[5563:334197] -A POSTROUTING_ZONES -g POST_public
[24878:1693871] -A POST_public -j POST_public_log
[24878:1693871] -A POST_public -j POST_public_deny
[24878:1693871] -A POST_public -j POST_public_allow
[81930:11078515] -A PREROUTING_ZONES -i eth0 -g PRE_public
[0:0] -A PREROUTING_ZONES -g PRE_public
[81930:11078515] -A PRE_public -j PRE_public_log
[81930:11078515] -A PRE_public -j PRE_public_deny
[81930:11078515] -A PRE_public -j PRE_public_allow
COMMIT
# Completed on Thu Aug 13 19:59:02 2020
# Generated by iptables-save v1.4.21 on Thu Aug 13 19:59:02 2020
*mangle
:PREROUTING ACCEPT [477222:238129658]
:INPUT ACCEPT [447004:234240830]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [415842:656698573]
:POSTROUTING ACCEPT [415842:656698573]
:FORWARD_direct - [0:0]
:INPUT_direct - [0:0]
:OUTPUT_direct - [0:0]
:POSTROUTING_direct - [0:0]
:PREROUTING_ZONES - [0:0]
:PREROUTING_ZONES_SOURCE - [0:0]
:PREROUTING_direct - [0:0]
:PRE_public - [0:0]
:PRE_public_allow - [0:0]
:PRE_public_deny - [0:0]
:PRE_public_log - [0:0]
[477222:238129658] -A PREROUTING -j PREROUTING_direct
[477222:238129658] -A PREROUTING -j PREROUTING_ZONES_SOURCE
[477222:238129658] -A PREROUTING -j PREROUTING_ZONES
[447004:234240830] -A INPUT -j INPUT_direct
[0:0] -A FORWARD -j FORWARD_direct
[415842:656698573] -A OUTPUT -j OUTPUT_direct
[415842:656698573] -A POSTROUTING -j POSTROUTING_direct
[384088:64944119] -A PREROUTING_ZONES -i eth0 -g PRE_public
[93134:173185539] -A PREROUTING_ZONES -g PRE_public
[477222:238129658] -A PRE_public -j PRE_public_log
[477222:238129658] -A PRE_public -j PRE_public_deny
[477222:238129658] -A PRE_public -j PRE_public_allow
COMMIT
# Completed on Thu Aug 13 19:59:02 2020
# Generated by iptables-save v1.4.21 on Thu Aug 13 19:59:02 2020
*security
:INPUT ACCEPT [409931:227948926]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [415842:656698573]
:FORWARD_direct - [0:0]
:INPUT_direct - [0:0]
:OUTPUT_direct - [0:0]
[409931:227948926] -A INPUT -j INPUT_direct
[0:0] -A FORWARD -j FORWARD_direct
[415842:656698573] -A OUTPUT -j OUTPUT_direct
COMMIT
# Completed on Thu Aug 13 19:59:02 2020
# Generated by iptables-save v1.4.21 on Thu Aug 13 19:59:02 2020
*raw
:PREROUTING ACCEPT [477222:238129658]
:OUTPUT ACCEPT [415842:656698573]
:OUTPUT_direct - [0:0]
:PREROUTING_ZONES - [0:0]
:PREROUTING_ZONES_SOURCE - [0:0]
:PREROUTING_direct - [0:0]
:PRE_public - [0:0]
:PRE_public_allow - [0:0]
:PRE_public_deny - [0:0]
:PRE_public_log - [0:0]
[477222:238129658] -A PREROUTING -j PREROUTING_direct
[477222:238129658] -A PREROUTING -j PREROUTING_ZONES_SOURCE
[477222:238129658] -A PREROUTING -j PREROUTING_ZONES
[415842:656698573] -A OUTPUT -j OUTPUT_direct
[384088:64944119] -A PREROUTING_ZONES -i eth0 -g PRE_public
[93134:173185539] -A PREROUTING_ZONES -g PRE_public
[477222:238129658] -A PRE_public -j PRE_public_log
[477222:238129658] -A PRE_public -j PRE_public_deny
[477222:238129658] -A PRE_public -j PRE_public_allow
COMMIT
# Completed on Thu Aug 13 19:59:02 2020
# Generated by iptables-save v1.4.21 on Thu Aug 13 19:59:02 2020
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [322708:483513034]
:FORWARD_IN_ZONES - [0:0]
:FORWARD_IN_ZONES_SOURCE - [0:0]
:FORWARD_OUT_ZONES - [0:0]
:FORWARD_OUT_ZONES_SOURCE - [0:0]
:FORWARD_direct - [0:0]
:FWDI_public - [0:0]
:FWDI_public_allow - [0:0]
:FWDI_public_deny - [0:0]
:FWDI_public_log - [0:0]
:FWDO_public - [0:0]
:FWDO_public_allow - [0:0]
:FWDO_public_deny - [0:0]
:FWDO_public_log - [0:0]
:INPUT_ZONES - [0:0]
:INPUT_ZONES_SOURCE - [0:0]
:INPUT_direct - [0:0]
:IN_public - [0:0]
:IN_public_allow - [0:0]
:IN_public_deny - [0:0]
:IN_public_log - [0:0]
:OUTPUT_direct - [0:0]
[389741:226716526] -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
[5566:334317] -A INPUT -i lo -j ACCEPT
[51697:7189987] -A INPUT -j INPUT_direct
[51697:7189987] -A INPUT -j INPUT_ZONES_SOURCE
[51697:7189987] -A INPUT -j INPUT_ZONES
[73:4550] -A INPUT -m conntrack --ctstate INVALID -j DROP
[37000:6287354] -A INPUT -j REJECT --reject-with icmp-host-prohibited
[0:0] -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
[0:0] -A FORWARD -i lo -j ACCEPT
[0:0] -A FORWARD -j FORWARD_direct
[0:0] -A FORWARD -j FORWARD_IN_ZONES_SOURCE
[0:0] -A FORWARD -j FORWARD_IN_ZONES
[0:0] -A FORWARD -j FORWARD_OUT_ZONES_SOURCE
[0:0] -A FORWARD -j FORWARD_OUT_ZONES
[0:0] -A FORWARD -m conntrack --ctstate INVALID -j DROP
[0:0] -A FORWARD -j REJECT --reject-with icmp-host-prohibited
[93134:173185539] -A OUTPUT -o lo -j ACCEPT
[322708:483513034] -A OUTPUT -j OUTPUT_direct
[0:0] -A FORWARD_IN_ZONES -i eth0 -g FWDI_public
[0:0] -A FORWARD_IN_ZONES -g FWDI_public
[0:0] -A FORWARD_OUT_ZONES -o eth0 -g FWDO_public
[0:0] -A FORWARD_OUT_ZONES -g FWDO_public
[0:0] -A FWDI_public -j FWDI_public_log
[0:0] -A FWDI_public -j FWDI_public_deny
[0:0] -A FWDI_public -j FWDI_public_allow
[0:0] -A FWDI_public -p icmp -j ACCEPT
[0:0] -A FWDO_public -j FWDO_public_log
[0:0] -A FWDO_public -j FWDO_public_deny
[0:0] -A FWDO_public -j FWDO_public_allow
[51697:7189987] -A INPUT_ZONES -i eth0 -g IN_public
[0:0] -A INPUT_ZONES -g IN_public
[51697:7189987] -A IN_public -j IN_public_log
[51697:7189987] -A IN_public -j IN_public_deny
[51697:7189987] -A IN_public -j IN_public_allow
[2121:165216] -A IN_public -p icmp -j ACCEPT
[6239:372132] -A IN_public_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[760:43448] -A IN_public_allow -p tcp -m tcp --dport 80 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[3894:222151] -A IN_public_allow -p tcp -m tcp --dport 443 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[32:1852] -A IN_public_allow -p tcp -m tcp --dport 25 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[105:5552] -A IN_public_allow -p tcp -m tcp --dport 21 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[88:4644] -A IN_public_allow -p tcp -m tcp --dport 2222 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[17:984] -A IN_public_allow -p tcp -m tcp --dport 587 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[0:0] -A IN_public_allow -p tcp -m tcp --dport 25 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[1337:80164] -A IN_public_allow -p tcp -m tcp --dport 465 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[0:0] -A IN_public_allow -p tcp -m tcp --dport 21 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[0:0] -A IN_public_allow -p tcp -m tcp --dport 80 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[0:0] -A IN_public_allow -p tcp -m tcp --dport 40000 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[0:0] -A IN_public_allow -p tcp -m tcp --dport 443 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[0:0] -A IN_public_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[27:1740] -A IN_public_allow -p udp -m udp --dport 53 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
[4:200] -A IN_public_allow -p tcp -m tcp --dport 53 -m conntrack --ctstate NEW,UNTRACKED -j ACCEPT
COMMIT
# Completed on Thu Aug 13 19:59:02 2020
4
  • 1
    ftp needs ports 20 and 21, sftp needs port 22
    – Panki
    Commented Aug 7, 2020 at 9:24
  • I have added port 20/tcp, but still not working. Could it be something other than the ports?
    – Sjors
    Commented Aug 7, 2020 at 9:29
  • Do you get an error message? That would help.
    – Panki
    Commented Aug 7, 2020 at 9:37
  • @Panki Be careful about what port 20 is used for. The port 20 is a connection from the server's local port 20 to a random port (negotiated in control port 21's data) on the client. Here 20 is the source of the initial connection, not the destination, and it's used only for active ftp, as explained in Haxiel's answer. So adding port 20 destination for incoming traffic in a stateful firewall for the FTP server has no effect (either it was already allowed if output isn't restricted, or else it's still not allowed, since it should be enabled as source port 20 in the output chain).
    – A.B
    Commented Aug 7, 2020 at 17:35

2 Answers 2

2

The ports being used can vary based on the 'mode' used for the FTP connection.

In active mode, the client connects to the server on the command port (21). It also provides the server with a random, unprivileged port on its end (> 1023). The server connects back to the client with this random port as the destination.

In passive mode, the client connects to the server on the command port (21). The server then opens a random, unprivileged port on its end and sends that information to the client. The client connects back to the server with this random port as the destination.

You can refer to the following article for a more detailed explanation: Active FTP vs. Passive FTP, a Definitive Explanation.


Based on your description of the error, your FTP client must be connecting in passive mode. As stated above, the server will then open a random, unprivileged port on its side and expect the client to connect back to it. The firewall would then block the incoming connection, which causes the whole thing to fail.

To support passive mode, the recommended approach is to configure your FTP server to use a specific range of ports, and then open that same range on the firewall. The number of ports you need to open will depend upon the number of clients you want to support.


If it is feasible, you might also want to look at SFTP as an alternative. SFTP is a completely different protocol that is built on top of SSH. It operates using the single, standard SSH port (22) which is easier to handle at the firewall level. SFTP also encrypts all communication, which is not natively possible with traditional FTP.

2

Here's an alternate answer to Haxiel's answer. If encryption is used (eg: with AUTH TLS) then Haxiel's answer must still be used: match the FTP's server configuration with the firewall configuration to use a reserved range of ports.


Some protocols, often old protocols, are not very firewall-friendly. FTP is one of them: for each command sent it negotiates between the client and the server an additional dynamic ephemeral port to send data through. Depending on active or passive FTP mode, the commands, replies and direction of connection made for this data connection varies.

The Linux kernel Netfilter's conntrack subsystem provides a few specific protocol helpers to cope with some of these protocols. Of course it provides such an helper for FTP, nf_conntrack_ftp:

Help text

Tracking FTP connections is problematic: special helpers are required for tracking them, and doing masquerading and other forms of Network Address Translation on them.

This is FTP support on Layer 3 independent connection tracking.

Once the module nf_conntrack_ftp is loaded, as long as FTP isn't encrypted (ie: no AUTH TLS: encryption prevents snooping) the kernel's Netfilter conntrack subsystem will snoop on TCP port 21 and analyze all the commands to know in advance what port will be needed, and pre-allow such port with the use of iptables' -m conntrack --ctstate RELATED (or nftables' ct state related):

RELATED

The packet is starting a new connection, but is associated with an existing connection, such as an FTP data transfer or an ICMP error.

Of course firewalld uses such rule in its ruleset, because it's the same also allowing for example ICMP errors to be received back by the application.

Should the system be doing NAT, then an additional module nf_nat_ftp will extend conntrack to also intercept data and change the negotiated ports so it works transparently for the client and the server.

So in short:

modprobe nf_conntrack_ftp

should solve OP's problem. It works in all FTP cases (on client or server, active or passive FTP). To load this module at boot time on CentOS7, one can for example add a file in /etc/modules-load.d/ and let it be handled by systemd. Eg, as root:

# echo nf_conntrack_ftp > /etc/modules-load.d/local-nfhelpers.conf
# systemctl enable systemd-modules-load
# systemctl restart systemd-modules-load

If the server is doing NAT somehow (redirecting the FTP port on an other port, routing a private LAN, hosting VMs or containers...) one can consider also adding the module nf_nat_ftp so NAT will be handled correctly too.

This is all that is needed on CentOS 7's kernel 3.10 by default.


Note:

On newer kernels, especially kernels >= 4.7, the automatic protocol port assignment (eg: for FTP, track port 21) is disabled by default and additional iptables rules should be added to select the exact cases where the helper should be activated for a given flow. There's now possible interactions with firewalld's work, so it might become more complicated. One can of course re-enable the former way, but it's considered more secure to select what conditions will activate the snooping.

More informations about this in this blog (made by some netfilter maintainers): To Linux and beyond ! Secure use of iptables and connection tracking helpers.

11
  • Thanks for explaining that to me with the required steps to solve the issue!
    – Sjors
    Commented Aug 10, 2020 at 12:40
  • note: you should remove all unneeded ports in firewalld's rules: 20/tcp (which is almost always an error to have on a stateful firewall, even if there's no security issue introduced) and possibly 49152-65534/tcp if it was added for this ftp problem.
    – A.B
    Commented Aug 10, 2020 at 13:26
  • Thanks, I have removed them. The only ports that I have now opened are 2222/tcp 587/tcp 25/tcp 465/tcp 21/tcp 80/tcp 40000/tcp 443/tcp 22/tcp 53/udp 53/tcp.
    – Sjors
    Commented Aug 10, 2020 at 18:29
  • I thought I figured it out, but I still experience the issue. I created the file and if I check # cat /etc/modules-load.d/local-nfhelpers.conf it gives the output nf_conntrack_ftp. Is this enough?
    – Sjors
    Commented Aug 13, 2020 at 17:16
  • What you need is lsmod | grep nf_conntrack_ftp has an output. On CentOS7 you must have run the systemd enable systemd-modules-load command for it to work at boot. If the module isn't loaded for some reason, you can manually do so with modprobe nf_conntrack_ftp as already written. If it still doesn't work when the module is loaded, This would need a dump of iptables-save -c which will be large considering how is working firewalld. If you're using something special beside firewalld (like Docker) you should tell it too.
    – A.B
    Commented Aug 13, 2020 at 17:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .