FastNetMon

Wednesday, 4 March 2015

Использование решения на базе Linux и netmap-ipfw для суперскоростной фильтрации паразитного трафика

Последнее время стали довольно распространены высокоскоростные атаки, осуществляемые с использованием различных вариантов усиления (amplification) трафика.

Как же с ними бороться? Довольно сложно, это стоит понимать. Дешевые решения в виде ACL на 10GE свиче либо аппаратных правил на NIC (сетевой карте) - могут блокировать лишь часть паразитного трафика с очень ограниченным набором параметров - src/dst IP, src/dst port и протокол.

Более серьезные решения требуют привлечения либо высокопроизводительных роутеров (речь про роутеры класса Juniper MX 120+, и к слову, возможности фильтрации трафика у роутеров тоже весьма ограничены) либо аппаратных фаерволлов, например, от Arbor. Роутеры стоят огромных денег, но почти всегда есть в ДЦ - это плюс. А специализированное железо от Arbor стоит совершенно непомерные суммы.

Итак, наше решение софт бокс-фильтр, машина 10GE картами, кучей процессорных мощностей и Linux либо FreeBSD на борту.

Стоит понимать, что стандартными средствами того же Linux атаку в 10Mpps не отбить, в конце концов, он просто упадет, например, вот так. Во FreeBSD ситуация во многом похожая и еще более усложняющаяся (для меня) не особо хорошим пониманием этой ОС.

Поэтому в качестве решения я обратился к системам прямого доступа к сетевой карте класса netmap, PF_RING, DPDK, а именно - мы будем использовать netmap, потому что он очень быстр, не требует лицензии и под него есть портированный фаерволл ipwf.

Итак, приготовим машину на Debian 7, с сетевыми картами ixgbe с по меньшей мере двумя интефейсами. Так как машина должна подключаться в разрез между атакуемой машиной и аплинком.

Сборка довольно проста, сначала нужно собрать netmap со всеми драйверами и загрузить его.

После этого нужно установить в систему заголовочные файлы для netmap, чтобы собрать тулкит:
Стягиваем заголовки:
cd /usr/include/net
wget https://raw.githubusercontent.com/luigirizzo/netmap/master/sys/net/netmap_user.h
wget https://raw.githubusercontent.com/luigirizzo/netmap/master/sys/net/netmap.h
Собираем:
cd /usr/src
git clone https://github.com/luigirizzo/netmap-ipfw.git
cd netmap-ipfw
make
Запускаем:
./kipfw netmap:eth4 netmap:eth6
После этого можем загрузить тестовое правило в ipfw:

cd ipfw
./ipfw add 1 deny tcp from any to any 80 in
Для испытания насколько это решение хорошо в продакшене я запустил syn флуд в 7mpps с тестовой машины силами trafgen.

Метрики моей  тесты машины (а это слабый CPU E5-2407 0 @ 2.20GHz x 4) на картинке:
top - 13:33:05 up 3 min,  2 users,  load average: 0.44, 0.13, 0.05
Tasks:  78 total,   2 running,  76 sleeping,   0 stopped,   0 zombie
%Cpu(s): 10.0 us, 14.8 sy,  0.0 ni, 74.6 id,  0.0 wa,  0.0 hi,  0.7 si,  0.0 st
KiB Mem:  32980328 total,   546592 used, 32433736 free,    45320 buffers
KiB Swap:  8387580 total,        0 used,  8387580 free,    32204 cached

  PID USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND                                                                                 
 3105 root      20   0  660m 9060 8908 R  99.5  0.0   0:25.36 kipfw    


  1  [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]     Tasks: 26, 3 thr, 52 kthr; 2 running
  2  [                                                             0.0%]     Load average: 0.60 0.19 0.07
  3  [                                                             0.0%]     Uptime: 00:03:48
  4  [|                                                            0.7%]
  Mem[|||                                                   458/32207MB]
  Swp[                                                         0/8190MB]
Как можно видеть, машина прогрузилась лишь на 1/5, но одно ядро выгружено полностью (об этом отдельно).

Вполне неплохой показатель для такого слабого железа!

Теперь попробуем ту же самую задачу простого бриджинга сделать на Linux.

Ставим бридж утилс:
apt-get install -y bridge-utils
Добавляем интерфейсы в него:
brctl addbr mybridge
brctl addif mybridge eth4
brctl addif mybridge eth6
ifconfig mybridge up
И после этого вливаем те же 7mpps:
 1  [||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||99.1%]     Tasks: 25, 3 thr, 48 kthr; 2 running
  2  [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]     Load average: 0.47 0.23 0.15
  3  [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]     Uptime: 1 day, 14:24:01
  4  [||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||99.1%]
  Mem[||||                                                  497/32207MB]
  Swp[                                                         0/8190MB]


