[pf] Файервол packet filter

Обновлена 23.06.2016

Введение.

Пару слов о самом pf. Пакетный фильтр (далее PF) OpenBSD предназначен для фильтрации TCP/IP трафика и трансляции адресов (NAT). PF так же способен нормализовать и преобразовывать TCP/IP трафик, управлять приоритетами пакетов и пропускной способностью. PF был включен в ядро GENERIC OpenBSD, начиная с OpenBSD 3.0. Предшествующие версии OpenBSD использовали другой файрвол/NAT пакет, который более не поддерживается. Позже он портировался на FreBSD (начиная с 5.3) и Solaris (начиная с Oracle Solaris 11.3)

PF изначально был разработан Даниэлем Хартмаером (Daniel Hartmeier), а теперь поддерживается и разрабатывается Даниелем и остальной командой OpenBSD.

Особенности pf

Зараннее отмечу пару фактов о фаерволле pf, которые обязательно надо иметь в виду.

PF — поддерживает SMP только во FreBSD (начиная с 10-ой версии), то есть можно распараллелить его работу на несколько ядер CPU.

Механизм altq работает только для исходящего траффика. Это действительно так, но эта особенность нам мешать не будет, поскольку мы сделаем так, как было упомянуто выше: применим altq к исходящему траффику на внутреннем интерфейсе (а не к входящему на внешнем).

В секцию фильтров пакеты попадают после обработки NAT-ом. Это тоже действительно так. Чем это может нам помешать? А тем, что по нужным очередям пакеты рассовываются именно в секции фильтров, а рассовывая их, нам надо знать, от какого пользователя (читай: с какого адреса) они пришли на шлюз. Но эта особенность нам мешать тоже не будет, поскольку фильтры мы определим для внутреннего интерфейса (через который пакеты приходят еще не обработанные NAT-ом). При этом интересно обратить внимание на то, что фильтр будет применяться к пакету на одном интерфейсе, и засовывать его в очередь другого интерфейса. (Такой подход в самом деле неочевиден — в рассылках встречались вопросы по pf вроде «зачем позволять присваивать очередям входящий траффик, если очереди работают только для исходящего» или «зачем позволять присваивать пакеты, проходящие через один интерфейс, в очередь на другом».)

При использовании keep-state пользовательскими фильтрами обрабатывается только первый (state-creating) пакет. Это мешает нам тем, что если пользователь отправляет один запрос, то он, как исходящий, становится в соответствующую очередь. Но когда пользователю приходит ответ, то при использовании keep-state таблица правил не просматривается, — вместо этого к новому пакету применяются те же правила, что и к первому. То есть он становится в очередь для исходящего траффика на внешнем интерфейсе — а это совсем не то, что нам требуется. Однако, эта особенность, опять же, мешать не будет — мы откажемся от keep-state.

Трудно поверить, но стройность этих теоретических рассуждений не подвела и на практике.

1) Установка pf.

Здесь можно пойти 2-мя путями: либо загрузить модулями либо пересобрать ядро. Я опишу оба способа, а вы же, выбирайте тот, который вам больше подходит.

— модулями (это лучше делать тогда, когда сервер физически удалён или нет возможности пересобрать ядро)

Здесь нам понадобиться подгрузить такие модули :

#kldload pf

, отвечает за загрузку собственно pf

#kldload pflog

, отвечает за возможность логгирования пакетов.

Замечу, что pf при включении лоялнее чем, например, тот же ipfw. Как это сказывается? Если вы работали с ipfw, то сразу вспомните, что при загрузке модуля этот файервол находится в режиме deny all (блокировать всё). pf — пошёл немного по другому пути. При загрузке модулем ничего не происходит. Что бы задействовать его, нужно выполнить команду (о ней речь пойдёт немного позже) и после этого файервол переходит в режим permit all (разрешено всё). Поэтому, многие мои знакомые любят pf именно за эту особенность, как врочем и я.

После загрузки модуля нужно задействовать его:

#pfctl -e
pf enabled

— пересобрать ядро (работает быстрее, чем когда подгружен модулем).

Что бы пересобрать ядро с поддержкой pf, нужно добавить такое в конфигурационный файл ядра:

device          pf
device          pflog

а если хотите использовать возможность использовать ограничения по скоростям, шейпинг, то нужно добавить ещё и такое:

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

После этого пересобираем ядро.

Хочу добавить ещё один немаловажный факт. Независимо от того, каким вы способом добавили поддержку pf нужно ОБЯЗАТЕЛЬНО добавить следующие строчки в /etc/rc.conf (ИНАЧЕ PF НЕ БУДЕТ ВКЛЮЧЁН)

