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
: