I have a Docker Compose system for testing, in which I am doing end-to-end testing of a single-page web app. Several buttons in the web site will result in an FTP connection being initiated in one container (missive-transmitter
), going to a test FTP server in another container (missive-testbox
).
My FTP logic in PHP always uses "passive" mode, and I think this is causing the problem. I have created a script to run in missive-transmitter
, which is a simplified version of the real thing. It is as follows, and is run directly from the console:
<?php
# ftptest.php
error_reporting(-1);
ini_set('display_errors', true);
$conn = ftp_connect('missive-testbox', 21);
$ok1 = ftp_login($conn, 'missive_test', 'password');
if (!$ok1)
{
die("Cannot log in\n");
}
// *** Start problem section
$ok2 = ftp_pasv($conn, true);
if (!$ok2)
{
die("Cannot switch to passive mode\n");
}
// *** End problem section
$info = ftp_systype($conn);
echo "Info: $info\n";
$ok3 = ftp_put($conn, 'ftptest.php', 'ftptest.php', FTP_ASCII);
if (!$ok3)
{
die("Cannot send a file\n");
}
Now, if I remove the ***
section (enabling passive mode) then the script will work. If I leave it in, I get this:
Info: UNIX
Warning: ftp_put(): php_connect_nonb() failed: Operation in progress (115) in /root/src/ftptest.php on line 23
Warning: ftp_put(): TYPE is now ASCII in /root/src/ftptest.php on line 23
Cannot send a file
I would like my FTP operation to work in PASV mode.
Oddly, if I install an FTP client then it seems to work in either active or passive modes, which is what I don't understand. On the missive-transmitter
side:
~/src $ # This is the `sh` shell in `missive-transmitter`
~/src $ #
~/src $ # Install LFTP in Alpine environment
~/src $ apk add lftp
~/src $ lftp missive_test@missive-testbox
Password:
lftp missive_test@missive-testbox:~> set ftp:passive-mode off
lftp missive_test@missive-testbox:~> put ftptest.php
457 bytes transferred
lftp missive_test@missive-testbox:/> set ftp:passive-mode on
lftp missive_test@missive-testbox:/> put ftptest.php
457 bytes transferred
lftp missive_test@missive-testbox:/>
Is PHP doing something differently, or am I not actually using PASV mode in the console client?
I have confirmed that both containers can ping
each other from their respective sh
consoles. They are on the same (custom) Docker network.
The missive-testbox
Docker container is based on gists/pure-ftpd
, so it should be configured correctly as far as I know.
Update
A useful point in an answer below is about how NAT might be making one side make a connection using the wrong IP address. However, the IP addresses appear to be on the same subnet, though I am no networking expert.
From missive-transmitter
:
~ # ping missive-testbox
PING missive-testbox (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.076 ms
And from missive-testbox
:
~ # ping missive-transmitter
PING missive-transmitter (172.19.0.4): 56 data bytes
64 bytes from 172.19.0.4: seq=0 ttl=64 time=0.119 ms
I think the fact they are both 172.19.0.x
addresses means they should be able to see each other fully, though I am open to correction on that assumption.
Update 2
It has been suggested that getting some FTP client or server logs would be a good way to debug this. The client is pretty easy. Here are the same ops as above, but in LFTP's debug mode.
Active mode is first:
~/src # lftp -d missive_test@missive-testbox
Password:
---- Resolving host address...
---- 1 address found: 172.19.0.2
lftp missive_test@missive-testbox:~> set ftp:passive-mode off
lftp missive_test@missive-testbox:~> put ftptest.php
---- Connecting to missive-testbox (172.19.0.2) port 21
<--- 220-Welcome to Pure-FTPd.
<--- 220-You are user number 1 of 5 allowed.
<--- 220-Local time is now 17:54. Server port: 21.
<--- 220-This is a private system - No anonymous login
<--- 220-IPv6 connections are also welcome on this server.
<--- 220 You will be disconnected after 15 minutes of inactivity.
---> FEAT
<--- 530 You aren't logged in
---> AUTH TLS
<--- 500 This security scheme is not implemented
---> USER missive_test
<--- 331 User missive_test OK. Password required
---> PASS XXXX
<--- 230 OK. Current directory is /
---> FEAT
<--- 500 Unknown command
---> PWD
<--- 257 "/" is your current location
---> TYPE I
<--- 200 TYPE is now 8-bit binary
---> PORT 172,19,0,4,159,62
<--- 200 PORT command successful
---> ALLO 457
<--- 500 Unknown command
---> STOR ftptest.php
---- Accepted data connection from (172.19.0.2) port 20
<--- 150 Connecting to port 40766
---- Closing data socket
<--- 226-File successfully transferred
<--- 226 0.000 seconds (measured here), 3.16 Mbytes per second
---> SITE UTIME 20171030154823 ftptest.php
<--- 500 Unknown command
---> SITE UTIME ftptest.php 20171030154823 20171030154823 20171030154823 UTC
<--- 500 Unknown command
457 bytes transferred
OK, that was successful. Here is the passive version in LFTP, again successful.
I notice the warning at the start, about an address needing to be fixed - could that be relevant? If either side advertises itself to the other as "localhost", that might be a problem :-)
:
lftp missive_test@missive-testbox:/> set ftp:passive-mode on
lftp missive_test@missive-testbox:/> put ftptest.php
---> PASV
<--- 227 Entering Passive Mode (127,0,0,1,117,54)
---- Address returned by PASV seemed to be incorrect and has been fixed
---- Connecting data socket to (172.19.0.2) port 30006
---- Data connection established
---> STOR ftptest.php
<--- 150 Accepted data connection
---- Closing data socket
<--- 226-File successfully transferred
<--- 226 0.000 seconds (measured here), 1.79 Mbytes per second
457 bytes transferred