pf_enable="YES"
pf_rules="/etc/pf.rules"
pflog_enable="YES"

Первая строчка включает собственно файервол, вторая — указывает на местополжение файла с правилами, ну а третья добавляет возможность логгирования. Напомню ещё раз: даже если вы добавили поддержку pf в ядро, без этих строчек он не будет задействован (в отличии от того же ipfw), поэтому, не забывайте об этом факте.

2) Начало работы.

И так, вы успешно добавили поддержку файервола pf в ядро. Как же с ним работать? Сразу после задействования pf, он не содержит ни одного правила, то есть просто «наблюдает» за пакетами.

Для управления файерволом pf существует исполняемый файл pfctl. Этот же бинарник отвечает и за включение/отключение файервола. Ниже приведено несколько примеров использования:

srv-test skeletor # pfctl -d
pfctl: pf not enabled
srv-test skeletor # pfctl -e
pf enabled
srv-test skeletor # pfctl -e
pfctl: pf already enabled
srv-test skeletor # pfctl -d
pf disabled

Теперь переходим к тому, как же добавлять, удалять, просматривать правила. Те, кто работал с ipfw увидят существенные отличия. Какие именно? Об этом ниже.

Первое: правила загружать можно только из файла

Второе: если при загрузке правил встретилась ошибка, то не применяются ВСЕ правила, то есть остаются те, которые были до измнений (в отличии от ipfw, в котором правила применяются, а на ошибке останавливается).

Есть и остальные моменты, но на мой взгляд пока только эти нужны новичкам.

Теперь перейдём к более тесному общению с утилитой pfctl. Она отвечает за управление файерволом pf. Что бы успешно работать с pf нужно знать как им управлять. И так, ниже будет описано несколько часто используемых опций pfctl:

-d —  выключить pf
-e — включить pf
-f file — загрузить правила из файла file
-g — включить «помогающий» вывод (используется при отладке)
-n — не загружать правила, только проверить на ошибки
-q — вывод только ошибок и предупреждений
-s modifier — просмотр параметра, указанного в modifier (то есть можно посмотреть правил NAT, трянсляции, статистики, …)
-v — широкий вывод (так называемый ружим verbose), позволяет посмотреть статистику по каждому правилу

Это те параметры, которые вам понадобятся на первое время. Остальные же (а так же детально и те, которые были описаны выше) можно узнать из справочного руководства man pfctl.

3) Пишем правила.

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

/etc/pf.rules:14: Rules must be in order: options, normalization, queueing, translation, filtering
pfctl: Syntax error in config file: pf rules not loaded

Правила в pf разделены на 7 частей:

a) Макросы: Определенные пользователем переменные, которые могут содержать IP адреса, имена интерфейсов и т.п.
b) Таблицы: Структуры используемые для хранения списков IP адресов.
c) Опции: Различные опции для управления работой PF.
d) Скраб: Пересборка пакетов для их нормализации и дефрагментации.
e) Очереди: Управление пропускной способностью и приоритетами пакетов (ALTQ).
f) Преобразования: Управление преобразованием сетевых адресов и перенаправлением пакетов (NAT, проброс портов).
g) Правила фильтрации: Позволяет выборочно фильтровать или блокировать пакеты, прошедшие через любой интерфейс.

За исключение макросов и таблиц, каждая часть в конфигурационном файле должна следовать в указанном порядке, хотя не обязательно должны присутствовать все части. Пустые строки игнорируются, а строки начинающиеся с # считаются коментариями.

А теперь пришло время писать правила.

Файерволы, в основном используются для фильтрации трафика, а точнее — для фильтрации пакетов. Фильтрация пакетов — это выборочное разрешение или запрещение прохождения пакетов данных через сетевой интерфейс. Параметры, которые использует PF(4) когда проверят пакеты, базируются на заголовках Layer 3 (IPv4 и IPv6) и Layer 4 (TCP, UDP, ICMP, и ICMPv6). Наиболее часто используются такие параметры как: адрес источника и назначения, порт источника и назначения, а также протокол. Правила фильтрации описывают параметры, которым должен соответствовать пакет, а также результирующее действие, которое производится когда соответствие найдено.

Главное отличие pf от «классического» файервола состоит в том, что применяется не первое правило, а последнее. Иными словами, если пакет удовлетворяет 3 правилам, то применится правило, которое располагается ниже остальных. Но! Если вы всё-таки привыкли к «классическому» понятию файервола, то разработчики предусмотрели и эту ситуацию. Что бы применялось первое правило — достаточно в него добавить ключевое слово quick.

