Skip to content
Advertisement

Controlling port numbers in use when connecting to FTP via Python’s ftplib behind a firewall

I’m trying to connect to a FTP server from behind a firewall that accepts outputs from the port range 6100–6200 only. I’m (rather naively, but based on a read of the documentation) trying:

from ftplib import FTP

host_address="the.ftp.ip.address"
ftp = FTP()
ftp.connect(host=hostaddress, source_address=("127.0.0.1", 6100))

But this gives the error:

In [42]: ftp = FTP(host_address, source_address=('127.0.0.1', 6100))
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-42-071ac06087bd> in <module>
----> 1 ftp = FTP(destination, source_address=('127.0.0.1', 6100))

~/SOFTWARE/anaconda3/envs/GDAL/lib/python3.9/ftplib.py in __init__(self, host, user, passwd, acct, timeout, source_address, encoding)
    117         self.timeout = timeout
    118         if host:
--> 119             self.connect(host)
    120             if user:
    121                 self.login(user, passwd, acct)

~/SOFTWARE/anaconda3/envs/GDAL/lib/python3.9/ftplib.py in connect(self, host, port, timeout, source_address)
    154             self.source_address = source_address
    155         sys.audit("ftplib.connect", self, self.host, self.port)
--> 156         self.sock = socket.create_connection((self.host, self.port), self.timeout,
    157                                              source_address=self.source_address)
    158         self.af = self.sock.family

~/SOFTWARE/anaconda3/envs/GDAL/lib/python3.9/socket.py in create_connection(address, timeout, source_address)
    841     if err is not None:
    842         try:
--> 843             raise err
    844         finally:
    845             # Break explicitly a reference cycle

~/SOFTWARE/anaconda3/envs/GDAL/lib/python3.9/socket.py in create_connection(address, timeout, source_address)
    829             if source_address:
    830                 sock.bind(source_address)
--> 831             sock.connect(sa)
    832             # Break explicitly a reference cycle
    833             err = None

OSError: [Errno 22] Invalid argument

From the same machine, I can successfully list the files using curl:

curl --ftp-port :6100-6200 --list-only $ftpserver

How can I connect to a regular (i.e. port 21) FTP server with ftplib circumventing my local firewall?

Advertisement

Answer

The curl --ftp-port switch enables the use of the active mode and sets the local listening ports to be used for incoming data connections.

That’s not what your code does. Your code uses the passive mode and sets source ports of outgoing control connection.


First, if you need to use the active mode, that generally means that your FTP server or your firewall is misconfigured. The active mode actually requires more firewall configuration than the passive mode. You should use the passive mode. That’s a way less work and more robust solution.

Note that in the passive mode, you cannot control the ports used, as that’s server’s decision, not client’s. You have to configure your local firewall (if any) to allow the data connection ports that the server is using.


If you really really need to use the active mode, use FTP.set_pasv.

ftp = FTP()
ftp.set_pasv(False)
ftp.connect(host=hostaddress)

The ftplib does not allow you to control the ports for the active mode. You would have to either edit the makeport method in the ftplib FTP class or override it in your code. Make it listen on a specific port in sock.bind:

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement