IdMappedPortTCP теперь требует "подталкивания" после подключения через telnet

Я использовал IdMappedPortTCP в конкретной программе, чтобы разрешить обычную переадресацию портов в течение многих лет. Я тестирую обновленную среду сборки / компонентов, и я столкнулся с проблемой. Во-первых, вот информация о старой и новой версии:

  • OS: W2kSP4 -> то же самое (Эй, почему все смеются?)
  • Delphi: 5 -> 7
  • Project Indy: 9.0.0.14 -> 9.[Последний SVN]

Я тестирую его, вставляя его в сеанс telnet с использованием стандартного консольного клиента telnet Windows и сервера Linux, и я вижу странное изменение в поведении.

  • Прямое соединение: клиент подключается, сразу видит приветствие сервера
  • Старая Инди: такая же как прямая
  • New Indy: клиент подключается, ничего не видит. Нажмите клавишу, увидит приветствие сервера + нажатие клавиши.

Вот сравнение цепочки событий:

Старый:

6/08/2017  6:47:16 PM - DEBUG: MappedPort-Connect
6/08/2017  6:47:16 PM -   TCP Port Fwd: Connect: 127.0.0.1:4325 --> 127.0.0.1:23
6/08/2017  6:47:16 PM - DEBUG: MappedPort-OutboundConnect
6/08/2017  6:47:16 PM -   TCP Port Fwd: Outbound Connect: 192.168.214.11:4326 --> 192.168.210.101:23
6/08/2017  6:47:16 PM - DEBUG: MappedPort-OutboundData
6/08/2017  6:47:16 PM - DEBUG: MappedPort-Execute
6/08/2017  6:47:16 PM - DEBUG: MappedPort-OutboundData
6/08/2017  6:47:16 PM - DEBUG: MappedPort-Execute
6/08/2017  6:47:16 PM - DEBUG: MappedPort-OutboundData
...

Новое:

6/08/2017  6:41:34 PM - DEBUG: MappedPort-Connect
6/08/2017  6:41:34 PM -   TCP Port Fwd: Connect: 127.0.0.1:1085 --> 127.0.0.1:23
6/08/2017  6:41:34 PM - DEBUG: MappedPort-OutboundConnect
6/08/2017  6:41:34 PM -   TCP Port Fwd: Outbound Connect: 192.168.214.59:1086 --> 192.168.210.101:23
6/08/2017  6:47:36 PM - DEBUG: MappedPort-Execute
6/08/2017  6:47:36 PM - DEBUG: MappedPort-OutboundData
6/08/2017  6:47:36 PM - DEBUG: MappedPort-Execute
6/08/2017  6:47:36 PM - DEBUG: MappedPort-OutboundData
6/08/2017  6:47:36 PM - DEBUG: MappedPort-Execute

В первом вы видите OutboundData сразу после подключения. Во-вторых, после подключения ничего не происходит до тех пор, пока я не отправлю нажатие клавиши (через 6 минут), когда вы увидите Execute, а затем первое событие OutboundData.

Это заставило меня задуматься: действительно ли оно подключается к серверу и только задерживает вывод, или само соединение задерживается?

Мой первый вывод состоял в том, что само соединение было отложено, и вот почему. У сервера есть 1-минутный тайм-аут в приглашении входа в систему. Если вы подключаетесь и получаете приветствие, но просто сидите там, сервер отключается через минуту. С новой версией Indy я сидел там после события подключения в течение 6 полных минут, затем без проблем получил приветствие сервера.

Однако... NETSTAT показывает соединение с удаленным сервером, установленное вскоре после регистрации события соединения! Итак, мне осталось сделать вывод, что соединение действительно установлено, но, возможно, "съедается" какой-то начальный персонаж или что-то, из-за чего getty не задействуется, пока не получит нажатие клавиши?

Какие-либо предложения? Знаете ли вы что-нибудь, что изменилось, что я мог бы искать - что-то, что я должен делать, но это не так? Любые идеи приветствуются.

(Если не считать хороших потенциальных клиентов, я думаю, что следующим шагом в моем слежке может быть прослушивание обеих машин с помощью WireShark, чтобы увидеть, что происходит после соединения.)

Обновление: Wireshark (одна нога)

Захват пакетов извне машин, показывающий трафик между MappedPort и сервером (но не трафик между клиентом и MappedPort), показывает, что сервер telnet отправляет "Do Authenticate", на который клиент (через MappedPort) отвечает с Аутентифицировать". Затем сервер отправляет подопцию аутентификации (и клиент соглашается), а затем все остальные параметры telnet. Наконец, увидев текст логина, клиент отправляет "do echo", и они оба остаются там до 1 минуты, после чего сервер отправляет TCP FIN для закрытия соединения. Это "старая добрая" версия.

В новой версии клиент не отвечает на "Аутентификацию будет", и они оба сидят там бесконечно. (Хммм, мне интересно, что это связывает с точки зрения серверных ресурсов - это может быть хорошей атакой для DOS. Однако это старый демон telnet, поэтому он, вероятно, уже исправлен...) Когда я наконец отправил первое нажатие клавиши, это все, что отправлено в этом пакете. ТОГДА клиент отправляет "аутентификацию" (без дополнительной подталкивания с сервера), и согласование продолжается точно так же, как обычно; последний пакет с сервера (содержащий параметры эха) также включает набранный символ. Таким образом, клиент не видит начальный пакет "выполнить проверку подлинности" с сервера, но как только вы начинаете вводить текст, он продолжает и отвечает, как будто он только что услышал его (после того, как он отправил нажатие клавиши).

6/13 Обновление: Wireshark (обе ноги)

Я запечатлел обе ноги в "сломанном" разговоре и проанализировал его. Интересное поведение. Итог: как только сервер получает соединение TCP, он отправляет обратно приглашение Telnet-DoAuth. IdMappedPortTCP удерживает этот пакет и не передает его клиенту - пока. Когда клиент наконец отправляет первое нажатие клавиши (через несколько секунд или минут), Id передает его на сервер. Затем Id передает пакет DoAuth, полученный от сервера, клиенту.

Вот более подробный учет пакетов:

65 11-59 TCP Syn
67 59-11 TCP SynAck
69 11-59 TCP Ack
71 59-101 TCP Syn
73 101-59 TCP SynAck
74 59-101 TCP Ack
76 101-59 DoAuth
77 59-101 TCP Ack
nothing for 23 seconds
79 11-59 Data:\r\n (I pressed Enter)
81 59-101 Data:\r\n
83 59-11 DoAuth
85 11-59 WillAuth
87 101-59 TCP Ack
88 59-101 WillAuth
90 101-59 TCP Ack
91 101-59 Authentication option
92 59-11 Authentication option
94 11-59 Authentication option reply
96 59-101 Authentication option reply
98 101-59 Will/do Encryption/terminal/env options
99 59-101 Will/do Encryption/terminal/env options
101 11-59 Don't encrypt
103 59-101 Don't encrypt
105 101-59 TCP Ack
106 59-11 TCP Ack
108 11-59 Won't/will list
110 59-101 Won't/will list
112 101-59 TCP Ack
113 101-59 Do window size
114 59-11 Do window size

Формат строки дампа пакета: Pkt# From-To Payload