Общий вид правлила таков (на самом деле я немного упростил правило, убрав параметры флагов и состояния):

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

Кратко, о том, что означают каждый из параметров.

action

Действие предпринимаемое к подходящим пакетам, либо pass, либо block. Действие pass будет пропускать пакеты в ядро для дальнейшей обработки, в то время как действие block будет реагировать так — как указано в опции block-policy. Значение по умолчанию которой можно изменить на block drop или block return.

direction

Направление движения пакета через интерфейс, либо in, либо out.

log

Означает что пакет должен быть зарегистрирован в pflogd(8). Если правило написано с опциями keep state, modulate state, или synproxy state, то регистрируется только тот пакет который создает это состояние (state). Для регистрации всех подряд пакетов, используйте log (all).

quick

Если пакет соответствует правилу написанному с quick, то это правило считается последним соответствующим правилом и производится описанное действие этого правила.

interface

Имя или группа сетевого интерфейсачерез который проходит пакет. Группа интерфейсов определяется именем интерфейса, но без цифрового значения. Например: ppp или fxp. Это заставит правило соответствовать любому пакету проходящему через любой ppp или fxp интерфейс, соответственно.

af

Семейство адресов пакета, либо inet для IPv4, либо inet6 для IPv6. PF обычно в состоянии определить этот параметр на основании адреса источника и/или назначения.

protocol

Протокол пакета 4-го уровня:

Любое значение из: tcp, udp, icmp, icmp6, правильное имя протокола из /etc/protocols, номер протокола между 0 и 255, несколько протоколов с использованием списка.

src_addr, dst_addr

Адрес источника/приемника в заголовке IP.

src_port, dst_port

Порт источника/приемника в заголовке пакета 4-го уровня.

Главный плюс (а может быть и минус) состоит в том, что правила можно сокращать, то есть опускать параметры, которые не используются. Например, правило

pass all from any to any

можно записать так:

pass all

Хочу заметить, что в pf отсутствует ключевое слово me (но вместо него можно использовать IP адрес, имя сетевого интерфейса), которое есть в ipfw и заменяет собой все свои IP-адреса. Это не очень удобно, так как если у вас несколько IP-адресов, то нужно указывать каждый из них (или для каждого из них писать правило).

4) Грамматика PF

Грамматика Пакетного Фильтра достаточно гибка и обеспечивает большую гибкость в написании правил. PF способен делать выводы из определённых ключевых слов, что означает, что они не должны быть чётко заданы в определённом стиле и определённом порядке и поэтому нет необходимости строго запоминать синтаксис.

Избавление от ключевых слов

Для определения политики по умолчанию, используются два правила:

block in  all
block out all

Это может быть уменьшено до:

block all

Когда не указано направление, PF будет считать, что правило применяется для пакетов направленных в обе стороны.

Подобным образом, выражения «from any to any» и «all» могут быть исключены из правил, например:

block in on rl0 all
pass  in quick log on rl0 proto tcp from any to any port 22 keep state

может быть упрощено до:

block in on rl0
pass  in quick log on rl0 proto tcp to port 22 keep state

Первое правило блокирует любые входящие пакеты на интерфейсе rl0, а второе правило впускает TCP трафик на rl0 порт 22.

Порядок ключевых слов

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

pass in log quick on rl0 proto tcp to port 22 flags S/SA keep state queue ssh label ssh

Может быть записано, как:

pass in quick log on rl0 proto tcp to port 22 queue ssh keep state label ssh flags S/SA

Другие, подобные варианты также будут работать.

Списки:

Списки позволяют определять множества, имеющие общие признаки в пределах правила — такие как IP адреса, номера портов и т.д. Таким образом, вместо прописывания нескольких правил фильтрации для каждого IP адреса, который должен быть заблокирован, мы можем определить список IP адресов в пределах одного правила. Списки должны находиться внутри скобок {}.

Когда pfctl(8) доходит до списка при загрузке наборов правил, он раскладывает их на отдельные правила, для каждого элемента списка. Для примера:

block out on fxp0 from { 192.168.0.1, 10.5.32.6 } to any

Будет преобразован в:

block out on fxp0 from 192.168.0.1 to any
block out on fxp0 from 10.5.32.6 to any

Множественные списки могут применяться не только для блокировки:

rdr on fxp0 proto tcp from any to any port { 22 80 } -> 192.168.0.6
block out on fxp0 proto { tcp udp } from { 192.168.0.1, 10.5.32.6 } to any port { ssh telnet }

Стоит отметить, что запятая в списке является необязательной.

Макросы:

