Huge amount of TIME_WAIT connections says netstat
root@wherever:# netstat Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 localhost:60930 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60934 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60941 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60947 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60962 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60969 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60998 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60802 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60823 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60876 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60886 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60898 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60897 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60905 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60918 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60921 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60673 localhost:sunrpc TIME_WAIT tcp 0 0 localhost:60680 localhost:sunrpc TIME_WAIT [etc. ] root@wherever:# netstat | grep 'TIME_WAIT' |wc -l 1942
That number is changing rapidly. I do have a pretty tight iptables config so I have no idea what can cause this. any ideas? Thanks, Tamas Edit: Output of ‘netstat -anp’:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:60968 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60972 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60976 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60981 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60980 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60983 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60999 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60809 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60834 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60872 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60896 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60919 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60710 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60745 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60765 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60772 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60558 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60564 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60600 127.0.0.1:111 TIME_WAIT - tcp 0 0 127.0.0.1:60624 127.0.0.1:111 TIME_WAIT -
Проблемы с очередью TIME_WAIT
Те кто разрабатывает активно работающие с сетью сервисы может наступить на особенности работы протокола TCP: переходу многих (или всех свободных) портов в состояние TIME_WAIT. В интернете много поверхностной информации и много не совсем корректной. Рассмотрим что это за ситуации, и определим возможные пути выхода из них.
Протокол TCP: закрытие соединения
Ниже типичная схема жизненного цикла TCP-соединения:
Не будем рассматривать её целиком, а сосредоточимся на наиболее важной для нас части — закрытии соединения.
Сторона, инициировавшая закрытие соединения называется «активной», вторая — «пассивной». Причем не важно кто из них был инициатором установки соединения.
Со стороны «пассивной» стороны всё просто. Получив пакет FIN, система должна ответить на него соответствующим ACK-пакетом, но имеет право продолжить отправку данных. С момента получения FIN пакета соединение у пассивной стороны находится в состоянии CLOSE_WAIT. По готовности, отправляется ответный FIN-пакет, после чего сторона дожидается ACK-пакета на него. По получении ACK на ответный FIN — соединение для пассивной стороны закрыто.
С точки зрения «активной» стороны всё несколько сложнее. После отправки FIN-пакета активная сторона переходит в состояние FIN_WAIT_1. Далее возможны три ситуации:
- Получение ACK на FIN-пакет. Этот статус обозначен FIN_WAIT_2, стороне могут быть доставлены данные, после чего ожидается ответный FIN-пакет, на который активная сторона отвечает ACK и переводит соединение в состояние TIME_WAIT.
- Если пассивная сторона готова к закрытию сессии, то ответный FIN может быть получен с одновременным ACK на исходный FIN пакет. В этом случае активная сторона отвечает на него ACKом и переводит соединение в TIME_WAIT, минуя FIN_WAIT_2.
- Возможна ситуация когда стороны одновременно инициировали закрытие. В этом случае обе стороны являются «активными», с обоих сторон соединение переходит в состояние TIME_WAIT.
Как видно из диаграммы и описания активная сторона отправляет последний пакет в сессии (ACK на пассивный FIN). Поскольку она не может узнать получен ли этот пакет, для неё предусмотрен статус TIME_WAIT. В данном состоянии соединение должно находиться время 2 * MSL (максимальное время жизни пакета): время доставки пакета пассивной стороне + время доставки возможного ответного пакета назад. На практике, в настоящее время, таймер TIME_WAIT устанавливается в 1 — 2 минуты. По истечению этого таймера соединение считается закрытым.
Проблема TIME_WAIT для исходящих соединений
Соединение в операционной системы идентифицируется четырьмя параметрами: локальный IP, локальный порт, удалённый IP, удаленный порт. Допустим, у нас есть клиент, который активно подключается/отключается к удаленной службе. Поскольку оба IP и удаленный порт остаются неизменными, то на каждое новое соединение выделяется новый локальный порт. Если клиент был активной стороной завершения TCP-сессии, то это соединение будет заблокировано какое-то время в состоянии TIME_WAIT. Если соединения в устанавливаются быстрее чем порты выходят из карантина, то при очередной попытке соединения клиент получит ошибку EADDRNOTAVAIL (errno=99).
Даже если приложения обращаются к разным службам, и ошибка не происходит, очередь TIME_WAIT будет расти, забирая системные ресурсы. Соединения в состоянии TIME_WAIT можно увидеть через netstat, обобщенную информацию удобно смотреть утилитой ss (с ключом -s).
- Значение интервала TIME_WAIT в Linux без перекомпиляции ядра изменить невозможно. В интернете можно встретить упоминания параметра net.ipv4.tcp_fin_timeout с формулировкой «в некоторых системах влияет на TIME_WAIT». Однако, что это за системы — неясно. Согласно документации параметр определяет максимальное время ожидания ответного FIN-пакета, т.е. ограничивает время нахождения соединения в состоянии FIN_WAIT_2, но не TIME_WAIT.
- Открывать меньше соединений. Ошибка чаще всего наблюдается при сетевом взаимодействии внутри кластера. В этом случае использование Keep-Alive будет разумным решением.
- При проектировании сервиса может иметь смысл переложить TIME_WAIT на другую сторону, для чего надо по возможности воздержаться от инициирования закрытия TCP-соединений.
- Если число подключений уменьшить затруднительно, то имеет смысл удаленную службу запустить на нескольких портах, и обращаться к ним по очереди.
- Параметр ядра «net.ipv4.ip_local_port_range» задает диапазон портов, используемых для исходящих соединений. Больше диапазон — больше доступных соединений с одной удаленной службой.
- Жесткий и крайне опасный способ: уменьшить значение параметра net.ipv4.tcp_max_tw_buckets до значения меньше чем число IP в диапазоне из ip_local_port_range. Этот параметр задает максимальный размер очереди TIME_WAIT и служит для защиты от DOS-атак. Этот «трюк» можно использовать временно, до выработки корректного решения.
- Включить параметр net.ipv4.tcp_tw_reuse. Данный параметр разрешает использовать для исходящих подключений соединения, находящиеся в состоянии TIME_WAIT.
- Включить параметр net.ipv4.tcp_tw_recycle
- Использовать режим SO_LINGER (устанавливается через setsockopt). В этом случае TCP-сессия будет не закрываться (обмен FIN-пакетами), а сбрасываться. Сторона желающая выполнить сброс посылает RST-пакет. По получении этого пакета соединение считается прекращенным. Однако согласно протоколу отправка RST-пакета должна производиться только в случае ошибки (получении данных явно не относящихся к данному соединению).
TIME_WAIT на серверах
Главная опасность которою несет разрастание очереди TIME_WAIT на сервере — это исчерпание ресурсов.
Тем не менее могут быть неприятные инциденты и при работе с NAT-клиентами (когда за одним IP находятся большое количество клиентов сервера). В случае малого значения времени карантина порта на Firewall велика вероятность что к серверу придёт запрос соединения с того же порта, соединение с которым ещё не закрыто (находится в TIME_WAIT). В этом случае возможны два три сценария:
- (Маловероятный) клиент угадает номер SEQ, что крайне маловероятно. В этом случае поведение не определено.
- Клиент отравит пакет с некорректным (с точки зрения сервера номером SEQ), на что сервер ответит ему последним ACK-пакетом, который будет уже не понятен клиенту. На этот ACK клиент обычно отправляет RST и ждет несколько секунд до новой попытки соединения. Если на сервере выключен параметр «net.ipv4.tcp_rfc1337» (по умолчанию выключено), новая попытка будет успешной. Тем не менее, в основном за счет таймаута, будет наблюдаться падение производительности.
- Если же при ситуации, описанной в пункте 2, параметр net.ipv4.tcp_rfc1337 включен, то сервер проигнорирует RST-пакет клиента. Повторный попытки подключения к серверу с того же порта будут неудачными. Для клиента сервис станет недоступным.
Что можно предпринять на сервере.
- Постараться переложить инициирование закрытия соединения на клиента. При этом необходимо установить разумные тайм-ауты.
- Быть аккуратным с параметром net.ipv4.tcp_max_tw_buckets. Установка слишком большого значения сделает сервер уязвимым к DOS-атаке.
- Использовать SO_LINGER для заведомо некорректных запросов. Если клиент подключается и шлёт «ерунду», то велика вероятность что имеет место атака, на которую лучше тратить минимальное количество ресурсов.
- Включить net.ipv4.tcp_tw_recycle если есть уверенность что клиенты не ходят через NAT. Важно заметить, что net.ipv4.tcp_tw_reuse на обработку входящих соединений не влияет.
- В ряде случае имеет смысл не «бороться» с очередью, а грамотно её распределить. В частности, могут помочь следующие рецепты:
- При использовании L7-балансировщика все пакеты исходят от одного IP, что провоцирует «попадания» в TIME_WAIT соединения, но в этом случае можно безопасно включить tcp_tw_recycle.
- При использовании L3-балансировщика, сервер видит исходные IP-адреса. IP-HASH балансировка, при этом, направит все соединения за одним NAT на один сервер, что тоже повышает вероятность коллизии. Round-Robin в этом плане надёжнее.
- Следует избегать использования NAT внутри сети где это возможно. При необходимости лучше предпочесть трансляцию 1-в-1.
- Увеличить число доступных соединений можно разместив службу на нескольких портах. Например для WEB-сервера нагрузку можно балансировать не на один 80-й порт, а на пул портов.
- Если проблема вызывается NAT внутри сети, можно решить ситуацию перенастройкой трансляции на сетевом устройстве: необходимо добиться чтобы время «карантина» порта на NAT было больше времени TIME_WAIT. Но в этом случае повышается риск исчерпания портов на NAT-трансляторе (как вариант решения — трансляция не в один IP, а в пул).
Параметры ядра net.ipv4.tcp_tw_reuse и net.ipv4.tcp_tw_recycle
В ядре Linux есть два параметра, позволяющие нарушать требования TCP-протокола, высвобождая соединения из TIME_WAIT раньше положенного срока. Обе эти опции базируются на расширении TCP-timestamps (маркировки пакетов относительными временными метками).
net.ipv4.tcp_tw_reuse позволяет использовать находящееся в TIME_WAIT соединение для нового исходящего подключения. При этом у нового подключения TCP timestamp должен быть на порядок больше чем последнее значение в предыдущей сессии. В этом случае сервер сможет отличить «опоздавший» пакет из предыдущего подключения от актуального. Использование параметра в большинстве случаев безопасно. Проблемы могут возникнуть в случае наличие «отслеживающего» firewall по пути, который решить не пропустить пакет в соединении, которое должно находиться в TIME_WAIT.
net.ipv4.tcp_tw_recycle сокращает время нахождения подключения в очереди TIME_WAIT до значения RTO (Re-Transmission Time-Out), которое вычисляется на основании Round-Trip-Time (RTT) и разброса этой величины. При этом в ядре сохраняется последнее значение TCP-timestamp и пакеты с меньшим значением просто отбрасываются. Эта опция сделает сервис недоступным для клиентов за NAT в случае если при трансляции «пропускаются» TCP-timestamp от клиентов (в случае если NAT их удаляет, или заменяет на свои — проблем не будет). Поскольку предвидеть настройки внешних устройств не представляется возможным, данная опция настоятельно не рекомендуется к включению на серверах, доступных из Internet. При этом на «внутренних» серверах, где NAT нет (или же используется вариант 1-в-1) опция безопасна.