воскресенье, 9 апреля 2017 г.

Решение проблемы с паразитным трафиком TCP ACK

Столкнулся с необычной проблемой. Имеется Zabbix-сервер, работающий под управлением FreeBSD, и Zabbix-прокси, работающий под управлением Linux. Между ними находится NAT-шлюз, работающий под управлением FreeBSD. В такой конфигурации Zabbix-сервер и Zabbix-прокси периодически начинают забрасывать друг друга огромным количеством пустых пакетов с флагом ACK:
09:59:13.868487 IP x.y.z.4.60272 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 3255861608], length 0
09:59:13.868496 IP x.y.z.8.10054 > x.y.z.4.60272: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.868498 IP x.y.z.4.53217 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 738884814], length 0
09:59:13.868504 IP x.y.z.8.10054 > x.y.z.4.53217: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.868990 IP x.y.z.4.60272 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 3255861608], length 0
09:59:13.868997 IP x.y.z.8.10054 > x.y.z.4.60272: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.868999 IP x.y.z.4.53217 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 738884814], length 0
09:59:13.869005 IP x.y.z.8.10054 > x.y.z.4.53217: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.869364 IP x.y.z.4.60272 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 3255861608], length 0
09:59:13.869372 IP x.y.z.8.10054 > x.y.z.4.60272: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.869374 IP x.y.z.4.53217 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 738884814], length 0
09:59:13.869380 IP x.y.z.8.10054 > x.y.z.4.53217: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.869863 IP x.y.z.4.60272 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 3255861608], length 0
09:59:13.869870 IP x.y.z.8.10054 > x.y.z.4.60272: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.869872 IP x.y.z.4.53217 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 738884814], length 0
09:59:13.869878 IP x.y.z.8.10054 > x.y.z.4.53217: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.870238 IP x.y.z.4.60272 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 3255861608], length 0
09:59:13.870245 IP x.y.z.8.10054 > x.y.z.4.60272: Flags [.], ack 4294967295, win 65535, length 0
09:59:13.870247 IP x.y.z.4.53217 > x.y.z.8.10054: Flags [.], ack 1, win 457, options [nop,nop,TS val 200464 ecr 738884814], length 0
09:59:13.870253 IP x.y.z.8.10054 > x.y.z.4.53217: Flags [.], ack 4294967295, win 65535, length 0
Тут видно два подключения с портов 60272 и 53217 на порт 10054. Интервалы времени между ответными пакетами составляют единицы микросекунд. В результате образуется взаимный шторм в десятки тысяч пакетов в секунду. Самое интересное, что остановка Zabbix-прокси не приводит к пропаданию этого трафика - пакеты продолжают сыпаться.

Имеется аналогичная конфигурация, в которой Zabbix-сервер работает под управлением Linux. То есть два Linux'а разделены NAT-шлюзом под управлением FreeBSD. В этой конфигурации подобной проблемы не наблюдается.

Как я выяснил, Zabbix тут вообще не при чём. Я нашёл описание заплатки для Linux, в которой описана эта проблема: Merge branch 'tcp_ack_loops'. Проблема проявляется, когда промежуточный шлюз вносит изменения в параметры TCP-подключения этих двух узлов - меняет номер последовательности, номер ACK или отметку времени. Один из узлов, получив такой пакет, сообщает противоположной стороне о несовпадении параметров установленного подключения, отправляя ей пакет ACK. Другая сторона получает этот пакет ACK, а параметры в нём тоже не совпадают с параметрами установленного подключения. Обе стороны начинают забрасывать друг друга ACK-пакетами, в которых сообщается, что противная сторона отправила пакет с параметрами, отличающимися от параметров установленного соединения.

