У меня есть система 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
адреса означают, что они должны видеть друг друга полностью, хотя я открыт для исправления этого предположения.
Предполагалось, что получение некоторых журналов 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
Трудно сказать, какие операции 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
Кажется, это работает нормально.