Skype: My status
Динамический шейпинг во FreeBSD на основе PF PDF Печать E-mail
Раздел: Настройка программного обеспечения . Категория: FreeBSD 7

Организовал небольшую сеть с каналом в 24 мегаббита, но вот, как оказалось, этот канал сильно занимается 1 пользователем, а остальные в пролете. Для решения даной проблемы решил поставить сервак под FreeBSD и поднять на нем NAT c динамическим шейпингом. То есть, при отсутствии активности одного из пользователей в интернете, весь канал будет отдаваться другим.

Вот в данном материале опишу как все далал

Отступление по поводу шейпнга вообще и PF в частности:

  1. Шейпить физически можно только исходящий траффик. Так как входящий физически шейпить невозможно. Даже если и ограничить скорость его получения, то канал это не отчистит. Так как пакеты на сетевуху уже пришли. Но тут нам в помощь вступает принцип действия протокола TCP. Если отправитель не будет получать уведомления о получении пакетов, то он будет отсылать их медленнее. Так вот шейпер просто, если пакеты не влазят в нужные ограничения, их дропает, ну а отправитель их соответственно начинает посылать медленнее. Но это происходит не моментально..
    Отсюда вытекает то, что шейпить можно только TCP пакеты. К счастью, большинство протоколов использует именно TCP
  2. PF, насколько я знаю, умеет шейпить только исходящий траффик, но, как мы уже выяснили, нам это не помешает
  3. Шейпить пакеты (делить их по юзерам) можно только на внутреннем интерфейсе, так как на внешнем интерфейсе они будут уже отначенные. PF умеет обрабатывать пакеты по пользователям, независимо от того на каком интерфейсе мы их шейпим.
  4. Во FreeBSD Начиная с 7.0 и в OpenBSD с 4.1 (если не ошибаюсь) ко всем правилам по умолчанию используется опция keep-state. Что она делает: кто-то изнутри отправляет пакет наружу. Пакет соответствует какому-либо разрешающему правилу с такой опцией. Ответ на этот пакет снаружи будет также попадать под это правило. Плюс такой схемы, что можно тупо закрыть все входящие соединения снаружи — нужные пакеты ходить будут. НО! В таком случае ответные пакеты на исходяшие соединения, попадающие в какую-либо исходящую очередь, будут попадать в неё же. А нам это, не нужно. Поэтому для правил “деления по юзерам” обязательно нужна опция no state. И чтобы не возникло недоразумений, советую её указаывать всегда, независимо от версии ОС.

Теперь от слов к делу. Имеется FreeBSD 7.0. Изначально PF подгружается модулем (что не есть тру для файрвола), да и нету в ядре поддержки механизма ALTQ (очереди пакетов. Именно через этот механизм работает шейпинг в PF). Так что первое, чем надо заняться, это пересобрать ядро:

  1. Если не установлены, то устанавливаем исходники ядра
  2. Идём в директорию /usr/src/sys/i386/conf
  3. Копируем файлик GENERIC в какой-нибудь другой. Например у меня это KERNELTUUPIC
  4. Редактируем его: меняем сроку ident на то что охота видеть при uname -i; дописываем следующие девайсы: device pf device pflog device pfsync

    и следующие опции:


    options ALTQ 
    options ALTQ_CBQ # Class Bases Queuing (CBQ) 
    options ALTQ_RED # Random Early Detection (RED) 
    options ALTQ_RIO # RED In/Out 
    options ALTQ_HFSC # Hierarchical Packet Scheduler (HFSC)
    options ALTQ_PRIQ # Priority Queuing (PRIQ) 
    options ALTQ_NOPCC # Required for SMP build

    Кому скучно, может ещё и всякие лишние девайсы убрать или ещё что-нить включить :)

  5. Собираем ядро

    cd /usr/src && make buildkernel KERNCONF="KERNELTUUPIC"

    и ставим его

    cd /usr/src && make installkernel KERNCONF="KERNELTUUPIC"

  6. Для работы NAT`а необходимо включить форвардинг в ядре. В /etc/rc.conf добавим gateway_enable="YES"

    Для включения форвардинга без перезагрузки можно выполнить

    sysctl net.inet.ip.forwarding=1

  7. reboot

Теперь можно работать с PF. Для начала добавим его в автозагрузку.
Правим /etc/rc.conf:

В OpenBSD так:

pf="YES"

Во FreeBSD так:

pf_enable="YES"

Укажем где лежит конфиг файл с правилами

pf_rules="/etc/pf.conf" #можно указать другой файл

Включим логирование если охота.
(аналогично как и включение PF)
Лог PF пишет в бинарном виде. И читать его надо tcpdump`ом.
Примерно вот так