Макросы — определяемые пользователем переменные, которые могут держать IP адреса, номера портов, имена интерфейсов, и т.д. Макросы позволят облегчить написание наборов правил и сделают поддержание набора правил несравненно легче. Имена макросов должны начаться с символа и могут содержать символы, цифры, и символы подчеркивания. Названия макросов не могут носить имена зарезервированных слов, типа pass, out, или queue.

ext_if = "fxp0"
block in on $ext_if from any to an

Это создаст макрос с именем ext_if. При использовании макроса, его имени должен предшествовать знак $. Макросы также могут быть расширены до списков.

friends = "{ 192.168.1.1, 10.0.2.5, 192.168.43.53 }"

Внимание: не используйте конструкцию ${host} в качестве определения макроса (такой синтаксис допускается в shell скриптах), это вызовет ошибку

Макросы могут определяться рекурсивно. В этом случае должен использоваться следующий синтаксис:

host1 = "192.168.1.1"
host2 = "192.168.1.2"
all_hosts = "{" $host1 $host2 "}"

Теперь макрос $all_hosts расширен до значений 192.168.1.1, 192.168.1.2.

Таблицы

Таблицы используются для хранения группы адресов IPv6 и/или IPv4. Поиски в таблице занимают гораздо меньше времени и потребляют меньше ресурсов, чем списки. По этой причине, таблица идеальна чтобы хранить большую группу адресов, поскольку время поиска в таблице, содержащей 50 000 адресов — не намного больше чем для 50 адресов. Таблицы могут использоваться следующими способами:

* источник и/или адрес назначения для filter, scrub, NAT, и redirection rules
* адрес трансляции для правил NAT
* адрес переназначения для правил редиректа
* адрес назначения для правил фильтрации route-to, reply-to, и dup-to

Таблицы определяются в pf.conf или с помощью pfctl(8).

Примечание.

Если во время просмотра pfctl -sr вы видите имя таблицы:

<__automatic_ *

то здесь описано как с этим разобраться.

Конфигурация:

В pf.conf таблицы создаются используя директиву table. Следующие атрибуты могут быть определены для каждой таблицы:

* const — содержание таблицы не может быть изменено после ее создания. Когда этот атрибут не определен, pfctl(8) может использоваться, чтобы добавлять или удалять адреса из таблицы в любое время, при выполнении с securelevel(7), равным двум и выше.
* persist — заставляет ядро сохранять таблицу в памяти, даже когда никакие правила к ней не обращаются. Без этого атрибута, ядро автоматически удалит таблицу, когда последнее правило, ссылающееся на нее будет отработано.

Пример:

table { 192.0.2.0/24 }
table const { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }
table persist
block in on fxp0 from { ,  } to any
pass  in on fxp0 from to any

Адреса могут также быть определены, используя модификатор типа отрицание (или «не»):

table { 192.0.2.0/24, !192.0.2.5 }

Таблица goodguys теперь содержит адреса сети 192.0.2.0/24, за исключением адреса 192.0.2.5. Обратите внимание, что имена таблицы всегда включаются в <>. Таблицы могут также могут заполняться из файлов, содержащих список адресов IP и сетей:

table  persist file "/etc/spammers"
block in on fxp0 from  to any

Файл /etc/spammers содержал бы список IP адресов или сетей CIDR. Любая строка начинающаяся с #, будет обработана как комментарий и проигнорирована.

5) Примеры.

# Разрешаем трафик входящий на dc0 из локольной сети, 192.168.0.0/24,
# на машину с OpenBSD и IP адресом 192.168.0.1. Также разрешаем
# исходящий трафик через dc0 в обратную сторону.
pass in  on dc0 from 192.168.0.0/24 to 192.168.0.1
pass out on dc0 from 192.168.0.1 to 192.168.0.0/24
# Разрешаем входищий TCP трафик на fxp0 обращенный к веб серверу запущенному
# на машине OpenBSD. Имя интерфейса, fxp0, используем как адрес
# назначения, что бы правилу соответствовали только пакеты
# предназначеные для OpenBSD машины.
pass in on fxp0 proto tcp from any to fxp0 port www
# пропускать весь трафик в и из локальной сети.
# эти правила будут создавать записи в таблице состояний,
# благодаря дефолтной опции "keep state", которая будет применена
# автоматически
pass in  on $int_if from $lan_net to any
pass out on $int_if from any to $lan_net

6) Трансляция адресов (NAT)

Обратите внимание: Преобразуемые пакеты должны проходить через фильтр и будут блокированы или пропущены, в зависимости от фильтрующих правил, которые были заданы. Единственное исключение из этих правил, это когда используется ключевое слово pass с правилом nat. В этом случает пакеты проходящие NAT преобразования будут проходить без проверки правилами.