Шапка top выглядит еще более ужасающе:
top - 13:21:10 up 1 day, 14:24,  2 users,  load average: 1.57, 0.53, 0.26
Tasks:  73 total,   5 running,  68 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,  0.4 id,  0.0 wa,  0.0 hi, 99.6 si,  0.0 st
KiB Mem:  32980328 total,   812976 used, 32167352 free,    40784 buffers
KiB Swap:  8387580 total,        0 used,  8387580 free,   263172 cached

  PID USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND                                                                                 
   15 root      20   0     0    0    0 R  93.5  0.0  57:08.71 ksoftirqd/2                                                                             
   10 root      20   0     0    0    0 R  90.9  0.0  39:51.09 ksoftirqd/1                                                                             
    3 root      20   0     0    0    0 R  65.8  0.0  71:59.15 ksoftirqd/0                                                                             
   19 root      20   0     0    0    0 R  64.8  0.0  59:43.95 ksoftirqd/3                                                                             
 2806 root      20   0 20520  824  548 S   1.3  0.0   0:12.81 irqbalance                                                                              
 4723 root      20   0     0    0    0 S   1.0  0.0   0:18.05 kworker/1:2                                                                             
  342 root      20   0     0    0    0 S   0.7  0.0   0:13.04 kworker/2:1                                                                             
 2866 ntp       20   0 39076 2404 1764 S   0.7  0.0   0:08.80 ntpd                                                                                    
 5163 root      20   0     0    0    0 S   0.7  0.0   0:32.55 kworker/0:1    
Как можете видеть - Linux с этой задачей не справился потому что в нем используется схема с многократным копированием пакета вместо одноразового. Кроме этого, сам по себе стек Linux довольно тяжел и плохо приспособлен к подобной нагрузке по умолчанию.

А теперь попробуем силами все того же netmap-ipfw отбить эту атаку по отпечатку. А именно используя ее снимок из tcpdump:
11:43:10.008418 IP 198.18.51.106.62978 > 10.10.10.248.http: Flags [S], seq 1137192741, win 16, length 0
11:43:10.008419 IP 198.18.51.93.34560 > 10.10.10.113.http: Flags [S], seq 4245548258, win 16, length 0
11:43:10.008419 IP 198.18.51.98.30252 > 10.10.10.89.http: Flags [S], seq 1765400726, win 16, length 0
11:43:10.008420 IP 198.18.51.144.19980 > 10.10.10.91.http: Flags [S], seq 1052340006, win 16, length 0
11:43:10.008420 IP 198.18.51.30.38034 > 10.10.10.38.http: Flags [S], seq 2198215300, win 16, length 0
11:43:10.008421 IP 198.18.51.140.60388 > 10.10.10.133.http: Flags [S], seq 2831049352, win 16, length 0

В Linux блокировка такого трафика была бы задачей нетривиальной, но тут мы можем попробовать использовать необычайно малый размер окна и отсутствие опций tcp пакета вообще (в реальной жизни это нонсенс):
./ipfw add 8 deny tcp from any to any tcpwin 16 tcpdatalen 0
Сразу убеждаемся, что в правило начал лететь трафик:
./ipfw -a list
connected to 127.0.0.1:5555
nalloc 2248 nbytes 124 ptr (nil)
00008 578904841 23156193640 deny tcp from any to any tcpwin 16 tcpdatalen 0
65535  18831554   753262160 allow ip from any to any
Нагрузка машины-фильтра при этом была в рамках разумного:
top - 13:46:15 up 16 min,  2 users,  load average: 0.87, 0.65, 0.40
Tasks:  74 total,   2 running,  72 sleeping,   0 stopped,   0 zombie
%Cpu(s): 21.8 us,  3.0 sy,  0.0 ni, 74.7 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
KiB Mem:  32980328 total,   547800 used, 32432528 free,    45708 buffers
KiB Swap:  8387580 total,        0 used,  8387580 free,    32824 cached

 Как я уже обращал внимание, процесс kipfw выедает одно ядро полностью и это будет (если уже не является) причиной провалов в трафике, потому что он не успевает отбрабатывать весь проходящий трафик корректно.

Что с этим делать? Нужно делать патчи для kipfw для параллельной обработки либо попробовать запустить число инстансов kipfw по числу аппаратных очередей на сетевой карте. Таким образом в самом худшем случае на каждый процессор выйдет нагрузка не более чем 14/4=3.5Mpps на ядро, что в принципе разумная цифра.

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

Если у кого есть желание присоединиться к проекту в качестве тестера или разработчика - мои контакты справа.

2 comments :

  1. Здравствуйте!

    Не совсе понятен вот этот момент:
    ./kipfw netmap:eth4 netmap:eth6
    Что в принципе происходит после этой команды? На сколько я понимаю обе карты переводятся в режим netmap и настройки сети для этих адресов, которые были ранее становятся недоступными.

    ReplyDelete
    Replies
    1. Именно так, они начинают работать как прозрачный бридж.

      Delete

Note: only a member of this blog may post a comment.