tcpdump -n -e -ttt -r /var/log/pflog

Также можно смотреть что идёт в логи на интерфейсе

tcpdump -n -e -ttt -i pflog0

.
В логи пишется только те пакеты, которые попадают в правила, у которых включено логгирование.

pflogd="YES" #OpenBSD
pflog_enable="YES" #FreeBSD

Также PF можно управлять через утилиту pfctl.

Например, включить pf можно так

pfctl -e

выключить

pfctl -d.

При включении через pfctl правила автоматически не загружаются. Для того, чтобы их загрузить надо выполнить команду

 

pfctl -f /etc/pf.conf

(вместо имени файла указать своё). Эта команда ещё пригодится много раз. Если в конфиг файле что-то изменили, то чтобы эти изменения вступили в силу надо их применить

pfctl -f /etc/pf.conf

Прочие команды писать не буду — их можно посмотреть в документации.

Теперь приступим собственно к составлению конфига.


Отмечу очень важное отличие pf от прочих файрволов (ipfw, iptables): в тех файрволах действует первое правило в списке под которое попадает пакет. И остальными правилами уже не обрабатывается. В pf же наоборот. Действует последнее правило. Но можно отменить дальнейшее рассмотрение правил если для правила применить параметр quick. Так что pf очень гибок в этом.

Вот как выглядит конфиг:

Макросы. Что-то типа переменных

ext_if="dc0" #собственно внешний интерфейс
int_if="vr0" # внутренний интерфейс
lan="192.168.0.0/24" #локальная внутренняя сеть

Собираем статистику на внешнем интефейсе. Полезно знать сколько ушло, сколько пришло и т.п. Статистику можно так собирать только на одном интефейсе. Для просмотра статистики используется команда

pfctl -s info

или

pfctl -si

set loginterface $ext_if

Таблицы. Удобная штука. Если адресов много, их можно запихать в таблицу. Есть ещё списки, но они работают медленнее. Далее в конфиге будет пример списка. По сути та же таблица, но таблица определяется заранее, а список во время работы.

table { 192.168.0.0/24, рабочая_сеть } #сети откуда я могу заходить на ssh и ftp.
table { 192.168.1.10, мой_внешний_ip, 192.168.0.1 } #собственно IP адреса, при обращени к которым считается, что обратились к самому серваку.
table { 192.168.0.2, 192.168.0.5 } #IP адреса первого пользователя
table { 192.168.0.3 } #IP второго пользователя

Нормализация всех входящих пакетов. Желательно.

scrub in all

Собственно определения очередей. Синтаксис такой:

altq on interface scheduler bandwidth bw qlimit qlim \
tbrsize size queue { queue_list }


interface — интерфейс
scheduler — планировщик очереди. Нас интересует cbq (есть ещё priq, но он не годится для наших целей)
bw — ширина очереди, и чаще всего ширина канала.
qlim – максимальное число пакетов в очереди. Необязательный параметр. По умолчанию — 50
size – размер token bucket regulator в байтах. Если не определен, то устанавливается на основе ширины полосы пропускания. (понятия не имею что и зачем)
queue_list — список дочерних очередей.

queue name [on interface] bandwidth bw [priority pri] [qlimit qlim] \
scheduler ( sched_options ) { queue_list }

interface — интерфейс. Так как у нас пакеты в очередь должны пихаться независимо от того на каком они интерфейсе, то параметр надо опустить.
bw — ширина очереди. Можно указывать в %, можно в бит/с.
scheduler — уже сказано.
pri — приоритет. Для cbq приоритет изменяется от 0 до 7, для priq диапазон от 0 до 15. Приоритет 0 считается самым низким. Если этот параметр не определён, ему назначается 1.
sched_option — опции планироващика. Нас интересует опция borrow. Позволяет занимать в случае необходимости и наличиния возможности ширину канала у родительской очереди и у очередей, также являющихся для данной родительской дочерними. (ппц замутил). Данная опция работает только с планировщиком cbq.
Также желательна опция red. Позволяет не допускать полного забивания канала. То есть начинает потихоньку дропать пакеты, задолго до полной забитости очереди. Чем более забита очередь, тем больше дропает.
queue_list — список дочерних очередей. То есть для основной они уже будут “внуками”. Возможно использовать только с планировщиком cbq.

