Конфигурация брандмауэра IPFW может быть «закрытой» или «открытой»
IPFW можно включить одним из двух способов: добавить нужные строки к конфигурационному файлу ядра и пересобрать его, или воспользоваться программой kldload и подгрузить нужный модуль ядра. Однако только метод пересборки ядра позволяет регулировать некоторые функции, например включить журналирование.
Динамическое включение IPFW:
#
kldload ipfw
Для статического включения IPFW в конфигурационный файл ядра следует поместить строку
options IPFIREWALL
затем пересобрать ядро и перезагрузить машину.
Но не всё так просто, как кажется. Нужны ещё некоторые телодвижения. Дело в том, что брандмауэр, как было сказано выше, может быть закрытым и открытым. По умолчанию он закрыт. Это может стать настоящим кошмаром при конфигурировании брандмауэра удалённо. Поначалу, пока не наберётесь опыта, старайтесь не включать IPFW удалённо ни статически ни динамически. Если такая неоходимость есть, попробуйте использовать команду at(1) для того, чтобы отменить включение брандмауэра через десять минут. Если всё будет в порядке, вы можете командой atrm(1) отменить поставленное задание по снятию IPFW.
Если вы хотите динамически включить IPFW удалённо, сделайте это так:
#
at +10minutes ipfw disable firewall Job 59 will be executed using /bin/sh#
kldload ipfw && \ ipfw -q add 65000 allow all from any to any#
atrm 59
Такое действие приведёт к тому, что брандмауэр после включения будет открыт и вы не потеряете связь с ним, а если всётаки что-то пойдёт не так, то через 10 минут брандмауэр откроется сам собой, благодаря команде at(1). При локальной работе за консолью, такое действие не требуется.
Включение IPFW статически на удалённой машине, требует некоторых
дополнительных действий. После того, как ядро пересобрано, перед
перезагрузкой, надо указать как минимум две дополнительные опции в
/etc/rc.conf(5)
для включения брандмауэра и
задания открытой политики:
firewall_enable="YES" firewall_type="OPEN"
Существуют и другие политики, мы рассмотрим их ниже в Раздел E.1.1.1.1.1, «Предопределённые типы брандмауэров» и Раздел E.1.1.1.1.2, «Пользовательские типы брандмауэров», однако открытый брандмауэр — рекомендуемая практика для новичков. Можно вкомпилировать политику непосредственно в ядро. Для этого в конфигурационном файле ядра следует указать следующую опцию:
options IPFIREWALL_DEFAULT_TO_ACCEPT
В этом случае нет необходимости задавать политику при включении
IPFW и нет необходимости делать это в
rc.conf(5)
, так как это уже сделано в ядре.
Однако, рекомендуемая практика состоит в том, чтобы всё равно
указывать политику в rc.conf(5)
, так как
позже мы будем в этом файле указывать свои собственные политики,
созданные в /etc/rc.firewall
. Если вы
включаете IPFW динамически, то вам тоже следует вписать описанные
выше строки в /etc/rc.conf(5)
—
опция firewall_enable="YES"
служит так
же и для подгрузки модуля ядра ipfw.ko
.
В дополнение к указанным опциям, в конфигурационном файле ядра можно указать ещё и следующие опции:
options IPFIREWALL_VERBOSE options IPFIREWALL_FORWARD options IPFIREWALL_VERBOSE_LIMIT=#
Кроме опций в конфигурационном файле ядра, данными возмоностями можно управлять через переменные ядра при помощи команд
#
sysctl -w net.inet.ip.fw.verbose=1#
sysctl -w net.inet.ip.fw.verbose_limit=#
IPFIREWALL_VERBOSE
Включение журналирования: каждое правило содержащее ключевое
слово log
порождает сообщение для
демона syslogd(8). Подробнее см. Раздел E.1.4, «Журналирование».
Соответствующая переменная ядра net.inet.ip.fw.verbose
IPFIREWALL_FORWARD
fwd
.
IPFIREWALL_VERBOSE_LIMIT=#
Предел журналирования — правило с ключевым
словом log
не может породить
журнальных сообщений больше, чем указано в этой опции.
(#
следует заменить на некоторое
число, 0 — отсутствие ограничения.)
Соответствующая переменная ядра net.inet.ip.fw.verbose_limit
Если система поддерживает IPv6, можно включить в ядре следующие опции:
options IPV6FIREWALL options IPV6FIREWALL_VERBOSE options IPV6FIREWALL_VERBOSE_LIMIT=100 options IPV6FIREWALL_DEFAULT_TO_ACCEPT
Специальных переменных ядра влияющих на журналирование IPv6 нет. Существуют и другие переменные ядра для настройки IPFW, однако они будут освещены в других местах, так как они не нужны для активации брандмауэра, а требуются для более тонкой настройки.
Переменные ядра следует указать так же в файле
/etc/sysctl.conf
. В этом случае они будут
устанавливаться при загрузке системы.
Кроме того, если ваша машина выступает в качестве шлюза, вам
понадобится включить проброс пакетов между интерфейсами. Для этого
в rc.conf
надо вписать опцию
gateway_enable="YES"
или оперировать переменной ядра net.inet.ip.forwarding
Независимо от того, указан ли тип брандмауэра в
/etc/rc.conf
, если в нём есть строка firewall_enable="YES"
, при запуске
системы будет выполняться сценарий
/etc/rc.firewall
. В этом сценарии, для
начала, будут выполнены следующие команды:
ipfw add 100 pass all from any to any via lo0 ipfw add 200 deny all from any to 127.0.0.0/8 ipfw add 300 deny ip from 127.0.0.0/8 to any
Их назначение — пропускать весь локальный трафик на кольцевом интерфейсе и предотвращать попытки обращения к внутренним адресам машины из внешнего мира. Если этих правил не будет, поломается множество внутренних сервисов — RPC, X11 и др.
Если тип брандмауэра в rc.conf
определёно
как OPEN
, то слеующим срабатывает
правило
ipfw add 65000 pass all from any to any
Это правило пропускает весь внешний трафик внутрь и весь
внутренний трафик наружу. Оно выполняет ту же функцию, что и
опция IPFIREWALL_DEFAULT_TO_ACCEPT
в ядре. Если в ядре вкомпилирована открытая политика, то при
этом создаётся правило номер 65535, allow ip from any to any
, вместо deny ip from any to any
, что делает
правило номер 65000 ненужным. Чтобы избежать добавления
правила номер 65000, можно использовать тип брандмауэра UNKNOWN
. Это имеет смысл сделать тем,
кто просто хочет поупражняться с брандмауэром или просто
заброкировать трафик с какого-то конкретного хоста. Для этого,
оставьте брандмауэр открытым и переходите к Раздел E.1.2, «Основы синтаксиса правил ipfw». Если же вы не хотите
использовать открытый брандмауэр, ни тип OPEN
, ни UNKNOWN
, разумеется, вам не годятся.
Есть две возможности: либо использовать предопределённые
типы брандмауэра из rc.firewall
, либо
создать пользовательский тип. Второе предпочтительнее по
следующим причинам:
rc.firewall
.
Разумеется последнее слово в выборе типа брандмауэра за
администратором. Если вам почему-то хочется именно
использовать один из предустановленных типов, хотя бы
посмотрите скрипт rc.firewall
, чтобы
понимать, что он делает. Тип брандмауэра задаётся в файле
/etc/rc.conf(5)
опцией
firewall_type="..."
Тип брандмауэра можно задавать в любом регистре: OPEN
, open
, OpEn
.
Помимо обсуждавшегося выше типа OPEN
имеются ещё три:
CLIENT
rc.firewall
,
так как в нём надо указать номера IP адресов хоста и
сети, а так же сетевую маску, так что лучше уж
написать пользовательский тип.
SIMPLE
Несмотря на название, этот тип более сложен, чем
CLIENT
. Назначение этого
типа брандмауэра — работа на
шлюзе (маршрутизаторе). В брандмауэре присутствует
защита от спуфинга (см. spoofing), Блокируется трафик в
немаршрутизируемые сети и из немаршрутизируемых
сетей на внешнем интерфейсе (что не всегда удачная
идея, будьте внимательны если у вас есть приватные
сети во внешнем окружении), осуществляется
трансляция NAT (при включении опции natd_enable
в
rc.conf
). Брандмауэр написан в
расчёте на то, что на шлюзе работает DNS сервер, Web
сервер и принимается почта, а так же работает служба
NTP. Остальной трафик блокируется политикой.
Этот тип вам так же придётся отредактировать, чтобы вписать реальные названия интерфейсов и адреса.
CLOSED
rc.firewall
расчитан на работу
при политике блокирующей любой трафик.
Внимание | |
---|---|
Корректная работа скрипта rc.firewall
возможна только если в ядре выбрана блокирующая политика.
Если вы выставили в ядре опцию IPFIREWALL_DEFAULT_TO_ACCEPT , все
перечисленные типы окажутся непригодны. В частности тип
Брандмауэра CLOSE ничего не
закроет. Мораль — остерегайтесь искуственного
интеллекта пока не поймёте как он работает. Пишите правила
фильтрации сами.
|
Разумная практика состоит в том, чтобы самому написать
правила брандмауэра, и подгружать их как пользовательский
тип в rc.conf
. Например так:
Загружать правила из файла
/etc/rc.firewall.rules
| |
Загружать правила «молча» — не выводить информации о загружаемых правилах на экран. |
Синтаксис файла rc.firewall.rules
будет слегка отличаться от
rc.firewall
.
rc.firewall.rules
—
самостоятельный sh(1) сценарий и в нём
не определены переменные, исользовавшиеся в
rc.firewall
. Если программирование в
sh(1) представляет для вам
проблему — не пугайтесь. Вам просто надо
подряд много раз вызвать команду
ipfw(8) с некоторыми аргументами,
которые мы ниже обсудим. Но если вы хотите использовать
всю мощь sh(1) обратитесь к Раздел 7.7, «Написание несложных Bourne-скриптов».
Синтаксис правил IPFW довольно прост. Каждое правило можно добавить в консоли при помощи команды ipfw(8). Прежде чем мы углубимся в изучение синтаксиса, посмотрим как можно просмотреть имеющиеся правила.
Команда list
позволяет просмотреть
список активированных правил:
#
ipfw list
65000 allow ip from any to any
65535 deny ip from any to any
Опция -t
позволяет увидеть когда прошёл
последний пакет соответствовавший данному правилу:
#
ipfw -t list
65000 Wed Jun 6 13:41:05 2007 allow ip from any to any
65535 deny ip from any to any
Опция -a
или команда show
показывает значение
счётчиков — сколько пакетов и сколько байт прошло
через каждое правило.
#
ipfw -a list 65000 722 331414 allow ip from any to any 65535 0 0 deny ip from any to any#
ipfw show 65000 760 336294 allow ip from any to any 65535 0 0 deny ip from any to any
Теперь посмотрим, какие правила можно использовать для настройки stateless фильтрации. (Т.е. фильтрации без учёта состояния соединений. stateful фильтрация — фильтрация с учётом состояния соединений, позволяет заметно упростить набор правил и повысить эффективность работы брандмауэра. Они будут рассмотрены в Раздел E.1.5, «Фильтрация с учётом состояния соединений»).
Правила в брандмауэр можно добавлять как при помощи утилиты ipfw(8) так и из файла:
#
ipfw /etc/ipfw.rules
В последнем случае в файл строка за строкой кладутся аргуметы команды ipfw(8). Например:
add 1000 allow all from any to any
Мы с вами уже встречали это правло раньше, когда смотрели на
открытый тип брандмауэра. Ключевые слова allow
, accept
, pass
и permit
— синонимы. В данном
правиле разрешено проходит всем пакетам в любом направлении. В
большинстве случаев, когда оказывается, что пакет соответствует
данному правилу, его прохождение по цепочке правил
прекращается и к нему применяется указанное в правиле
действие. Т.е. первое правило выигрывает.
В простейшем случае синтаксис правил IPFW выглядит следующим образом:
<команда> [<номер правила>] <действие> <протокол> from <источник> to <назначение>
Важные команды: add
и delete
— их смысл очевиден
из названия. Номера правил колеблются от 0 до 65535. Последнее
правило зарезервировано — это политика указанная в
ядре. Даже если у вас открытый тип брандмауэра, последнее
правило будет отражать политику указанную в ядре. Однако,
поскольку первое правило выигрывает, правило с номером 65000,
открывающее брандмауэр, будет работать, а на политику не
попадёт ни один пакет.
Действие может быть одним из следующего списка:
allow
или accept
или pass
или permit
deny
или drop
reset
count
Посчитать пакеты, соответствующие правилу. При этом пакеты продолжают идти по цепочке правил. Данное правило используется при написании биллинговых систем (систем учёта трафика).
Пример:
#
ipfw add 100 allow icmp from 172.19.0.2 to any 00100 allow icmp from 172.19.0.2 to any#
ipfw add 200 allow icmp from any to any 00200 allow icmp from any to any$
ping -c3 172.19.0.34 PING 172.19.0.34 (172.19.0.34): 56 data bytes 64 bytes from 172.19.0.34: icmp_seq=0 ttl=64 time=1.263 ms 64 bytes from 172.19.0.34: icmp_seq=1 ttl=64 time=1.987 ms 64 bytes from 172.19.0.34: icmp_seq=2 ttl=64 time=1.095 ms --- 172.19.0.34 ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max/stddev = 1.095/1.448/1.987/0.387 ms#
ipfw show 00100 3 252 allow icmp from 172.19.0.2 to any 00200 3 252 allow icmp from any to any 65535 0 0 deny ip from any to any
Итак, в этом примере мы создали два одинаковых правила, Под правило номер 100 попадают исходящие пакеты ICMP («пинги»), а под второе попадают все пакеты ICMP, но так как пинги уже пропущены сотым правилом, то правилу номер 200 достались только ответные пакеты («понги»). Поэтому оба правила насчитали три пакета.
Теперь заменим в 100-м правиле действие allow
на действие count
:
#
ipfw delete 100#
ipfw add 100 count icmp from 172.19.0.2 to any 00100 count icmp from 172.19.0.2 to any#
ipfw zero 200 Entry 200 cleared.$
ping -c3 172.19.0.34 PING 172.19.0.34 (172.19.0.34): 56 data bytes 64 bytes from 172.19.0.34: icmp_seq=0 ttl=64 time=1.453 ms 64 bytes from 172.19.0.34: icmp_seq=1 ttl=64 time=4.426 ms 64 bytes from 172.19.0.34: icmp_seq=2 ttl=64 time=1.413 ms --- 172.19.0.34 ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max/stddev = 1.413/2.431/4.426/1.411 ms#
ipfw show 00100 3 252 count icmp from 172.19.0.2 to any 00200 6 504 allow icmp from any to any 65535 0 0 deny ip from any to any
В этом примере, мы изменили правило номер 100, затем
обнулили счётчик у правила 200 (команда zero
) и повторили эксперимент.
Теперь до 200-го правила добрались все ICMP пакеты: и
«пинги» и «понги».
Действие count
позволяет
разделить функции подсчёта трафика и функции фильтрации.
skipto
numЛюбой пакет соответствующий правилу будет передан правилу, чей номер больше либо равен num.
add 1400 skipto 1800 all from any to any
Это правило заставит брандмауэр игнорировать все правила с номерами от 1400 до 1800.
В поле протокола можно указать любой протокол транспортного
уравня. Как правило это tcp
, udp
или icmp
, но может быть и любой другой из
указанных в файле /etc/protocols
.
Протокол можно указывать как по имени, так и по номеру.
Адрес источника и адрес назначения имеют одинаковый формат:
me
me
означает
множество адресов присвоенных интерфейсам нашей машины.
any
any
означает
любой адрес.
table(number[,value])
hostname
Можно указать имя хоста из файла
/etc/hosts
или разрешающееся через
DNS. Имейте в виду: если имя разрешается в несколько
адресов по схеме round-robin, только одно имя будет
реально добавлено в брандмауэр:
#
host mx1.yandex.ru mx1.yandex.ru has address 213.180.223.89 mx1.yandex.ru has address 213.180.223.90 mx1.yandex.ru has address 213.180.200.1 mx1.yandex.ru has address 213.180.200.10 mx1.yandex.ru has address 213.180.200.11 mx1.yandex.ru has address 213.180.223.88#
ipfw add 100 allow 1 from mx1.yandex.ru to any 00100 allow icmp from 213.180.223.88 to any
Можно конкретизировать какие именно хосты из указанной сети вас интересуют. Для этого применяются квадратные скобки. Если маска в этой форме не указана, подразумевается маска 24 бита.
#
ipfw add 100 allow all from me to '172.19.0.34{123,5-55}'
00100 allow ip from me to 172.19.0.0/24{5-55,123}
Обратите внимание: фигурные скобки обладают своим смыслом в оболочке bash(1). Если вы пользуетесь ею, то вам надо брать адрес в кавычки, как показанно в этом примере.
Перед каждым адресом можно указать ключевое слово not
с очевидным смыслом. Адреса можно
указывать через запятую:
#
ipfw add 100 allow all from mx1.yandex.ru, mx2.yandex.ru to me
00100 allow ip from 213.180.200.10,213.180.223.121 to me
После указания хоста или сети, можно указать номер порта или
название протокола из файла
/etc/services
. Перед портами можно
указывать, а можно не указывать ключевые слова dst-port
или src-port
.
#
ipfw add 100 allow all from me to any 22,25,80,1024-65535 00100 allow ip from me to any dst-port 22,25,80,1024-65535#
ipfw add 200 allow all from me to 172.19.0.34 telnet 00200 allow ip from me to 172.19.0.34 dst-port 23
Если в названии протокола встретится знак минус, его
необходимо защищать обратным слешем: ftp\-data
. (А если вы вводите команды в
оболочке, или пишите сценарий, то сам обратный слеш тоже надо
экранировать: ftp\\-data
.) Если его
не защищать, то минус будет воспринят как диапазон:
#
ipfw add 100 allow tcp from any to me ftp\\-data-ftp
00100 allow tcp from any to me dst-port 20-21
Если операции с масками сети кажутся вам неясными, попробуйте обратиться к Раздел 6.8.2, «Маска подсети в формате CIDR».
Хотя рассмотренные правила покрывают множество простых ситуаций, вам могут понадобиться более сложные правила, в ситуации когда у вашей системы более одного сетевого интерфейса или вы хотите генерировать какие-то специальные ответы на пакеты или хотите лучше контролировать направление движения трафика.
Для начала приведём синтаксическую диаграмму правил IPFW:
<команда> [<номер правила>] <действие> [log [logamount <число>]] <протокол> from <источник> to <назначение> [<интерфейс>] [<опции>]
Всё, что находится в квадратных скобках, это необязательные аргументы, которые дают системе новый функционал. Эти-то правила и будут рассмотрены в данном разделе. Мы рассмотрим некоторые действия, которых не коснулись в предыдущем разделе.
Дествие unreach
«код>
— отбрасывает пакет и
генерирует ответное ICMP сообщение с указанным кодом
недостижимости. (Например network unreachable или host
unreachable, т.е. сеть недоступна или хост недоступен). Код
можно указывать как по номеру, так и по имени. Если вы не
знаете, что значат эти коды, то значит вам не надо их
употрелять.
Важная возможность брандмауэра IPFW не рассмотренная в Раздел E.1.2, «Основы синтаксиса правил ipfw» — возможность контроля за тем, на каком интерфейсе появился пакет и в каком он движется направлении. До сих пор мы делали предположения о направлении пакетов просто на основании адресов источника и назначения, однако узнать откуда пакет пришёл в самом деле мы не могли.
Если вы хотите, чтобы правило срабатывало только в отношении
пакетов идущих внутрь или наружу, вы можете указывать ключевые
слова in
и out
. Оба ключевых слова должны
указываться там, где на приведённой выше диаграмме указано
слово <интерфейс>
, т.е. в
конце правила, перед возможными опциями.
Например, если мы хотим обрабатывать все входящие пакеты вне зависимости от их направления, мы можем использовать правило
add 1000 allow all from any to any in
Например:
#
ipfw add 100 count icmp from any to any in 00100 count icmp from any to any in#
ipfw add 200 count icmp from any to any out 00200 count icmp from any to any out#
ipfw add 300 count icmp from any to any 00300 count icmp from any to any$
ping -c3 172.19.0.34 PING 172.19.0.34 (172.19.0.34): 56 data bytes 64 bytes from 172.19.0.34: icmp_seq=0 ttl=64 time=1.358 ms 64 bytes from 172.19.0.34: icmp_seq=1 ttl=64 time=1.110 ms 64 bytes from 172.19.0.34: icmp_seq=2 ttl=64 time=1.174 ms --- 172.19.0.34 ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max/stddev = 1.110/1.214/1.358/0.105 ms#
ipfw show 00100 3 252 count icmp from any to any in 00200 3 252 count icmp from any to any out 00300 6 504 count icmp from any to any 65535 0 0 deny ip from any to any
Как видно, правило 100 считало только исходящие «пинги», правило 200 считало входящие «понги», а правило 300 считало и те и другие пакеты. («пинг» — echo-request пакет, «понг» — echo-reply пакет.)
Чтобы определять пакеты, идущие через конкретный интерфейс,
используйте ключевое слово via
и
имя интерфейса. Например, если вы используете сетевую карту
PCI 3Com 3c59x, Ваш сетевой интерфейс вероятно называется xl0.
Чтобы пропустить все пакеты идущие через этот интерфейс вне
зависимости от направления, используйте правило:
add 1100 allow all from any to any in via xl0
Или если вы хотите детектировать трафик направленный от любого хоста любому хосту, но выходящий через любой интерфейс:
add 1200 allow all from any to any out via any
В старых реализациях IPFW, если вы использовали сочетание
ключевых слов in via xl0
в выводе
команды ipfw list
они заменялись на
recv xl0
, а out via xl0
на xmit xl0
, сейчас это не так:
#
ipfw add 100 allow all from any to any out via rl0 00100 allow ip from any to any out via rl0#
ipfw list 00100 allow ip from any to any out via rl0 65535 deny ip from any to any
Однако вы можете использовать ключевые слова recv
и xmit
в правилах. Причём даже одновременно. Например, следующее
правило ищет исходящие пакеты, которые пришли через интерфейс
ed0, а уйти собираются через интерфейс ed1:
ipfw add allow ip from any to any out recv ed0 xmit ed1
Пакеты ICMP, TCP и UDP бывают различного типа в зависимости от выставленных в них флагах. Мы можем определять типы этих пакетов при помощи описанных ниже правил.
Правило icmptype <type>
ищет ICMP пакеты указанного типа. Ниже приведены возможные
типы пакетов:
Таблица E.2. типы ICMP сообщений опознаваемые брандмауэром IPFW
Тип | Расшифровка | Комментарий |
---|---|---|
0 | Echo Reply | «Понг» — ответ на «пинг» |
3 | Destination Unreachable | Сообщение о недостижимости хоста, эти пакеты в свою очередь могут быть разного типа, см. Таблица E.1, «ICMP unreachable коды» |
4 | Source Quench | |
5 | Redirect | Этот пакет может влиять на содержимое маршрутной таблицы и уж потому потенциально опасен. |
8 | Echo Request | «Пинг» |
9 | Router Advertisement | |
10 | Router Silicitation | |
11 | Time-to-Live Exceeded | Пакет высылается по истечении параметра TTL у пакета IP. Если его заблокировать, не будет работать программа traceroute(8). См. Раздел 6.4.2, «traceroute(1)». |
12 | IP header bad | |
13 | Timestamp Request | |
14 | Timestamp Reply | |
15 | Information Request | |
16 | Information Reply | |
17 | Address Mask Request | |
18 | Address Mask Reply |
К примеру, следующие три правила позводяют машине, защищаемой IPFW пинговать всех, но не дают никому пинговать нашу машину:
1000 allow icmp from any to any out icmptypes 8 1100 allow icmp from any to any in icmptypes 0 1200 deny icmp from any to any in icmptypes 8
Здесь правило номер 1000 — разрешает исходящие «пинги», правило 1100 — разрешает ответные «понги», правило номер 1200 — блокирует внешине «пинги».
Надо заметить, что практика запрещения «пингов» весьма распространена, однако не они наиболее разрушительны для системы. Например, пакет ICMP-redirect может использоваться для подделки таблицы маршрутизации на вашем хосте, что намного серьёзнее. Такое внимание «пингам» обусловлено не в поледнюю очередь историческими причинами. (См. POD.)
Опция tcpflags <flags>
помогает обнаружить пакеты с одним из следующих флагов:
fin
syn
rst
psh
ack
urg
Подробнее о флагах TCP можно прочитать в Раздел B.1.4.3, «TCP».
Флаги можно указывать через запятую. Восклицательный знак означает отсутсвие флага. Например:
#
ipfw 'add 100 allow all from any to me 80 tcpflags syn,!ack'
00100 allow ip from any to me dst-port 80 setup
Восклицательный знак в некоторых оболочках обладает
специальным значением, поэтому мы экранировали строку
кавычками. Обратите внимание на отклик системы, вместо того,
чтобы сказать syn,!ack
, система
ответила setup
.
Один из наибелее важных флагов для нас — флаг
SYN, так как он выставляется у пакета открывающего TCP
соединение. Из-за его важности у ipfw(8)
есть специальное правило, которое отслеживает именно этот
флаг — setup
. Правило
setup
эквивалентно правилу tcpflags syn,!ack
, таким образом, оно
ловит первый, но не второй пакет тройного рукопожатия (см.
Раздел B.1.4.3.2, «Открытие соединения TCP, тройное рукопожатие»).
Таким образом, в приведённом выше примере мы разрешили
открывать соединения к нам на 80-й
порт. А как же быть с остальными пакетами, которые пойдут по
установленному соединению? Характерной особенностью этих
пакетов является то, что в них выставлен флаг
RST — обрыв соединения, или флаг
ACK — подтверждение о доставке предыдущих
пакетов. Чтобы ловить пакеты в которых выстален либо флаг
RST, либо ACK есть специальное правило established
.
С применением правил setup
и
established
фильтр можно
выстроить примерно так:
allow 1000 from any to me established allow 1100 from any to me 22,25,80 setup deny 65535 from any to any
Первое правило пропускает все пакеты принадлежащие уже
установленным соединениям. Этих пакетов будет большинство,
поэтому мы вынесли данное правило в самое начало
брандмауэра, это способствует оптимизации пропускной
способности системы. Второе правило разрешает открывать
соединения на порты номер 22, 25 и 80. Третье правило
блокирует весь остальной трафик. Таким образом, мы запретили
устанавливать соединения на неразешённые порты, а
следовательно established
пакетов
на неразрешённые порты не появится. Подробнее см. Раздел E.1.5.1, «Основы учёта состояний соединений (stateful inspection)»
Ключевое слово frag
используется
для того, чтобы детектировать (и блокировать)
фрагментированный трафик. Обилие такого трафика может
свидетельствовать о совершении на вас DOS атаки (см. DOS атака и POD). С
появлениес stateful фильтрации значение этой опции пошло на
убыль, так как брандмауэры, осуществляющие фильтрацию на
основе таблицы состояний, собирают заново фрагментированный
трафик. Тем не менее, если вы используете stateless
брандмауэр, с правилами подобными описанным в предыдущем
разделе (setup
и established
), не лишне будет добавить
перед ними такое правило:
900 deny all from any to any in frag
Одна из интереснейших возможностей IPFW —
фильтрация на основе UID и GID сокета от которого получен
пакет. Это значит, что исходящие соединения можно фильтровать
по признаку «кто это соединение открыл». После
ключевых слов uid
и/или gid
указывается номер или имя
пользователя или группы.
Например, если мы хотим, чтобы только george мог ходить в интернет с нашей машины, мы можен написать следующее правило:
1000 allow all from me to any uid george 65535 deny all from any to any
Данную возможность можно применять для ограничения возможностей мета-пользователей на случай их взлома. Речь идёт о пользователях от имени которых запускаются сервисы в системе.
Другое возможное назначение таких правил — ограничение полосы пропускания применительно к конкретным локальным пользователям в нашей системе. При этом надо понимать, что речь идёт лишь о локальных пользователях, если полосу пропускания надо ограничить пользователю вне нашего шлюза, находящемуся в локальной сети, на основе логина и пароля, то для этого удобнее использовать пакетный фильтр OpenBSD. (см. Раздел C.2.3.4, «Authpf: авторизация в пакетном фильтре»).
Преимущества журналирования многочислены. При помощи системных журналов вы можете по прошествии времени ответить на вопрос что когда как произошло в вашей системе. В тоже время неаккуратное ведение журнала в состоянии причинить существенный вред системе. Одна из разновдностей DOS атак может состоять в попытке переполнения вашего жёсткого диска путём заполнения журнальных файлов избыточной информацией.
Общие советы касающиеся журналирования могут выглядеть следующим образом:
Для включения журналирования в конфигурационном файле ядра можно указать пару опций, пересбрать ядро и перезагрузить систему:
options IPFIREWALL_VERBOSE options IPFIREWALL_VERBOSE_LIMIT=#
Последняя опция ограничивает количество обращений правил брандмауэра к системе syslogd(8). Если вы укажете в этой опцмм число 10, то каждое правило, которое должно отсылать сообщение демону syslogd(8) будет отсылать не более 10 сообщений. После достижения этого предела, сообщения перестанут направляться в систему журналирования до тех пор, пока не будут сброшены журнальные счётчики в брандмауэре. Журнальные счётчики, это не тоже самое, что обычные счётчики пакетов. Они сбрасываются при помощи следующей команды:
#
ipfw resetlog
Эти же опции можно включать не пересобирая ядро, при помощи переменных ядра:
#
sysctl -w net.inet.ip.fw.verbose=1#
sysctl -w net.inet.ip.fw.verbose_limit=#
Куда будут записаны сообщения от брандмауэра, зависит от
настроек сделанных в файле
/etc/syslog.conf(5)
. Обычно эти
сообщения попадают в файл
/var/log/messages
.
Чтобы застивить syslogd(8) журналировать сообщения от брандмауэра IPFW в отдельный журнальный файл, следует выполнить следующие три действия:
Во-первых.
Создайте пустой журнальный файл, а может даже специальный каталог для журналов и уже в нём журнальный файл:
#
mkdir /var/log/ipfw#
touch /var/log/ipfw/ipfw.log#
chmod -R go-rwx /var/log/ipfw
Убедитесь, что никто не может читать из этих файлов.
Во-вторых.
Сконфигурируйте syslogd(8) так, чтбы он
писал журнал в этот файл. Для этого в самый конец (это
важно, в конец файла)
/etc/syslog.conf
добавьте следующие
две строки:
!ipfw *.* /var/log/ipfw/ipfw.log
В некоторые другие файлы, в частности на консоль и терминал суперпользователя, тоже будут попадать некоторые сообщения. Не пытайтесь отменить журналирование на консоль и терминал суперпользователя. Такое поведение системы позволяет вам быстро отреагировать на то, что в системе происходит что-то не то.
В-третьих.
Не забудьте послать сигнал SIGHUP демону syslogd(8), для того, чтобы он перечитал сделанные вами изменения в его конфигурационном файле:
#
killall -HUP syslogd
Итак, мы настроили журналирование событий через демон syslogd(8). Теперь нам надо настроить вращение журнального файла, в противном случае, он будет всё время расти в объёме и переполнит партицию, меж тем нам не нужна информация о том, что происходило в сети год назад.
Чтобы настроить вращение журнала достаточно добавить всего
одну строку в файл
/etc/newsyslog.conf(5)
:
/var/log/ipfw/ipfw.log 600 10 * $W0D2 J
Эта строка означает следующее: система
newsyslog(8) будет вращать файл
/var/log/ipfw/ipfw.log
. Новый файл
после вращения будет создаваться с пермиссиями 600, будет
храниться не более 10 старых файлов, вращение будет
происходить еженедельно в воскресенье в 2 часа ночи. После
вращения журнал будет сжат при помощи команды
bzip2(1).
Наша система полностью готова к принятию журнальной информации, теперь мы можем задавать правила в брандмауэре, которые будут журналировать сообщения через syslogd(8).
Ключевое слово log
заставляет
брандмауэр генерировать сообщение для системного журнала
каждый раз, когда срабатывае данное правило. данное ключевое
слово должно идти вслед за действием:
65000 deny log all from any to any
Параметр logamount
<num>
— указывает на ограничение
количества пакетов журналируемых через данное правило. Этот
параметр перебивает глобальные настройки сделанные при помощи
опции ядра IPFIREWALL_VERBOSE_LIMIT
.
65000 deny log logamount 100 all from any to any
При журналировании в сохраняется следующая информация:
Например:
#
sysctl net.inet.ip.fw.verbose=1 net.inet.ip.fw.verbose: 0 -> 1#
ipfw add 100 allow log icmp from me to any 00100 allow log icmp from me to any$
ping -c3 172.19.0.34 PING 172.19.0.34 (172.19.0.34): 56 data bytes 64 bytes from 172.19.0.34: icmp_seq=0 ttl=64 time=1.603 ms 64 bytes from 172.19.0.34: icmp_seq=1 ttl=64 time=3.701 ms 64 bytes from 172.19.0.34: icmp_seq=2 ttl=64 time=1.220 ms --- 172.19.0.34 ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max/stddev = 1.220/2.175/3.701/1.091 ms$
ping -c3 194.87.0.50 PING www.ru (194.87.0.50): 56 data bytes 64 bytes from 194.87.0.50: icmp_seq=0 ttl=59 time=5.026 ms 64 bytes from 194.87.0.50: icmp_seq=1 ttl=59 time=5.322 ms 64 bytes from 194.87.0.50: icmp_seq=2 ttl=59 time=5.600 ms --- www.ru ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max/stddev = 5.026/5.316/5.600/0.234 ms#
cat /var/log/ipfw.log Jun 7 12:55:58 house kernel: ipfw: 100 Accept ICMP:8.0 172.19.0.2 172.19.0.34 out via rl1 Jun 7 12:56:00 house last message repeated 2 times Jun 7 12:56:28 house kernel: ipfw: 100 Accept ICMP:8.0 172.19.0.2 194.87.0.50 out via rl1
Обратите внимание как демон syslogd(8)
обрабатывает ситуацию с повторяющимися событиями. Мы послали
три пинга на адрес 172.19.0.34. syslogd(8)
занёс в журнал первое событие, а остальные поместил в буфер,
так как они по его мнению однотипные. После этого мы послали
три пинга на другой адрес, это другое событие, поэтому
syslog(8) освободил свой буфер выдав
сообщение о двух непрожурналированных событиях: last message
repeated 2 times. С одной строны, такое поведение экономит
место в журнале, с другой стороны, оно затрудняет работу
парсеров и главное, препятсвует сбору данных о времени
совершения атаки. К сожалению, данную особенность
syslogd(8) отключить невозможно. В этом
смысле журналирование средствами пакетного фильтра
OpenBSD выглядит более разумно (см. Раздел C.2.3.1, «Журналирование в пакетном фильтре»). Зато такая система может быть
применена для конструирования системы активного реагирования
на события, так как журналируемые события можно направлять из
syslogd(8) на стандартный ввод любой
программе, которая, например, может автоматически на лету
переписывать правила брандмауэра. Впрочем, лучший вариант
состоит в заворачивании пакетов в программу при помощи правила
divert
.
При фильтрации трафика без учёта состояний соединений, брандмауэр каждый пакет фильтрует индивидуально, без учёта других пакетов. Такой брандмауэр прост в реализации и может быть эффективно использован для:
Напротив, при фильтрации трафика с учётом состояния соединений, брандмауэр трактует трафик не как множество независящих друг от друга пакетов, а как совокупность потоков, каждый из которых, принадлежит некоторому соединению. Все соединения в большинстве протоколов используют некоторое число, указывающее в каком порядке должны собираться пакеты в сокете назначения. Брандмауэр, использующий stateful inspection, на основании этого числа может опознать пакеты как принадлежащие одному соединению. В особенности это относится к протоколам поддерживающим информацию о соединениях. Таким как TCP.
Такой брандмауэр более сложен в реализации, однако он может:
Брандмауэр с учётом состояния соединения создаёт динамические правила для каждого соединения и автоматически удаляет эти правила по истечении некоторого времени. Всё это позволяет такому брандмауэру принимать более интеллектуальные решения на основе сетевых критериев более высоких уровней. С другой стороны, такое поведение не даёт брандмауэру принимать решения индивидуально по каждому пакету, поскольку динамические правила относятся сразу ко всем пакетам принадлежащим установленному соединению и препятствуют какому-то анализу кроме как анализу по принципу правильности осуществления процедуры открытия/закрытия соединения. Разумный подход заключается в объединении обоих методов фильтрации, для того, чтобы использовать сильные стороны и того и другого подхода.
Все приведённые выше примеры правил не учитывали состояние
соединения. Единственным исключением были критерии tcpflags
, setup
и established
, которые позволяли оценить
состояние соединения TCP на основе TCP-флагов. Однако эти
правила не позволяли создавать динамических правил фильтрации. И
всё-таки, с помощью этих правил можно создать примитивный
брандмауэр учитывающий состояние соединений. Долгое время это
была единственная возможность создания stateful inspection в
IPFW, однако начиная с FreeBSD 4.0 у нас
появилась возможность создавать полноценные stateful-брандмауэры
на основании IPFW.
Для нашего первого примера мы используем старый функционал
IPFW. Многие, кто использует в качестве примера для своего
брандмауэра файл /etc/rc.firewall
могут
найти там подобные строки. В приведённом ниже примере мы,
на основании TCP-флагов, разрешаем прохождение через наш
брандмауэр. трафика SSH.
add 1000 allow tcp from any to any established add 2000 allow tcp from any to any 22 in setup
Итак, мы предполагаем, что политика брандмауэра закрытая,
т.е. в ядре отсутствует опция IPFIREWALL_DEFAULT_TO_ACCEPT
, и у нас
нигде нет правила вроде allow all from any
to any
. Приведённые строки разрешают весь уже
установленный TCP трафик и разрешают открывать TCP соединения
на 22-й порт. Мы можем расчитывать на то, что никакой другой
TCP трафик через брандмауэр не пройдёт, так как правило номер
2000 позволяет открывать только соединения идущие на 22-й
порт. В тоже время, данные правила будут пропускать
«неправильные» пакеты, осколки не принадлежащие
никаким соединениям на том основании, что в них есть флаг ACK.
Настоящий брандмауэр с учётом состояния соединений должен был
бы отфильтровать такие пакеты.
Аналогичного эффекта можно добиться при помощи правил, в которых вообще нет критериев изучающих флаги заголовка TCP, т.е. при помощи заведомо stateless брандмауэра:
add 1000 allow tcp from any to any out add 2000 allow tcp from any to any 22 in
В приведённом примере разрешён любой исходящий трафик в любом направлении, а входящий трафик разрешён только на 22-й порт.
В общем случае, тактика брандмауэра с учётом состояния соединений состоит в том, чтобы сперва пропускать все уже установленные соединения, а потом перечислить какие соединения можно открывать.
Вот более комплексный пример. Приведём правила, которые разрешают трафик FTP, SSH, SMTP и DNS в сеть 172.16.0.0/27:
add 1000 allow tcp from any to any established add 2000 allow tcp from any to 172.16.0.0/27 21,22,25 setup add 3000 allow udp from 172.16.0.0/27 to any 53 add 3100 allow udp from any 53 to 172.16.0.0/27
Поскольку трафик DNS осуществляется по протоколу UDP, мы не
используем в правилах 3000 и 3100 ключевые слова established
и setup
. Заметим так же, что в
приведённом примере мы открыли только командный канал для FTP.
Между тем как нам надо ещё пропустить данные FTP. Подробнее о
функционировании этого протокола можно прочитать в Раздел C.2.3.3.1, «Режимы FTP».
Как уже было сказано выше, проверка только флагов заголовка
TCP ограничивает применимость stateful фильтрации. Начиная с
FreeBSD 4.0 в IPFW появилась
полноценная фильтрация с учётом состояния соединений. Это
достигнуто путём создания так называемых динамических правил.
Динамические правила образуются автоматически, если пакет
соответствует правилу, в котором присутсвует ключевое слово keep-state
или ключевое слово limit
.
keep-state
limit {src-addr | src-port | dst-addr | dst-port} N
keep-state
, но
динамическое правило заводится только в том случае, если
не превышен указанный предел. Таким образом, можно легко
ограничить количество одновременных подключений к
некоторому хосту или порту.
Динамические правила находятся в цепочке правил там же, где
они заведены, т.е. если правило keep-state
имеет номер 50000 и перед
ним имеется пятьсот правил, проверяющих разнообразные
параметры пакетов, то каждый из потенциально разрешённых
пакетов, проходит проверку на этих пятистах правилах. Это не
очень удачное решение с точки зрения производительности. Чтобы
пропустить все потенциально разрешённые пакеты, мы можем в
самом начале брандмауэра указать правило check-state
:
add 1000 check-state add 2000 allow tcp from any to any 22 in setup keep-state
Вот переменные ядра влияющие на величины таймаутов:
$
sysctl -a | grep dyn.*lifetime net.inet.ip.fw.dyn_ack_lifetime: 300 net.inet.ip.fw.dyn_syn_lifetime: 20 net.inet.ip.fw.dyn_fin_lifetime: 1 net.inet.ip.fw.dyn_rst_lifetime: 1 net.inet.ip.fw.dyn_udp_lifetime: 10 net.inet.ip.fw.dyn_short_lifetime: 5$
sysctl -ad | grep dyn.*lifetime net.inet.ip.fw.dyn_ack_lifetime: Lifetime of dyn. rules for acks net.inet.ip.fw.dyn_syn_lifetime: Lifetime of dyn. rules for syn net.inet.ip.fw.dyn_fin_lifetime: Lifetime of dyn. rules for fin net.inet.ip.fw.dyn_rst_lifetime: Lifetime of dyn. rules for rst net.inet.ip.fw.dyn_udp_lifetime: Lifetime of dyn. rules for UDP net.inet.ip.fw.dyn_short_lifetime: Lifetime of dyn. rules for other situations
Мне кажется, что таймауты по умолчанию принятые в FreeBSD для корпоративного шлюза являются слишком жёсткими. Возможно в ряде случаев их стоит увеличить. Особенно если вы не слишком лимитированы объёмом оперативной памяти и можете увеличить размер таблицы с динамическими правилами (см. ниже).
Рассмотрим, как работают приведённые здесь два правила.
keep-stte
не сработает и динамическое
правило для него не будет заведено.
Надо заметить, что в примере приведённом в Раздел E.1.5.1, «Основы учёта состояний соединений (stateful inspection)», поддельный пакет был бы
пропущен правилом с ключевым словом established
.
Существует так же такой тип сетевых атак как SYN-флуд. Он заключается в том, что злоумышленник организует большой поток TCP пакетов с выставленным в них флагом SYN. (См. так же Раздел C.2.1.4.9, «TCP SYN proxy».) Такой флуд переполняет таблицы сокетов на атакуемой машине, что не даёт возможности открыть соединения для легальных пользователей. Т.е. совершается DOS-атака.
В брандмауэре такая атака приводит к росту количества
динамических правил. Текущее количество динамических правил
можно узнать из переменной net.inet.ip.fw.dyn_count
, а размер
таблицы с динамическими правилами ограничен переменной net.inet.ip.fw.dyn_max
, которая по
умолчанию равна 1000.
Если количество разрешённых соединений достигнет предела, то новые динамические правила перестанут образовываться и новые соединения не будут разрешены в брандмауэре.
Фильтрация с учётом состояния применима не только к трафику TCP. Например, ниже приведено правило, позволяющее нам пинговать из внутренней сети внешнюю, но не пропускающее обратные пинги:
add 1000 check-state add 2000 allow icmp from any to any out icmptypes 8 keep-state
Правило номер 2000 заводит динамическое правило в момент,
когда мы посылаем пинг, нас можно будет пропинговать только с
той машины, которую мы пингуем, и только в тот самый момент,
пока не истёк таймаут от нашего пинга. (По умолчанию 5
секунд — net.inet.ip.fw.dyn_short_lifetime
).
Впрочем, надо заметить, что во-первых, чтобы мы могли пинговать хост снаружи, нам надо, чтобы он откликался быстрее чем за 5 секунд, а это не всегда так. Во-вторых, если злоумышленник нащупает время, когда мы пингуем его, он может продлевать действие динамического правила непрерывно бомбя нас пингами. Опять-таки, я говорю это не как о потенциальной опастности, а для понимания механизма действия динамических правил.
Для просмотра текущих динамических правил, можно применять
агрумент -d
для команды
ipfw:
#
ipfw -d show
01000 0 0 check-state
02000 1194 704133 allow tcp from any to any keep-state
65535 0 0 deny ip from any to any
## Dynamic rules (18):
02000 44 38497 (296s) STATE tcp 194.1.161.14 2316 <-> 172.19.0.2 80
02000 26 18377 (296s) STATE tcp 194.1.161.14 2318 <-> 172.19.0.2 80
02000 4 320 (1s) STATE tcp 172.19.0.34 38100 <-> 172.19.0.2 22
Обратите внимание, что в счётчике указано 18 динамических
правил, а в листинге приведено только три. Это связано с
тем, что правила, которые истекли по таймауту не удалены из
таблицы динамических правил. Они не работают, но в таблице
присутсвуют и будут замещены только когда это понадобится.
Полностью таблицу динамических правил можно просмотреть при
помощи опции -e
(от слова expired):
#
ipfw -de show
01000 0 0 check-state
02000 1194 704133 allow tcp from any to any keep-state
65535 0 0 deny ip from any to any
## Dynamic rules (18):
02000 44 38497 (296s) STATE tcp 194.1.161.14 2316 <-> 172.19.0.2 80
02000 26 18377 (296s) STATE tcp 194.1.161.14 2318 <-> 172.19.0.2 80
02000 4 320 (1s) STATE tcp 172.19.0.34 38100 <-> 172.19.0.2 22
02000 0 0 (0s) STATE tcp 195.222.87.11 62801 <-> 172.19.0.2 80
02000 8 1320 (0s) STATE tcp 195.222.87.11 56912 <-> 172.19.0.2 80
02000 9 1660 (0s) STATE tcp 193.108.240.20 45900 <-> 172.19.0.2 80
02000 8 1321 (0s) STATE tcp 195.222.87.11 54613 <-> 172.19.0.2 80
02000 0 0 (0s) STATE tcp 195.222.87.11 61268 <-> 172.19.0.2 80
02000 9 1906 (0s) STATE tcp 193.108.240.20 45910 <-> 172.19.0.2 80
02000 9 1958 (0s) STATE tcp 193.108.240.20 45911 <-> 172.19.0.2 80
02000 8 1603 (0s) STATE tcp 193.108.240.20 45909 <-> 172.19.0.2 80
02000 8 1598 (0s) STATE tcp 193.108.240.20 45907 <-> 172.19.0.2 80
02000 9 1662 (0s) STATE tcp 193.108.240.20 45904 <-> 172.19.0.2 80
02000 8 1585 (0s) STATE tcp 193.108.240.20 45905 <-> 172.19.0.2 80
02000 123 108527 (0s) STATE tcp 80.249.130.195 61576 <-> 172.19.0.2 80
02000 41 26403 (0s) STATE tcp 80.249.229.26 57170 <-> 172.19.0.2 80
02000 79 67509 (0s) STATE tcp 80.249.130.195 58511 <-> 172.19.0.2 80
02000 164 136800 (0s) STATE tcp 195.222.87.11 55679 <-> 172.19.0.2 80
Управление трафиком подразумевает регулировку полосы пропускания
(скорость связи), внедрение задержек, «случайная»
потеря пакетов и др. Если регулировка скорости трафика может
быть полезна для провайдеров доступа к сети, то прочие
«фокусы» могут использоваться для исследовательских
целей. Например для того, чтобы эмулировать соединение через
модем при подключении через FastEthernet. Мы можем использовать
dummynet(4)
совместно с IPFW для
контроля за полосой пропускания конкретных пользователей, для
внедрения задержек в трафике с экспериментальными или другими
целями. Единственное, что можно сделать в IPFW на эту тему без
привлечения dummynet(4)
—
вероятностное срабатывание правил. Эта возможность встроена в
IPFW.
Обсуждаемые возможности не могут быть применены совместно с динамическими правилами.
В составе IPFW имеется полезный инструмент для тестирования
сети, с помощью которого можно случайно отбрасывать пакеты с
некоторой вероятностью. Эта возможность активируетя при помщи
опции prob
и следующей за ним
вероятностью — дробным числом от 0 до 1. Т.е.
критерий prob 0.9
будет
удовлетворён в 90% случаев и неудовлетворён в 10% случаев.
Ниже приведена синтаксическая диаграмма:
<command> [<rule #>] [prob <match_probability>] <action> [log [logamount <number>]] <proto> from <source> to <destination> [<interface-spec>] [<options>]
Например, чтобы отбрасывать 20% «пингов», мы можем использовать следующее правило:
add 1000 prob 0.8 allow icmp from any to any in icmptypes 8
Или мы можем отбрасывать половину пакетов TCP SYN идущих к web-серверу, для того, чтобы эмулировать состояние перегрузки:
add 1000 prob 0.5 allow tcp from any to any in setup via ep0
Любые дополнительные возможности по контролю за трафиком
требуют наличия dummynet(4)
. Эта
система должна быть встроена в ядро при помощи опции
options DUMMYNET
либо подгружена в виде модуля:
#
kldload dummynet
Когда dummynet(4)
окажется
доступен, мы сможем используя ключевое слово pipe
направлять трафик в некоторые
пронумерованные каналы. Например:
#
kldload dummynet#
ipfw pipe 10 config bw 100Kb/s#
ipfw pipe list 00010: 100.000 Kbit/s 0 ms 50 sl. 0 queues (1 buckets) droptail
Это простое правило позволяет ограничить полосу пропускания
для трафика направляемого в 10-й канал величиной 100 килобит
в секунду. Ширину полосы пропускания можно указывать в разных
единицах: bit/s, Byte/s, Kbit/s, KByte/s Mbit/s, MByte/s.
Ключевое слово bw
указывает на то,
что данное правило влияет на полосу пропускания (bandwidth).
Другое действие, которое можно выполнить при помощи dummynet(4)
— введение
задержек, при помощи которых можно эмурировать реальные
задержки происходящие при работе в распределённых сетях:
pipe 10 config delay 100
После ключевого слова delay
указывается величина задержки в миллисекундах. В приведённом
примере трафик принадлежащий 1-му каналу будет задерживаться
на 100 миллисекунд.
Ключевое слово plr
позволяет
средствами dummynet(4)
реализовывать то же, что мы делали в предыдущем разделе при
помощи опции prob
. Например, того
же эффекта, что и при помощи опции prob
0.8
можно добиться при помощи канала
pipe 10 config plr 0.2
plr
расшифровывается как packet
loss rate и определяет вероятность, с которой пакеты теряются
в распределённой сети.
При регулировании полосы пропускания при помощи каналов, следует уделить внимание регулировке размера буфера или очереди (queue). Размер очереди задаётся в слотах, размер которых равен размеру MTU — максимальный размер кадра на канальном уровне модели OSI (см. MTU). Размер MTU можно узнать при помощи команды ifconfig(8):
$
ifconfig rl0
rl0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
options=8<VLAN_MTU> ~~~~~~~~
inet 172.20.0.2 netmask 0xffffff00 broadcast 172.20.0.255
ether 00:80:48:2d:f7:15
media: Ethernet autoselect (none)
status: no carrier
Если на интерфейсе с достаточно большим размером MTU сильно заузить полосу пропускания, то большая очередь будет заполняться слишком долго. Например, если мы организуем канал, эмулирующий работу модема 56Kbit/s:
pipe 10 config bw 56Kbit/s
то окажется, что очередь в таком канале заполняется в течение 1500*8(MTU)*50(slot)/56000=10.7 секунд. Здесь 1500*8 — величина MTU в битах, 50 слотов — размер очереди по умолчанию.
Чтобы снизить задержки можно либо уменьшить размер MTU при помощи команды ifconfig(8) (см. Раздел 6.2.2, «ifconfig(8) — настройки сетевых интерфейсов»), что явно неудачное решение, либо уменьшить количество слотов в очереди:
pipe 10 config bw 56Kbit/s queue 5Kbytes
Размер очереди можно задавать как в слотах, так и в байтах (см. пример). В последнем сучае, размер очереди не будет зависеть от MTU.
Чем уже полоса пропускания канала, тем меньше должен быть размер очереди.
В одном канале может быть несколько очередей. Допустим у нас во внутренней сети класса C хосты должны иметь скорость не более 100Kbit/s. Чтобы обеспечить такое ограничение мы можем либо завести 254 каналов (по каналу на каждый хост), или, что лучше, организовать динамические каналы при помощи масок. Пусть у нас будет маска 0.0.0.255. При накладывании такой маски на адрес 192.168.0.34 мы получим число 0.0.0.34. Это число является идентификатором динамически созданного канала. В битовой маске единицы могут стоять в любой позиции, т.е. маска не обязана быть дополнительной к сетевой маске и вычисляться по правилу 2n-1.
Маски возможны следующих типов:
dst-ip
src-ip
dst-port
src-port
proto
all
dst-ip
).
Маска указывает идентификатор канала. Например, если мы сделаем маску 0.0.0.255, то все адреса отличающиеся в третьем октете (т.е. из разных сетей класса C) будут принадлежать разным динамическим каналам, а при совпадении первых трёх октетов — одному каналу.
Для рассматриваемой ситуации мы должны были бы сделать следующие правила:
pipe 10 config mask src-ip 0x000000ff bw 100Kbit/s queue 10Kbytes pipe 20 config mask dst-ip 0x000000ff bw 100Kbit/s queue 10Kbytes add 1000 add pipe 10 all from 192.168.0.0/16 to any out via xl0 add 2000 add pipe 20 all from 192.168.0.0/16 to any in via xl0
Здесь мы впервые столкнулись с правилом, которое направляет трафик в канал. Итак, мы организуем два канала, один (10-й) заводит динамические каналы по IP-адресам источника, другой по адресам назначения (20-й). Правило 1000 направляет весь исходящий трафик в 10-й канал, а правило 2000 направляет весь исходящий рафик в 20-й канал. Как видно из приведённого примера, маски можно указывать не только в точечно-десятичной, но и в шестнадцатеричной форме.
Обратите внимание, в этом примере в учебных целях присутствует одна странность: в канал направляется трафик из сети 192.168.0.0/16, а маска канала 0.0.0.255. Это приведёт к тому, что хосты 192.168.0.34, 192.168.5.34, 192.168.10.34, 192.168.16.34, 192.168.56.34, и т.д. будут находиться в одном канале с идентификатором 34 и будут делить общую полосу пропускания. В жизни это не самое удачное решение, оно приведено здесь с учебной целью.
Пакеты прошедшие через канал могут вернуться в цепочку
правил, для этого надо приравнять к нулю переменную ядра
net.inet.ip.fw.one_pass
, которая
по умолчанию равна единице (т.е. по умолчанию пакеты в
брандмауэр не возвращаются). Хотя разумнее было бы сперва
отфильтровать ненужные пакеты и только потом заниматься
ограничением полосы пропускания у оставшихся.