Внимание: начиная с версии OpenBSD 4.6 в фильтре pf произошли изменения по синтаксису использования NAT’a, подробнее описано здесь . Во FreeBSD (вплоть до 9-ой версии синтаксис остался прежним)

Так же помните, что трансляция происходит до фильтрации, фильтр будет видеть уже преобразованные пакеты с преобразованным IP адресом и портом

Основной формат правил NAT в pf.conf выглядит как:

nat [pass] [log] on interface [af] from src_addr [port src_port] to dst_addr [port dst_port] -> ext_addr [pool_type] [static-port]

nat

ключевое слово, с которого начинается правило NAT

pass

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

log

логировать пакеты с помощью pflogd(8). Обычно только первый пакет заносится в журнал. Для логирования всех пакетов используйте log (all).

interface

Название интерфейса, или группы интерфейсов на котором будут проводиться преобразования.

af

Семейство адресов, inet для IPv4 или inet6 для IPv6. PF как правило в сам в состоянии определить этот параметр c помощью исходных адресов и адресов назначения.

src_addr

Исходные (внутренние) адреса пакетов, которые будут преобразованы. Исходные адреса могут быть указаны, как:

Единственный IPv4 или IPv6 адрес.

Сетевой блок CIDR

— Полное доменное имя, которое будет преобразовано через DNS сервер при загрузке правила. Полученные адреса окажутся в правиле.
— Название сетевого интерфейса или группы сетевых интерфейсов. Любые IP адреса принадлежащие интерфейсу будут подставлены в правило, во время загрузки.
— Название сетевого интерфейса сопровождающегося /netmask (например, /24). Каждый IP адрес на интерфейсе, совмещённый с сетевой маской, образует блок CIDR и оказывается в правиле.
— Название сетевого интерфейса или группы сетевых интерфейсов, сопровождающихся модификаторами:

:network — заменяется сетевым блоком CIDR (например, 192.168.0.0/24)
:broadcast — заменяется широковещательным адресом сети (например, 192.168.0.255)
:peer — заменяется peer IP адресом другой стороны point-to-point линка

Кроме того, модификатор :0 может быть добавлен к любому интерфейсу или к любому из вышеуказанных модификаторов, для указания, что PF не должен затрагивать alias IP адреса. Этот модификатор может использоваться, при указании интерфейса в круглых скобках. Пример: fxp0:network:0

Таблица.

— Любое из вышеперечисленного, но в отрицании, используя модификатор ! («не»).
— Набор адресов, используя списки.
— Ключевое слово any обозначающее все адреса

src_port

Исходный порт в заголовке пакета. Порты могут быть указаны, как:

— Номер от 1 до 65535
— Актуальное название сервиса смотрите в /etc/services
— Набор портов, используя списки
— Диапазон:

!= (не равно)
< (меньше)
> (больше)
<= (меньше или равно)
>= (больше или равно)
>< (диапазон)
<> (обратный диапазон)

Последние два бинарных оператора (они используют два аргумента) не включают аргументы в этот диапазон

: (включающий диапазон)

Включающий диапазон, также бинарные операторы и включают аргументы в диапазон.
Опция port не часто используется в nat правилах, потому что обычно стоит задача преобразовывать весь трафик, не зависимо от используемых портов.

dst_addr

Адрес назначения преобразуемых пакетов. Адрес назначения указывается так же, как и исходный адрес.

dst_port

Порт назначения. Порт указывается так же, как и исходный порт.

ext_addr

Внешний (преобразуемый) адрес на NAT шлюзе, в который будут пробразованы пакеты. Внешний адрес может быть указан как:

— Единственный IPv4 или IPv6 адрес.
— Сетевой блок CIDR
— Полное доменное имя, которое будет преобразовано через DNS сервер при загрузке правила. Полученные адреса окажутся в правиле.
— Название сетевого интерфейса. Любые IP адреса принадлежащие интерфейсу будут подставлены в правило, во время загрузки.

Название сетевого интерфейса указанного в круглых скобках ( ). Это говорит PF обновлять правило, если IP адрес(а) на указанном интерфейсе сменился. Полезно на интерфейсах, которые получают IP адреса по DHCP или используют dial-up, чтобы каждый раз при смене адреса не перегружать правила.

Название сетевого интерфейса, сопровождающееся одним из этих модификаторов:

:network — заменяется сетевым блоком CIDR (например, 192.168.0.0/24)
:peer — заменяется peer IP адресом другой стороны point-to-point линка

