Cannot connect to a FTP server using the :ftp module

Hi there,

Yesterday I had to set up a FTP server to allow customers to upload some XML files (they don’t want to use our API).

The server runs on a cheap GCP VM instance. I can access it myself through FileZilla without any issue, but I can’t retrieve documents on it within my Phoenix app.

I followed a tutorial and slightly updated it due to future deprecations in OTP26 (:ftp.start_service/1:ftp.open/1, etc.). Here is my code:

url = "ftp://bob:bob@12.34.56.78:21/ftp"

%URI{
  host: host,
  scheme: "ftp",
  userinfo: userinfo,
  port: _port,
  path: _path
} = URI.parse(url)

# useless????
:ftp.start()
{:ok, client_pid} = :ftp.open(host |> String.to_charlist())

if userinfo do
  [user, pass] = userinfo |> String.split(":")
  :ok = :ftp.user(client_pid, ~c(#{user}), ~c(#{pass}))
end

:ok = :ftp.ls(client_pid)

:ftp.ls returns {:error, :timeout} after ~30 s. I know the credentials are correct.
What am I doing wrong here?

1 Like

Maybe you can turn on verbose.

{:ok, pid} =
      :ftp.open(@host,
        verbose: true,
        timeout: 3_000,
        dtimeout: 3_000

      )

Perhaps try also :ftp.nlist(pid)…

Hi @Werner, thanks for your reply.

Here is what I get in verbose mode:

"Receiving: 220 (vsFTPd 3.0.3)"
"Sending: USER bob"
"Receiving: 331 Please specify the password."
"Sending: PASS bob"
"Receiving: 230 Login successful."
"Sending: PASV"
"Receiving: 227 Entering Passive Mode (10,156,0,2,160,188)."

Also, I tried IO.inspect(:ftp.nlist(pid)) and got the same return: {:error, :timeout}.

Hi,
really strange, after the line with “Receiving: 227 Entering Passive Mode…” there should be something like
“Sending: NLST”.

We are using :ftp with two external ftp server (not our’s) and this work’s fine.
Possibly the ftp server is not configured 100% correctly, even though it may work with Filezilla client?

Maybe it has to do with this:
FTP passive mode always times out
and this:
passive reply with unroutable address

The first four numbers here are intended to tell the client the server’s IP. 10.156.0.2 is a private class-A address, not routable from the outside of the network - that’s why the connection hangs. You’ll need to figure out how to tell your FTP server its public, routable address.

The last two numbers are the port - so you’ll want to make sure the server’s firewall permits connections on port 41148 (160*256 + 188) - there’s usually a way to configure the range of these ports in your FTP server.

Hmm, isn’t that the job of the firewall guarding the (passive) ftp server by means of protocol inspection?

Clients connecting on the control connection, are told to (re-)connect on a data channel and the firewall should translate between the internal IP address (10.156.0.2) to the external accessible IP address (12.34.56.78:xyz).

Having seen the control connection on the application level (ftp) dialog, the firewall should allow the session initiated by the client on the data channel to the public address. Except in this case, you’re seeing its local address instead of its public address, so its look like the ftp-firewall is not inspecting/manipulating the IP addresses seen in the application level dialog.

In other words, looks like the passive ftp server is not guarded by a firewall that uses/handles the ftp protocol appropriately.

Okay, sorry, I thought that was clear from the two links. The second link from my post above also explains why the filezilla client works.

It seems like we’re all saying the same thing along different lines of thinking: the client should never see the internal, private, IP address of the ftp server even though some clients (like filezilla) work around that. I did a cursory glance of blogs/articles related to " GCP ftp server" and by the looks of it (i.e. the cursory glance) Google’s Cloud Platform offers a stateful firewall but I did not see any mention of protocol inspection. So it looks like al2o3cr was indeed correct in that you need to tell the ftp server to respond with its public ip address in response to a pasv command from the client.

The above is all assuming (warning: danger mr Robinson) that:

  1. the phoenix app is not in the GCP environment itself (otherwise you’d need to use its private IP address and a connection to its public address probably wouldn’t have worked in the first place)
  2. customers are using filezilla (or equivalent savvy) client to upload their xml files

Disclaimer: I work in an enterprise environment with mostly checkpoint firewalls that handle this type
of stuff. So take this feedback with a grain (or two) of salt.

Anyway, I’m curious as to how this problem got resolved! So let us know :slight_smile:

1 Like

Hi everyone, thanks for your answers!

@al2o3cr : yes, this is the internal IP address, the public one is the one I’m using:
Screen Shot 2022-04-08 at 16.09.44

@Werner : this might be a misconfiguration issue, yes. I followed this tutorial line by line: Set up an FTP Server on Google Cloud Platform - SiteYaar

@dwark : your assumptions are correct, my Phoenix app runs on Fly and customers are using a FileZilla-like client to connect.

OK, I reconfigure my GCP environment and I let you guys know!
Thank you all for your answers :heart:

Since the example you followed uses vsftpd, perhaps this article may be helpful: stackoverflow.
One of the few articles that showed setting the public IP of the ftp server, in this case via:

# vim /etc/vsftpd.conf
...
listen_address=0.0.0.0
pasv_min_port=12000
pasv_max_port=12100
pasv_address=888.888.888.888 # My server IP
...

Hope that helps!