powerman
@powerman
Systems Architect, Senior Go/Perl Linux Developer

Почему не отправляется TCP/IP пакет с флагом RST?

Возможно я просто туплю, но мне всегда казалось, что если машина A посылает TCP-пакет машине B, который относится к (уже) не существующему TCP-соединению, то машина B должна ответить на него RST-пакетом (если, конечно, такие левые пакеты не DROP-аются файрволом, etc.). Но в данный момент я наблюдаю несколько иную картину, и не могу понять, то ли я что-то не знаю про TCP, то ли это какие-то баги в ядре линуха (честно говоря, традиционно грешил на conntrack, но отключение файрвола ничего не изменило).



Итак, есть два линух-сервера (назовём их A и B). Они подключены к одному (неуправляемому) свичу. Файрвол на обоих серверах изначально был включен (и использовался conntrack — "-m state --state NEW"), но потом я его сбросил (удалил все правила во всех таблицах и выставил ACCEPT по умолчанию во всех цепочках). Таким образом, ничего не должно блокировать/модифицировать пакеты между этими серверами.



Теперь, запускаем на сервере A TCP-сервис, который принимает входящие подключения и начинает непрерывно отправлять данные подключившимся клиентам. Он не пытается читать что-то от клиентов, и ожидает получить EPIPE при write() в клиента, который отвалился.



Далее, запускаем на сервере B TCP-клиент (обычный nc — netcat), он подключается к серверу A, начинает принимать от него данные, и тут мы отключаем nc (по Ctrl-C).



После этого TCP-сервис на сервере A блокируется при вызове write() в этого, уже отключившегося клиента, не получая (даже спустя пару минут) ни EPIPE ни другую ошибку.



Я погонял на обоих серверах tcpdump, и вот что он показывает:



  1. после прерывается nc на сервере B, B посылает FIN на A, на который A корректно отвечает ACK-шкой — таким образом, на этом этапе мы получаем вполне корректное полу-открытое(одностороннее) TCP-соединение;
  2. далее, A пытается продолжать отправлять данные B (обычными пакетами с флагами ACK и PSH,ACK) — что тоже ожидаемо и корректно;
  3. НО, B никак на эти пакеты от A не отвечает (хотя B-то знает, что процесс nc уже вышел, так что это TCP-соединение уже закрыто/не существует, и, как я ожидаю, он должен ответить А RST-пакетом);
  4. A, не получив никакого ответа, перестаёт принимать новые данные от TCP-сервиса (который в этот момент блокируется в вызове write()) и начинает перепосылать B пакеты с предыдущими данными (на которые тоже не получает никакого ответа от B).


Что интересно, если запустить и клиент и сервис на одном физическом сервере, то всё работает как я ожидаю — RST-пакет отправляется, сервис получает EPIPE при write() сразу после остановки nc по Ctrl-C.



Отсюда вопрос: из-за чего может не отправляться RST пакет когда клиент и сервис запущены на разных серверах (и правильно ли я понимаю, что он вообще обязан отправиться)?



На обоих серверах обновленный Hardened Gentoo Linux, ядро 2.6.39-hardened-r8, никаких специфичных сетевых настроек через sysctl нет.



Не знаю, важно ли это, но на обоих серверах достаточно большая сетевая активность — ` netstat -alnp` выдаёт порядка 5000 записей в любой момент, и, я думаю, порядка 1000 tcp-соединений устанавливается и разрывается каждую секунду. Эта активность не имеет отношения к описанному выше TCP-сервису — она относится к HTTP прокси (3proxy), причём в логах ядра на порт 3proxy постоянно выдаётся что-то в таком духе:




TCP: Possible SYN flooding on port XXXXX. Sending cookies.
net_ratelimit: 19 callbacks suppressed


Вот пример дампа описанной выше TCP-сессии: i54.tinypic.com/1zz10mx.jpg
  • Вопрос задан
  • 7449 просмотров
Решения вопроса 1
@bear11
2.6.39-hardened-r8
Попробуйте последнее vanilla ядро с kernel.org
Возможно, с hardened они что-то накрутили.
Ответ написан
Пригласить эксперта
Ответы на вопрос 6
@square
А помоему B и не должен ругаться, он находится в состоянии FIN_WAIT_2 и ожидает только FIN от A
Ответ написан
@bear11
Можете ли выложить запись сессии в wireshark-формате?
Ответ написан
Поведение зависит от того, установлена ли на сокет опция SO_LINGER. Если клиент устанавливает SO_LINGER — то ресетов не будет, пока пакеты идут в рамках TCP-соединения.
Ответ написан
@square
А код сервера очень секретный? Может опубликуете? Очень интересная задачка, хочу докопаться до сути
Ответ написан
@square
Вспомнилась ещё вот что ru.wikipedia.org/wiki/TCP_hijacking
может быть как-то связано
Ответ написан
@bear11
Посмотрите книгу «Ричард Стивенс. Протоколы TCP IP».
Ее можно найти или купить в интернете. Там этот вопрос обстоятельно разобран.
В главе «Обнаружение полуоткрытых соединений» четко сказано, что на такое действие (write в полузакрытый сокет) действительно должен приезжать RST.
Посмотрите также поведение ARP в этот момент — не теряет ли сервер, который должен отправлять RST информацию о том, куда ее отправлять?
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы