From: Воробьёв Олег <mfoleg@mail.ru.>
Date: Mon, 24 Oct 2007 14:31:37 +0000 (UTC)
Subject: Заметки об IPFW
Оригинал: http://www.lissyara.su/?id=1536
Вместо предисловия:
Вопрос- "В FreeBSD есть 3 разных фаервалла, какой использовать?"
Ответ - "Правильно насторенный."
Далее по тексту обсуждается IPFW как наиболее часто используемый во
FreeBSD фаервалл.
Как задействовать IPFW?
Как известно существует два способа подключения IPFW:
1. Подключение скомпилированного модуля ядра при загрузке системы.
2. Комплияция IPFW в ядро системы.
Начнем с последнего - компиляция IPFW в ядро, в MAN-ах этот пункт
достаточно подробно освещен:
Обычно ипользуются следующие опции в конфиге ядра:
options IPFIREWALL
подключение IPFW
options IPFIREWALL_VERBOSE
проходящие пакеты записываются в лог-файл, при использовании опции
log в правилах
options IPFIREWALL_VERBOSE_LIMIT=100
ограничение количества записей в лог-файл по одному правилу, в
правилах IPFW значение можно изменить через опцию logamount
options IPFIREWALL_FORWARD
форвардинг пакетов, очень полезная опция при настройке прозрачного
прокси на машине, где одновременно работает IPFW и прокси-сервер
(например SQUID или FROX)
options IPDIVERT
подключение поддержки NATD (трансляция адресов)
options DUMMYNET
поключение функций управления трафиком (ограничение ширины канала,
имитация задержек и потерь пакетов), и наконец для правила по
умолчанию, которое присутствует в конфиге IPFW в любом случае под
номером 65535, будет
allow ip from any to any
если включена опция
options IPFIREWALL_DEFAULT_TO_ACCEPT
либо,
deny ip from any to any
если отсутствует.
После сборки и установки нового ядра получаем IPFW встроенный в ядро
системы.
Теперь о подключение модулем, тут все проще и сложнее одновременно.
Подключение IPFW в качестве модуля ядра, осуществляется одной
командой:
kldload ipfw
при этом загружается модуль ipfw.ko, который в стандартной поставке
имеет поддержку функций управления трафиком (DUMMYNET), к сожалению
функции форвардинга (FORWARD) не поддерживаются и без перекомпиляции
тут не обойтись.
Поддержку демона NATD в IPFW можно получить, загрузив аналогичной
командой модуль ipdivert.ko
kldload ipdivert
IPFW, загруженный таким образом, содержит всего одно правило по
умолчанию:
deny ip from any to any
Рассмотрим теперь как происходит подключение IPFW в процессе загрузки
операционой системы. Имеем достаточно типовые переменные в файле
/etc/rc.conf
Первая строка фактически разрешает исполнение стартового скрипта
/etc/rc.d/ipfw, который в свою очередь, выполняет уже известную
команду
kldload ipfw
если IPFW грузится модулем ядра, затем запускает на выполнение
скрипт с правилами IPFW, указанный во второй строке. Заметим что
строка
firewall_enable="YES"
требуется как для загрузки IPFW в виде модуля (иначе запускать
придется вручную), так и для IPFW компиллированном в ядро (иначе не
выполнится скрипт с правилами из второй строки, хотя IPFW все равно
запустится с одним правилом по умолчанию). В процессе выполнения
скрипт /etc/rc.d/ipfw установит значение системной переменной равным
единице (TRUE):
net.inet.ip.fw.enable=1
Она указывает системе использовать ли IPFW вообще, так как если
переменная равна нулю (FALSE), то IPFW использоваться не будет,
независимо от того подгружаем ли мы модулем IPFW или он скомпилирован
в ядро, попутно отметим следующее: все переменные, которыми можно
управлять через sysctl действуют на IPFW одинаково, независимо от
способа подключения IPFW.
Продолжим строка:
firewall_script="/usr/local/etc/ipfw.rules"
указывает расположение нашего скрипта с правилами для IPFW, в принципе
её может и не быть, но тогда запустится на исполнение скрипт
/etc/rc.firewall, в котором есть стандартные наборы правил,
сгруппированные в секции "OPEN","SIMPLE","CLOSED","UNKNOWN", можно
также приспособить его под свои нужды (хотя это и не наш метод :-)),
например добавив секцию "RULES", тогда данная строка приобретёт вид:
firewall_type="RULES"
По поводу строк с NATD сказать особо нечего, все аналогично уже
рассмотренному с той разницей что исполняется скрипт /etc/rc.d/natd, и
если IPFW используется в виде модуля ядра -загрузится модуль
ipdivert.ko, потом скрипт выполнит команду вида:
natd -interface ${natd_interface} -same_ports
и ещё, если IP интефейсa на котором крутится NATD получен от
DHCP-сервера, скрипт добавит опцию "-dynamic".
Замечание 1.
Как все-таки подключать IPFW? Пожалуй решение такое: если FreeBSD
используется в целях обучения, отладки и т.д. - проще подключать
модулем, а если речь идет уже о "боевом" применении - лучше компиляция
в ядро.
Как IPFW работает?
Общая схема
Замечание 2.
IP- Пакет, попадая в фаервал IPFW, следует согласно порядку
расположений его правил до первого удовлетворяющего, где над этим
IP-пакетом, согласно данному правилу, совершаются какие-либо действия
(пропускается, отбрасывается, возвращается обратно в фаервал и т.д.).
Замечание 3.
Входящие (IN) и исходящие (OUT) пакеты следует рассматривать
относительно операционной системы, а не относительно сетевых
интерфейсов.
Замечание 4.
Каждый маршрутизируемый IP-пакет попадает в фаервал как на входе в
операционную систему, так и на выходе из неё.
Рассмотрим, с учётом этих замечаний, следующие примеры и попытаемся в
итоге понять проходят IP-пакеты по правилам IPFW и как составлять эти
самые правила.
Предположим:
1. Система имеет 2 сетевых интерфейса - {iif}- внутренний(fxp0) и
{oif}-внешний(fxp1), псевдоинтефейc-{lo}, который мы намеренно опустим
из виду.
2. {iip} - IP- адрес для {iif}, и соответственно {oip} - для {oif}.
3. {MyLan} - внутренняя сеть
4. В системе работает простейший демон NATD по трансляции внутренних
адресов во внешний и наоборот.
Пример No.1. Как составлять правила?
Имеем следующее не совсем очевидное, зато очень часто встречающееся
правило:
{fwcmd} add allow ip from {MyLan} to any
Обычно когда пишут такое правило - подразумевают что разрешен доступ
от хостов внутренней сети к любому хосту (при этом, часто полагают что
речь идет только о внутренней сети :-) ), но есть и еще одна сторона
медали, постараюсь её показать.
С учетом вышеописанных замечаний данное правило распишем в следующий
набор правил:
1. {fwcmd} add allow ip from {MyLan} to any in via {iif}
2. {fwcmd} add allow ip from {MyLan} to any out via {iif}
3. {fwcmd} add allow ip from {MyLan} to any out via {oif}
4. {fwcmd} add allow ip from {MyLan} to any in via {oif}
Правило No.1 - мы разрешаем обращение от любого хоста внутренней сети
на любой xoст любой сети через внутренний интерфейс, аналогично и для
правила No.3, но через внешний интерфейс.
Фактически эти два правила разрешают исходящие IP-пакеты от хостов
внутренней сети к любым хостам любой сети.
А теперь о том, что не так очевидно: правило No.2, с учётом правила
No.1 фактически разрешает IP-пакеты между хостами локальной сети через
интерфейс {iif}, что нормально, если предположить что в локальной сети
хакеров нет, пользователи послушные, сами на роутер не полезут и
делать там нечего плохого не будут. Однако сюрприз!
Теперь правило No.4 - с одной стороны оно достаточно бессмысленное.
Действительно откуда снаружи взяться IP-пакетам от хостов внутренней
сети?
Правильно - хакеры, типичный спуфинг.
Критики могут заметить, что со спуфингом умеет бороться чуть ли не
каждая железяка, что демоны давно поумнели и т.д. и т.п. Тем не менее,
согласитесь, такие правила боевой роутер совсем не красят, поэтому
сюрприз No.2.
Можно уже делать первые выводы:
Вывод No.1.
При составлении правил обязательно указывать о какой сетевом
интерфейсе идет речь.
Вывод No.2.
При составлении правил желательно указывать направление IP-пакетов.
Вывод No.3.
При составлении правил по возможности конкретизировать сети и IP -
адреса.
Первые выводы сделаны, давайте попробуем разобраться как IP-пакеты
бегают по правилам, которые мы напишем.
Пример No.2. Связка IPFW-NATD, как работает, какие нужны правила?
Итак: исходные заданы, напишем правила IPFW, и прокомментируем их.
#Разрешим трафик в локальной сети, конкретизировать по
# направлению не будем и так понятно:
1 {fwcmd} add allow ip from {MyLan} to {MyLan} via {iif}
#Вспомним про NATD.
2. {fwcmd} add divert natd ip from {MyLan} to any out via {oif}
3. {fwcmd} add divert natd ip from any to {oip} in via {oif}
#Роутер должен же как-то работать - изнутри мы уже все открыли,
#будем последовательны тоже сделаем и снаружи.
4. {fwcmd} add allow ip from {oip} to any out via {oif}
5. {fwcmd} add allow ip from any to {oip} in via {oif}
#Роутер у нас или нет? Давайте разрешим локальным хостам
# свободно лазить во внешнюю сеть.
#Для исходящих пакетов от хостов внутренней сети.
6. {fwcmd} add allow ip from {MyLan} to any in via {iif}
7.{fwcmd} add allow ip from {MyLan} to any out via {oif}
#Для входящих IP-пакетов к хостам внутренней сети.
8. {fwcmd} add allow ip from any to {MyLan} in via {oif}
9. {fwcmd} add allow ip from any to {MyLan} out via {iif}
#Для порядка завершим
10. {fwcmd} add deny ip from any to any
Получили такой вот конфиг IPFW, хотя могли обойтись всего-то одной
строчкой
{fwcmd} add allow ip from any to any
Примечание - Фактически правило No.1 не нужно, т.к. его расширили
правилами No.6,No.9, однако оставим его, поскольку в реальных условиях
надо редактировать как раз с No.6 по No.8, да и само по себе правило
No.1 не самое удачное решение в плане широковещательных запросов.
Пусть хост внутренней сети (192.168.0.75) запрашивает почту с
gmail.com (64.233.183.109:995), для нашего роутера внешний IP-
195.34.32.55.
Забудем про DNS, сразу к делу - какими видит IPFW проходящие пакеты:
Правила No.-No. 1-5 не подходят, а вот No.6 как раз в точку:
TCP 192.168.0.75:1499 64.233.183.109:995 in via fxp0
так пошел запрос на внутренний интерфейс (fxp0) роутера, с хоста
внутренней сети. Операционная система приняла его и по таблице
маршрутизации отправила на внешний интерфейс (fxp1), здесь этот пакет
стал исходящим и снова уперся в фаервал:
TCP 192.168.0.75:1499 64.233.183.109:995 out via fxp1
тут вступает в действие NATD (правило No.2) он переписывает IP в
заголовке пакета, составляет таблицу где запоминает что он сотворил с
пакетом и благополучно отпускает путешествовать по правилам IPFW
дальше, что собственно будет выглядеть так:
TCP 195.34.32.55:1499 64.233.183.109:995 out via fxp1
кстати NATD сохранил еще и первоначальный порт 1499, что бывает далеко
не всегда. Этот пакет дойдет до правила No.4, благополучно покинет
фаервал и отправится путешествовать в сеть.
Теперь рассмотрим как идет ответ сервера:
Ответный пакет входит в файервал снаружи через внешний интерфейс fxp1:
TCP 64.233.183.109:995 195.34.32.55:1499 in via fxp1
как видно с какого порта запросили на тот ответ и получили. Правила
No.1,No.2 он благополучно миновал а в правиле No.3 его радостно
встречает NATD, который проверяет свою таблицу, находит запись и
переписывает заголовок уже на:
TCP 64.233.183.109:995 192.168.0.75:1499 in via fxp1
по правилу No.7 пакет выходит из IPFW и попадает в операционную
систему, которая маршрутизирует пакет на интерфейс внутренней сети
(fxp0), здесь пакет опять упрется в фаервал но уже, как исходящий с
интерфейса внутренней сети fxp0.
TCP 64.233.183.109:995 192.168.0.75:1499 out via fxp0
правило No.9 благополучно выпустит пакет из фаервала. Счастливый
хост получил ответ на свой запрос.
Дайте рассмотрим ещё один случай связки IPFW-NATD - перенаправление
входящего запроса на сервер внутренней сети.
Предположим мы хотим открыть для доступа снаружи Web-сервер внутренней
сети с IP-адресом 192.168.0.3, для чего изменим в rc.conf строку на
В логах IPFW можно будет увидеть примерно следующее:
1. TCP 195.34.32.56:1076 195.34.32.55:80 in via fxp1
2. TCP 195.34.32.56:1076 192.168.0.3:80 in via fxp1
3. TCP 195.34.32.55:1076 192.168.0.3:80 out via fxp0
4. TCP 192.168.0.3:80 195.34.32.56:1076 in via fxp0
5. TCP 192.168.0.3:80 195.34.32.56:1076 out via fxp1
6. TСP 195.34.32.55:80 195.34.32.56:1076 out via fxp1
Ситуация аналогична уже рассмотренной:
первая строка и вторая строка - один и тот же пакет - входящий запрос,
который попадает в NATD через внешний интрефейс fxp1. NATD
переписывает заголовок пакета и далее по правилам IPFW пакет попадает
в операционную систему.
третья строка - пакет прошедший по таблице маршрутизации операционной
системы, исходящий через внутренний интерфейс fxp0.
четвертая строка - ответ сервера входящий на внутренний интерфейс
fxp0.
пятая и шестая строки - один и тот же пакет, прошедший через NATD и
ставший исходящим через внешний интерфейс fxp1.
Примечание: Относительно DIVERT в MAN-е по IPFW присутствует
присутствует некая двусмысленность, согласно MAN-у для пакета
попавшего под правило с DIVERT дальнейший поиск прекращается, а что
происходит с самим пакетом - неясно.
Экпериментально установлено, что в случае трансляции адресов, пакет
прошедший через DIVERT-сокет, продолжает путь по правилам IPFW сразу
после правила с DIVERT, имея переписаный (транслированный) IP в
заголовке.
Чтобы закончить со связкой IPFW-NATD, составим примерный список правил
для конфига IPFW, обеспечивающий работоспособность данной связки :
#Разрешаем все по интерфейсу обратной петли
{fwcmd} add allow ip from any to any via lo
#Разрешаем все внутри локальной сети,
#кроме того эти правила необходимы для связки IPFW-NATD
#Для защиты роутера от пользователей локальной сети перед этими правилами
#нужно вcтавить что-то запрещающее
#Если есть необходимость роутеру в широковещательных запросах
#нужно вставить что-то разрешающее
{fwcmd} add allow ip from {MyLan} to any in via {iif}
{fwcmd} add allow ip from any to {MyLan} out via {iif}
#Разрешаем входящие соединения к роутеру,
#это правило напрямую к связке IPFW-NATD отношения не имеет,
#оно необходимо для обеспечения работоспособности демонов роутера.
#Естественно правило необходимо расширить с учетом потребностей демонов.
#Внимание!!! правила расположены выше DIVERT,
# т.е в этом примере для хостов внутренней сети
#DNS сервер должен быть внутри сети
{fwcmd} add allow ip from any to {oip} 53 in via {oif}
{fwcmd} add allow ip from any 53 to {oip} in via {oif}
#NATD для исходящих соединений
{fwcmd} add divert natd ip from {MyLan} to any out via {oif}
#Внимание!!! Правило - разрешает исходящие соединения как для самого роутера,
#так и для хостов внутренней сети, IP которых транслировал NATD
{fwcmd} add allow ip from {oip} to any out via {oif}
#NATD для входящих соединений
{fwcmd} add divert natd ip from not {MyLan} to {oip} in via {oif}
#Внимание!!! Правило - разрешает входящие соединения только к хостам внутренней сети,
#IP которых транслировал NATD
{fwcmd} add allow ip from not {MyLan} to {MyLan} in via {oif}
{fwcmd} add deny ip from any to any
Ну и последнее замечание по поводу IPFW-NATD:
Что делать если требуется что-то прикрыть в интернете для хостов
внутренней сети? Ответ достаточно очевиден:
1. Впереди правил для локальной сети (строки 2-3) пишем свои запреты.
Ну и наоборот если требуется все запретить, разрешив только что-то: к
примеру HTTP?
2. Приводим строки 2-3 к следующему виду:
{fwcmd} add allow ip from {MyLan} to {MyLan} via {iif}
{fwcmd} add allow tcp from {MyLan} to any http in via {iif}
{fwcmd} add allow tcp from any http to {MyLan} out via {iif}
Полагаю тема связки IPFW-NATD закрыта.
Продолжим исследования, разберем еще один частоиспользуемый случай
IPFW-SQUID.
Сначала без прозрачного прокси. Вернемся к первому варианту конфига
IPFW и рассмотрим что происходит. Вот и подходящий кусочек лога:
TCP 192.168.0.112:1212 192.168.0.9:3128 in via fxp0
-входящий пакет от внутреннего хоста к роутеру с работающим Squid -
правило No.1, Squid его обработает и сам отправит в сеть.
TCP 195.34.32.55:52439 64.12.24.102:443 out via fxp1
-исходящий пакет от SQUID к удаленному хосту - правило No.4
TCP 64.12.24.102:443 195.34.32.55:52439 in via fxp1
- входящий ответ на интерфейс внешней сети - правило No. 5, Squid
опять же его обработал, и ответил хосту локальной сети
TCP 192.168.0.9:3128 192.168.0.112:1212 out via fxp0
- правило No.1, пакет вышел из фаервала и побежал к адресату.
Пример несколько утрированный, однако интересен следующим:
Первое - NATD не используется, а вернее так: в NATD входящий пакет от
хоста 64.12.24.102:443 все равно попадет, правило No.3 никто не
отменял. Однако NATD об этом пакете ничего не знает, поэтому делать
ничего не будет, он отправит пакет дальше путешествовать по правилам
IPFW, пока последний не доберется до правила No.4.
Второе - сам адресат: 64.12.24.102:443 - протокол https, а хост из
сети 64.12.0.0/16 AOL-MTC, т.е. ICQ.
Продолжим, теперь на очереди прозрачный прокси, для него изменим
правила IPFW следующим образом: добавив строку для форвардинга в итоге
получим:
1. {fwcmd} add allow ip from {MyLan} to {MyLan} via {iif}
2. {fwcmd} add fwd {iip},3128 tcp from {MyLan} to not {MyLan} 80 in via {iif}
3. {fwcmd} add divert natd ip from {MyLan} to any out via {oif}
4. {fwcmd} add divert natd ip from to any to {oip} in via {oif}
5. {fwcmd} add allow ip from {oip} to any out via {oif}
6. {fwcmd} add allow ip from any to {oip} in via {oif}
7. {fwcmd} add allow ip from {MyLan} to any in via {iif}
8. {fwcmd} add allow ip from {MyLan} to any out via {oif}
9. {fwcmd} add allow ip from any to {MyLan} in via {oif}
10. {fwcmd} add allow ip from any to {MyLan} out via {iif}
11. {fwcmd} add deny ip from any to any
Предположим хост внутренней сети с IP 192.168.0.100 запрашивает
www.yandex.ru, на входе в фаервал имеем:
TCP 192.168.0.100:1083 77.73.24.4:80 in via fxp0
пакет доходит до правила No.2, по которому переправляется на вход
локального прокси-сервера, далее уже знакомая картина самостоятельной
обработки прокси-сервером запроса:
TCP 195.34.32.55:57415 77.73.24.4:80 out via fxp1
TCP 77.73.24.4:80 195.34.32.55:57415 in via fxp1
наконец ответ хосту, а здесь уже интересно, поскольку прокси-сервер
ответил следующим образом:
TCP 77.73.24.4:80 192.168.0.100:1083 out via fxp0
Как видим для хоста внутренней сети все махинации с прокси-сервером
остались не заметны.
Опять оговоримся: в примере рассмотрен только принцип прохождения
пакета, реальный лог совсем не такой однозначный.
Чтобы закончить тему связки IPFW-SQUD напишем примерные правила для
IPFW:
{fwcmd} add allow ip from any to any via lo
#Собственно следующие строки не только для локальной сети,
#они ещё задают варианты использование прокси для обычного достаточно
{fwcmd} add allow ip from {MyLan} to {MyLan} via {iif}
#для прозрачного
#Здесь мы попали в засаду с портами, впрочем это только начало
#{fwcmd} add fwd {iip},3128 tcp from {MyLan} to not {MyLan} 80,443 in via {iif}
#{fwcmd} add allow ip from {MyLan} to any 80,443 in via {iif}
#{fwcmd} add allow ip from any 80,443 to {MyLan} out via {iif}
#{fwcmd} add allow ip from {MyLan} to {MyLan} via {iif}
#Так подробно расписано специально чтобы была видна разница в правилах.
#Разрешаем входящие соединения к роутеру,
#вообще эти правила напрямую к связке IPFW-SQUID отношения не имеют,
#правда с DNS не все так однозначно.
{fwcmd} add allow ip from any to {oip} 53 in via {oif}
{fwcmd} add allow ip from any 53 to {oip} in via {oif}
#Правила для SQUID, тут чтобы опять не попасть в засаду с портами,
#извернемся следующим образом
{fwcmd} add deny tcp from any to {oip} in via {oif} tcpflags syn,!ack
{fwcmd} add allow tcp from any to {oip} in via {oif}
#Завершим традиционно
{fwcmd} add allow ip from {oip} to any out via {oif}
{fwcmd} add deny ip from any to any
Расставим точки над i - обьединим два рассморенных случая вместе, тем
самым создадим эдакий скелет конфига IPFW для подгонки под свои
требования.
# Хосты сети, которым разрешим свободно ходить в инет через роутер
pdc=xxx.xxx.xxx.xxx,xxx.xxx.xxx.xxx
${fwcmd} flush -f
#Стандартное средство защиты от спуффинга :-)
${fwcmd} add deny ip from any to any not verrevpath in
# Убьем фрагменты
${fwcmd} add deny ip from any to any frag
${fwcmd} add allow ip from any to any via lo
${fwcmd} add denny ip from any to 127.0.0.0/8
${fwcmd} add denny ip from 127.0.0.0/8 to any
${fwcmd} add allow ip from ${MyLan} to ${MyLan} via {iif}
${fwcmd} add allow ip from ${pdc} to any in via ${iif}
${fwcmd} add allow ip from any to ${pdc} out via ${iif}
${fwcmd} add divert natd ip from ${MyLan} to any out via ${oif}
${fwcmd} add allow ip from ${oip} to any out via ${oif}
${fwcmd} add divert natd ip from any to ${oip} in via {oif}
${fwcmd} add allow ip from any to ${MyLan} in via {oif}
#Правила для демонов перенесли ниже потому что они
# перекрывают входящие пакеты для NATD
#Следующие два правила равносильны одному
#${fwcmd} add allow tcp from any to ${oip} in via ${oif} established
${fwcmd} add deny tcp from any to ${oip} in via ${oif} tcpflags syn,!ack
${fwcmd} add allow tcp from any to ${oip} in via ${oif}
${fwcmd} add allow udp from any to ${oip} 53 in via ${oif}
${fwcmd} add allow udp from any 53 to ${oip} in via ${oif}
${fwcmd} add allow icmp from any to ${oip} in via ${oif} icmptype 0,4,8,11,12
${fwcmd} add deny log ip from any to any
Как правило IPFW, NATD, SQUID и еще куча различных демонов крутится на
одной машине-роутере, как уже было видно для каждого демона по
отдельности правила IPFW имеют много общего, но и каждый имеет свои
нюансы, поэтому при составлении (редактировании) необходимо крайне
внимательно проверять то что уже есть, добавляя только то что
требуется, иначе вместо логичной и непробиваемой огненной стены можно
получить абсолютно непонятное решето.
Очень хочется думать, что статья поможет вам в создании безопасных, а
главное осознанных правил IPFW, спасибо всем кто сумел сие прочитать,
за сим откланиваюсь.
-cat-
P.S. Автор сознательно не использовал конструкции setup-established и
check-state keep-state.
setup-established запутали бы процесс понимания правил, keep-state же
напустил бы туману еще больше.
P.P.S. В процессе сочинения сего использовались логи реально
работающего роутера, а также различные виртуальные машины, логи
собраны при помощи строки {fwcmd} add count log all from any to any.
Совпадения IP - реальных случайны, случайных - не реальны. ;-).