altq on $int_if cbq bandwidth 100Mb queue { inet_in, default_in }
queue inet_in bandwidth 240Kb { user1_in, user2_in }
queue user1_in bandwidth 50% cbq(red, borrow)
queue user2_in bandwidth 50% cbq(red, borrow)
queue default_in bandwidth 99% cbq(default) #обязательно должна быть очередь default на интерфейсе. В неё будут пихаться все пакеты не вошедшие в остальные очереди.

Очередь на внутреннем интерфейсе для входящего траффика (хотя для внутреннего интерфейса он будет исходящим) Ширина канала 256Кбит/с. На всякий случай сделал запас и определил как 240. Юзерам идёт пополам. Сама сетевуха на 100 мегабит. Для очереди дефаулт ширина 99мегабит. Чтобы с нормально скоростью работать с шарой на сервере.

altq on $ext_if cbq bandwidth 100Mb queue { inet_out, default_out }
queue inet_out bandwidth 400Kb { user1_out, user2_out }
queue user1_out bandwidth 50% cbq(red, borrow)
queue user2_out bandwidth 50% cbq(red, borrow)
queue default_out bandwidth 99% cbq(default)

Аалогично для внешнего интерфейса.

Собственно NAT

nat on $ext_if from $lan to !$lan -> ($ext_if)

Думаю, всё понятно. Если пакет на внешнем интерфейсе идёт из локалки в мир, его отнатить, подставив адрес внешнего интерфейса. (у меня это всё потом ещё раз натится на модеме )
UPDATED: Если интерфейс в скобках, то в качестве обратного адреса подставляется любой адрес на интерфейсе. Сделано для динамических IP, чтобы не надо было перечитывать правила. Если без скобок, то при смене IP, надо будет выполнить pfctl -f путь_к_конфигу. Очень часто рекомендуют писать в скобках, но это может вызвать проблемы. 

Фильтры. Синтаксис скопировал из доки и немного подправил:

action [direction] [log] [quick] [on interface] [af] [proto protocol] \
[from src_addr [port src_port]] [to dst_addr [port dst_port]] \
[flags tcp_flags] [state]

action – Разрешить (pass) или послать (block)
direction – Направление пакета при прохождении через интерфейс: in или out.
log – Писать об этом в лог pflogd. Если указаны опции keep state, modulate state или synproxy state — в журнал попадают только пакеты открывшие соединение. Если надо, чтобы в журнал попали вообще все пакеты, применяйте правило log (all).
quick — Выполнить правило и больше не обрабатывать этот пакет следующими правилами
interface — интерфейс на котором применять это правило
af — тип адреса. inet (для Ipv4) и inet6 (для Ipv6). Обычно, нафиг не надо. PF и так догадается что за адрес
protocol — протокол транспортного уровня. Может быть tcp, udp, icmp, имя протокола из файла /etc/protocols, номер протокола от 0 до 255. Можно использовать список
src_addr, dst_addr — адрес исчтоника и назначения. Может быть одиночный адрес, сеть, таблица, доменное имя (в момент применения конфига оно заменится соответствующим IP), имя интерфейса (аналогично с доменным именем), any (любой адрес), all (то же самое, что и from any ti any). Можно использовать отрицание ( ! )
src_port, dst_port -порт источника или назначения в заголовке транспортного уровня. Может быть список. Можно использовать отрицание.
tcp_flags – Указывает какие флаги TCP должны быть выставлены в пакете, если указано proto tcp. Неинтресны для наших целей. Поэтому не буду описывать (тем более, что и сам не особо понимаю о чём)
state — Интересны только 2 варианта keep state (зачем это я уже писал), и no state. Для правил распихивающим пакеты в очереди, ОБЯЗАТЕЛЬНО no state