Кроме того, модификатор :0 может быть добавлен к любому интерфейсу или к любому из вышеуказанных модификаторов, для указания, что PF не должен задействовать alias IP адреса. Этот модификатор может использоваться при указании интерфейса в круглых скобках. Пример: fxp0:network:0

Ряд адресов, используя список.

pool_type

Указывается тип диапазона адресов используемого для трансляции.

static-port

Не преобразовывать исходные порты в TCP и UDP пакетах. Как правило, для большинства случаев подойдёт что то вроде этого:

nat on tl0 from 192.168.1.0/24 to any -> 24.5.0.5

Эти правила говорят выполнять NAT на интерфейсе tl0 для любых входящих пакетов из 192.168.1.0/24 и заменять исходный IP адрес на 24.5.0.5. Не смотря на то, что вышеуказанное правило является корректным, использовать подобную форму не рекомендуется. Обслуживание может оказаться сложным, поскольку любое изменение чисел внешней или внутренней сети потребует изменения правил. Сравните с более простым в обслуживании правилом (tl0 внешний интерфейс, dc0, внутренний):

nat on tl0 from dc0:network to any -> tl0

Преимущества на лицо : вы можете менять IP адреса на любом интерфейсе без изменения правила.

Когда указывается название интерфейса для трансляции адресов, как в примере выше, то IP адрес определяется в pf.conf во время загрузки, а не на «лету». Если вы используете DHCP для настройки ваших внешних интерфейсов, это может оказаться проблемой. Если ваши присваиваемые IP адреса меняются, NAT будет продолжать преобразовывать исходящие пакеты используя старый IP адрес. Это приведёт к остановке функционирования исходящих соединений. Чтобы этого избежать, вы можете сказать PF автоматически обновлять преобразуемый адрес, указав круглые скобки вокруг названия интерфейса:

nat on tl0 from dc0:network to any -> (tl0)

Этот метод работает для преобразования IPv4 и IPv6 адресов.

Двунаправленное отображение (отображение 1:1)

Двунаправленное отображение может быть установлено использованием правила binat. Правило binat устанавливает один к одному отображение между внутренним IP адресом и внешним. Это может быть полезно, например, для предоставления веб сервера во внутренней сети со своим личным внешним IP адресом. Соединения из Интернета на внешний адрес будут транслироваться на внутренний адрес а соединения от веб сервера (такие как DNS запрос) будут преобразовываться во внешний адрес. TCP и UDP порты никогда не изменяются с binat правилом.

Пример:

web_serv_int = "192.168.1.100"
web_serv_ext = "24.5.0.6"
binat on tl0 from $web_serv_int to any -> $web_serv_ext

Исключения правил трансляции

Исключения в правилах трансляции могут быть сделаны используя ключевое слово no. Например, если изменить пример указанный выше, то он будет выглядеть так:

no nat on tl0 from 192.168.1.208 to any
nat on tl0 from 192.168.1.0/24 to any -> 24.2.74.79

Тогда указанная сеть 192.168.1.0/24 будет транслироваться через внешний адрес 24.2.74.79, за исключением адреса 192.168.1.208.

Обратите внимание, что первое правило главнее; Если существует ключевое слово no тогда пакеты не транслируются. Ключевое слово no так же может быть использовано с binat и rdr правилами.

Проверка NAT статуса

Для просмотра активных NAT трансляций используется pfctl(8) с опцией -s state. Эта опция выведет список всех текущих NAT сессий:

# pfctl -s state
fxp0 TCP 192.168.1.35:2132 -> 24.5.0.5:53136 -> 65.42.33.245:22 TIME_WAIT:TIME_WAIT
fxp0 UDP 192.168.1.35:2491 -> 24.5.0.5:60527 -> 24.2.68.33:53   MULTIPLE:SINGLE

Объяснение (только первая строка):

fxp0

Отображает интерфейс к которому привязан стейт. Слово self будет отображаться если стейт плавающий floating.

TCP

Используемый соединением протокол.

192.168.1.35:2132

IP адрес (192.168.1.35) машины во внутренней сети. Исходный порт (2132) показан после адреса. Также адрес, который находится в IP заголовке.

24.5.0.5:53136

IP адрес (24.5.0.5) и порт (53136) на шлюзе, в который будут транслированы пакеты.

65.42.33.245:22

IP адрес (65.42.33.245) и порт (22) к которому внутренняя машина соединяется.

TIME_WAIT:TIME_WAIT

Показывает в каком состоянии прибывает TCP соединение

7) Перенаправление (Проброс портов)

