Может ли другое клиентское приложение закрыть TCP-соединение, которое с сервера открыло аварийное клиентское приложение? - PullRequest
0 голосов
/ 26 августа 2018

Рассмотрим следующую последовательность:

  1. Клиентское приложение (веб-браузер) открывает несколько соединений TCP с различными веб-серверами;
  2. Кабель Ethernet затем отключается;
  3. Клиентское приложение затем закрывается;
  4. Кабель Ethernet остается отключенным в течение нескольких часов;
  5. Кабель Ethernet повторно подключается;
  6. Я вижу пакеты "TCP keep-alive" (каждые 60секунд, в течение нескольких часов) с нескольких серверов, к которым было подключено давно закрытое клиентское приложение!

Обычно, когда приложение закрывается, оно инициирует закрытие каждого открытого сокета,и тогда уровень TCP будет пытаться отправить пакет FIN каждой удаленной конечной точке.Если физически возможно отправить пакет FIN, и такая отправка действительно происходит, то локальная конечная точка переходит из состояния ESTABLISHED в состояние FINWAIT_1 (и ожидает получения ACK от удаленной конечной точки и т. Д.).Но если физическая связь не работает, то локальная конечная точка TCP не может отправить этот FIN, и сервер по-прежнему предполагает, что TCP-соединение все еще существует (и вызов на стороне клиента для функции close будет блокироваться на неопределенный срок, пока физическая связь не будет выполнена).ссылка была восстановлена, предполагая, что сокет был установлен в режим блокировки, верно?).

В любом случае, после повторного подключения кабеля Ethernet через некоторое время, когда все обычные сетевые приложения (например, веб-браузеры) давно закрыты, я получаю пакеты «TCP Keep-Alive» от трех отдельных веб-серверов точно на 60-секундные интервалы для ЧАСОВ!

Wireshark показывает номера локальных портов, на которые отправляются эти TCP Keep-Alive пакеты, но ни TCPView, ни netstat -abno не показывают те номера локальных портов, которые используются какими-либо приложениями.Просмотр свойства «TCP / IP» каждого запущенного процесса с использованием Process Explorer также не показывает совпадения номеров портов.Я не думаю, что порты удерживаются из-за зомби-записи процесса (скажем, процесса веб-браузера) из-за какого-либо продолжающегося дочернего процесса (например, приложения-плагина), но я не уверен, что мои наблюденияс TCPView / netstat / Process Explorer было достаточно, чтобы исключить эту возможность.

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

Если соединения появились в TCPView, я мог бы простовыберите их и закройте вручную.Однако конечные точки TCP на стороне клиента, похоже, давно прошли.

Между тем, я озадачен, почему серверы столько раз повторяют попытки получить ответ на свои пакеты keep-alive.

Поведение TCP-keep-alive обычно контролируется тремя параметрами: \

(1) время ожидания следующей попытки «пакетной» или «пробной» проверки;

(2) интервал времени между отправкой каждого пакета проверки активности во время одной попытки «проверки»;

(3) Максимальное количество попыток «проверки» до того, как «пакетная передача» будет считаться отказом (и, следовательно, соединение TCP считается окончательно разорванным).

Для поддержания активности TCPВ пакетах, которые я вижу с трех разных серверов, интервал между «пробными» повторениями составляет ровно 60 секунд.Но, похоже, что максимальное количество повторных попыток «зондирования» бесконечно, что кажется очень плохим выбором для любого сервера!

Хотя мне любопытно, как этот непрерывный поток средств поддержки был создан и поддерживаетсяМеня больше интересует, как можно использовать приложение на стороне клиента для принудительного закрытия конечных точек на стороне сервера, учитывая, что не существует локальных конечных точек TCP, принимающих эти пакеты поддержки активности.

Моя грубая идея - создать приложение, которое создает сокет в режиме TCP, привязывает (с разрешенным повторным использованием номера порта) к номеру порта, на который направляются входящие сообщения активности, и затем вызывает «open», после чего следует"close", надеясь, что конечная точка сервера выполнит переходы состояния TCP, чтобы так или иначе достичь закрытого состояния!Другим способом может быть создание сокета в необработанном режиме и получение пакета поддержки активности TCP (который является просто ACK), а затем формирование и отправка соответствующего пакета FIN (с правильным порядковым номером и т. Д.), Чтобы определить, гдедолгосрочное клиентское приложение, очевидно, прервалось), а затем получить ACK и FIN перед отправкой окончательного ACK.

