E.1. IPFW HOWTO

[+]E.1.1. Как включить ipfw(4)
[+]E.1.1.1. rc.firewall и открытый брандмауэр
[+]E.1.1.1.1. Загрузка правил
[+]E.1.2. Основы синтаксиса правил ipfw
[+]E.1.2.1. Просмотр текущей конфигурации ipfw
[+]E.1.2.2. Основные команды и действия
[+]E.1.2.3. Указание протоколов
[+]E.1.2.4. Указание адресов источника и назначения пакета
[+]E.1.3. Углублённое изучение синтаксиса правил ipfw
[+]E.1.3.1. Действие unreach
[+]E.1.3.2. Контроль за потоком данных, проверка на каком интерфейсе встретился пакет
[+]E.1.3.3. Фильтрация по типам ICMP и TCP пакетов
[+]E.1.3.3.1. Типы ICMP
[+]E.1.3.3.2. Флаги TCP, правила setup и established
[+]E.1.3.4. Перехват фрагментированных пакетов
[+]E.1.3.5. Фильтрация на основе UID и GID сокетов, которым принадлежит пакет
[+]E.1.4. Журналирование
[+]E.1.4.1. Конфигурирование системы журналирования
[+]E.1.4.1.1. Опции ядра
[+]E.1.4.1.2. Конфигурирование syslogd(8)
[+]E.1.4.1.3. Конфигурирование newsyslog(8) для вращения журнальных файлов
[+]E.1.4.2. Правила ipfw направленные на журналирование
[+]E.1.5. Фильтрация с учётом состояния соединений
[+]E.1.5.1. Основы учёта состояний соединений (stateful inspection)
[+]E.1.5.2. Сохранение информации о соединениях в динамических правилах. Истинная stateful inspection
[+]E.1.5.3. Просмотр динамических правил
[+]E.1.6. Управление трафиком при помощи dummynet(4)
[+]E.1.6.1. Вероятностное срабатывание правил
[+]E.1.6.2. Dummynet
[+]E.1.6.2.1. Конфигурирование очередей (буферов) в каналах
[+]E.1.6.2.2. Направление трафика в объявленный канал, маски
[+]E.1.6.2.3. Возвращение пакетов в брандмауэр

E.1.1. Как включить ipfw(4)

Конфигурация брандмауэра 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

E.1.1.1. rc.firewall и открытый брандмауэр

Независимо от того, указан ли тип брандмауэра в /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, разумеется, вам не годятся.

E.1.1.1.1. Загрузка правил

Есть две возможности: либо использовать предопределённые типы брандмауэра из rc.firewall, либо создать пользовательский тип. Второе предпочтительнее по следующим причинам:

  • Вы можете создавать собственные правила соответствующие вашим потребностям не модифицируя системный скрипт rc.firewall.
  • Вы будете вынуждены разобраться в синтаксисе ipfw(8), что, быть может, стимулирует вас к созданию действительно качественного межсетевого экрана.
E.1.1.1.1.1. Предопределённые типы брандмауэров

Разумеется последнее слово в выборе типа брандмауэра за администратором. Если вам почему-то хочется именно использовать один из предустановленных типов, хотя бы посмотрите скрипт 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 ничего не закроет. Мораль — остерегайтесь искуственного интеллекта пока не поймёте как он работает. Пишите правила фильтрации сами.
E.1.1.1.1.2. Пользовательские типы брандмауэров

Разумная практика состоит в том, чтобы самому написать правила брандмауэра, и подгружать их как пользовательский тип в rc.conf. Например так:

firewall_enable="YES"
firewall_type="/etc/rc.firewall.rules" 1
firewall_quiet="YES" 2
              
1 Загружать правила из файла /etc/rc.firewall.rules
2 Загружать правила «молча» — не выводить информации о загружаемых правилах на экран.