Если у вас есть работающий NAT в вашем офисе, то вы имеете выход в Интернет на всех машинах. Что если у вас есть машина позади NAT шлюза, которая должна быть доступна из вне? Вот где вступает в работу перенаправление. Перенаправление позволяет входящему трафику быть посланным машине находящейся позади NAT шлюза.

Давайте рассмотрим пример:

rdr on tl0 proto tcp from any to any port 80 -> 192.168.1.20

Эта строка перенаправляет трафик входящий на 80 TCP порт (веб сервер) на машину внутри сети с ip 192.168.1.20. Поэтому, даже несмотря на то, что 192.168.1.20 находится за шлюзом и внутри вашей сети, внешний мир может иметь к этой машине доступ.

Часть правила from any to any может быть весьма полезна. Если вы знаете какой адрес или подсеть должны иметь доступ к веб серверу на 80 порт, то вы можете это указать:

rdr on tl0 proto tcp from 27.146.49.0/24 to any port 80 -> 192.168.1.20

Это будет перенаправлять только указанную подсеть. Обратите внимание, это означает, что вы можете перенаправлять определённые входящие хосты на определённые машины находящиеся за натом. Это может оказаться полезным. Например, вы можете давать удалённым пользователями доступ на их собственные рабочие компьютеры, если вы знаете кто с какого IP адреса будет соединяться:

rdr on tl0 proto tcp from 27.146.49.14 to any port 80 -> 192.168.1.20
rdr on tl0 proto tcp from 16.114.4.89 to any port 80 -> 192.168.1.22
rdr on tl0 proto tcp from 24.2.74.178 to any port 80 -> 192.168.1.23

Диапазон портов также может быть перенаправлен:

rdr on tl0 proto tcp from any to any port 5000:5500 -> 192.168.1.20
rdr on tl0 proto tcp from any to any port 5000:5500 -> 192.168.1.20 port 6000
rdr on tl0 proto tcp from any to any port 5000:5500 -> 192.168.1.20 port 7000:*

Эти примеры показывают перенаправление портов от 5000 до 5500 включительно, на машину 192.168.1.20. В правиле #1 порт 5000 перенаправляется на 5000, 5001 на 5001, и т.д. В правиле #2 указанный диапазон портов перенаправляется на 6000 порт. И в правиле #3 порт 5000 перенаправляется на 7000, 5001 на 7001, и т.д.

Перенаправление и Фильтрация Пакетов

Обратите внимание: Преобразованные пакеты проходят через фильтр и будут блокированы или пропущены, в зависимости от правил фильтрации.

Единственное исключение для этого правила это когда ключевое слово pass используется с rdr правилом. В этом случае перенаправленные пакеты будут пропущены сквозь работу фильтра: эти пакеты не будут оцениваться правилами фильтрации. Это сокращённый путь добавления фильтрующего правила pass для каждого правила перенаправления. Воспринимайте это как обыкновенное rdr правило (без ключевого слова pass) объединённое с фильтрующим правилом pass, с ключевым слово keep state. Однако, если вы хотите использовать более специфичные фильтрующие опции, такие, как synproxy, modulate state, и т.д. то вам необходимо отдельно использовать pass правило, потому что эти опции не работают в правилах перенаправления.

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

192.0.2.1 — хост в Интернет.
24.65.1.13 — внешний адрес OpenBSD роутера.
192.168.1.5 — внутренний адрес веб сервера.

Перенаправляющее правило:

rdr on tl0 proto tcp from 192.0.2.1 to 24.65.1.13 port 80 -> 192.168.1.5 port 8000

Пакет до обработки rdr правилом:

Исходный адрес: 192.0.2.1
Исходный порт: 4028
Адрес назначения: 24.65.1.13
Порт назначения: 80

Пакет после обработки rdr правилом:

Исходный адрес: 192.0.2.1
Исходный порт: 4028
Адрес назначения: 192.168.1.5
Порт назначения: 8000

Фильтр увидит пакет в том виде, в котором он представлен после трансляции.

Пример заворота пакетов на прокси-сервер squid:

rdr on sk0 inet proto tcp from 192.168.0.0/24 to any port 80 -> 127.0.0.1 port 3128

Если же некоторому IP-адресу нужно выходить в интернет, минуя прокси-сервер, то нужно добавить соответствующее правило Выше, чем правило для заворота:

# пускаем тех, кому нужно ходить мимо прокси
no rdr on sk0 inet proto {tcp,udp} from 192.168.0.33/32 to any
# squid
rdr on sk0 inet proto tcp from 192.168.0.0/24 to any port 80 -> 127.0.0.1 port 3128

