# TCP states (LISTEN, ESTABLISHED, TIME_WAIT) _Сеть: L4 и выше · LinuxLab Knowledge Base_ **TL;DR:** TCP-сессия проходит через 11 состояний от LISTEN до CLOSED. Самые важные на проде: LISTEN, ESTABLISHED, TIME_WAIT, CLOSE_WAIT. ## State machine TCP описывается state machine из 11 состояний (RFC 793). Каждое соединение в каждой стороне находится в одном из них. Переходы происходят при отправке/получении SYN/ACK/FIN/RST или по таймерам. ## Стороны клиента и сервера ``` Server (passive open) Client (active open) CLOSED CLOSED │ │ │ listen() │ connect() ▼ ▼ LISTEN SYN_SENT │ │ │ rcv SYN, send SYN-ACK │ rcv SYN-ACK, send ACK ▼ ▼ SYN_RECV ESTABLISHED │ │ rcv ACK ▼ ESTABLISHED ``` ## Главные состояния - **LISTEN** - серверный сокет ждёт SYN. Видно в `ss -tln` - **SYN_SENT** - клиент отправил SYN, ждёт ответа - **SYN_RECV** - сервер получил SYN, отправил SYN-ACK, ждёт ACK - **ESTABLISHED** - handshake закрыт, можно слать данные - **FIN_WAIT_1** - мы инициировали закрытие (отправили FIN) - **FIN_WAIT_2** - peer ACK'нул наш FIN, ждём его FIN - **CLOSE_WAIT** - peer первым отправил FIN, мы ack'нули, **наша задача теперь вызвать close()**. Если приложение не закрывает - застрянет здесь - **LAST_ACK** - отправили ответный FIN после CLOSE_WAIT, ждём ACK - **TIME_WAIT** - обе стороны послали FIN+ACK; ждём 2*MSL (~60 сек) на случай задержавшихся пакетов - **CLOSED** - нет соединения ## TIME_WAIT - почему его так много После закрытия активная (закрывшая первой) сторона висит в TIME_WAIT ~60 секунд. Это **по дизайну** - защита от пакетов от старого соединения, попадающих в новое с такой же 4-tuple (src/dst IP+port). На клиенте, если делает много коротких подключений (HTTP/1.0 без keep-alive, redis-clients без pooling), исходящие порты быстро кончаются: ```bash ss -tn state time-wait | wc -l # тысячи - потенциальный source-port exhaustion ``` Решения: - HTTP keep-alive / connection pooling в приложении - `net.ipv4.tcp_tw_reuse=1` - переиспользовать TIME_WAIT-сокеты для исходящих connect (на клиенте) - `net.ipv4.ip_local_port_range` - расширить диапазон ephemeral портов ## CLOSE_WAIT - приложение забыло close() CLOSE_WAIT означает «peer закрыл, ядро ждёт что **ты** вызовешь `close()`». Если их растёт со временем - у приложения **leak**: получает EOF на сокете и не закрывает дескриптор. Классическая ошибка в обработке EOF в node.js / java / питоне. Видно через `ss -tn state close-wait`. ## Команды ```bash ss -tn ``` Все TCP-сессии и их состояния ```bash ss -tnl ``` Только LISTEN-сокеты (что слушает) ```bash ss -tn state time-wait | wc -l ``` Сколько TIME_WAIT - сигнал о коротких соединениях ```bash ss -tn state close-wait ``` Сокеты в CLOSE_WAIT - приложение забывает close() при обнаружении EOF ```bash ss -tnp '( dport = 443 or sport = 443 )' ``` Что и от кого подключено к 443 (с PID-процессом, нужен sudo) ## См. также - [TCP three-way handshake](/kb/tcp-handshake.md) - [TCP keepalive](/kb/tcp-keepalive.md) - [Conntrack - память Linux о всех сетевых соединениях](/kb/conntrack.md) - [Порт - как несколько сервисов делят один IP](/kb/port.md)