Синтаксис файла rc.firewall.rules будет слегка отличаться от rc.firewall. rc.firewall.rules — самостоятельный sh(1) сценарий и в нём не определены переменные, исользовавшиеся в rc.firewall. Если программирование в sh(1) представляет для вам проблему — не пугайтесь. Вам просто надо подряд много раз вызвать команду ipfw(8) с некоторыми аргументами, которые мы ниже обсудим. Но если вы хотите использовать всю мощь sh(1) обратитесь к Раздел 7.7, «Написание несложных Bourne-скриптов».

E.1.2. Основы синтаксиса правил ipfw

Синтаксис правил IPFW довольно прост. Каждое правило можно добавить в консоли при помощи команды ipfw(8). Прежде чем мы углубимся в изучение синтаксиса, посмотрим как можно просмотреть имеющиеся правила.

E.1.2.1. Просмотр текущей конфигурации ipfw

Команда 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
          

E.1.2.2. Основные команды и действия

Теперь посмотрим, какие правила можно использовать для настройки 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
Любой пакет соответствующий правилу с данным действием будет отброшен, но не молча: отправителю будет отослан пакет TCP с выставленным флагом RST. Подразумевается, что этому правилу будут соответствовать только пакеты TCP. Устанавливая данное правило вы облегчаете злоумышленникам сканирование вашей сети. Однако во внутренней сети вы можете применять данное правило.
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.

E.1.2.3. Указание протоколов

В поле протокола можно указать любой протокол транспортного уравня. Как правило это tcp, udp или icmp, но может быть и любой другой из указанных в файле /etc/protocols. Протокол можно указывать как по имени, так и по номеру.

E.1.2.4. Указание адресов источника и назначения пакета

Адрес источника и адрес назначения имеют одинаковый формат:

me
Ключевое слово me означает множество адресов присвоенных интерфейсам нашей машины.
any
Ключевое слово any означает любой адрес.
table(number[,value])
Адреса можно заносить в таблицы, управляя которыми вы можете налету блокировать некоторые IP адреса, при этом таблицы могут быть довольно большого размера. Пакет соответствует правилу, если адрес в пакете входит в таблицу номер number. Дополнительно можно указать некоторое 32-битное целое число, в этом случае правило считается удовлетворённым, если это число есть в таблице. Эта функция пришла в IPFW из пакетного фильтра OpenBSD (см. Раздел C.2.1.3, «Таблицы»).
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
                
IP
IP адрес хоста
IP/len
IP адрес сети в нотации CIDR: через / указывается длина маски подсети. Например: 192.168.0.0/24
IP[/len]{num,num-num}

Можно конкретизировать какие именно хосты из указанной сети вас интересуют. Для этого применяются квадратные скобки. Если маска в этой форме не указана, подразумевается маска 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). Если вы пользуетесь ею, то вам надо брать адрес в кавычки, как показанно в этом примере.

IP:mask
IP адрес сети с маской в точечно-десятичной форме записи. Например: 192.168.0.0:255.255.255.0

Перед каждым адресом можно указать ключевое слово 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».

E.1.3. Углублённое изучение синтаксиса правил ipfw

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

Для начала приведём синтаксическую диаграмму правил IPFW:

<команда> [<номер правила>] <действие> [log [logamount <число>]] <протокол>
from <источник> to <назначение> [<интерфейс>] [<опции>]
        

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

E.1.3.1. Действие unreach

Дествие unreach «код> — отбрасывает пакет и генерирует ответное ICMP сообщение с указанным кодом недостижимости. (Например network unreachable или host unreachable, т.е. сеть недоступна или хост недоступен). Код можно указывать как по номеру, так и по имени. Если вы не знаете, что значат эти коды, то значит вам не надо их употрелять.

Таблица E.1. ICMP unreachable коды

КодИмя кода
0net
1host
2protocol
3port
4needfrag
5srcfail
6net-unknown
7host-unknown
8isolated
9net-prohib
10host-prohib
11tosnet
12toshost
13filter-prohib
14host-precedence
15precedence-cutoff

E.1.3.2. Контроль за потоком данных, проверка на каком интерфейсе встретился пакет

Важная возможность брандмауэра 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
          

E.1.3.3. Фильтрация по типам ICMP и TCP пакетов

Пакеты ICMP, TCP и UDP бывают различного типа в зависимости от выставленных в них флагах. Мы можем определять типы этих пакетов при помощи описанных ниже правил.

E.1.3.3.1. Типы ICMP

Правило icmptype <type> ищет ICMP пакеты указанного типа. Ниже приведены возможные типы пакетов:

Таблица E.2. типы ICMP сообщений опознаваемые брандмауэром IPFW

ТипРасшифровкаКомментарий
0Echo Reply «Понг» — ответ на «пинг»
3Destination UnreachableСообщение о недостижимости хоста, эти пакеты в свою очередь могут быть разного типа, см. Таблица E.1, «ICMP unreachable коды»
4Source Quench 
5Redirect Этот пакет может влиять на содержимое маршрутной таблицы и уж потому потенциально опасен.
8Echo Request«Пинг»
9Router Advertisement 
10Router Silicitation 
11Time-to-Live Exceeded Пакет высылается по истечении параметра TTL у пакета IP. Если его заблокировать, не будет работать программа traceroute(8). См. Раздел 6.4.2, «traceroute(1)».
12IP header bad 
13Timestamp Request 
14Timestamp Reply 
15Information Request 
16Information Reply 
17Address Mask Request 
18Address 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.)

E.1.3.3.2. Флаги TCP, правила setup и established

Опция tcpflags <flags> помогает обнаружить пакеты с одним из следующих флагов:

fin
Заявка на закрытие соединения
syn
Заявка на открытие соединения
rst
Сброс соединения — пакет высылается в том случае, если открывающей стороне отказано в соединении.
psh
Указание на то, что принимающая сторона должна сбросить буфер. Флаг выставляется в пакетах принадлежажих интерактивным соединениям. Таким как ssh(1), telnet(1) и т.п.
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)»

E.1.3.4. Перехват фрагментированных пакетов

Ключевое слово frag используется для того, чтобы детектировать (и блокировать) фрагментированный трафик. Обилие такого трафика может свидетельствовать о совершении на вас DOS атаки (см. DOS атака и POD). С появлениес stateful фильтрации значение этой опции пошло на убыль, так как брандмауэры, осуществляющие фильтрацию на основе таблицы состояний, собирают заново фрагментированный трафик. Тем не менее, если вы используете stateless брандмауэр, с правилами подобными описанным в предыдущем разделе (setup и established), не лишне будет добавить перед ними такое правило:

900 deny all from any to any in frag
          

E.1.3.5. Фильтрация на основе UID и GID сокетов, которым принадлежит пакет

Одна из интереснейших возможностей 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: авторизация в пакетном фильтре»).

E.1.4. Журналирование

Преимущества журналирования многочислены. При помощи системных журналов вы можете по прошествии времени ответить на вопрос что когда как произошло в вашей системе. В тоже время неаккуратное ведение журнала в состоянии причинить существенный вред системе. Одна из разновдностей DOS атак может состоять в попытке переполнения вашего жёсткого диска путём заполнения журнальных файлов избыточной информацией.

Общие советы касающиеся журналирования могут выглядеть следующим образом:

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

E.1.4.1. Конфигурирование системы журналирования

E.1.4.1.1. Опции ядра

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

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.

E.1.4.1.2. Конфигурирование syslogd(8)

Чтобы застивить 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
              
E.1.4.1.3. Конфигурирование newsyslog(8) для вращения журнальных файлов

Итак, мы настроили журналирование событий через демон 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).

E.1.4.2. Правила ipfw направленные на журналирование

Наша система полностью готова к принятию журнальной информации, теперь мы можем задавать правила в брандмауэре, которые будут журналировать сообщения через 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
          

При журналировании в сохраняется следующая информация:

  • Дата и время
  • Номер сработавшего правила
  • Действие
  • IP адреса источника и наначения
  • Порт источника и порт назначения
  • Направление движения пакета
  • Интерфейс, на котором побывал пакет

Например:

# 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.