(Не возражайте против пропуска пакетов; клиент и прокси-сервер работают на виртуальных машинах, размещенных на машине, с которой я выполнял захват, поэтому Wireshark увидел две копии пакетов. Я включил только pkt#, чтобы я мог ссылаться на оригинал дамп позже если захочу.)

С / К Машины:

10 = Linux client (see below)
11 = Windows client
59 = proxy
101 = server

Интересная диверсия: клиент Linux

Хотя все мои тесты проводились с использованием различных клиентов Windows (потому что это то, что используется в производстве), я "случайно" использовал Linux (потому что это то, что я запускаю на своей рабочей станции, где я запускал Wireshark), потому что это было удобно. Этот клиент ведет себя по-другому - более агрессивно - и, таким образом, избегает проблемы. Вот как выглядит этот дамп:

1 10-59 TCP Syn
2 59-10 TCP SynAck
3 10 59 TCP Ack
4 10-59 Do/Will list
5 59-101 TCP Syn
7 101-59 TCP SynAck
8 59-101 TCP Ack
10 59-101 Do/Will list
12 101-59 TCP Ack
13 101-59 DoAuth
14 59-10 DoAuth
15 10-59 TCP Auth
16 10-59 WontAuth
17 59-101 WontAuth
19 101-59 Will/Do list
20 59-10 Will/Do list
21 10-50 Do window size
22 59-101 Do window size

Как видите, клиент не ждет, пока сервер telnet заговорит первым - как только TCP-соединение установлено, он отправляет полный список Do/Will. Это в свою очередь передается на сервер, как только Id открывает это соединение. Сервер отправляет обратно тот же "DoAuth", что и изначально; разница в том, что на этот раз, уже передавая трафик от клиента, Id передает его немедленно. Затем клиент отправляет флаги аутентификации, и все идет своим чередом.

Итак, если клиент говорит первым, IdMappedPortTCP работает нормально; только когда сервер говорит первым, он сохраняет свое сообщение и не передает его клиенту, пока клиент не скажет что-либо.

27.09 Обновление: найдено изменение кода

Понижение до 9.0.0.14 решило проблему. Сравнивая исходный код двух версий для IdMappedPortTCP.pas, я обнаружил, что единственное отличие состоит в том, что более новая версия добавила блок кода в процедуру TIdMappedPortThread.OutboundConnect:

  DoOutboundClientConnect(Self);

  FNetData := Connection.CurrentReadBuffer;
  if Length(FNetData) > 0 then begin
    DoLocalClientData(Self);
    FOutboundClient.Write(FNetData);
  end;//if

except

(Первая и последняя строки уже существуют и показаны только для контекста.)

Я подтвердил, что добавление этого кода в 9.0.0.14 привело к проблеме.

Я проверил репозиторий SVN, и вы добавили оскорбительный код 9/7/2008. Комментарий коммита:

Обновлен метод TIdMappedPortThread.OutboundConnect() для проверки наличия ожидающих данных в InputBuffer входящего клиента после выхода из обработчика события OnOutboundConnect.

Я не до конца понимаю причину или следствия изменения - очевидно, у вас была веская причина для этого - но, похоже, оно производит эффект, который я описал ("удерживая" первоначальный вывод сервера, пока клиент не отправит что-то).

1 ответ

Решение

В Индии 9 TIdTCPConnection.CurrentReadBuffer() звонки TIdTCPConnection.ReadFromStack() перед возвратом любых данных, хранящихся в TIdTCPConnection.InputBuffer имущество:

function TIdTCPConnection.CurrentReadBuffer: string;
begin
  Result := '';
  if Connected then begin
    ReadFromStack(False); // <-- here
  end;
  Result := InputBuffer.Extract(InputBuffer.Size);
end;

Независимо от того, что уже может быть в InputBuffer, ReadFromStack() ожидает, пока сокет не получит новые данные для добавления в InputBuffer, Он не завершается до тех пор, пока новые данные не поступят или не ReadTimeout интервал истекает. TIdTCPConnection.ReadTimeout свойство установлено в 0 по умолчанию, поэтому, когда CurrentReadBuffer() звонки ReadFromStack(), это заканчивается использованием бесконечного времени ожидания:

function TIdTCPConnection.ReadFromStack(const ARaiseExceptionIfDisconnected: Boolean = True;
  ATimeout: Integer = IdTimeoutDefault; const ARaiseExceptionOnTimeout: Boolean = True): Integer;
// Reads any data in tcp/ip buffer and puts it into Indy buffer
// This must be the ONLY raw read from Winsock routine
// This must be the ONLY call to RECV - all data goes thru this method
var
  i: Integer;
  LByteCount: Integer;
begin
  if ATimeout = IdTimeoutDefault then begin
    if ReadTimeOut = 0 then begin
      ATimeout := IdTimeoutInfinite; // <-- here
    end else begin
      ATimeout := FReadTimeout;
    end;
  end;
  ...
end;

Так когда TIdMappedPortTCP.OutboundConnect() звонки CurrentReadBuffer() после подключения его OutboundClient на сервер, он действительно ожидает поступления данных от клиента, а затем читает данные с сервера. Чтобы избежать этого, вы можете установить неограниченный ReadTimeout значение в TIdMappedPortTCP.OnConnect или же TIdMappedPortTCP.OnOutboundConnect событие, например:

AThread.Connection.ReadTimeout := 1;

В Indy 10 эта проблема была исправлена ​​в TIdMappedPortTCP избегая этого начального ожидания данных клиента после подключения к серверу. Я сейчас обновил TIdMappedPortTCP в Indy 9 сделать то же самое.

Другие вопросы по тегам