Сети и протоколы
SYN cookie, TFO и TIME-WAIT при высокой нагрузке
Нагруженный балансировщик начинает возвращать EADDRNOTAVAIL на исходящих соединениях к бэкенду. Команда ss -tan показывает 30 000 сокетов в состоянии TIME-WAIT. Одновременно публичный слушатель атакуется SYN flood. Оба явления решаются через sysctl Linux — но неправильный sysctl ухудшает ситуацию.
Внутреннее устройство SYN cookie
При SYN flood очередь полуоткрытых соединений на сервере переполняется. Вместо выделения request_sock для каждого SYN ядро использует путь в net/ipv4/tcp_ipv4.c:cookie_v4_init_sequence:
ISN_в_SYN-ACK = MAC от (saddr, sport, daddr, dport, isn, secret)Старшие 5 бит кодируют индекс MSS (8 вариантов), следующие 6 бит — счётчик минут (окно повтора), нижние 21 бит — HMAC. Соединение тут же забывается. Когда приходит ACK, cookie_v4_check пересчитывает и проверяет. Если проверка прошла — ядро выделяет полный сокет. Если нет — ACK молча отбрасывается.
Компромисс: CPU вместо памяти — хорошо для больших серверов, не для маленьких. Цена: в cookie выживает только MSS. Масштаб окна, SACK и метки времени, предложенные в исходном SYN, молча отбрасываются для соединений, прошедших проверку через cookie. На высококачественном пути с большим RTT во время flood пропускная способность легитимных клиентов деградирует — окно ограничено 64 КиБ без масштабирования.
Linux включает SYN cookie, когда очередь полуоткрытых соединений превышает tcp_max_syn_backlog (зависит от системы, обычно 4096). Включается через net.ipv4.tcp_syncookies=1 (по умолчанию с ядра 2.4). Значение 2 форсирует безусловную генерацию cookie для тестирования.
- Cookie защищает от
- SYN flood / переполнения очереди полуоткрытых
- Что выживает в cookie
- Только индекс MSS (5 бит)
- Что молча отбрасывается
- Масштаб окна, SACK, метки времени
- Окно без масштабирования
- Максимум 64 КиБ на соединение
- Дефолт Linux
- tcp_syncookies=1 (условный)
- Принудительно для тестов
- tcp_syncookies=2
Трассировка tcpdump SYN flood с включёнными SYN cookie
$ sudo tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0' -tnn
14:23:45.123456 IP 203.0.113.50.34521 > 198.51.100.10.443: Flags [S], seq 1000000, win 29200, length 0
14:23:45.123489 IP 198.51.100.10.443 > 203.0.113.50.34521: Flags [S.], seq 3851629874, ack 1000001, win 28960, length 0
14:23:45.124000 IP 203.0.113.50.34522 > 198.51.100.10.443: Flags [S], seq 2000000, win 29200, length 0
14:23:45.124050 IP 198.51.100.10.443 > 203.0.113.50.34522: Flags [S.], seq 4025814937, ack 2000001, win 28960, length 0
14:23:45.125000 IP 203.0.113.50.34521 > 198.51.100.10.443: Flags [.], seq 1000001, ack 3851629875, win 29200, length 0
14:23:45.125010 IP 203.0.113.50.34522 > 198.51.100.10.443: Flags [.], seq 2000001, ack 4025814938, win 29200, length 0
14:23:45.200000 IP 203.0.113.100.55000 > 198.51.100.10.443: Flags [S], seq 5000000, win 29200, length 0
14:23:45.200050 IP 198.51.100.10.443 > 203.0.113.100.55000: Flags [S.], seq 2941837465, ack 5000001, win 28960, length 0
(ACK от 203.0.113.100 не пришёл; SYN flood с разных поддельных адресов) Два ACK пришли (легитимные клиенты завершили рукопожатие), но третий источник ACK не отправил. Есть ли риск для сервера?
TCP Fast Open (TFO, RFC 7413)
Стандартное рукопожатие стоит 1 RTT до данных приложения. TFO устраняет это для последующих соединений:
- Первое соединение: сервер отправляет опцию Fast Open Cookie в SYN-ACK; клиент кэширует её для пары (IP-сервера, порт-сервера).
- Последующие соединения: клиент помещает cookie в SYN вместе с данными приложения размером до MSS; сервер проверяет и обрабатывает данные, пока завершает рукопожатие — экономя 1 RTT.
На RTT 280 мс (Лондон → Сидней) TFO экономит 280 мс на каждом тёплом соединении.
Реальность развёртывания: проблемы с промежуточными устройствами убили широкое серверное распространение TFO. Middlebox-устройства (файрволы, DPI, некоторые NAT-шлюзы) вырезают опцию TFO из SYN, ломая доставку cookie. Некоторые NAT-шлюзы ломают cookie при смене внешнего IP клиента. Откат обязателен, но добавляет задержку. Серверное развёртывание ограничено; большинство интереса к 0-RTT сместилось к QUIC, который работает поверх UDP и обходит протокольное окостенение сетевой инфраструктуры. Linux поддерживает обе стороны с ядра 3.6 (2012) через битовую маску net.ipv4.tcp_fastopen (1=клиент, 2=сервер, 3=оба).
Нагруженный балансировщик возвращает EADDRNOTAVAIL на исходящих соединениях к одному апстриму. Проследите диагностику и исправление.
Старший инженер утверждает, что SYN cookie не имеют никакой производительностной цены. В чём ошибка этого утверждения?
Что делал tcp_tw_recycle и почему он был удалён из ядра Linux в 4.12?
Почему это работает
Почему интерес к TFO сместился к QUIC. TFO требует, чтобы клиентская библиотека и серверное приложение явно подключились, чтобы middlebox-устройства не вырезали опцию, и чтобы NAT-шлюзы не меняли адреса в полёте. QUIC обходит всё это, работая поверх UDP — middlebox-устройства, которые его не понимают, просто пересылают, и протокол может эволюционировать без изменений ядра. 0-RTT возобновление QUIC достигает той же экономии задержки, что и TFO, но с гораздо лучшим успехом развёртывания. Это паттерн, повторяющийся в сетях: когда существующий протокольный уровень слишком закостенел для эволюции, инновации перемещаются на уровень выше.
- 01Объясните механизм SYN cookie: что кодируется в cookie, что теряется и когда ядро активирует cookie?
- 02Что такое исчерпание TIME-WAIT, что его вызывает и каковы три производственных исправления?
- 03Почему TCP Fast Open получил ограниченное серверное распространение, несмотря на доступность с Linux 3.6?
SYN cookie защищают нагруженные серверы от SYN flood, вычисляя криптографический токен из четырёхкортежа соединения и кодируя его в порядковом номере SYN-ACK, так что память не выделяется до прихода валидного ACK. Цена: в cookie выживает только MSS — SACK, масштаб окна и метки времени отбрасываются, ухудшая пропускную способность для легитимных клиентов с большим RTT во время flood. Включайте через tcp_syncookies=1 (по умолчанию) и убедитесь, что backlog достаточно велик, чтобы cookie не нужны были при стационарной нагрузке. Исчерпание TIME-WAIT — EADDRNOTAVAIL на connect() — устраняется через tcp_tw_reuse=1 (безопасное переиспользование на основе меток времени согласно RFC 6191), расширением ip_local_port_range и использованием пула соединений. Никогда не используйте tcp_tw_recycle (удалён в Linux 4.12, ломает NAT). TCP Fast Open экономит 1 RTT для тёплых соединений, но требует непрерывных middlebox-устройств; QUIC вытеснил большинство интереса к TFO.