Одно последнее замечание - и я знаю, будет замечательнои насмешка: рабочая среда здесь - Windows XP SP3, работающая в VirtualBox на Windows 7!Поэтому я бы предпочел код или приложение с открытым исходным кодом, которое могло бы достичь цели (закрытие полуоткрытого TCP-соединения) в Windows XP SP3.Конечно, я мог бы перезапустить снимок, который может закрыть соединения - но мне больше интересно узнать, как получить больше информации о состоянии сетевых подключений, и что я могу сделать для обработки такого родаПроблема состояния TCP.

Ответы [ 2 ]

0 голосов
/ 27 августа 2018

Мне удалось спровоцировать закрытие каждого кажущегося полуоткрытого TCP-соединения, написав простую программу (полный код приведен ниже), которая привязывает локальный сокет к порту, к которому сервер считает, что он уже подключен, пытается установить новое соединение, а затем закрывает соединение.

(Примечание. Если соединение установится успешно, я сделаю HTTP-запрос GET только потому, что в моем случае фантомные протоколы поддержки TCP в моем случае явно исходят от простых HTTP-серверов, и мне было интересно, какой ответ я мог бы получить. Я думаю, вызовы "send" и "recv" могут быть удалены без ущерба для способности кода достичь желаемого результата.)

В следующем коде переменная src_port_num представляет номер порта на стороне клиента (в настоящее время не используется), на который сервер отправляет пакеты "TCP keep-alive", и dst_ip_cstr это IP-адрес сервера (например, веб-сервер Akamai), а dst_port_num - это номер порта (в моем случае это обычный HTTP-сервер на порту 80).

ВНИМАНИЕ! Поделившись этим кодом, я не имею в виду, что его теория работы может быть строго объяснена пониманием спецификации протокола TCP. Я просто догадался, что утверждение о том, что заброшенный локальный порт, на который удаленная конечная точка отправляет пакеты поддержания активности TCP, и попытка установить новое подключение к той же самой удаленной конечной точке, так или иначе подтолкнет удаленную конечную точку к закрытию несвежее полуоткрытое соединение - и у меня получилось .

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

void main()
{
  // Local IP and port number
  char * src_ip_cstr  = "10.0.2.15";
  int    src_port_num = 4805;

  // Remote IP and port number
  char * dst_ip_cstr  = "23.215.100.98";
  int    dst_port_num = 80;

  int res = 0;
  WSADATA wsadata;
  res = WSAStartup( MAKEWORD(2,2), (&(wsadata)) );
  if (0 != res) { printf("WSAStartup() FAIL\n"); return; }

  printf( "\nSRC IP:%-16s Port:%d\nDST IP:%-16s Port:%d\n\n",
  src_ip_cstr, src_port_num, dst_ip_cstr, dst_port_num );

  sockaddr_in src;
  memset( (void*)&src, 0, sizeof(src) );
  src.sin_family           = AF_INET;
  src.sin_addr.S_un.S_addr = inet_addr( src_ip_cstr );
  src.sin_port             = htons( src_port_num );

  sockaddr_in dst;
  memset( (void*)&dst, 0, sizeof(dst) );
  dst.sin_family           = AF_INET;
  dst.sin_addr.S_un.S_addr = inet_addr( dst_ip_cstr );
  dst.sin_port             = htons( dst_port_num );

  int s = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
  if ((-1) == s) { printf("socket() FAIL\n"); return; }

  int val = 1;
  res = setsockopt( s, SOL_SOCKET, SO_REUSEADDR, 
  (const char*)&val, sizeof(val) );
  if (0 != res) { printf("setsockopt() FAIL\n"); return; }

  res = bind( s, (sockaddr*)&src, sizeof(src) );
  if ((-1) == res) { printf("bind() FAIL\n"); return; }

  res = connect( s, (sockaddr*)&dst, sizeof(dst) );
  if ((-1) == res) { printf("connect() FAIL\n"); return; }

  char req[1024];
  sprintf( req, "GET / HTTP/1.1\r\nHost: %s\r\nAccept: text/html\r\n"
  "Accept-Language: en-us,en\r\nAccept-Charset: US-ASCII\r\n\r\n", 
  dst_ip_cstr );
  printf("REQUEST:\n================\n%s\n================\n\n", req );

  res = send( s, (char*)&req, strlen(req), 0 );
  if ((-1) == res) { printf("send() FAIL\n"); return; }

  const int REPLY_SIZE = 4096;
  char reply[REPLY_SIZE];
  memset( (void*)&reply, 0, REPLY_SIZE );
  res = recv( s, (char*)&reply, REPLY_SIZE, 0 );
  if ((-1) == res) { printf("recv() FAIL\n"); return; }
  printf("REPLY:\n================\n%s\n================\n\n", reply );

  res = shutdown( s, SD_BOTH );
  res = closesocket( s );

  res = WSACleanup();
}

