Система защиты от DDOS атак - Syncookied

Так или иначе любой провайдер сталкивается с проблемами DDOS атак на свои ресурсы либо, что менее приятно, со своих ресурсов. Когда в нашей компании BeGet.com встала задача прозрачной фильтрации трафика на конечные сервера, мы написали свое решение Syncookied, которое изначально предполагалось только для защиты от syn, ack, data flood, но потом переросло в достаточно большую и обширную систему по защите от разного типа атак. Данным решением мы бы хотели поделиться со всем сообществом, так как на текущий момент аналогов ему мы не нашли (или мы о них не знаем). Есть платные решения, но стоят они очень дорого.

Вначале хотелось бы выразить свое мнение о лицах, которые организуют DDOS. Вместо того, чтобы конкурировать честным способом, они пытаются сделать плохо своим коллегам/конкурентам (не важно, кто жертва: сервер линейки, интернет магазин или отдельно взятая страна). Часто бывает, что за неимением конкурентов, но желанием монетизировать имеющиеся возможности «нехорошие» люди используют шантаж — заплатите 100$ и мы не будем DDOS`ить Ваш сайт. Но не лучше мы относимся и к фирмам, которые предлагают защиту от DDOS — фактически, это тот же шантаж, игра на страхе людей, но легальный. Вместо того, чтобы внести вклад в решение этой проблемы, на ней пытаются зарабатывать деньги. Надеюсь, что наши усилия не пропадут даром.

Исходный код проекта: https://github.com/LTD-Beget/syncookied.

Немного теории TCP
Технология Syncookie
Технология SynProxy
Зачем свой велосипед
Принцип работы
Настройка
Тесты производительности

Немного теории TCP

DDOS атаки бывают разные, как и их классификации. Атаки могут забивать канал, нагружать приложение, могут быть направлены на стек TCP/IP. В данном случае я хотел бы более подробно рассказать об атаке типа SynFlood (и похожих ACK, DATA), когда на конечную машину приходит много пакетов на открытие соединения.

Соединение в TCP открывается так называемым «тройным рукопожатием», для установки соединения сервер пассивно ожидает входящего пакета (выполнив системные вызовы LISTEN и ACCEPT).

  • Клиент посылает специальный пакет с флагом SYN, не содержащим данные, в котором передается начальный порядковый номер передающей последовательности (далее SEQ от клиента к серверу) и могут передаваться дополнительные опции TCP для согласования параметров соединения: mss, wscale, timestamp, sack_permitted

  • Сервер, получив пакет, сохраняет у себя значения SEQ и параметры TCP, полученные из SYN пакета, далее подготавливает ответный пакет, выставляя в нем флаги SYN+ACK. Сервер передает начальный номер последовательности (SEQ) от сервера к клиенту и возвращает стартовый номер последовательности принятой от клиента, прибавив к нему +1. Дополнительно в данном пакете передаются параметры соединения, которые согласует сервер.

  • Клиент, получив SYN+ACK пакет, сохраняет у себя параметры SEQ последовательности, принятой от сервера, добавляет к ней +1 и возвращает его в ответном пакете с установленным флагом ACK. C этого момента клиент предполагает, что соединение открыто.

  • После приема ACK пакета сервер открывает соединение.

Номера последовательностей — это, фактически, номера пакетов (байт в байтовом потоке), которые будут приняты или посланы, они необходимы для дальнейшей сборки данных в правильном порядке (протокол IP не гарантирует доставку пакетов в правильной последовательности), а также для идентификации потерянных или испорченных пакетов. У каждой стороны есть две последовательности: одна для передачи, одна для приема.

Основное назначение установки соединения — согласовать передающие последовательности и параметры соединения, которые указываются дополнительными опциями TCP. По RFC начальная последовательность должна быть произвольным числом, плавно увеличиваемым во времени.

Установить соединения можно и за 4 пакета, отправив отдельные пакеты c флагами SYN и ACK. Так же есть технология TCP Fast Open, о которой стоит поговорить отдельно.

Более подробно об TCP/IP можно почитать в WIKI

На открытие соединения у сервера тратятся значительные ресурсы - принимая SYN пакет, сетевая карта (NIC) генерирует прерывание (не на группу пакетов, как обычно, а именно на каждый), далее этот пакет копируется с NIC в память системы (или сетевая может его писать сразу в RAM, используя технологию DMA), далее пакет направляется в систему фильтрации трафика (iptables) и на последнем шаге попадает в ядро. Ядру необходимо сохранить у себя в хеш-таблице трекинга соединений значения, полученные в SYN пакете — это IP:PORT принимающей и передающей стороны, принятая последовательность, отправленная последовательность, дополнительные параметры TCP. Само собой, чем больше размер таблицы и чем чаще в нее идет запись — тем медленней она работает. Суть в том, что операционной системе Linux необходимо проделать большое количество действий для открытия соединения, это было необходимо сделать для гибкости в обработке пакетов и, что еще более важно, размер таблицы трекинга соединений - не бесконечен.

О простоте обработки пакетов говорит приведенная ниже схема работы сетевого стека ядра Linux:

В стандартной системе без применения дополнительных механизмов и тюнинга сетевого стека сервер начинает умирать уже при 100к-150к SYN-пакетов в секунду.

Технология Syncookie

В 1997 году в ядро был добавлен механизм защиты, позволяющий не сохранять данные в таблице тракинга соединений, а подписывать переданную последовательность в SYN+ASK пакете через куку, которая размещается на месте опций TCP.

/*
 * Compute the secure sequence number.
 * The output should be:
 *   HASH(sec1,saddr,sport,daddr,dport,sec1) + sseq + (count * 2^24)
 *      + (HASH(sec2,saddr,sport,daddr,dport,count,sec2) % 2^24).
 * Where sseq is their sequence number and count increases every
 * minute by 1.
 * As an extra hack, we add a small "data" value that encodes the
 * MSS into the second hash value.
 */
u32 count = tcp_cookie_time();
return (cookie_hash(saddr, daddr, sport, dport, 0, 0) +
	sseq + (count << COOKIEBITS) +
	((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
	 & COOKIEMASK));
Подписывание выполняется на основе алгоритма SHA1, где для валидации используется метки времени и секретный ключ, генерируемый каждый раз новый при старте системы. Кука записывается в значения seq, TCP опций в поля Timestamp (с 31 по 6 байт), ECN, SACK, WScale (4 байта из 6). Тут нужно пояснить: в Timestamp с 0 по 5 байт передается метка времени, которая также участвует в процессе валидации пакета (в среднем обновляется раз в 2 минуты), то есть если ответный пакет пришел через час — он будет не валидный. Также накладывается ограничение на количество возможных значений MSS, в последних ядрах используются следующие значения:

static __u16 const msstab[] = {
         536,
         1300,
         1440,   /* 1440, 1452: PPPoE */
         1460,
 };

Более подробно все аспекты можно посмотреть в коде ядра в файле syncookies.c

Данная технология позволила значительно увеличить способность системы обрабатывать SYN пакеты. В наших тестах, без особого тюнинга, система выдерживала 300к — 350к пакетов в секунду (данные приведены для сетевой карты 1G с 4 прерываниями). Но данное решение имеет недостатки в виде ограниченности выбора дополнительных параметров соединения — MSS ограничивается только 4 значениями, а также игнорировании части опций TCP.

Для включения в Linux Syncookie необходимо выполнить:

sysctl -w net.ipv4.tcp_syncookies = 2

В значении 1 Syncookie включается только при переполнении таблицы тракинга соединений. Для значения по умолчанию 2048 (параметр net.ipv4.tcp_max_syn_backlog), скорей всего, Ваше приложение уже перестанет отвечать на запросы.

Следующим этапом развития этой технологии, начиная с ядра 3.12 и инструментария IPTables 1.4.21, стало добавление в iptables новой цели (target) - SYNPROXY (и аналогичный проект synsanity, сравнение можно посмотреть тут ), смысл которой - выполнение все тех же действий, исключая SYN пакеты из тракинга соединений, что позволяет миновать много дополнительных действий и обрабатывать пакеты в сетевом фильтре, а не в ядре. На данной технологии и адекватной сетевой карте можно обрабатывать 1-3М (и более) пакетов в секунду.

На OpenNet есть статья по настройке IPTables + SynProxy.

Не так давно в ядро были добавлены патчи от Eric Dumazet, значительно повышающие производительность сетевого стека. По заявленным тестам позволяющие обрабатывать до 6М пакетов в секунду. За проделанную работу по улучшению сетевой подсистемы ядра хочется сказать огромное спасибо.

Технология SynProxy

Защищаться на конечном сервере не всегда удачная идея, проблем может быть много — начиная от слабого железа и заканчивая полосой пропускания, если, например, сервер подключен 1G, а на него идет 2G. Подключать все сервера 10G - достаточно накладное занятие.

Такие компании как F5, Arbor, Juniper и большинство фирм по защите от DDOS реализовали технологию SynProxy. Фактически фаерволы представляются конечной машиной и отвечают SynCookie на каждый SYN пакет, при этом для увеличения производительности не всегда используется SHA1 (в наших тестах мы использовали XOR и добивались колоссальной производительности в расчете на одно ядро). Проблема в том, что кука генерируется с использованием невалидного с точки зрения защищаемого сервера секретного ключа и метки времени. Если ответный ACK пакет просто переслать на конечную машину, она разорвет соединения. Поэтому, после получения от клиента ACK пакета с валидной кукой (валидность проверяет фаервол), необходимо симулировать открытие соединения от имени конечной машины уже без использования syncookie. При этом номера последовательностей, сохраненные на клиенте и передаваемые сервером, будут разные. В задачи фаервола будет входить замена данных последовательностей в каждом пакете от клиента к серверу и от сервера к клиенту.

Этот метод дает некоторые неоспоримые преимущества:

  • нет необходимости использовать тяжелый алгоритм SHA1
  • любые невалидные пакеты вне соединений не будут доходить на сервер (то есть фактически на 4 уровне OSI сервер полностью под защитой)
  • защищаемый сервер может работать под любой OS и даже не знать, что на него идет атака. (это очень удобно для серверов, доступа к которым нет)
Но есть и недостатки:
  • все недостатки технологии Syncookie
  • обрыв всех существующих соединений при активации защиты (есть возможность избежать, если фильтровать только SYN) и ее отключении (гарантированно).
  • оборудование необходимо встраивать в разрыв, то есть через него должен проходить весь трафик к серверу и от него.

Зачем изобретать свой велосипед

Вариант с использованием SynCookie нас не устраивал - не на всех серверах есть лишние ресурсы для подсчета SHA1, не на всех стоят подходящие сетевые карты (если на сервере 50 мегабит трафика, встроенная в материнскую плату сетевая карта вполне справляется со своей задачей) и отнюдь не все сервера подключены 10 гигабитным интерфейсом. А генерировать 1,1 гигабит SYN флуда с подменой адреса отправителя не такая уж и сложная задача, с которой может справиться любой школьник, если провайдер не фильтрует исходящий трафик. Поэтому необходимо было решение, работающее не на конечном сервере.

Использование технологии SynProxy было логичным выходом, но, как оказалось, стоимость решения от вендоров, к которым мы обращались, для наших целей превышало 100 000$ и представляло больше стоимость ПО, а не железа.

Решили разобраться в проблеме более подробно и, по возможности, написать простое решение, идеально подходящее для наших задач.

Принцип работы

Мы сделали систему, которая фильтрует трафик на любой сервер в нашей сети, просто включается и настраивается (автоматически), не требует больших ресурсов и, что не менее важно, не сильно дорога в реализации.

Логичным продолжением технологии Syncookie является вынос ее за пределы сервера, для этого мы написали модуль к ядру и демон, который общается с системой фильтрации по внутренней сети, передает секретный ключ и синхронизирует метки времени, а также включает Syncookie на сервере в случае необходимости. То есть на защищаемый сервер устанавливается модуль для ядра, который создает файл /proc/tcp_secrets, и демон, который передает данные с этого файла на фаервол.

Пояснения:

Роутер в нашем случае граничный маршрутизатор MX480.
Фаервол сервер, на котором запущена система фильтрации, соединен с главным коммутатором (Juniper EX4550) двумя линками по 10G.
Защищаемый сервер защищаемый сервер, подключен линком 1G.

Физическая организация нашего стенда (атаки на канальную емкость фильтруются на роутере):
Роутер подключен в главный коммутатор линком 40G, с главного коммутатора на фаервол идет два линка по 10G (один для входящего на фаервол трафика, другой для исходящего с него) и защищаемый сервер подключен еще через несколько коммутаторов линком 1G.

В обычном состоянии роутер и защищаемый сервер общаются напрямую, в случае обнаружения атаки на роутере прописывается статическая привязка ip адреса сервера к mac адресу фаервола, после этого весь трафик, идущий на защищаемый сервер, идет на фаервол, который имеет гораздо большую полосу пропускания (10G вместо 1G). Фаервол при получении SYN пакета (через первый интерфейс) отправляет в сеть SYN-ACK пакет (через второй интерфейс) от имени защищаемого сервера (с его mac адреса и IP) с валидной с точки зрения защищаемого сервера syncookie (так как у него есть секретный ключ и метка времени от защищаемого сервера), в случае получения ACK пакета (с кукой), фаервол проверяет его валидность, затем или меняет в пакете mac адрес на mac адрес защищаемого сервера и отправляет пакет обратно в сеть, или удаляет его, если кука не валидна. Защищаемый сервер, получая ACK пакет, открывает соединения и исходящие пакеты отправляет напрямую на роутер.

На сервере, так же как и в технологии SynProxy, ведется отслеживание соединений, так как мы видим начало открытия соединения и можем считать его закрытым по timeout (в случае если его закрыл сервер).

Алгоритм отслеживания соединений следующий:

приходит ACK пакет, проверяем куку, если валидная, сохраняем по ключу 3-tuple (src-ip, dst-port, src-port) статус Established, дальше перенаправляем пакет на защищаемый сервер.
если получаем RST пакет для соединения в статусе Established — удаляем соединение.
если получаем FIN пакет — меняем статус соединения на Closing и сохраняем таймстемп.

2 раза в минуту проходим по таблице отслеживания соединений и удаляем соединения в статусе Closing, для которых закончился Timeout.

Таблица возможных состояний соединения и последовательности передаваемых пакетов:

Также на фаерволе возможны любые правила фильтрации трафика — блокировка по IP, порту, протоколу и любым другим параметрам, которые задаются в pcap-filter в конфиге и могут применяться на лету. Например, для защищаемого сервера — запрещаем весь UDP трафик, разрешаем только TCP на порты 22, 80, 443 и для этих портов включаем SynProxy. Описание конфигурационного файла будет ниже в статье.

Во время защиты сервера от атак весь входящий трафик направляется на фаервол, и с него на сервер от имени роутера идут только валидные пакеты. Пакеты с защищаемого сервера идут напрямую на роутер в обход фаервола.

То есть, фактически, система включается только тогда, когда идет атака, и состоит из четырех основных частей:

модуля к ядру.
демона на защищаемом сервере.
софта на фаерволе.
управляющего скрипта, который определяет наличие атаки, меняет конфиг на фаерволе и вносит изменения на роутере.

Данная схема позволяет:

Фильтровать трафик в удобном месте.
Не разрывать соединения после отключения защиты. Само отключение заключается в удалении статической привязки mac адреса к IP адресу защищаемого сервера на роутере, после чего трафик идет напрямую на защищаемый сервер без обработки его фаерволом.
Использовать относительно недорогое железо с единственной хорошей сетевой картой.

Настройка

На защищаемом сервере

1 Установите модуль ядра tcpsecrets для доступа к tcp syncookie key и timestamp
2 Запустите syncookied в режиме сервера: syncookied server {ip:port}. Запуск этой команды автоматически установит значение net.ipv4.tcp_syncookies в 2 (всегда) и откроет UDP на указанном ip:port

На фаерволе

1 Установите netmap и убедитесь, что он работает.
2 Запретите использование NIC offloading features на интерфейсе, который собираетесь использовать
ethtool -K eth2 gro off gso off tso off lro off rx off tx off
ethtool -A eth2 rx off tx off
ethtool -G eth2 rx 2048 tx 2048
3 Настройте привязку прерываний к ядрам процессора. В данном примере мы привязываем 12 очередей к 12 первым процессорным ядрам:
QUEUES=12
ethtool -L eth2 combined $QUEUES
./set_irq_affinity -x 0-11 eth2
set_irq_affinity доступен по адресу: https://github.com/majek/ixgbe/blob/master/scripts/set_irq_affinity.

4 Создайте конфигурационный файл hosts.yml в рабочий директории, например:
- ip: 185.50.25.4
local_ip: 192.168.3.231:1488
mac: 0c:c4:7a:6a:fa:bf
где:
ip - IP адрес защищаемого сервера
local_ip - IP адрес и порт, на котором запущен UDP сервер на защищаемом сервере
mac - mac адрес защищаемого сервера


5 Запустите демон
syncookied -i eth2
Configuration: 185.50.25.4 -> c:c4:7a:6a:fa:bf
interfaces: [Rx: eth2/3c:fd:fe:9f:a8:82, Tx: eth2/3c:fd:fe:9f:a8:82] Cores: 24
12 Rx rings @ eth2, 12 Tx rings @ eth2 Queue: 1048576
Starting RX thread for ring 0 at eth2
Starting TX thread for ring 0 at eth2
Uptime reader for 185.50.25.4 starting
...
6 Настройте сеть таким образом, чтобы трафик на защищаемый сервер шел на фаервол.
7 В любой момент можно изменить конфигурацию путем изменения файла host.yml и отправки демону SIGHUP.
8 Наслаждайтесь превосходной защитой от DDOS атак =)

У syncookied есть много дополнительных опций, которые влияют на работу и производительность. Cписок всех опций:

# syncookied  --help
syncookied 0.1.8
Alexander Polyakov 

USAGE:
    syncookied [FLAGS] [OPTIONS] --input-interface  [SUBCOMMAND]

FLAGS:
        --debug      Log to stdout
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -c, --config                       path to hosts.yml file
    -C, --first-cpu                     First cpu to use for Rx
    -i, --input-interface             Interface to receive packets on
    -I, --input-mac       Input interface mac address
        --metrics-server     host:port of influxdb udp listener
    -o, --output-interface            Interface to send packets on (input interface will be used if not set)
    -O, --output-mac      Output interface mac address
    -N, --queue-length                 Length of buffer queue

SUBCOMMANDS:
    help      Prints this message or the help of the given subcommand(s)
    server    Run /proc/beget_uptime reader
Если у Вас на сервере более одного интерфейса, Вы можете указать второй интерфейс в опции -O для разделения исходящего трафика - это значительно улучшит производительность и позволит уменьшить задержки, так как переадресуемый трафик и SYN ответы будут разделяться.

Дополнительная фильтрация

Есть возможность указывать дополнительные фильтры в конфигурации хоста, фильры пишутся в pcap формате, политика по умолчанию пропускать трафик. Фильры применяются до 4 уровня модели ISO. Более подробно о формате фильтров можно прочесть в man pcap-filter.

- ip: 185.50.25.4
  secrets_addr: 127.0.0.1:1488
  mac: 0c:c4:7a:6b:0a:78
  filters:
   tcp and dst port 53: drop
   tcp and dst port 22: pass
   default: pass

InfluxDB

И в качестве приятного бонуса - система может писать метрики в InfluxDB, что позволит наслаждаться красивыми графиками при DDOS атаке::

Тесты производительности

Самые ресурсоемкие операции - это SHA1, IP checksum и TCP checksum. Все тестирование производительности осуществлялось на процессорах Intel Xeon E5 и Intel Xeon E3. Тестовый стенд собран на машине с одним процессором Intel(R) Xeon(R) CPU E5-2680 v3 с 32 гигабайтами памяти. В качестве сетевой карты используется Intel Ethernet Controller X710.

SHA1

В ядре есть много реализаций алгоритма SHA1 — на С, на ассемблере с использованием разных инструкций. Написали небольшую программу для тестирования скорости, результаты представлены ниже:

Implementation: native
 Result: 127472272531951331940158434155305718461562518521000000000000000000000000000
 Time elapsed: 0.217409 for 1000000 rounds
Implementation: ssse3
 Result: 127472272531951331940158434155305718461562518521000000000000000000000000000
 Time elapsed: 0.113001 for 1000000 rounds
Implementation: avx
 Result: 127472272531951331940158434155305718461562518521000000000000000000000000000
 Time elapsed: 0.106489 for 1000000 rounds
Implementation: avx2
 Result: 127472272531951331940158434155305718461562518521000000000000000000000000000
 Time elapsed: 0.118119 for 1000000 rounds
По результатам к проекту было решено подключить код на асемблере, использующим инструкции avx. Но, ходят слухи, что intel хочет сделать аппаратную реализацию SHA.

TCP/IP checksum

Аналогичным образом тестировали TCP и IP контрольную сумму. Код представлен здесь. По результатам тестирования выбрали самый быстрый алгоритм:
TCP checksum

v1: Elapsed: Duration { secs: 0, nanos: 48877805 }
v2: Elapsed: Duration { secs: 0, nanos: 34372707 }
v3: Elapsed: Duration { secs: 0, nanos: 17266739 }

IP checksum

v1: Elapsed: Duration { secs: 0, nanos: 16588409 }
v2: Elapsed: Duration { secs: 0, nanos: 15291254 }
v3: Elapsed: Duration { secs: 0, nanos: 8055500 }

Общее тестирование производительности

В отсутсвие трафика syncookied для уменьшения задержек постоянно опрашивает сетевую карту, что создает небольшую нагрузку: Нагрузка, создаваемая фильтрацией трафика 12.755pps (теоретический предел при размере пакета 74 байта + 4 байта заголовок ethernet) При фильтрации UDP или применении правил по портам/протоколам нагрузка будет неотличима от нагрузки в отсутствие трафика.

Фактически 10 ядер процессора Intel Xeon E5-2680v3 могут обрабатывать до 10 гигабит трафика. Один физический сервер способен обрабатывать более 40 гигабит трафика.

Исходный код проекта: https://github.com/LTD-Beget/syncookied.