block in quick proto tcp from ! to port { 21, 22 } #закрываем ftp и ssh порты от злых хакеров с ботнетами и брутфорсом. Пример списка
block in quick from $lan to 192.168.1.10 #закрываю внешний IP от доступа из локалки. Мало ли какая винда дуркнет. Если качать с сервера по внешнему IP, то пакет считается, как пакет из интернета, и его скорость урезается.

Фильтров всего 2, так как всё остальное фильтрит модем. В нём всё закрыто, кроме ssh и ftp

И в самом конце правила, распихивающие пакеты по очередям. Думаю, тут должно быть всё понятно.

pass in on $int_if from to !$lan queue user1_out no state
pass in on $int_if from to !$lan queue user2_out no state
pass out on $int_if from !$lan to queue user1_in no state
pass out on $int_if from !$lan to queue user2_in no state

Чтобы посмотреть все правила фильрации используется команда

pfctl -s rules

или

pfctl -sr.

 

Чтобы посмотреть подробно (сколько пакетов этот фильтр заблокировал, или послал в очередь)

pfctl -sr -v

 

Чтобы посмотреть список очередей

pfctl -s queue

или

pfctl -sq

 

Чтобы посмотреть сколько пакетов в какую очередь попало

pfctl -sq -v

 

Чтобы наблюдать за очередями почти в реальном времени

pfctl -sq -vv

  (две v, а не дубль ве). Выведет то же, что и

pfctl -sq -v

но будет обновляться каждые 5 секунд и показывать текщие скорости пакетов в очереди.

 

Для данного материала я написал специальный генератор конфиг файла - http://pf.serviscentr.net.ua

 

Мониторинг можно настроить по статье - pfstat - рисуем графики

 

Частично взято с http://reonaydo.org.ru/dinamicheskij-shejping-na-freebsd/



Комментарии  

 
+1 #8 Шагунов Антон 13.11.2010 11:44
Генератор конфига PF V1.2b
Добавлена генерация pfstat.conf
Цитировать
 
 
+3 #7 Шагунов Антон 11.11.2010 01:40
Сегодня опубликован Генератор конфига PF V1.0 :lol:
Все вопросы по нему можно писать здесь.
Цитировать
 
 
+2 #6 Шагунов Антон 10.11.2010 10:07
Цитирую Шагунов Антон:
Есть идея написать программку, которая будет формировать конфиг для PF. Сейчас ищу готовые

решения, но если не найду, то обязательно напишу! + к данной статье нужно добавить учет трафика, возможно, тоже буду писать

скрипт. Если у Вас есть готовые варианты - прошу поделится.

Да! Ждемс полезная вещь будет... Запарился вручную прописывать
Цитировать
 
 
+1 #5 ssh 10.11.2010 10:04
Цитирую Шагунов Антон:
Есть идея написать программку, которая будет формировать конфиг для PF. Сейчас ищу готовые решения, но если не найду, то обязательно напишу! + к данной статье нужно добавить учет трафика, возможно, тоже буду писать скрипт. Если у Вас есть готовые варианты - прошу поделится.

Да! Ждемс полезная вещь будет... Запарился вручную прописывать
Цитировать
 
 
+1 #4 Шагунов Антон 27.10.2010 21:46
Мониторинг можно проводить при помощи pfstat. Скоро напишу как это сделать.
Цитировать
 
 
+3 #3 Шагунов Антон 21.10.2010 15:49
Есть идея написать программку, которая будет формировать конфиг для PF. Сейчас ищу готовые решения, но если не найду, то обязательно напишу! + к данной статье нужно добавить учет трафика, возможно, тоже буду писать скрипт. Если у Вас есть готовые варианты - прошу поделится.
Цитировать
 
 
-1 #2 НАТ 21.10.2010 15:41
Много полезного и нужного для понимающих людей )
Цитировать
 
 
+1 #1 Шагунов Антон 12.10.2010 23:32
Материал в процессе написания!
Цитировать
 

Добавить комментарий


Защитный код
Обновить

Последние комментарии

Пользователи : 463
Статьи : 65
Просмотры материалов : 846468

Реклама

С реальной скидкой отливы цокольные на лучших условиях.
заработок в интернете без вложений и без минималки
Посетите наш сайт seo новости по привлекательной цене.
046-1 справка
Бронированные двери