ДЕНИС НАЗАРОВ
Пакетный Фильтр OpenBSD
Часть 2
Вот и вышел очередной новогодний выпуск этого замечательного журнала из печати, на улице холодно, а мы будем сидеть в тепле, пить горячий кофе и с удовольствием познавать то, что несет нам этот номер.
В прошлой статье я рассказал об основных возможностях пакетного фильтра операционной системы OpenBSD и почему так важно использование фильтров. Недавно нам поступило тревожное сообщение от одной из организаций о том, что их сервер «барахлит», мне пришлось ехать и проверять, но я и не подозревал, насколько всё серьёзно! Сеть организации была спроектирована довольно хорошо, однако единственным слабым местом был Linux-сервер, на котором крутились Java-сервлеты и обслуживали денежные транзакции. Пограничным хостом (firewall) была давненько установленная нами, в то время еще OpenBSD 3.1-stable, с PF в качестве пакетного фильтра. При осмотре Linux-сервера сразу стало понятно, что его атаковали, и не раз, я нашел огромное количество различных exploits на сервере и несколько скриптов, которые в тот момент держали открытыми порты с shell, запущенными от прав www-сервера. После проверки сервера различными методами я пришел к выводу, что злоумышленникам так и не удалось ни похитить данные, ни проникнуть на сервер. Мне стало интересно, как же данные со взломанного сервера (сервер действительно считался взломанным по нашим критериям) так и не ушли в чужие руки.
Так как сервер останавливать было нельзя, пришлось вживую искать все ниточки. Все свелось к одному – на сервере с OpenBSD, посмотрев лог-файлы пакетного фильтра, сразу стало ясно, что любые попытки добраться к тем shell на Linux-сервере оканчивались провалом, ибо фильтрация была настолько четкой, что помимо входящих соединений контролировались и все исходящие. Дальнейшее исследование показало все места, откуда были произведены атаки, и подтвердило их безуспешность. Сейчас в той организации стоит новый мощный сервер под управлением Sun Solaris 10 и теперь уже OpenBSD 3.6-stable. К чему вся эта история? Показать на конкретном примере, как важно использование пакетных фильтров и как они могут защитить заведомо опасные приложения.
Итак, приступим к продолжению нашей статьи, я расскажу вам о следующих вещах:
- Распределение по очередям (Queuing).
- Роутинг (маршрутизация).
Распределение по очередям (Queuing)
Любые пакеты могут быть разделены по очередям для контроля полосы пропускания. Зачем это надо? Распределять нагрузку на канал равномерно для всей сети, например, или же, наоборот, урезать трафик для определенного сервиса или IP-адреса. Для определения «очереди» (queue) нужны всего лишь две декларации, и в дальнейшем все пакеты можно будет распределять по очередям, используя «имя очереди». Согласно компоненту фильтрации пакетного фильтра (PF), для правила pass данный пакет будет помещен в очередь, на которую ссылалось последнее(!) правило. Немножко запутанно?
Поясню на примере:
pass in on $ext_if inet proto tcp from any to any port 80 keep state queue www-1
pass in on $ext_if inet proto tcp from any to any port 80 keep state queue www-2
Здесь будет применено последнее правило, т.е пакет будет присвоен очереди с именем «www-2».
Очередями управляют «планировщики». На данный момент PF поддерживает 3 типа «планировщиков»:
- CBQ (Class Based Queueing) – очереди, прикрепленные к интерфейсу, создают «дерево», в котором каждая из этих очередей может иметь своих «потомков». Очереди имеют приоритет и полосу пропускания. Приоритет определяет, через какую очередь пакет пройдет первым, а полоса пропускания уже непосредственно влияет на скорость прохождения. CBQ выполняет двухсторонее разделение полосы пропускания вашего канала, используя иерархически-структурированные классы. Каждый класс имеет свою собственную «очередь» и присвоенную ей способность пропускания. Класс-потомок может заимствовать у родительского класса только(!) ту пропускную способность, которая в данный момент доступна для него.
- PRIQ (Priority Queueing) – очереди просто присоединены к интерфейсу и не могут иметь очередей-потомков. Каждая такая очередь имеет свой уникальный «приоритет», который может принимать значения от 0 до 15. Пакеты в очереди с наибольшим приоритетом будут обработаны первыми.
- HFSC (Hierarchical Fair Service Curve) – описание очень схоже с CBQ за исключением некоторых дополнений. HFSC есть не что иное как «исправленный» механизм, базирующийся на модели QoS. Его уникальность состоит в возможности разъединять связь между задержкой пакета и очередью. То есть HFSC обрабатывает очереди и пакеты отдельно друг от друга, однако после процесса обработки пакет и очередь опять логически связываются. В некоторых ситуациях это позволяет добиться максимальной производительности от трафик-шейпера. Очень часто это используется для сервисов, работающих в режиме реального времени.
Активизировать возможность распределения по очередям нужно в конфигурационном файле вашего фильтра (по умолчанию /etc/pf.conf) при помощи директивы «altq on», которая имеет свои дополнительные параметры:
- <interface> – имя интерфейса, для которого мы включаем данную возможность, если не определено, то очереди будут активированы для всех интерфейсов.
- <scheduler> – планировщик, в данный момент PF поддерживает cbq, priq, hfsc.
- bandwidth <value> – максимальный битрейт для всех очередей на данном интерфейсе. Значение может быть определено как процент от общей пропускной способности интерфейса, так и числовой величиной. Для этого могут использоваться слова b, Kb, Mb, Gb. Что соответственно – биты, килобиты, мегабиты, гигабиты. Если значение bandwidth не задано, то будет использована максимальная скорость.
- qlimit <value> – количество пакетов, которое может обслуживать очередь, по умолчанию 50, это значение отлично подходит для 100-мегабтиных сетей.
- queue <list> – определяет список очередей, которые будут созданы для данного интерфейса.
В этом примере интерфейс dc0 будет распределять в очереди 5Mbit/s, используя CBQ.
altq on dc0 cbq bandwidth 5Mb queue { std, http, mail, ssh }
После включения altq мы можем создавать очереди-потомки. Для них используются те же дополнительные параметры, что и для директивы altq, за исключением и queue, т.к они уже определены. Имя родительской очереди должно совпадать с определением в altq.
Например:
queue std bandwidth 10% cbq(default)
Для есть список параметров, которые помогают контролировать работу очереди.
- default – попадая в эту очередь, пакет не проверяется дальше, а тут же обрабатывается.
- red – Random Early Detection – RED отбрасывает пакеты, которые, предположительно могут перегрузить очередь.
- ecn – Explicit Congestion Notification – практически то же самое, что и RED. Отличается лишь алгоритмом обработки очередей, в итоге результат тот же, что и при использовании RED. RED использует более оптимизированный алгоритм, и за его счет обработка очередей происходит гораздо быстрее.
Для CBQ предусмотрены дополнительные параметры.
- borrow – очередь-потомок может «заимствовать» свободную пропускную способность у родительской очереди в случае необходимости.
Теперь параметры для HFSC.
- realtime <sc> – минимальная доступная пропускная способность для очереди.
- upperlimit <sc> – максимальная доступная очереди пропускная способность.
- <sc> – список так называемых service curve (для realtime задач), которые имеют формат (m1, d, m2), работают они по принципу «для первых d мили-секунд выдавать пропускную способность m1, после этого m2».
Синтаксис определения очередей CBQ и HFSC одинаков, разница лишь в дополнительных параметрах (см. выше). Однако стоит помнить, что скорость пропускания для очередей-потомков не может быть больше той, что определена для родительской очереди.
Дополнительный параметр для определения очередей – priority – указывает приоритет очереди. Для CBQ и HFSC значения могут быть от 0 до 7, для PRIQ – от 0 до 15. Значение по умолчанию для всех очередей 1.
Присвоение пакетов очередям происходит при помощи ключевого слова queue в правилах фильтрации(!). В нормальном режиме указать можно только одну очередь, однако, если же указана вторая, то пакеты будут обрабатываться на основе TOS (Type Of Service).
Что ж, теперь давайте посмотрим, как все это выглядит на практике, ибо в теории мало что понятно.
Включаем очереди:
altq on dc0 cbq bandwidth 5Mb queue { std, http, mail, ssh }
Определяем родительские очереди и очереди-потомки.
queue std bandwidth 10% cbq(default)
queue http bandwidth 60% priority 2 cbq(borrow red) { employees, developers }
queue developers bandwidth 75% cbq(borrow)
queue employees bandwidth 15%
queue mail bandwidth 10% priority 0 cbq(borrow ecn)
queue ssh bandwidth 20% cbq(borrow) { ssh_interactive, ssh_bulk }
queue ssh_interactive priority 7
queue ssh_bulk priority 0
И назначаем правила:
block return out on dc0 inet all queue std
pass out on dc0 inet proto tcp from $developerhosts to any port 80 keep state queue developers
pass out on dc0 inet proto tcp from $employeehosts to any port 80 keep state queue employees
pass out on dc0 inet proto tcp from any to any port 22 keep state queue(ssh_bulk, ssh_interactive)
pass out on dc0 inet proto tcp from any to any port 25 keep state queue mail
Итак, очередь std будет забирать 10% от нашего 5Mbit/s канала.
Очередь http будет забирать 60% и иметь двух потомков employees и developers с механизмом определения преждевременной нагрузки RED и возможностью «заимствования» полосы пропускания у родителя. У очереди http приоритет равен 2, что в данной конфигурации является наивысшим приоритетом (т.к значения больше, чем «2» не определены). Значит, данная очередь будет обрабатывать пакеты первой. Для очередей-потомков установлены скорости в процентном отношении соответственно 75% и 15%. Важно понимать, что вычисляться эти проценты будут из того куска общей пропускной способности канала, который отдан родительской очереди.
Очередь mail определена как 10% от общей полосы пропускания и имеет низший приоритет (0), чем другие.
Очередь ssh делится на ssh_interactive и ssh_bulk, имеющие приоритеты 7 и 0. Как только появляется пакет с портом назначения 22 и его помещают в очередь ssh_bulk. После того как соединение считается установленным, пакет переходит в очередь ssh_interactive и имеет наивысший приоритет для родительской очереди. А зачем так сложно?
Стоит помнить, что «контроль состояний» (stateful in-spection) применяется не только для фильтрации, но и для контроля полосы пропускания, а это означает, что даже при огромных правилах фильтров и различных ограничений скорости ваш сервер не будет иметь проблем с загрузкой процессора.
Роутинг (маршрутизация)
Тут все просто и мощно. Управлять пакетом нам позволяют те же правила фильтрации, однако с добавлением одной из нижеперечисленных опций. Помните – когда создается «состояние» (state), все пакеты для данного правила фильтрации и роутинга попадают под него.
- fastroute – обычный режим, система обрабатывает пакет обычным образом.
- route-to – система направляет пакет на нужный интерфейс с возможностью указания IP для «следующего хоста». Надо помнить, что помимо route-to нужно создать правила, позволяющие направлять пакет с одного интерфейса на другой в правилах фильтрации, иначе все ваши пакеты будут зарезаны фильтром.
- reply-to – на сей раз система отвечает пакетом с указанным IP-адресом и интерфейсом.
- dup-to – создает дубликат пакета и отправляет его с помощью route-to. Настоящий пакет обрабатывается обычным способом. Dup-to полезно использовать для обнаружения конфликтов в сети, когда рабочий сервер трогать нельзя, а трафик, проходящий через него, нужно анализировать.
Комбинируя правила фильтрации и опции роутинга, можно добиться очень сложных схем маршрутизации, но для таких целей придумали Cisco routers. А это уже совсем другая история.