Как отладить, почему PHP FTP не работает в режиме PASV, когда консольный FTP работает нормально?
У меня есть система Docker Compose для тестирования, в которой я выполняю комплексное тестирование одностраничного веб-приложения. Несколько кнопок на веб-сайте приведут к установлению FTP-соединения в одном контейнере (missive-transmitter
), перейдя на тестовый FTP-сервер в другом контейнере (missive-testbox
).
Моя логика FTP в PHP всегда использует "пассивный" режим, и я думаю, что это вызывает проблему. Я создал скрипт для запуска missive-transmitter
, который является упрощенной версией реальной вещи. Это выглядит следующим образом и запускается прямо из консоли:
<?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");
}
Теперь, если я удалю ***
раздел (включение пассивного режима), тогда скрипт будет работать. Если я оставлю это, я получу это:
Информация: UNIX
Предупреждение: ftp_put(): php_connect_nonb() не выполнен: выполняется операция (115) в /root/src/ftptest.php в строке 23
Предупреждение: ftp_put(): TYPE теперь ASCII в /root/src/ftptest.php в строке 23
Невозможно отправить файл
Я хотел бы, чтобы моя операция FTP работала в режиме PASV.
Как ни странно, если я устанавливаю FTP-клиент, он работает в активном или пассивном режимах, чего я не понимаю. На missive-transmitter
боковая сторона:
~/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:/>
PHP делает что-то по-другому, или я на самом деле не использую режим PASV в консольном клиенте?
Я подтвердил, что оба контейнера могут ping
друг друга от их соответствующих sh
консолей. Они находятся в одной (настраиваемой) сети Docker.
missive-testbox
Контейнер Docker основан на gists/pure-ftpd
, поэтому он должен быть настроен правильно, насколько я знаю.
Обновить
Полезный момент в ответе ниже о том, как NAT может заставить одну сторону установить соединение, используя неправильный IP-адрес. Тем не менее, IP-адреса, кажется, находятся в той же подсети, хотя я не эксперт по сети.
От 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
И из 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
Я думаю, что они оба 172.19.0.x
адреса означают, что они должны видеть друг друга полностью, хотя я открыт для исправления этого предположения.
Обновление 2
Было высказано предположение, что получение некоторых журналов FTP-клиента или сервера было бы хорошим способом отладки этого. Клиент довольно прост. Здесь те же операции, что и выше, но в режиме отладки LFTP.
Активный режим первый:
~/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
ОК, это было успешно. Вот пассивная версия в LFTP, снова успешная.
В начале я замечаю предупреждение об адресе, который необходимо исправить. Может ли это быть актуальным? Если одна из сторон рекламирует себя как "localhost", это может быть проблемой :-)
:
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
2 ответа
Трудно сказать, какие операции FTP выполняются здесь. Но может быть, что PHP использует PASV
пока lftp использует EPSV
установить пассивный режим.
В случае PASV
сервер отправляет как IP-адрес, так и номер порта, где он будет ожидать соединения. С EPSV
вместо этого сервер предоставляет только номер порта, а целевой IP-адрес - это адрес текущего управляющего соединения FTP. Если задействован NAT (преобразование сетевых адресов) (что весьма вероятно в настройках Docker), сервер может увидеть собственный внутренний IP-адрес, отличный от того, который внешне виден с FTP-клиента, что означает, что клиент не может подключиться на (неправильный) IP-адрес, указанный в ответе на PASV
команда. С EPSV
эта проблема не существует, так как клиент не использует предоставленный сервером IP-адрес в качестве цели.
Сочетание полезных ответов и комментариев, а также предупреждения от LFTP привели меня к решению. Эта проблема связана с видимостью ИС в PASV
режим, как и предполагал Штеффен. Клиент LFTP услужливо обнаружил, что мой сервер был неправильно настроен, и поэтому прозрачно исправил его, что и привело к путанице.
Стоит отметить, что PHP не такой умный или добрый - он не делает таких исправлений (что, конечно, технически правильно).
Причиной такой конфигурации являются настройки Dockerfile по умолчанию:
ENV PUBLIC_HOST localhost
Итак, на данный момент я заменил это в моем собственном Dockerfile на IP-адрес локальной сети:
ENV PUBLIC_HOST 172.19.0.2
Один перезапуск позже, исправление пропало из LFTP, и мой PHP-скрипт работает.
Обновить
Поскольку я не знаю, могу ли я полагаться на статичность вышеуказанного IP-адреса, я изменил его на имя контейнера:
ENV PUBLIC_HOST missive-testbox
Кажется, это работает нормально.