В новых версиях ядра Linux должна появиться переменная net.ipv4.tcp_invalid_ratelimit, описание которой можно найти здесь - /proc/sys/net/ipv4/* Variables. Приведу перевод описания этой переменной ядра:
tcp_invalid_ratelimit - ЦЕЛОЕ

Ограничивает максимальную скорость отправки дубликатов подтверждений в ответ на входящие пакеты TCP, которые соответствуют существующему подключению, но по одной из причин считаются неправильными:
  1. номер последовательности за пределами окна,
  2. номер подтверждения за пределами окна, или
  3. ошибка проверки PAWS (Protection Against Wrapped Sequence numbers - защита от завёрнутых номеров последовательности).
Может помочь смягчить простые DoS-атаки "цикл ACK", когда неисправный хост или злоумышленник между ними может переписать поля заголовка TCP таким образом, что это заставит каждую из сторон считать, что другая отправляет неисправные сегменты TCP, что заставит каждую из сторон отправлять непрекращающийся поток дубликатов подтверждений о неисправных сегментах.

Значение 0 отключит ограничение скорости дубликатов подтверждений в ответ на неисправные сегменты; в противном случае значение указывает минимальный интервал между отправкой дубликатов подтверждений в миллисекундах.

Значение по умолчанию: 500 миллисекунд.
Обновлять ядро Linux мне не хотелось, т.к. в конфигурации Linux-FreeBSD-Linux всё работало нормально и без этой заплатки. Я стал искать обходные решения.

На правильное направление меня вывела вот эта статья - FreeBSD Tuning and Optimization. Приведу перевод фрагмента, описывающего возможные проблемы от включения функции Syncookies:
Syncookies обладают как плюсами, так и минусами. Syncookies полезны если вы находитесь под DoS-атакой, поскольку с их помощью можно отличать настоящих клиентов от компьютеров злоумышленников. Но, поскольку опции TCP из начального SYN-пакета не сохраняются в Syncookies, опции TCP не применяются к подключению, препятствуя использованию функций масштабирования окна, отметок времени или точному определению размера MSS. Поскольку при получении ответного ACK устанавливаетcя подключение, злоумышленник может попробовать наводнить машину пакетами ACK, чтобы попытаться создать подключение.

Дополнительно для переполнения получателя настоящих SYN-куки злоумышленник может добавить к пакету данные. Тогда злоумышленник сможет отправлять данные сетевому демону FreeBSD, даже используя поддельный IP-адрес отправителя. Это приведёт к тому, что FreeBSD будет обрабатывать данные, которые не обрабатывались бы, если бы Syncookies были выключены. Несмотря на то, что Syncookies полезны во время DoS-атак, мы собираемся отключить синкуки.
Я попробовал выключить на Zabbix-сервере под управлением FreeBSD использование опции Syncookies:
# sysctl -w net.inet.tcp.syncookies=0
Затем заблокировал на время исходящий трафик на Zabbix-прокси, работающем под управлением Linux:
# iptables -A OUTPUT -d x.y.z.8 -p tcp -m tcp --dport 10054 -j DROP
Выждав полминуты, снова его разблокировал:
# iptables -D OUTPUT -d x.y.z.8 -p tcp -m tcp --dport 10054 -j DROP
В результате паразитный трафик пропал.

То есть, как я понял, изменения, вносимые в TCP-пакеты промежуточным шлюзом, осуществляющим трансляцию адресов, могут приводить к тому, что обе стороны будут слать друг другу ACK-пакеты с несовпадающими параметрами. Эти ACK-пакеты сервером FreeBSD будут восприниматься как пакеты, продолжающие процесс установки нового подключения - FreeBSD посчитает, что отправитель ACK-пакета ранее отправлял ей SYN-пакет и уже получил в ответ подтверждение в виде SYN-ACK-пакета. Таким образом FreeBSD создаст новое подключение. В дальнейшем обе стороны будут обмениваться друг с другом сообщениями о получении пакета, не соответствующего тем параметрам подключения, которые обе стороны согласовали друг с другом ранее. Не претендую на полное понимание ситуации и точность объяснения, но отключение Syncookies на стороне Zabbix-сервера под управлением FreeBSD помогло избавиться от проблемы.

Чтобы Syncookies отключались после перезагрузки системы, нужно внести соответствующие настройки в файл /etc/sysctl.conf:
net.inet.tcp.syncookies=0

Комментариев нет: