Как отладить, почему 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

1

Решение

Трудно сказать, какие операции FTP выполняются здесь. Но может быть, что PHP использует PASV пока lftp использует EPSV установить пассивный режим.

В случае PASV сервер отправляет как IP-адрес, так и номер порта, где он будет ожидать соединения. С EPSV вместо этого сервер предоставляет только номер порта, а целевой IP-адрес — это адрес текущего управляющего соединения FTP. Если задействован NAT (преобразование сетевых адресов) (что весьма вероятно в настройках Docker), сервер может увидеть собственный внутренний IP-адрес, отличный от того, который внешне виден с FTP-клиента, что означает, что клиент не может подключиться на (неправильный) IP-адрес, указанный в ответе на PASV команда. С EPSV эта проблема не существует, так как клиент не использует предоставленный сервером IP-адрес в качестве цели.

1

Другие решения

Сочетание полезных ответов и комментариев, а также предупреждения от 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

Кажется, это работает нормально.

0

По вопросам рекламы [email protected]