ОГРОМНОЕ / СТРАШНОЕ / УДИВИТЕЛЬНОЕ РАСКРЫТИЕ

Как я уже упоминал в своем первоначальном вопросе, я наблюдал эти пакеты "TCP keep-alive" с Wireshark в VirtualBox под управлением Windows XP SP3, где основной операционной системой была Windows 7.

Когда я проснулся этим утром и снова посмотрел на это явление с чашкой кофе и свежими глазами, с пакетами «TCP keep-alive», которые все еще появляются каждые 60 секунд, даже через 24 часа, я сделал смешное открытие: пакеты продолжали приходить с трех разных IP-адресов, точно с 60-секундными интервалами (но в шахматном порядке для трех IP-адресов), , даже когда я отключил кабель Ethernet от Интернета ! Мой разум был взорван!

Итак, хотя эти три IP-адреса действительно соответствовали реальным веб-серверам, к которым мой веб-браузер подключался давно, пакеты поддержки активности TCP явно происходили из какого-то локального программного компонента.

Это откровение, каким бы шокирующим оно ни было, не изменило моего мышления о ситуации: с моей точки зрения программного обеспечения на стороне клиента были TCP-соединения "на стороне сервера", которые я хотел спровоцировать на закрытие.

В VirtualBox, выбрав «Устройства» -> «Сеть» -> «Подключить сетевой адаптер» , включает или отключает виртуальный сетевой адаптер, как если бы кабель виртуального Ethernet был подключен или отключен. Переключение в отключенное состояние привело к тому, что фантомные пакеты поддержки активности TCP перестали поступать в Wireshark. Впоследствии переключение в подключенное состояние привело к тому, что пакеты поддержки активности TCP возобновили поступление в Wireshark.

В любом случае, иногда мне нужно было запустить код выше ДВАЖДЫ, чтобы успешно закрыть полуоткрытое соединение. При первом запуске кода Wireshark показывал пакет с аннотацией «[Незаметный сегмент TCP ACKed]», который является всего лишь разновидностью путаницы с газовым освещением TCP, которую я надеялся создать, ха-ха! Поскольку новая конечная точка клиента является неожиданной для удаленной конечной точки, вызов «connect» зависает на 30 секунд, прежде чем завершится с ошибкой. Для пары полуоткрытых соединений зомби / фантомов достаточно было запустить программу только один раз, чтобы вызвать также пакет RST.

Мне нужно было несколько раз изменить программу, чтобы изменить комбинацию номера локального порта, удаленного IP-адреса и номера удаленного порта, чтобы соответствовать каждому фантомному TCP-пакету поддержки активности, который я наблюдал в Wireshark.(Я оставляю реализацию удобных для пользователя параметров командной строки дорогому читателю (это вам!).) После нескольких раундов изменения и запуска программы все пакеты поддержки активности зомби были остановлены.«Молчание пакетов», можно сказать.

ЭПИЛОГ

[В смокинге с бокалом мартини в руке, задумчиво глядя на океан с палубыо яхте, в компании других хакеров] «Я так и не узнал, откуда взялись эти зомби-пакеты ... Был ли это виртуальный Ethernet-адаптер VirtualBox Host-Only Network?? Только Oracle знает!»

0 голосов
/ 26 августа 2018

Ничего не нужно делать, чтобы закрыть удаленный сокет, он уже встроен в протокол TCP.Если система получает TCP-пакеты, которые не создают новое соединение (т.е. имеют установленный SYN) и не принадлежат ни к какому установленному соединению, она ответит пакетом RST.Таким образом, одноранговый узел узнает, что конечной точки больше нет, и откажется от соединения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...