Возможно я просто туплю, но мне всегда казалось, что если машина 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, и вот что он показывает:
- после прерывается nc на сервере B, B посылает FIN на A, на который A корректно отвечает ACK-шкой — таким образом, на этом этапе мы получаем вполне корректное полу-открытое(одностороннее) TCP-соединение;
- далее, A пытается продолжать отправлять данные B (обычными пакетами с флагами ACK и PSH,ACK) — что тоже ожидаемо и корректно;
- НО, B никак на эти пакеты от A не отвечает (хотя B-то знает, что процесс nc уже вышел, так что это TCP-соединение уже закрыто/не существует, и, как я ожидаю, он должен ответить А RST-пакетом);
- 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