E.1.5. Фильтрация с учётом состояния соединений

При фильтрации трафика без учёта состояний соединений, брандмауэр каждый пакет фильтрует индивидуально, без учёта других пакетов. Такой брандмауэр прост в реализации и может быть эффективно использован для:

  • Фильтрации испорченных, фрагментированных пакетов;
  • Фильтрации пакетов принадлежащих протоколам не образующим соединений: ICMP, UDP, IGMP и т.п.
  • Фильтрации на основе критериев сетевого, и более низких уровней модели OSI (см. OSI). Например по IP адресам.

Напротив, при фильтрации трафика с учётом состояния соединений, брандмауэр трактует трафик не как множество независящих друг от друга пакетов, а как совокупность потоков, каждый из которых, принадлежит некоторому соединению. Все соединения в большинстве протоколов используют некоторое число, указывающее в каком порядке должны собираться пакеты в сокете назначения. Брандмауэр, использующий stateful inspection, на основании этого числа может опознать пакеты как принадлежащие одному соединению. В особенности это относится к протоколам поддерживающим информацию о соединениях. Таким как TCP.

Такой брандмауэр более сложен в реализации, однако он может:

  • определять состояние соединения;
  • определить, правильно ли открывается соединение и фильтровать трафик соответственно этому.

Брандмауэр с учётом состояния соединения создаёт динамические правила для каждого соединения и автоматически удаляет эти правила по истечении некоторого времени. Всё это позволяет такому брандмауэру принимать более интеллектуальные решения на основе сетевых критериев более высоких уровней. С другой стороны, такое поведение не даёт брандмауэру принимать решения индивидуально по каждому пакету, поскольку динамические правила относятся сразу ко всем пакетам принадлежащим установленному соединению и препятствуют какому-то анализу кроме как анализу по принципу правильности осуществления процедуры открытия/закрытия соединения. Разумный подход заключается в объединении обоих методов фильтрации, для того, чтобы использовать сильные стороны и того и другого подхода.

Все приведённые выше примеры правил не учитывали состояние соединения. Единственным исключением были критерии tcpflags, setup и established, которые позволяли оценить состояние соединения TCP на основе TCP-флагов. Однако эти правила не позволяли создавать динамических правил фильтрации. И всё-таки, с помощью этих правил можно создать примитивный брандмауэр учитывающий состояние соединений. Долгое время это была единственная возможность создания stateful inspection в IPFW, однако начиная с FreeBSD 4.0 у нас появилась возможность создавать полноценные stateful-брандмауэры на основании IPFW.

E.1.5.1. Основы учёта состояний соединений (stateful inspection)

Для нашего первого примера мы используем старый функционал 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».

E.1.5.2. Сохранение информации о соединениях в динамических правилах. Истинная stateful inspection

Как уже было сказано выше, проверка только флагов заголовка TCP ограничивает применимость stateful фильтрации. Начиная с FreeBSD 4.0 в IPFW появилась полноценная фильтрация с учётом состояния соединений. Это достигнуто путём создания так называемых динамических правил. Динамические правила образуются автоматически, если пакет соответствует правилу, в котором присутсвует ключевое слово keep-state или ключевое слово limit.

keep-state
Правило с данным ключевым словом образует в брандмауэре динамическое правило для обработки всех пакетов принадлежащих данному соединению. Динамическое правило будет существовать до тех пор, пока через данное соединение идут пакеты или до тех пор, пока оно не будет удалено по таймауту. Размеры таймаутов регулируются через переменные ядра утилитой sysctl(8).
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 для корпоративного шлюза являются слишком жёсткими. Возможно в ряде случаев их стоит увеличить. Особенно если вы не слишком лимитированы объёмом оперативной памяти и можете увеличить размер таблицы с динамическими правилами (см. ниже).

Рассмотрим, как работают приведённые здесь два правила.

  1. Пусть некоторый хост 172.16.0.1 открывает соединение в нашу внутреннюю сеть со своего порта 1234 на порт 22 хоста 192.168.0.1. В первом пакете такого соединения будет выставлен флаг TCP SYN.
  2. Правило 1000 не найдёт в списке динамических правил соответствующего правила, так как у неё нет записей о коннектах между адресом 172.16.0.1:1234 и 192.168.0.1:22.
  3. Правило 2000 соответствует нашему пакету, поэтому будет заведено динамическое правило для соединения между 172.16.0.1:1234 и 192.168.0.1:22, с таймаутом 20 секунд.
  4. Теперь клиент и серевер будут обмениваться пакетами с флагом ACK идущими сквозь наш брандмауэр.
  5. Когда эти пакеты достигнут нашего брандмауэра, они будут пропущены правилом номер 1000, благодаря заведённому выше динамическому правилу.
  6. Пусть на наш брандмауэр попал поддельный пакет. (В современных системах такой вид атаки практически невероятен, см. spoofing, мы рассматриваем эту ситуацию ислючительно с учебной целью, когда-то Windows, получив такой пакет, мог повиснуть).
  7. Правило номер 1000 проверит поддельный пакет и увидит, что он не соответвует ни одному правилу, так как номер порта источника злоумышленник выбрал случайным образом. Таким образом, пакет не соответвует правилу 1000 и проверка переходит к правилу 2000.
  8. Правило 2000 увидит, что в пакете нет флага SYN, поэтому он не соответствует данному правилу keep-stte не сработает и динамическое правило для него не будет заведено.
  9. Поскольку пакет не соответствовал ни одному правилу, он попадёт на политику, где и будет отброшен.

Надо заметить, что в примере приведённом в Раздел 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 секунд, а это не всегда так. Во-вторых, если злоумышленник нащупает время, когда мы пингуем его, он может продлевать действие динамического правила непрерывно бомбя нас пингами. Опять-таки, я говорю это не как о потенциальной опастности, а для понимания механизма действия динамических правил.

E.1.5.3. Просмотр динамических правил

Для просмотра текущих динамических правил, можно применять агрумент -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
            

E.1.6. Управление трафиком при помощи dummynet(4)

Управление трафиком подразумевает регулировку полосы пропускания (скорость связи), внедрение задержек, «случайная» потеря пакетов и др. Если регулировка скорости трафика может быть полезна для провайдеров доступа к сети, то прочие «фокусы» могут использоваться для исследовательских целей. Например для того, чтобы эмулировать соединение через модем при подключении через FastEthernet. Мы можем использовать dummynet(4) совместно с IPFW для контроля за полосой пропускания конкретных пользователей, для внедрения задержек в трафике с экспериментальными или другими целями. Единственное, что можно сделать в IPFW на эту тему без привлечения dummynet(4) — вероятностное срабатывание правил. Эта возможность встроена в IPFW.

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

E.1.6.1. Вероятностное срабатывание правил

В составе 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
          

E.1.6.2. Dummynet

Любые дополнительные возможности по контролю за трафиком требуют наличия 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 и определяет вероятность, с которой пакеты теряются в распределённой сети.

E.1.6.2.1. Конфигурирование очередей (буферов) в каналах

При регулировании полосы пропускания при помощи каналов, следует уделить внимание регулировке размера буфера или очереди (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.

Чем уже полоса пропускания канала, тем меньше должен быть размер очереди.

E.1.6.2.2. Направление трафика в объявленный канал, маски

В одном канале может быть несколько очередей. Допустим у нас во внутренней сети класса 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 и будут делить общую полосу пропускания. В жизни это не самое удачное решение, оно приведено здесь с учебной целью.

E.1.6.2.3. Возвращение пакетов в брандмауэр

Пакеты прошедшие через канал могут вернуться в цепочку правил, для этого надо приравнять к нулю переменную ядра net.inet.ip.fw.one_pass, которая по умолчанию равна единице (т.е. по умолчанию пакеты в брандмауэр не возвращаются). Хотя разумнее было бы сперва отфильтровать ненужные пакеты и только потом заниматься ограничением полосы пропускания у оставшихся.