8) Журналирование в пакетном фильтре

Журналирование в пакетном фильтре осуществляется при помощи демона pflogd(8) слушающего сетевой интерфейс pflog0 и записывающего пакеты в журнальный файл /var/log/pflog в бинарном формате libpcap, который можно просматривать при помощи программы tcpdump(1) или wireshark(1), она же ethereal(1) (см. Раздел 6.11, «Демонстрация основных навыков работы с утилитой tcpdump(1)»). У программы tcpdump(1) есть специальные правила для работы с пакетным фильтром. Кроме того, программа tcpdump(1) позволяет просматривать журнал «на лету» если запустить её на прослушивание интерфейса pflog0. Для помещения в журнал, можно применять ключевое слово log (или log (all)) в правилах фильтрации.

Для журналирования пакета надо поместить ключевое слово log в правило фильтрации, nat или rdr. Заметьте, что в пактном фильтре нельзя создать правило только для журналирования пакета — должна присутствовать либо директива block либо pass.

Ключевому слову log можно передать следующие опции:

all

Помещает в журнал не только начальные пакеты, но вообще все пакеты соединения. Может применяться в правилах keep state.

user

Помещает в журнал UID и GID сокета, которому адресован пакет.

to

Начиная с OpenBSD 4.1 можно создавать несколько интерфейсов для журналирования и сообщения от разных правил посылать на разные интерфейсы. Ни в одной другой системе эта возможность пока не реализована.

Опции указываются в круглых скобках после ключевого слова log. Несколько опций можно указать через запятую или через пробел:

pass in log (all) on $ext_if inet proto tcp to $ext_if port 22 keep state

Это правило помещает в журнал все входящие пакеты, идущие на 22-й порт.

Чтение журнала

Журнальный файл записанный pflogd(8) имеет бинарный формат, его нельзя читать при помощи текстового редакора. Он предназначен для чтения утилитой tcpdump(1) (или другой программой скомпилированной с поддержкой библиотеки libpcap, например wireshark(1)).

Для просмотра журнального файла выполните команду

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

Для просмотра журнала в режиме реального времени:

# tcpdump -n -e -ttt -i pflog0

Правила фильтрации в tcpdump(1) специально расширены для взаимодействия с pflogd(8).

Ещё один пример:

# tcpdump -n -e -ttt -i pflog0 inbound and action block and on wi0

В этом примере мы в режиме реального времени следим за входящими пакетами блокирующимися на интерфейсе wi0.

При написании статьи использовались материалы со следующих источников:

opennet.ru
bsdportal.ru
openbsd.org
house.hcn-strela.ru
lissyara.su

[pf] Файервол packet filter: 9 комментариев

  1. Алексей

    «table { 192.0.2.0/24, !192.0.2.5 }
    Таблица goodguys теперь содержит адреса сети 192.0.2.0/24, за исключением адреса 192.0.2.5. »
    вы не ошиблись случаем? таблица содержит все адреса 192.0.2.0/24 + !192.0.2.5, то есть она содержит просто все адреса 192.0.2.0.24. 192.0.2.5 содержится в 192.0.2.0.24.

    1. skeletor Автор записи

      Нет, не ошибся. «!» — означает отрицание. Из диапазона 192.0.2.0/24 исключается адрес 192.0.2.5

      1. Алексей

        да исключается, а после к нему прибавляете весь диапазон 192.0.2.0/24, конъюнкция 192.0.2.0/24 и !192.0.2.5 =192.0.2.0/24

  2. Алексей

    пардон ошибся, ваша таблица содержит вообще все адреса.
    192.0.2.0/24+ !192.0.2.5=0.0.0.0/0

    1. skeletor Автор записи

      Тут не совсем классическая конъюнкция: имеется ввиду, взять диапазон 192.0.2.0/24, и одновременно, что бы не было !192.0.2.5

    1. skeletor Автор записи

      Если вы посмотрите на дату поста, то поймёте, что тогда ещё не было SMP.

  3. Сергей

    Почему 1е правило прекрасно работает а второе — никак?

    rdr on $ext inet proto tcp from to $ext port 209 -> 192.168.0.209 port 80
    rdr on $ext inet proto tcp from to $ext port 2345 -> 192.168.0.209 port 22

    $ext — внешний интерфейс, — наблица внешних IP для доступа?

    Бьюсь над этой загадкой второй месяц. Отключаю PF — работают ОБА правила! Включаю — только первое.

    1. skeletor Автор записи

      Значит проброс прописан где-то ещё. При отключении файервола проброс не может работать, так как собственно нет механизма, который бы пробрасывал пакеты.

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *