Русский
Русский
English
Статистика
Реклама

Блог компании интернет контроль сервер

Файрвол PF в ОС FreeBSD

12.03.2021 16:12:58 | Автор: admin

Введение


В Рунете есть множество статей о настройке FreeBSD и PF, но все они разрозненны и несистематичны. А за любыми более-менее интересными вещами, такими, как маршрутизация средствами файрвола (Policy Based Routing), приходится читать оригинальную документацию, например, OpenBSD PF FAQ. Однако, есть отличия в синтаксисе команд для FreeBSD и OpenBSD. В этом цикле статей мы пробуем систематизировать и разобрать возможности PF от простого к сложному. Наверняка, эта вводная статья будет похожа на все остальные статьи о Packet Filter. Поэтому тем, кто уже знаком с базовыми возможностями, будет не интересно. Однако, без вступления не обойтись, а мясо будет в следующих статьях.


PF Packet Filter это межсетевой экран, изначально созданный в рамках проекта OpenBSD. В 2003 году был портирован во FreeBSD. В 2004 году был интегрирован в основную систему. Основные возможности:

  • Фильтрация на основе адресов, портов, протоколов, интерфейсов
  • NAT Source NAT, подмена адреса отправителя. Destination nat, подмена адреса получателя, проброс порта
  • Scrub нормализация сетевого трафика. Помогает от некоторых видов dos атак, основанных на формировании специально подготовленных пакетов
  • SYN-proxy Защита от SYN-flood атак
  • Балансировка соединений
  • Отказоустойчивость pfcync позволяет синхронизировать состояние файрволов на нескольних хостах, что, в сочетании с протоколом CARP, позволяет создать отказоустойчивый файрвол, который продолжит обрабатывать соединения после падения активной ноды
  • Прозрачный файрвол (Без собственного IP-адреса, включая фильтр 2 уровня)
  • Макросы аналог переменных
  • Таблицы динамически изменяемые без перезагрузки конфигурации списки IP адресов
  • Метки позволяет метить пакеты, если простой фильтрации недостаточно
  • Якоря (anchors) наборы правил, похожи на таблицы IPTables в Linux
  • Сбор статистики и вывод графиков с помощью утилиты pfstat
  • Автоматическая оптимизация правил при загрузке

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

Управление и полезные команды


Для включения PF достаточно в файле /etc/rc.conf указать опции:

pf_enable=YES # включает pf и загружает модуль
pf_flags="" # дополнительные флаги pfctl
pf_rules="/etc/pf.conf" # файл конфигурации
pflog_enable=YES # запуск pflog
pflog_flags="" # флаги pflog
pflog_logfile="/var/log/pflog" # файл лога

Основные команды управления файрволом:

pfctl - # Включить файрвол
pfctl -d # Выключить файрвол
pfctl -nf # Проверить синтаксис файла
pfctl -f # Перечитать правила из файла
pfctl -Rf # Перечитать правила фильтрации из файла
pfctl -Nf # Перечитать правила NAT из файла
pfctl -sa # Просмотр всех состояний
pfctl -s # Просмотр правил фильтрации
pfctl -sn # Просмотр правил NAT
pfctl -s Anchors -v # Просмотр дерева якорей
pfctl -ss # Просмотр текущих соединений

Структура файла конфигурации и базовые настройки


Файл конфигурации состоит из разделов:

  1. Макросы
  2. Таблицы
  3. Опции
  4. Правила нормализации трафика (scrub)
  5. Очереди, приоритезация и контроль скорости
  6. NAT трансляции адресов
  7. Фильтрация пакетов

Правила в общем случае имеют следующий синтаксис:

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

action что следует сделать с пакетом
direction in out, направление
log попадёт ли пакет в pflog
quick если пакет попал под это правило, то дальнейшей обработки не будет. Это правило будет последним для пакета
interface название сетевого интерфейса
af address family, inet или inet6, IPv4 или IPv6 соответственно
protocol протокол 4 уровня, к примеру: tcp, udp, icmp
scr_addr, dst_addr адреса источника и назначения
src_port, dst_port порты
tcp_flags флаги tcp
state опции сохранения состояния. Например, keep state будет означать, что соединение сохранится в таблице состояний, и ответные пакеты могут проходить. Поведение по умолчанию.

Возьмем простейший веб-сервер в вакууме. Необходимо открыть входящие соединения по портам tcp 22, 80, 443 (ssh, http, https). Также нужно открыть исходящие соединения по портам tcp 22, 80, 443 (ssh, http, https) и udp 53, 123 (dns и ntp). Всё остальное запретить.

# ee pf.conf#macros sectionpermit_tcp_ports="22,80,443"permit_udp_ports="53,123"#table section# в данный момент пустая#options sectionset block-policy return # разрываем соединения с ответом, а не просто дропаем пакетыset skip on lo0 # пропускаем проверку на локальной петле, там фильтрация не нужна#scrub sectionscrub in all # нормализация всего входящего трафика#Queueing section# в данный момент пустая#nat section# пустая, у нас нечего транслировать#filtering sectionblock all # запрет всего по умолчанию, помним, что это не означает окончание обработки пакета.

pass in proto tcp to port { $permit_tcp_ports } # разрешаем входящие соединения
pass out proto tcp to port { $permit_tcp_ports } # разрешаем исходящие соединения tcp
pass out proto udp to port { $permit_udp_ports } # разрешаем исходящие соединения udp
pass out inet proto icmp # разрешаем исходящий icmp

Затем вводим команду проверки синтаксиса:
pfctl -nf pf.conf

Если сообщений об ошибках нет, вводим команду применения правил:
pfctl -f pf.conf

Для проверки посмотрим правила фильтрации:

# pfctl -sr scrub in all fragment reassemble block return all pass out proto tcp from any to any port = ssh flags S/SA keep state pass out proto tcp from any to any port = http flags S/SA keep state pass out proto tcp from any to any port = https flags S/SA keep state pass out proto udp from any to any port = domain keep state pass out proto udp from any to any port = ntp keep state pass out inet proto icmp all keep state pass in proto tcp from any to any port = ssh flags S/SA keep state pass in proto tcp from any to any port = http flags S/SA keep state pass in proto tcp from any to any port = https flags S/SA keep state 

Как видно, макросы развернулись в отдельные правила по каждому порту, порядок изменен автоматически. В остальном всё так, как нужно.

Итог


Мы разобрались, что такое Packet Filter, узнали его основные возможности. Разобрались в структуре конфигурации PF и её основных секциях. Создали самую простую конфигурацию, которая, однако вполне функциональна, включает макросы, нормализацию пакетов и фильтрацию входящих и исходящих пакетов. В следующей статье разберем более подробно правила фильтрации, управление состояниями и флаги.

Наше решение, Интернет Контроль Сервер это шлюз безопасности, построенный на базе FreeBSD. Данная система была выбрана ввиду своей стабильности, скорости сетевого стэка, встроенной в ядро поддержки ZFS и отсутствия заморочек с лицензиями. Кроме того, работа с FreeBSD достаточно приятна, хоть и не во всем проста. Но мы будем рады делиться с вами этим опытом!
Подробнее..

Фильтрация трафика PF

19.03.2021 14:20:53 | Автор: admin

Фильтрация трафика PF


Введение


  1. Файрвол PF в ОС FreeBSD
  2. Фильтрация трафика PF <- Вы здесь

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


В этот раз попробуем различные варианты более продвинутой фильтрации:


  • Макросы в различных местах конфигурационного файла.
  • Списки набор параметров. PF раскроет его в отдельные правила.
  • Правила, ограничивающие количество подключений с одного IP, их частоту.
  • Таблицы. Это список IP адресов с которыми может сравнивать правило. И возможные варианты динамического их заполнения. И проверку состояния наших таблиц.


Задача будет такая:


  • Разрешить доступ с доверенных IP на любой порт
  • Создать ban-list с загрузкой из файла
  • Разрешить доступ с crm и api нашей компании на порт 8080 и 8443
  • Разрешить исходящий доступ по порам 80 и 443 к CRM и API
  • Создать простейшую защиту от брут-форса на порту 22 с внесением хостов в бан-лист на час
  • Создать простейшую защиту от DDOS и DOS на портах 80 и 443, с внесением хостов в другой бан-лист

Итак, приступим.


Макросы и списки


С макросами всё относительно просто. Это вставка куска строки в нужное (практически любое) место конфигурационного файла. В макросе можно держать имя интерфейса, IP адрес (или адреса), опции tcp, и так далее. IP адреса и имена интерфейса взяты из головы, все совпадения случайны.


# macros sectionIf="re0" # интерфейс нашего сервераIfIp="192.169.12.10" # IP нашего сервераpermit_tcp_ports="22,53"permit_udp_ports="53,123"web_ports = "http,https"

После этого можно использовать их в правилах подобного вида:


pass in on $IfIp inet proto tcp from any to $IfIp port {$permit_tcp_ports}

После загрузки правила будут выглядеть так:


pfctl -sr...pass in on re0 inet proto tcp from any to 172.16.1.1 port = ssh flags S/SA keep statepass in on re0 inet proto tcp from any to 172.16.1.1 port = http flags S/SA keep statepass in on re0 inet proto tcp from any to 172.16.1.1 port = https flags S/SA keep state...

Как видим, был подставлен интерфейс и порты из макроса. Затем, для каждого порта было создано отдельное правило в соответствии со списком. Были установлены значения по умолчанию. flags это tcp флаги, установленные у пакета для соответствия правилу. S/SA это пакеты с установленным флагом SYN. То есть, если упростить, первый пакет сессии.


Keep state означает сохранение соединения в таблице состояний или сессий. Это означает, что только первый пакет из соединения пробежит по правилам файрвола. Все остальные пакеты этого соединения будут проверяться по таблице состояний (state table), не проходя проверок.


Важно для оптимизации конфига помнить опцию quick. Она указывает файрволу прекратить дальнейшую обработку этого пакета, и сразу применить текущее действие. К примеру:


pass in quick on em0 from { 192.168.1.0/24 } to 192.168.1.1

Таблицы


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


Таблицы могут быть нескольких видов:


  • const этот флаг указывает на неизменность таблицы. Пользователь не может добавлять и удалять IP "на лету"
  • counters включает ведение статистики по каждому IP в таблице
  • persist указывает ядру хранить таблицу, даже если в ней нет записей

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


Простая таблица:


# администраторы, им разрешено всёtable <admins> { 173.18.12.22, 173.18.12.45 } 

Для использования в правиле файрвола:


pass in quick on $If from <admins> to $IfIp

Это правило разрешит всё, что приходит от IP адресов, перечисленных в таблице, на сетевой интерфейс и IP сервера.


Таблица с загрузкой из файла:


# хосты, которые нам просто не нравятсяtable <block_list> file "/etc/pf.blocklist.conf" 

Используем в правиле:


block in quick on $If from <block_list> to any

Правило заблокирует любой трафик от <simple-ban> на интерфейсе $If


Сама таблица будет иметь следующий вид:


# cat /etc/pf.blocklist.conf1.1.1.1111.12.46.0/24!111.12.46.17

В таблице можно указать знак отрицания для исключения из таблицы. В данном случае будет соответствие IP 1.1.1.1, и сети 111.12.46.0/24, кроме IP 111.12.46.17. Наполнением этого файла можно заниматься вручную. Затем загрузить таблицу из файла снова:


pfctl -t block_list -T replace -f /etc/pf.blocklist.conf3 addresses added.

В таблицу можно добавлять как IP адреса, так и доменные имена. Доменные имена разрешатся при загрузке. Для обновления адресов можно поставить загрузку таблицы в cron каждый час, к примеру. Однако, если хотя бы один адрес разрешить не удастся, обновление таблицы не удастся:


# cat  /etc/pf.blocklist.confya.ru1.2.3.4mail.ru

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


# pfctl -t blocklist -T show   1.2.3.4   87.250.250.242   94.100.180.200   94.100.180.201   217.69.139.200   217.69.139.202   2a00:1148:db00:0:b0b0::1   2a02:6b8::2:242

Можно удобным образом добавить/удалить записи:


# pfctl -t blocklist -T add 123.12.34.5# pfctl -t blocklist -T delete 123.12.34.5

Конечно, эти записи не попадут магически в файл, и после перезагрузки таблицы их не будет.
Важно помнить о возможности проверки соответствия IP таблице. Можно проверить и доменное имя.


# pfctl -t blocklist -T test 1.2.3.41/1 addresses match.# pfctl -t blocklist -T test 1.1.1.10/1 addresses match.# pfctl -t blocklist -T test ya.ru2/2 addresses match.# pfctl -t blocklist -T test 1.1.1.1 1.2.3.41/2 addresses match.

Задания в cron для обновления:


# cat /etc/crontab...0 * * * * root /sbin/pfctl -t block_list -T replace -f /etc/pf.blocklist.conf...

Теперь добавим возможность ходить на корпоративные ресурсы по портам http(s) и обратно по портам 8080 и 8443. Создаём макросы со списком портов, можно использовать названия протоколов:


permit_corp_ports ="http,https" # порты для доступа к корп. ресурсамpermit_add_tcp_ports = "8080,8443" # дополнительные порты, на которых слушают сервера для внутренних нужд

Таблицу, загружаемую из файла, со списком хостов:


table <corp_res> file "/etc/pf.corpres.conf" # корпоративные ресурсы, доступ по http(s)

Для примера, в таблице будет один хост:


# cat /etc/pf.corpres.confexample.com

И собственно правила:


pass in quick on $If proto tcp from <corp_res> to $IfIp ports { $permit_add_tcp_ports }pass out quick on $If proto tcp from $IfIp to <corp_res> port { $permit_corp_ports }

Дополнительные параметры фильтрации


Теперь задача интереснее. Необходимо защитить наш сервер от перебора паролей ssh. Еще одна возможность PF лимитировать количество соединений, соответствующих правилу, по различным параметрам.


Таблица:


table <block_ssh> persist # Брутфорсеры ssh. если таблица пустая, PF её удалит. Для отмены такого поведения используется ключевое слово persist

persist держать в памяти, даже если пустая. Если не указать такое поведение, PF удалит пустую таблицу.


И правила:


block in quick on $If proto tcp from <block_ssh> to any port 22pass in on $If proto tcp to $IfIp port { 22 } \           keep state (max-src-conn 10, max-src-conn-rate 3/10, \           overload <block_ssh> flush)

В данном случае max-src-conn 10 только 10 одновременных соединений с одного адреса, max-src-conn-rate 3/10 только 3 новых соединения за 10 секунд, overload <block_ssh> добавлять в таблицу адреса источника нарушителей, flush сбрасывать все соединения от нарушителя, созданные этим правилом.


По образу и подобию разрешение пользователю ходить на веб-сервер.


Таблица:


table <block_web> persist # заблокированный доступ до веб

И правила:


# разрешаем входящие соединенияblock in quick on $If from <block_web>pass in on $If proto tcp to $IfIp port { $permit_tcp_ports } \           synproxy state (max-src-conn 100, max-src-conn-rate 20/1, \           overload <block_web> flush global)

Параметры этого правила надо очень аккуратно подбирать под ваш сервис. Synproxy включает проксирование syn запросов, защиту от synflood атак. А flush global означает сброс всех соединений с источника-нарушителя, даже если они не относятся к этому правилу.


Устаревание записей в таблицах заблокированных обеспечиваются командами такого вида:


pfctl -t block_ssh -T expire 3600

Эта команда удалит все записи, старше часа из соответствующей таблицы. Команды поставим в крон:


...0 * * * * root /sbin/pfctl -t block_ssh -T expire 36000 * * * * root /sbin/pfctl -t block_web -T expire 3600...

Таким образом каждый час таблицы будут вычищаться.


Итоговая конфигурация


# cat /etc/pf.conf# macros sectionIf="re0" # интерфейс нашего сервераIfIp="192.169.12.10" # ip нашего сервераpermit_tcp_ports="22,53"permit_udp_ports="53,123"web_ports = "http,https"permit_corp_ports ="http,https"permit_add_tcp_ports = "8080,8443"# table section# администраторы, им разрешено всёtable <admins> { 192.168.11.0/24, 173.18.12.22, 173.18.12.45 }# хосты, которые нам просто не нравятся, находятся тутtable <block_list>  file "/etc/pf.blocklist.conf"# корпоративные ресурсы, доступ по hppt(s)table <corp_res> file "/etc/pf.corpres.conf"# Брутфорсеры ssh. если таблица пустая, PF её удалит.#Для отмены такого поведения используется ключевое слово persisttable <block_ssh> persist# заблокированный доступ до вебtable <block_web> persist # options section# разрываем соединения с ответом, а не просто дропаем пакетыset block-policy return# пропускаем проверку на локальной петле, там фильтрация не нужна set skip on lo0 # scrub sectionscrub in all # нормализация всего входящего трафика# Queueing section# в данный момент пустая# nat section# пустая, у нас нечего транслировать# filtering section# запрет всего по умолчанию, помним,# что это не означает окончание обработки пакета.block all# разрешаем админов и корпоративные ресурсы # ключевое слово quick - прекращать на этом правиле обработку.pass in quick on $If from <admins> to $IfIp pass in quick on $If proto tcp from <corp_res> to $IfIp \        port { $permit_add_tcp_ports }# разрешаем ssh с защитой от брутфорсаblock in quick on $If proto tcp from <block_ssh> port 22pass in on $If proto tcp to $IfIp port { 22 } \           keep state (max-src-conn 10, max-src-conn-rate 3/10, \           overload <block_ssh> flush)# разрешаем Web с защитой от ботов и прочегоblock in quick on $If from <block_web>pass in on $If proto tcp to $IfIp port { $web_ports } \           keep state (max-src-conn 100, max-src-conn-rate 20/1, \           overload <block_web> flush global)# разрешаем серверу ходить до корпоративных ресурсовpass out quick on $If proto tcp from $IfIp to <corp_res> port { $permit_corp_ports }# и прочее по мелочи # разрешаем исходящие соединения udp# разрешаем исходящие соединения tcppass out proto udp to port { $permit_tcp_ports }# разрешаем исходящие соединения udppass out proto udp to port { $permit_udp_ports } pass out inet proto icmp # разрешаем исходящий icmp

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


Проверяем:


# pfctl -nf pf.conf

Загружаем:


# pfctl -f pf.conf

Заключение


Мы рассмотрели возможности PF в части фильтрации входящего и исходящего трафика. Познакомились с макросами и таблицами. И создали неплохой конфигурационный файл для файрвола, который способен отразить большинство типовых атак на сервер. Описанные технологии наша команда использует для создания шлюза Интернет Контроль Сервер. Протестировать ИКС можно в течение 35 дней или бесплатно использовать версию до 9 пользователей.


Ну а в следующей статье познакомимся с якорями, метками и трансляцией адресов (NAT). Как и прежде, будем рады вашим комментариям и пожеланиям!

Подробнее..

FreeBSD. Трансляции, тэги и якоря в PF

26.03.2021 14:15:31 | Автор: admin
  1. Файрвол PF в ОС FreeBSD
  2. FreeBSD. Фильтрация трафика PF
  3. FreeBSD. Трансляции, тэги и якоря в PF <- Вы здесь

Введение


В прошлых статьях мы разобрали, что такое PF вообще, общие принципы построения правил. Узнали о таблицах и способах фильтрации трафика PF. И создали конфигурационные файлы, которые способны защитить сервер в Интернет. В этой статье попробуем разобраться с NAT трансляциями и тэгами (tags). Еще пройдемся по якорям (anchors), их иногда называют еще закладками.



Думаю, объяснять, что такое NAT трансляции в общем случае не стоит. PF поддерживает три вида трансляций.


Anchors это отдельные наборы правил PF, которыми, подобно таблицам, можно управлять динамически.


Tag это способ пометить пакет, чтобы потом обработать в другом правиле. Удобно, например, метить пакеты, проходящие через проброс, чтобы потом их разрешить скопом в одном правиле.


Задача


Сразу оговоримся, что конфигурация вполне рабочая, но, для упрощения, везде будут использоваться "серые" сети.


У нас есть офис. Обычный офис, 2 локальные сети.
В сети 172.16.2.0/24 находится бухгалтерия. Ну и главный бухгалтер, особое отношение 172.16.2.2.


Во второй сети 172.16.3.0/24 находится сервер (172.16.3.1), который надо странслировать в интернет 1 в 1. Компьютер системного администратора, особое отношение плюс надо сделать проброс порта для торрентов (59715 TCP и UDP). Компьютер директора, особое отношение.


Кроме того, нужно запретить ходить на сайты социалочек, для примера возьмем несколько доменов вроде vk.com и facebook.com, для всех, кроме избранных. И сделать для штрафников запрет входа в интернет, кроме нескольких сайтов, пусть будут google.com и yandex.ru, необходимых для работы.


Всем нужно ходить только до сервера в Интернет, 172.16.1.2. Провайдер нам даёт сеть 172.16.1.1/24 со шлюзом 172.16.1.254 и дополнительный IP для внутреннего сервера 172.16.1.5.


Схема сети офиса


Подготовка


Для начала подготовим наш роутер. Пусть сетевые интерфейсы и маршрут по умолчанию уже настроен.


Во-первых нужно добавить возможность маршрутизации. Для этого в /etc/rc.conf добавляем строку:


gateway_enable="yes"

И перезагружаем машину.


Всё, маршрутизация уже работает.


Добавляем макросов, таблиц, скраб, опции, очереди (которых нет)
#macros sectionExtIf="re0" # внешний интерфейсIntIf1="em0" # локальный 0, бухгалтерияIntIf2="em1" # локальный 1, всё остальное.ExtIfIp1="172.16.1.1" # Внешний IP адресExtIfIp2="172.16.1.5" # Дополнительный адрес от провайдераIntIf1Ip="172.16.2.254" # IP локального 0IntIf1Net="172.16.2.0/24"IntIf2Ip="172.16.3.254" # IP локального 1IntIf2Net="172.16.3.0/24"IntSrv="172.16.3.1"ExtSrv="172.16.1.2"torrent_port="59715"permit_tcp_ports="22,53"permit_udp_ports="53,123"web_ports = "http,https"permit_corp_ports ="http,https"permit_add_tcp_ports = "8080,8443"#table sectiontable <privileged_If1> { 172.16.2.2 } # главбух и избранные из бухгалтерииtable <privileged_If2> { 172.16.3.15, 172.16.3.125 } # избранные из общей сетиtable <bad_users> persist # табличка для тех, кто нам не нравитсяtable <block_list>  file "/etc/pf.blocklist.conf" # хосты, которые нам просто не нравятся, находятся тутtable <corp_res> file "/etc/pf.corpres.conf"# корпоративные ресурсы, доступ по hppt(s)table <sites_for_all> file "/etc/pf.sitesforall.conf" # сайты, доступные всемtable <bad_sites> file "/etc/pf.badsites.conf"table <block_ssh> persist # Брутфорсеры ssh. если таблица пустая, PF её удалит. Для отмены такого поведения используется ключевое слово persisttable <block_web> persist # заблокированный доступ до веб до сервера#options sectionset block-policy return # разрываем соединения с ответом, а не просто дропаем пакетыset skip on lo0 # пропускаем проверку на локальной петле, там фильтрация не нужна#scrub sectionscrub in all # нормализация всего входящего трафика#Queueing section# в данный момент пустая

Трансляции


Время добавить немного трансляций. Правила строятся по общему синтаксису (описан во вводной статье). Однако добавилось исключение действия.


PF поддерживает 3 типа.


  • binat двунаправленная трансляция, статическая трансляция, или трансляция один в один в оба направления.
  • nat source nat, подмена источника. То, что называют NAT почти везде.
  • rdr проброс портов, трансляция адреса назначения, destination nat, dnat.

BINAT


Начнем со статической трансляции. Синтаксис команды (из официальной документации):


[ "no" ] "binat" [ "pass" [ "log" [ "(" logopts ")" ] ] ]              [ "on" interface-name ] [ af ]              [ "proto" ( proto-name | proto-number ) ]              "from" address [ "/" mask-bits ] "to" ipspec              [ "tag" string ] [ "tagged" string ]              [ "->" address [ "/" mask-bits ] ]

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


no binat on $ExtIf from $IntSrv to 172.16.34.0/24binat on $ExtIf from $IntSrv to any -> $ExtIfIp2

Это означает, что весь трафик, кроме исключения, от хоста IntSrv (172.16.3.1) будет при выходе в интернет странслирован в адрес ExtIfIp2(172.16.1.5), и наоборот, всё, что пришло из интернета на этот внешний адрес, улетит на наш сервер. Удобно, когда надо пробросить кучу портов, и в некоторых других случаях. Например для выделенной АТС. В конфигурацию пойдет вот такое правило:


binat on $ExtIf from $IntSrv to any -> $ExtIfIp2

NAT


Теперь NAT из локальной сети в Интернет. Синтаксис правила:


[ "no" ] "nat" [ "pass" [ "log" [ "(" logopts ")" ] ] ]              [ "on" ifspec ] [ af ]              [ protospec ] hosts [ "tag" string ] [ "tagged" string ]              [ "->" ( redirhost | "{" redirhost-list "}" )              [ portspec ] [ pooltype ] [ "static-port" ] ]

И строка в конфиге:


nat on $ExtIf from { $IntIf1Net, $IntIf2Net } to any -> $ExtIfIp1

Это правило говорит о том, что всё, идущее в интернет нужно закрыть первым IP внешнего интерфейса. Можно поставить исключения, подобно binat. Кроме обычной трансляции в один статический IP, можно, например, чтобы PF сам вычислял этот IP с интерфейса. Полезно, если провайдер даёт динамический IP по PPPoE или DHCP:


nat on $ExtIf from { $IntIf1Net, $IntIf2Net } to any -> ($ExtIf)

Если на интерфейсе несколько IP, или сеть то её можно указать как напрямую, так и используя такой синтаксис, будет указана вся сеть внешнего интерфейса (поддерживается только round-robin):


nat on $ExtIf from { $IntIf1Net, $IntIf2Net } to any -> ($ExtIf:network)

По умолчанию PF будет распределять соответствие соединений и внешний IP по стратегии round-robin. Это поведение можно поменять, используя явное указание сети. Доступны ещё стратегии source-hash, когда он выбирает для каждого адреса IP из получившегося пула, основываясь на внутреннем, либо случайный выбор random. Можно включить прилипание, sticky-address, гарантирующее, что один внутренний IP всегда будет закрываться одним и тем же внешним, пока есть живые соединения. Если живых соединений нет, то адрес отлипает:


nat on $ExtIf from { $IntIf1Net, $IntIf2Net } to any -> 172.16.1.0/27 \                                     source-hash sticky-address

RDR


Ну и последнее, проброс портов. Отличий мало.


[ "no" ] "nat" [ "pass" [ "log" [ "(" logopts ")" ] ] ]        [ "on" ifspec ] [ af ]        [ protospec ] hosts [ "tag" string ] [ "tagged" string ]        [ "->" ( redirhost | "{" redirhost-list "}" )        [ portspec ] [ pooltype ] [ "static-port" ] ]

Правило в конфиг:


rdr on $ExtIf inet proto {tcp, udp} from any to $ExtIfIp1 \    port { $torrent_port } -> 172.16.3.15 port $torrent_port

Правило завернет входящие пакеты на IP администратора. Из дополнительных возможностей можно организовать простенький балансировщик:


rdr on $ExtIf inet proto {tcp, udp} from any to $ExtIfIp1 \    port { $torrent_port } -> { 172.16.3.15, 172.16.3.15 } \     port $torrent_port round-robin

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


Тэги


  • В одно время у пакета может быть только один тэг
  • Правило может переопределить тэг пакета, но не удалить его
  • Правила отбора можно инвертировать знаком отрицания перед ключевым словом:! tagged PASS

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


В случае обычного хоста всё просто. Есть интерфейсы, есть входящий на них трафик, есть исходящий, который производит сам хост. Однако в случае маршрутизатора пакет сперва входит на интерфейс, проходит по соответствующим правилам in, вроде:


pass in quick on $IntIf1 from $IntIf1Net to $IntIf2Net

затем, перед выходом с другого интерфейса, проходит по правилам out:


pass out quick on $IntIf2 from $IntIf1Net to $IntIf2Net

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


pass in quick on $IntIf1 from $IntIf1Net to $IntIf2Netpass out quick on $IntIf2 from $IntIf1Net to $IntIf2Netpass in quick on $IntIf2 from $IntIf2Net to $IntIf1Netpass out quick on $IntIf1 from $IntIf2Net to $IntIf1Net

Есть два варианта избежать этого. Первый не указывать интерфейсы и направления. Соорудить что-то вроде:


pass quick from $IntIf1Net to $IntIf2Net

Но, это не наши методы, потому что снижает "строгость" файрвола.


Решение подобной проблемы очень простое:


pass in quick on $IntIf1 from $IntIf1Net to $IntIf2Net tag PASSpass in quick on $IntIf2 from $IntIf2Net to $IntIf1Net tag PASSpass out tagged PASS

Тэг, сохраняясь на всём пути прохождения пакета, в конце концов попадёт на правило pass out, и трафик пройдёт.


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


А еще правила фильтрации PF, в случае, если на интерфейсе есть трансляция, увидят уже странслированные пакеты.


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


pass in on $ExtIf proto { tcp, udp } from any to 172.16.3.15 \                             port { $torrent_port } tag PASS

Если собрать всё в кучу, то, на данный момент, получится следующее
######## macros section ########ExtIf="re0" # внешний интерфейсIntIf1="em0" # локальный 0, бухгалтерияIntIf2="em1" # локальный 1, всё остальное.ExtIfIp1="172.16.1.1" # Внешний IP адресExtIfIp2="172.16.1.5" # Дополнительный адрес от провайдераIntIf1Ip="172.16.2.254" # IP локального 0IntIf1Net="172.16.2.0/24"IntIf2Ip="172.16.3.254" # IP локального 1IntIf2Net="172.16.3.0/24"IntSrv="172.16.3.1"ExtSrv="172.16.1.2"torrent_port="59715"permit_tcp_ports="22,53"permit_udp_ports="53,123"web_ports = "http,https"permit_corp_ports ="http,https"permit_add_tcp_ports = "8080,8443"######## table section ########table <privileged_If1> { 172.16.2.2 } # главбух и избранные из бухгалтерииtable <privileged_If2> { 172.16.3.15, 172.16.3.125 } # избранные из общей сетиtable <bad_users> persist # табличка для тех, кто нам не нравится# хосты, которые нам просто не нравятся, находятся тутtable <block_list>  file "/etc/pf.blocklist.conf" # корпоративные ресурсы, доступ по hppt(s)table <corp_res> file "/etc/pf.corpres.conf"table <sites_for_all> file "/etc/pf.sitesforall.conf" # сайты, доступные всемtable <bad_sites> file "/etc/pf.badsites.conf"# Брутфорсеры ssh. если таблица пустая, PF её удалит. Для отмены такого поведения используется ключевое слово persisttable <block_ssh> persist# заблокированный доступ до веб до сервераtable <block_web> persist######## options section ######### разрываем соединения с ответом, а не просто дропаем пакетыset block-policy return# пропускаем проверку на локальной петле, там фильтрация не нужнаset skip on lo0 #scrub section# нормализация всего входящего трафикаscrub in all #Queueing section# в данный момент пустая######## nat section #########статическая трансляция для сервераbinat on $ExtIf from $IntSrv to any -> $ExtIfIp2#NAT для клиентовnat pass on $ExtIf from { $IntIf1Net, $IntIf2Net } to any -> $ExtIfIp1# прооброс портов на машину администратораrdr on $ExtIf inet proto {tcp, udp} from any to $ExtIfIp1 port { $torrent_port } -> 172.16.3.15 port $torrent_portrdr on $ExtIf inet proto {tcp, udp} from any to $ExtIfIp1 \    port { $torrent_port } -> { 172.16.3.15, 172.16.3.15 } port $torrent_port round-robin######## filtering section ######### запрет всего по умолчанию, помним, что это не означает окончание обработки пакета.block log all## Разрешения для входящих пакетов из Интернет. ### разрешаем ssh с защитой от брутфорсаblock in quick proto tcp from <block_ssh> port 22pass in quick on $ExtIf proto tcp to { $ExtIfIp1 $IntSrv } port { 22 } \           keep state (max-src-conn 10, max-src-conn-rate 3/10, \           overload <block_ssh> flush) tag PASS# разрешаем Web с защитой от ботов и прочегоblock in quick on $ExtIf from <block_web>pass in on $ExtIf proto tcp to $IntSrv port { $web_ports } \           synproxy state (max-src-conn 100, max-src-conn-rate 20/1, \           overload <block_web> flush global) tag PASS# разрешаем ходить торрентамpass in on $ExtIf proto { tcp, udp } from any to 172.16.3.15 port { $torrent_port } tag PASS## Разрешения для внутренних сетей ### разрешаем серверу ходить до корпоративных ресурсовpass in quick on $IntIf2 proto tcp from $ExtIfIp2 to $ExtSrv port { $web_ports, $permit_corp_ports, $permit_add_tcp_ports } tag PASS# Если надо разрешить ему ходить в интернет, то раскомментируем# pass in quick on $IntIf2 from $IntSrv to any tag PASS# разрешаем ходить между локалками всемpass in quick on $IntIf1 from $IntIf1Net to $IntIf2Net tag PASSpass in quick on $IntIf2 from $IntIf2Net to $IntIf1Net tag PASS# разрешаем всё привилегированным пользователямpass in on $IntIf1 from <privileged_If1> to any tag PASSpass in on $IntIf2 from <privileged_If2> to any tag PASS# разрешаем ДНС, icmp всемpass in quick on $IntIf1 proto tcp from ($IntIf1:network) to any port { $permit_tcp_ports } tag PASSpass in quick on $IntIf2 proto tcp from ($IntIf2:network) to any port { $permit_tcp_ports } tag PASS# разрешаем всем корпоративные ресурсыpass in quick on $IntIf1 proto tcp from ($IntIf1:network) to $ExtSrv port { $web_ports } tag PASSpass in quick on $IntIf2 proto tcp from ($IntIf2:network) to $ExtSrv port { $web_ports } tag PASSpass in quick on $IntIf1 from ($IntIf1:network) to <sites_for_all> tag PASSpass in quick on $IntIf2 from { $IntIf2Net } to <sites_for_all> tag PASSblock in quick from <bad_users> to anyblock in quick from { $IntIf1Net, $IntIf2Net } to <bad_sites>pass in quick from { $IntIf1Net,$IntIf2Net } to any tag PASS## разрешаем исходящий трафик ##pass out log tagged PASSpass out on { $IntIf1,$IntIf2 }# разрешаем самому хосту ходить на некоторые портыpass out quick on $ExtIf proto tcp from ($ExtIf) to any port { $permit_tcp_ports }pass out quick on $ExtIf proto udp from ($ExtIf) to any port { $permit_udp_ports }

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


Якоря


Так же известные как anchors. Это отдельные наборы правил PF. Основные свойства:


  • Якоря могут содержать другие якоря, выстраиваясь в древовидную структуру
  • Якорь можно загружать из файла
  • Якорями можно управлять динамически. Удалять, добавлять правила, etc
  • Макросы необходимо определить в том же файле, что и якорь

Есть несколько способов определения якорей:


  • nat-anchor исполнит правила nat
    rdr-anchor исполнит правила rdr
    binat-anchor исполнит правила binat
    anchor исполнит правила фильтрации
    load anchor from команда загрузки из файла

    Во время определения anchor так же можно указать, какой трафик будет отправлен в него.


    Простое определение anchor с загрузкой из файла:


    anchor localnet1load anchor localnet1 from "/etc/pf-anchor.localnet1.conf"
    

    Можно обойтись и без загрузки из файла, просто определить anchor первой строкой, затем подгружать в него правила из командной строки.


    # echo "pass in quick on $IntIf1 from $IntIf1Net to $IntIf2Net tag PASS" | \pfctl -a localnet1 -f -
    

    Просмотреть правила, добавленные в якорь:


    # pfctl -a "localnet1" -sr
    

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


    Перезагрузить якорь из файла:


    # pfctl -a "localnet1" -f /etc/pf-anchor.localnet1.conf
    

    Удалить все правила из якоря:


    # pfctl -a "localnet1" -F rules
    

    Anchor в конфигурационном файле вызывается по месту определения. Как и обычное правило, можно сделать anchor с условиями:


    anchor localnet1 in on $IntIf1 from any to any
    

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


    anchor "localnet1" in on $IntIf1 {    pass in quick on $IntIf1 from $IntIf1Net to $IntIf2Net tag PASS    pass in quick on $IntIf1 proto tcp from ($IntIf1:network) to any \                                   port { $permit_tcp_ports } tag PASS}
    

    Если внутри якоря в правиле указана опция quick, то обработка прекращается для всех правил файрволла, в том числе и после него. Можно так же указать опцию quick для определения anchor, тогда обработка остановится на последнем правиле этого набора. Метки так же сохранятся.


    Внутри якоря можно так же определять таблицы. Они будут видны в этом и всех вложенных якорях.


    Теперь можно перенести все правила, относящиеся к входящему трафику на интерфейсе $IntIf1 в anchor, загружаемый из файла. Не забываем макросы.


    ###### macros section ######IntIf1="em0" # локальный 0, бухгалтерияIntIf1Ip="172.16.2.254" # IP локальногоIntIf1Net="172.16.2.0/24"IntIf2Net="172.16.3.0/24"ExtSrv="172.16.1.2"permit_tcp_ports="22,53"permit_udp_ports="53,123"web_ports = "http,https"###### filter section ####### разрешаем ходить между локалками всемpass in quick on $IntIf1 from $IntIf1Net to $IntIf2Net tag PASS# разрешаем всё привелигированным пользователямpass in on $IntIf1 from <privileged_If1> to any tag PASS# разрешаем ДНС, icmp всемpass in quick on $IntIf1 proto tcp from ($IntIf1:network) to any port { $permit_tcp_ports } tag PASS# разрешаем всем корпоративные ресурсыpass in quick on $IntIf1 proto tcp from ($IntIf1:network) to $ExtSrv port { $web_ports } tag PASS# правила для сайтовpass in quick on $IntIf1 from ($IntIf1:network) to <sites_for_all> tag PASSblock in quick from <bad_users> to anyblock in quick from { $IntIf1Net } to <bad_sites>pass in quick from { $IntIf1Net } to any tag PASS
    

    Теперь можно удалить эти правила из конфигурации, оставив только anchor, а остальные, относящиеся к другому интерфейсу, определить в якоре по месту
    ######## macros section ########ExtIf="re0" # внешний интерфейсIntIf1="em0" # локальный 0, бухгалтерияIntIf2="em1" # локальный 1, всё остальное.ExtIfIp1="172.16.1.1" # Внешний IP адресExtIfIp2="172.16.1.5" # Дополнительный адрес от провайдераIntIf1Ip="172.16.2.254" # IP локального 0IntIf1Net="172.16.2.0/24"IntIf2Ip="172.16.3.254" # IP локального 1IntIf2Net="172.16.3.0/24"IntSrv="172.16.3.1"ExtSrv="172.16.1.2"torrent_port="59715"permit_tcp_ports="22,53"permit_udp_ports="53,123"web_ports = "http,https"permit_corp_ports ="http,https"permit_add_tcp_ports = "8080,8443"######## table section ########table <privileged_If1> { 172.16.2.2 } # главбух и избранные из бухгалтерииtable <privileged_If2> { 172.16.3.15, 172.16.3.125 } # избранные из общей сетиtable <bad_users> persist # табличка для тех, кто нам не нравится# хосты, которые нам просто не нравятся, находятся тутtable <block_list>  file "/etc/pf.blocklist.conf" # корпоративные ресурсы, доступ по hppt(s)table <corp_res> file "/etc/pf.corpres.conf"table <sites_for_all> file "/etc/pf.sitesforall.conf" # сайты, доступные всемtable <bad_sites> file "/etc/pf.badsites.conf"# Брутфорсеры ssh. если таблица пустая, PF её удалит. Для отмены такого поведения используется ключевое слово persisttable <block_ssh> persist# заблокированный доступ до веб до сервераtable <block_web> persist######## options section ######### разрываем соединения с ответом, а не просто дропаем пакетыset block-policy return# пропускаем проверку на локальной петле, там фильтрация не нужнаset skip on lo0 #scrub section# нормализация всего входящего трафикаscrub in all #Queueing section# в данный момент пустая######## nat section #########статическая трансляция для сервераbinat on $ExtIf from $IntSrv to any -> $ExtIfIp2#NAT для клиентовnat pass on $ExtIf from { $IntIf1Net, $IntIf2Net } to any -> $ExtIfIp1# проброс портов на машину администратораrdr on $ExtIf inet proto {tcp, udp} from any to $ExtIfIp1 port { $torrent_port } -> 172.16.3.15 port $torrent_portrdr on $ExtIf inet proto {tcp, udp} from any to $ExtIfIp1 \    port { $torrent_port } -> { 172.16.3.15, 172.16.3.15 } port $torrent_port round-robin######## filtering section ######### запрет всего по умолчанию, помним, что это не означает окончание обработки пакета.block log all## Разрешения для входящих пакетов из Интернет. ### разрешаем ssh с защитой от брутфорсаblock in quick proto tcp from <block_ssh> port 22pass in quick on $ExtIf proto tcp to { $ExtIfIp1 $IntSrv } port { 22 } \           keep state (max-src-conn 10, max-src-conn-rate 3/10, \           overload <block_ssh> flush) tag PASS# разрешаем Web с защитой от ботов и прочегоblock in quick on $ExtIf from <block_web>pass in on $ExtIf proto tcp to $IntSrv port { $web_ports } \           synproxy state (max-src-conn 100, max-src-conn-rate 20/1, \           overload <block_web> flush global) tag PASS# разрешаем ходить торрентамpass in on $ExtIf proto { tcp, udp } from any to 172.16.3.15 port { $torrent_port } tag PASS## Разрешения для внутренних сетей ### разрешаем серверу ходить до корпоративных ресурсовpass in quick on $IntIf2 proto tcp from $ExtIfIp2 to $ExtSrv port { $web_ports, $permit_corp_ports, $permit_add_tcp_ports } tag PASS# Если надо разрешить ему ходить в интернет, то раскомментируем# pass in quick on $IntIf2 from $IntSrv to any tag PASSanchor localnet1 in on $IntIf1 from any to anyload anchor localnet1 from "/etc/pf-anchor.localnet1.conf"anchor localnet2 in on $IntIf2 {    # разрешаем ходить между локалками всем    pass in quick on $IntIf2 from $IntIf2Net to $IntIf1Net tag PASS    # разрешаем всё привилегированным пользователям    pass in on $IntIf2 from <privileged_If2> to any tag PASS    # разрешаем ДНС, icmp всем    pass in quick on $IntIf2 proto tcp from ($IntIf2:network) to any port { $permit_tcp_ports } tag PASS    # разрешаем всем корпоративные ресурсы    pass in quick on $IntIf2 proto tcp from ($IntIf2:network) to $ExtSrv port { $web_ports } tag PASS    # правила для сайтов    pass in quick on $IntIf2 from { $IntIf2Net } to <sites_for_all> tag PASS    block in quick from <bad_users> to any    block in quick from { $IntIf2Net } to <bad_sites>    pass in quick from { $IntIf2Net } to any tag PASS}## разрешаем исходящий трафик ##pass out log tagged PASSpass out on { $IntIf1,$IntIf2 }# разрешаем самому хосту ходить на некоторые портыpass out quick on $ExtIf proto tcp from ($ExtIf) to any port { $permit_tcp_ports }pass out quick on $ExtIf proto udp from ($ExtIf) to any port { $permit_udp_ports }
    

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


    Заключение


    В этот раз мы разобрались с трансляциями, тэгами и якорями. Это последние из базовых возможностей PF, о которых я хотел рассказать в этом цикле. Создали конфигурацию, способную защитить небольшой офис. Для более углубленного изучения стоит обратиться к man pf.conf, а так же официальной документации OpenBSD. Однако того, что рассказано в этих статьях должно хватить для решения большинства задач. Если какая-то тема не раскрыта, или раскрыта не полностью, пишите комментарии, обсудим. Возможно, в следующей статье будет разбор policy-based routing, условной маршрутизации.


    Традиционно, приглашаю всех протестировать наше решение шлюз безопасности Интернет Контроль Сервер, в основе которого лежит обожаемая нами система FreeBSD, а еще есть куча полезных функций для защиты сети, управления юзерами и фильтрации контента. Скачать можно тут. Триал 35 дней, есть бесплатная версия на 9 пользователей.

Подробнее..

FreeBSD. Условная маршрутизация средствами PF

02.04.2021 14:12:19 | Автор: admin
  1. Файрвол PF в ОС FreeBSD
  2. FreeBSD. Фильтрация трафика PF
  3. FreeBSD. трансляции, тэги и якоря в PF
  4. FreeBSD. Условная маршрутизация средствами PF <- Вы здесь

Введение


В предыдущих статьях мы разобрали базовые элементы конфигурации PF. Создали конфигурации, способные защитить сервер в Интернет и простейший офис.


В этой статье разберем возможности PF и FreeBSD в части продвинутой маршрутизации. Policy Based Routing (PBR), Source Based Routing (SBR), условная маршрутизация, маршрутизация на основе политик, все эти понятия, по сути, равнозначны и описывают одну возможность. Выбирать маршруты, либо таблицы маршрутизации для трафика на основе правил фаервола.



Можно, к примеру, отправить разные локальные сети через разных провайдеров, либо устроить распределение трафика между ВПН каналами. Рассмотрим оба эти варианта.


Для уменьшения файлов конфигурации и большей наглядности, в этой статье правила фильтрации будут максимально упрощены.


Задачи


Возьмем офис из прошлой статьи и добавим туда второго провайдера.


  1. Нужно отправить трафик от одной локальной сети через одного провайдера.
  2. Вторую локальную сеть через второго провайдера.
  3. Так же нужно пробросить веб-порты сервера через обоих провайдеров так, чтобы были доступны оба IP адреса.

В эту схему добавим второй офис, у которого так же 2 провайдера. Между офисами есть 2 туннеля.


  1. Нужно пустить трафик до второго офиса через ВПН1
  2. кроме сервера, который должен работать через ВПН2

Решим эти задачи средствами исключительно PF и отдельно средствами множественных таблиц маршрутизации.


Получается вот такая схема окончательной настройки:



Подготовка


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


Итак, как всегда, начинаем с макросов, таблиц, опций
 ######## macros section ########ExtIf1="re0" # внешний интерфейсExtIf1Ip1="172.16.1.1" # Внешний IP адрес первогоExtIf1Gw="172.16.1.254" # шлюз первого провайдераExtIf2="re1" # второй внешний интерфейсExtIf2Ip1="172.17.1.1" # Внешний IP адрес второго провайдераExtIf2Gw="172.17.1.254" # шлюз второго провайдераIntIf1="em0" # локальный 1IntIf1Ip="172.16.2.254" # IP локального 0IntIf1Net="172.16.2.0/24"IntIf2="em1" # локальный 2IntIf2Ip="172.16.3.254" # IP локального 1IntIf2Net="172.16.3.0/24"IntSRV="172.16.3.1"# туннелиTun0="gif0"Tun0Ip="10.0.0.1"Tun0Gw="10.0.0.2"Tun1="gif1"Tun1Ip="10.0.1.1"Tun1Gw="10.0.1.2"# удалённый офисRemoteExtIp1="172.16.1.2"RemoteExtIp2="172.17.1.2"RemoteSRV="172.17.2.1"# разрешенные порты для исходящих соединенийpermit_tcp_ports="22,53,80,443"permit_udp_ports="53,123"######## table section ######### сети удалённого офисаtable <remote_nets> { 172.17.2.0/24 }######## options section ######### разрываем соединения с ответом, а не просто дропаем пакетыset block-policy return# пропускаем проверку на локальной петле, там фильтрация не нужнаset skip on lo0 #scrub section# нормализация всего входящего трафикаscrub in all #Queueing section# в данный момент пустая

Дальше трансляции. Так как у нас 2 провайдера, nat нужен для каждого.


#NAT для клиентовnat pass on $ExtIf1 from { $IntIf1Net, $IntIf2Net } to any -> $ExtIf1Ip1nat pass on $ExtIf2 from { $IntIf1Net, $IntIf2Net } to any -> $ExtIf2Ip1

Пробросы портов http(s) и порт 2022 с каждого провайдера на порт 22 внутреннего сервера.


#Проброс на серверrdr on $ExtIf1 proto tcp from any to $ExtIf1Ip1 port { http, https} tag DSTNAT -> $IntSRVrdr on $ExtIf2 proto tcp from any to $ExtIf2Ip1 port { http, https} tag DSTNAT -> $IntSRVrdr on $ExtIf1 proto tcp from any to $ExtIf1Ip1 port { 2022 } tag DSTNAT -> $IntSRV port sshrdr on $ExtIf2 proto tcp from any to $ExtIf2Ip1 port { 2022 } tag DSTNAT -> $IntSRV port ssh

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


Маршрутизация ответных пакетов


Основная проблема работы через двух провайдеров в том, что в нашей таблице маршрутизации есть только один шлюз по умолчанию. Во FreeBSD нет метрик маршрутов, так что дефолтный маршрут действительно может быть только один. Соответственно, наш роутер попытается ответить именно с того интерфейса, куда указывает этот маршрут. То есть, получили пакет на re1 и отправили с re0 ответ. Так работать не будет.


Решается вопрос очень легко. У правила pass в PF есть опция reply-to (Interface [Gateway]), которая указывает, куда отправить ответный пакет. Все опции маршрутизации лучше указывать на входящих правилах.


Как это работает:


  1. Приходит первый пакет, проходит по правилу.
  2. PF создаёт стейт, соединение, по которому пакеты пойдут как в одну, так и в другую сторону.
  3. Пакет отправляется по адресу. На него приходит ответ.
  4. И вот этот ответ маршрутизируется туда, куда указано в опции reply-to.

Разрешающие правила из Интернет.

pass in quick on $ExtIf1 proto tcp to { $ExtIf1Ip1 } port 22

И для второго интерфейса:


pass in quick on $ExtIf2 reply-to ( $ExtIf2 $ExtIf2Gw ) proto tcp to { $ExtIf2Ip1 } port 22

Этого достаточно, чтобы полученные на 22 порту со второго провайдера соединения работали.


Добавим icmp


pass in quick on $ExtIf1 proto icmppass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) proto icmp

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


pass in quick on $ExtIf1 tagged DSTNATpass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) tagged DSTNAT

Теперь разрешения ходить к нам со второго филиала:


pass in quick on $ExtIf1 from $RemoteExtIp1 tag PASSpass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) from $RemoteExtIp2 tag PASS

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


Маршрутизация средствами PF


Нам нужно отправить трафик от первой локальной сети через первого провайдера, а от второй через второго. Для этого у правила pass есть опция route-to (Interface [Gateway]). Указывает, куда отправлять трафик, прошедший по этому правилу.


Правило, для маршрутизации к серверу через второй туннель:


pass in quick on $IntIf1 route-to ( $Tun1 $Tun1Gw ) from ($IntIf1:network) to $RemoteSRV tag PASSpass in quick on $IntIf2 route-to ( $Tun1 $Tun1Gw ) from ($IntIf2:network) to $RemoteSRV tag PASS

Разрешаем ходить в Интернет первой сети:


pass in on $IntIf1 from { ($IntIf1:network) } tag PASS

И второй, указывая, что нужно пойти через второго провайдера:


pass in quick on $IntIf2 route-to ( $ExtIf2 $ExtIf2Gw ) from { $IntIf2Net}

Если есть необходимость, можно сменить шлюз на выходе из интерфейса правилом такого вида:


pass out quick on $ExtIf1 route-to ($ExtIf1 $ExtIf1SomeOtherGw) \         proto tcp from $ExtIf1 to $SomeServer port 80 

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


Итоговая конфигурация
######## macros section ########ExtIf1="re0" # внешний интерфейсExtIf1Ip1="172.16.1.1" # Внешний IP адрес первогоExtIf1Gw="172.16.1.254" # шлюз первого провайдераExtIf2="re1" # второй внешний интерфейсExtIf2Ip1="172.17.1.1" # Внешний IP адрес второго провайдераExtIf2Gw="172.17.1.254" # шлюз второго провайдераIntIf1="em0" # локальный 1IntIf1Ip="172.16.2.254" # IP локального 0IntIf1Net="172.16.2.0/24"IntIf2="em1" # локальный 2IntIf2Ip="172.16.3.254" # IP локального 1IntIf2Net="172.16.3.0/24"IntSRV="172.16.3.1"# туннелиTun0="gif0"Tun0Ip="10.0.0.1"Tun0Gw="10.0.0.2"Tun1="gif1"Tun1Ip="10.0.1.1"Tun1Gw="10.0.1.2"# удалённый офисRemoteExtIp1="172.16.1.2"RemoteExtIp2="172.17.1.2"RemoteSRV="172.17.2.1"# разрешенные порты для исходящих соединенийpermit_tcp_ports="22,53,80,443"permit_udp_ports="53,123"######## table section ######### сети удалённого офисаtable <remote_nets> { 172.17.2.0/24 }######## options section ######### разрываем соединения с ответом, а не просто дропаем пакетыset block-policy return# пропускаем проверку на локальной петле, там фильтрация не нужнаset skip on lo0 #scrub section# нормализация всего входящего трафикаscrub in all #Queueing section# в данный момент пустая######## nat section #########NAT для клиентовnat pass on $ExtIf1 from { $IntIf1Net, $IntIf2Net } to any -> $ExtIf1Ip1nat pass on $ExtIf2 from { $IntIf1Net, $IntIf2Net } to any -> $ExtIf2Ip1#Проброс на серверrdr on $ExtIf1 proto tcp from any to $ExtIf1Ip1 port { http, https} tag DSTNAT -> $IntSRVrdr on $ExtIf2 proto tcp from any to $ExtIf2Ip1 port { http, https} tag DSTNAT -> $IntSRVrdr on $ExtIf1 proto tcp from any to $ExtIf1Ip1 port { 2022 } tag DSTNAT -> $IntSRV port sshrdr on $ExtIf2 proto tcp from any to $ExtIf2Ip1 port { 2022 } tag DSTNAT -> $IntSRV port ssh######## filtering section ######### запрет всего по умолчанию, помним, что это не означает окончание обработки пакета.block  log all## Разрешения для входящих пакетов из Интернет. ##pass in quick from 192.168.11.0/24 tag PASS# разрешаем ssh и icmppass in quick on $ExtIf1 proto tcp to { $ExtIf1Ip1 } port 22pass in quick on $ExtIf2 reply-to ( $ExtIf2 $ExtIf2Gw ) proto tcp to { $ExtIf2Ip1 } port 22 pass in quick on $ExtIf1 proto icmppass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) proto icmppass in quick on $ExtIf1 tagged DSTNATpass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) tagged DSTNAT# разрешаем всё с нашего второго филиалаpass in quick on $ExtIf1 from $RemoteExtIp1 tag PASSpass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) from $RemoteExtIp2 tag PASS## Разрешения для внутренних сетей ### маршрутизируем трафик на удалённый серверpass in quick on $IntIf1 route-to ( $Tun1 $Tun1Gw ) from ($IntIf1:network) to $RemoteSRV tag PASSpass in quick on $IntIf2 route-to ( $Tun1 $Tun1Gw ) from ($IntIf2:network) to $RemoteSRV tag PASS# разрешаем ходить в удалённую сетьpass in quick on $IntIf1 from $IntIf1Net to <remote_nets> tag PASSpass in quick on $IntIf2 from { $IntIf2Net }  to <remote_nets> tag PASSpass in quick on $IntIf1 from { ($IntIf1:network) } tag PASSpass in quick on $IntIf2 route-to ( $ExtIf2 $ExtIf2Gw ) from { $IntIf2Net }## И для туннелей ##pass in quick on $Tun0 from <remote_nets> tag PASSpass in quick on $Tun1 reply-to ( $Tun1 $Tun1Gw) from <remote_nets> tag PASS## разрешаем исходящий трафик ##pass out on { $IntIf1,$IntIf2 }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto tcp to any port { $permit_tcp_ports }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto udp to any port { $permit_udp_ports }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto icmppass out tagged DSTNATpass out tagged PASS

Альтернативные таблицы маршрутизации


Для упрощения правил фаервола в ОС FreeBSD можно использовать multiple routing tables. Для включения дополнительных таблиц достаточно в /etc/loader.conf прописать строку


net.fibs=2

И перезагрузить сервер. Это число таблиц маршрутизации. Максимально 16.


После этого, пользуясь утилитой setfib можно запускать приложения в контексте соответствующей таблицы.


Основная таблица маршрутизации системы нулевая


# netstat -4 -rn# setfib 0 netstat -4 -rn

Команды равнозначны. Основная таблица (лишние строки удалены):


Routing tablesInternet:Destination        Gateway            Flags     Netif Expiredefault            172.16.1.254       UGS         re010.0.0.2           link#7             UH         gif010.0.1.2           link#8             UH         gif1127.0.0.1          link#5             UH          lo0172.16.1.0/24      link#1             U           re0172.16.2.0/24      link#2             U           em0172.16.3.0/24      link#3             U           em1172.17.1.0/24      link#4             U           re1172.17.2.0/24      10.0.0.2           UGS        gif0

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


# setfib 1 netstat -4 -rn

Альтернативная таблица маршрутизации будет выглядеть вот так:


Routing tables (fib: 1)Internet:Destination        Gateway            Flags     Netif Expiredefault            172.17.1.254       UGS         re110.0.0.2           link#7             UH         gif010.0.1.2           link#8             UH         gif1127.0.0.1          link#5             UH          lo0172.16.1.0/24      link#1             U           re0172.16.2.0/24      link#2             U           em0172.16.3.0/24      link#3             U           em1172.17.1.0/24      link#4             U           re1172.17.2.0/24      10.0.1.2           UGS        gif1

Маршрут по умолчанию через второго провайдера, маршрут до удалённой сети через второй туннель.


Для маршрутизации по нужной таблице нужно в правиле PF указать её номер опцией rtable:


pass in quick on $IntIf1 from ($IntIf1:network) to $RemoteSRV rtable 1 tag PASSpass in quick on $IntIf2 from ($IntIf2:network) to $RemoteSRV rtable 1 tag PASSpass in quick on $IntIf2 from { $IntIf2Net } rtable 1 tag PASS

Получившийся конфигурационный файл
######## macros section ########ExtIf1="re0" # внешний интерфейсExtIf1Ip1="172.16.1.1" # Внешний IP адрес первогоExtIf1Gw="172.16.1.254" # шлюз первого провайдераExtIf2="re1" # второй внешний интерфейсExtIf2Ip1="172.17.1.1" # Внешний IP адрес второго провайдераExtIf2Gw="172.17.1.254" # шлюз второго провайдераIntIf1="em0" # локальный 1IntIf1Ip="172.16.2.254" # IP локального 0IntIf1Net="172.16.2.0/24"IntIf2="em1" # локальный 2IntIf2Ip="172.16.3.254" # IP локального 1IntIf2Net="172.16.3.0/24"IntSRV="172.16.3.1"# туннелиTun0="gif0"Tun0Ip="10.0.0.1"Tun0Gw="10.0.0.2"Tun1="gif1"Tun1Ip="10.0.1.1"Tun1Gw="10.0.1.2"# удалённый офисRemoteExtIp1="172.16.1.2"RemoteExtIp2="172.17.1.2"RemoteSRV="172.17.2.1"# разрешенные порты для исходящих соединенийpermit_tcp_ports="22,53,80,443"permit_udp_ports="53,123"######## table section ######### сети удалённого офисаtable <remote_nets> { 172.17.2.0/24 }######## options section ######### разрываем соединения с ответом, а не просто дропаем пакетыset block-policy return# пропускаем проверку на локальной петле, там фильтрация не нужнаset skip on lo0 #scrub section# нормализация всего входящего трафикаscrub in all #Queueing section# в данный момент пустая######## nat section #########NAT для клиентовnat pass on $ExtIf1 from { $IntIf1Net, $IntIf2Net } to any -> $ExtIf1Ip1nat pass on $ExtIf2 from { $IntIf1Net, $IntIf2Net } to any -> $ExtIf2Ip1#Проброс на серверrdr on $ExtIf1 proto tcp from any to $ExtIf1Ip1 port { http, https} tag DSTNAT -> $IntSRVrdr on $ExtIf2 proto tcp from any to $ExtIf2Ip1 port { http, https} tag DSTNAT -> $IntSRVrdr on $ExtIf1 proto tcp from any to $ExtIf1Ip1 port { 2022 } tag DSTNAT -> $IntSRV port sshrdr on $ExtIf2 proto tcp from any to $ExtIf2Ip1 port { 2022 } tag DSTNAT -> $IntSRV port ssh######## filtering section ######### запрет всего по умолчанию, помним, что это не означает окончание обработки пакета.block  log all## Разрешения для входящих пакетов из Интернет. ##pass in quick from 192.168.11.0/24 tag PASS# разрешаем ssh и icmppass in quick on $ExtIf1 proto tcp to { $ExtIf1Ip1 } port 22pass in quick on $ExtIf2 reply-to ( $ExtIf2 $ExtIf2Gw ) proto tcp to { $ExtIf2Ip1 } port 22 pass in quick on $ExtIf1 proto icmppass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) proto icmppass in quick on $ExtIf1 tagged DSTNATpass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) tagged DSTNAT# разрешаем всё с нашего второго филиалаpass in quick on $ExtIf1 from $RemoteExtIp1 tag PASSpass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) from $RemoteExtIp2 tag PASS## Разрешения для внутренних сетей ### маршрутизируем трафик на удалённый серверpass in quick on $IntIf1 from ($IntIf1:network) to $RemoteSRV rtable 1 tag PASSpass in quick on $IntIf2 from ($IntIf2:network) to $RemoteSRV rtable 1 tag PASS# разрешаем ходить в удалённую сетьpass in quick on $IntIf1 from $IntIf1Net to <remote_nets> tag PASSpass in quick on $IntIf2 from $IntIf2Net  to <remote_nets> tag PASSpass in quick on $IntIf1 from { ($IntIf1:network) } tag PASSpass in quick on $IntIf2 from { $IntIf2Net } rtable 1 tag PASS## И для туннелей ##pass in quick on $Tun0 from <remote_nets> tag PASSpass in quick on $Tun1 reply-to ( $Tun1 $Tun1Gw) from <remote_nets> tag PASS## разрешаем исходящий трафик ##pass out on { $IntIf1,$IntIf2 }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto tcp to any port { $permit_tcp_ports }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto udp to any port { $permit_udp_ports }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto icmppass out tagged DSTNATpass out tagged PASS

Конфигурация второго филиала
######## macros section ########ExtIf1="re0" # внешний интерфейсExtIf1Ip1="172.16.1.2" # Внешний IP адрес первогоExtIf1Gw="172.16.1.254" # шлюз первого провайдераExtIf2="re1" # второй внешний интерфейсExtIf2Ip1="172.17.1.2" # Внешний IP адрес второго провайдераExtIf2Gw="172.17.1.254" # шлюз второго провайдераIntIf1="em0" # локальный 1IntIf1Ip="172.17.2.254" # ip локального 0IntIf1Net="172.17.2.0/24"IntSRV="172.17.2.1"# туннелиTun0="gif0"Tun0Ip="10.0.0.2"Tun0Gw="10.0.0.1"Tun1="gif1"Tun1Ip="10.0.1.2"Tun1Gw="10.0.1.1"# удалённый офисRemoteExtIp1="172.16.1.1"RemoteExtIp2="172.17.1.1"RemoteSRV="172.17.3.1"# разрешенный порты для исходящих соединенийpermit_tcp_ports="22,53,80,443"permit_udp_ports="53,123"######## table section ######### сети удалённого офисаtable <remote_nets> { 172.16.2.0/24, 172.16.3.0/24 }######## options section ######### разрываем соединения с ответом, а не просто дропаем пакетыset block-policy return# пропускаем проверку на локальной петле, там фильтрация не нужнаset skip on lo0 #scrub section# нормализация всего входящего трафикаscrub in all #Queueing section# в данный момент пустая######## nat section #########NAT для клиентовnat pass on $ExtIf1 from { $IntIf1Net } to any -> $ExtIf1Ip1nat pass on $ExtIf2 from { $IntIf1Net } to any -> $ExtIf2Ip1#Проброс на серверrdr on $ExtIf1 proto tcp from any to $ExtIf1Ip1 port { http, https} tag DSTNAT -> $IntSRVrdr on $ExtIf2 proto tcp from any to $ExtIf2Ip1 port { http, https} tag DSTNAT -> $IntSRVrdr on $ExtIf1 proto tcp from any to $ExtIf1Ip1 port { 2022 } tag DSTNAT -> $IntSRV port sshrdr on $ExtIf2 proto tcp from any to $ExtIf2Ip1 port { 2022 } tag DSTNAT -> $IntSRV port ssh######## filtering section ######### запрет всего по умолчанию, помним, что это не означает окончание обработки пакета.block  log all## Разрешения для входящих пакетов из Интернет. ##pass in quick from 192.168.11.0/24 tag PASS# разрешаем ssh и icmppass in quick on $ExtIf1 proto tcp to { $ExtIf1Ip1 } port 22pass in quick on $ExtIf2 reply-to ( $ExtIf2 $ExtIf2Gw ) proto tcp to { $ExtIf2Ip1 } port 22 pass in quick on $ExtIf1 proto icmppass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) proto icmppass in quick on $ExtIf1 tagged DSTNATpass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) tagged DSTNAT# разрешаем всё с нашего второго филиалаpass in quick on $ExtIf1 from $RemoteExtIp1 tag PASSpass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) from $RemoteExtIp2 tag PASS## Разрешения для внутренних сетей ##pass in on $IntIf1 from { ($IntIf1:network) } tag PASS## И для туннелей ##pass in quick on $Tun0 from <remote_nets> tag PASSpass in quick on $Tun1 reply-to ( $Tun1 $Tun1Gw) from <remote_nets> tag PASS## разрешаем исходящий трафик ##pass out on { $IntIf1,$IntIf2 }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto tcp to any port { $permit_tcp_ports }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto udp to any port { $permit_udp_ports }pass out quick on { $ExtIf1, $ExtIf2, gif0, gif1 } proto icmppass out tagged DSTNATpass out tagged PASS

Заключение


Мы разобрали, как обеспечить симметричную маршрутизацию через провайдеров и туннели. Source Based Routing это гибкий инструмент в руках администратора, сильно упрощающий жизнь в сложных сетях.


У нас в ИКС поддерживается условная маршрутизация средствами PF, что позволяет легко встроить его в самую сложную инфраструктуру, имея базовое понимание принципов построения сетей.


В следующей статье будет погружение в сетевой стэк FreeBSD и его взаимодействие с файрволами. А пока я буду ее готовить, у вас хватит времени протестировать ИКС в своей корпоративной сети. Триал 35 дней, бонус в виде доброжелательной бесплатной техподдержки.

Подробнее..

FreeBSD. Путь сетевого пакета внутри ядра

23.04.2021 16:06:54 | Автор: admin
  1. Файрвол PF в ОС FreeBSD
  2. FreeBSD. Фильтрация трафика PF
  3. FreeBSD. трансляции, тэги и якоря в PF
  4. FreeBSD. Условная маршрутизация средствами PF
  5. FreeBSD. Путь сетевого пакета внутри ядра. <- Вы здесь

В прошлых статьях мы разобрали PF, его основные возможности, и попробовали применить этот файрвол в различных ситуациях. Однако, простого знания, какие правила и в какой последовательности надо поставить в конфигурации, для достижения цели не всегда достаточно. Многое становится понятно только если взглянуть чуть глубже: на уровень ядра ОС, и того, как файрволы с ним взаимодействуют.



Мы не будем разбирать уровни драйвера сетевой карты (2 уровень модели OSI), а сразу поднимемся на 3 уровень, где работает tcp/ip стек ядра.


Для каждого входящего пакета первым делом создаётся объект mbuf. Это базовая структура данных ядра, в которой хранится как сам пакет, так и все метаданные относительно него: aдрес источника и назначения, маршрут (next hop), ttl, метки, и прочие атрибуты. В таких же объектах хранятся буферы локальных сокетов. Состоит он из заголовков и небольшого внутреннего буфера данных. Затем этот mbuf начинает путешествие по стэку ядра.


Вот упрощенная диаграмма пути пакета:

ip_input()


На выходе со второго уровня пакет попадает в функцию ip_input().


  • Проверяется ttl и прочие параметры.
  • Затем проходит по условиям ALTQ (система приоритезации трафика), если такие назначены.
  • IPSEC_CAPS(), если назначен перехватчик пакетов ipsec, то пакет будет передан ему, и, если он не вернет пакет, обработка завершится. На выходе демон ipsec положит расшифрованный пакет обратно в начало ip_input(), так что обработка заканчивается.
  • pfil. Здесь пакет передаётся файрволам в порядке регистрации. То есть, кто первый был загружен, того и тапки. Если в rc.conf первым указан PF, то в него пакет попадёт в первую очередь.
  • Проверяется назначение пакета. Если назначение пакета не локальный IP, то он передаётся функции ip_forward(). Либо обработка продолжается.
  • Проходит попытка сборки (reassemble), если пакет фрагментирован.
  • Локальный пакет отправляется в IPSEC_INPUT().
  • Отправка в локальный сокет, если это возможно. Если невозможно пакет отбрасывается с отправкой обратно icmp error через функцию ip_output().

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


ip_forward()


Здесь происходят различные проверки на возможность отправки.


  • Происходит вызов функции обработки ipsec IPSEC_FORWARD().
  • Различные проверки. Например, установлен ли параметр net.inet.ip.forwarding в значение 1. Если что-то пошло не так, то отправляется icmp error в функцию ip_output().
  • Если пакет должен выйти с того же интерфейса, с которого пришел, источнику будет отправлен icmp_redirect, если источник в той же сети.
  • Декрементится ttl.
  • Пакет отправляется в ip_output().

ip_output()


Функция подготовки и отправки пакета через сетевой интерфейс.


  • Поиск маршрута. Если пакет принадлежит потоку, то маршрут будет взят из кэша маршрутов.
  • Передача пакета обработчику ipsec IPSEC_OUTPUT().
  • pfil второй вызов файрволов в порядке регистрации.
  • Проверка, изменилось ли назначение (dst), или таблица маршрутизации (fib) пакета после выхода из файрволов. Если изменилось, то возврат в начало. Однако, второй раз для пакета уже не будут вызваны обработчики файрволов.
  • При необходимости, происходит попытка фрагментации пакетов.
  • Пакет передаётся на второй уровень для отправки.

Выводы


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


К примеру, в предыдущих статьях мы использовали правила pf с ключевыми словами in и out.


pass in on re0 inet...

Такие правила будут обрабатываться только при вызове ip_input. Соответственно правила с out в ip_output.


pass out quick on $ExtIf2 ...

В сочетании с anchors это позволяет добиться впечатляющей оптимизации даже при большом количестве правил.


Ключевые слова route-to или reply-to поменяют next hop для соответствующих пакетов:


pass in quick on $ExtIf2 reply-to ($ExtIf2 $ExtIf2Gw ) tagged DSTNATpass in quick on $IntIf1 route-to ( $Tun1 $Tun1Gw ) from ($IntIf1:network)

Или укажет необходимость обработки пакета в контексте таблицы маршрутизации опцией rtable:


pass in quick on $IntIf2 from { $IntIf2Net } rtable 1 tag PASS

Заключение


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


Описанные схемы мы применяем для создания нашего ИКСа универсального шлюза безопасности. Защита сети, прокси, встроенный антивирус, фильтрация контента по спискам Минюста, контроль доступа и учет трафика, VPN для удаленной работы, почта, ftp-, web- и jabber-сервер, IP-телефония Тестируйте все и сразу, скачав ИКС с официального сайта. Триал 35 дней, есть бесплатная версия на 9 пользователей.

Подробнее..

Работа с пакетами в изолированной среде. Использование zfs datasets и jailов

29.04.2021 16:13:12 | Автор: admin
В предыдущей статье мы рассказали, как работает система дистрибуции плагинов в новой версии ИКС. Сегодня речь пойдет о том, как разворачивать отдельно взятый плагин в системе.
Предпочтительнее всего, чтобы каждый плагин запускался в изолированной среде, подготовленной специально с учетом требований конкретного плагина. Причем, в среде, которая позволит взаимодействовать системе с плагином и плагинами между собой. Далее, для понимания, что это применимо к стандартной работе с пакетами, мы будем использовать термин пакет



Что выбрать?



Что используют для этого в большинстве случаев? Конечно, Docker!

pkg install docker

pkg: No packages available to install matching 'docker' have been found in the repositories



У Dockerа нет официальной поддержки FreeBSD, и такого пакета в репозитории конечно же нет. В предыдущей версии ИКС мы использовали неофициальный самописный порт dockera, но его поддержка достаточно трудоемка, а кроме того всплывает немало подводных камней при его использовании.



Но у FreeBSD есть собственная, изначальная поддержка изолированных сред. Она называется jail и для наших целей подходит не хуже, чем docker. Стоит заметить, что она появилась намного раньше, чем контейнеры в Linux. Посмотрим как настраивается типичный контейнер (клетка в терминологии bsd).

Подготовка датасета и установка пакета



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

По умолчанию в системе есть датасет с базовым миром (world) FreeBSD, от которого создаем снапшот (снимок):

zfs snapshot zp000111/freebsd@snap

zp000111 это имя нашего корневого пула файловой системы. Мы можем посмотреть его через команду zfs mount. zp000111/freebsd имя датасета, в котором хранится мир. Имя снимка указывается через символ @. Этот снимок мы создаем один раз и в дальнейшем все датасеты для пакетов будут наследоваться от него.

От этого снимка мы создаем клон, в котором будет жить наш пакет:

zfs clone zp000111/freebsd@snap zp000111/packets/<packet_name>

Предварительно сделаем монтирование датасета к текущей системе:

mount -t zfs zp000111/packets/<packet_name> /mnt/packets/<packet_name>

И теперь мы можем выполнить в него установку нужного пакета:

pkg -r /mnt/packets/<packet_name> install -y <packet_name>

Ключ -r обеспечивает установку внутрь указанной директории.
Если есть необходимость, можем установить другие нужные пакеты, например mc для удобной работы внутри пакета.

Выполняем размонтирование. Это не обязательно, но полезно, чтобы в дальнейшем пройти весь путь для запуска клетки:

umount /mnt/packets/<packet_name>

Подготовка клетки



Для начала необходимо определиться с хранением данных пакета в рабочей системе. Пусть это будет директория /usr/local/share/packets
Добавляем в нее директорию <packet_name>

mkdir /usr/local/share/packets/<packet_name>

Теперь создаем файл с точкой монтирования <packet_name>.fstab. В нем находится информация для клетки о том, какая директория в смонтированном датасете синхронизируется:

/usr/local/share/packets/<packet_name> /mnt/packets/<packet_name>/usr/local/share/data nullfs rw 0 0

Так мы сообщаем, что к директории в нашей рабочей системе будет подключена директория /usr/local/share/data в нашем примонтированном пакете. Это клетка (файловая система nullfs), разрешено как чтение, так и запись для обмена данными с внешней системой (rw), сохранение информации о файловой системе и проверка ее целостности не требуется (0 и 0).

Сохраняем файл в удобном месте, например в /usr/local/etc/packets/<packet_name>/<packet_name>.fstab

Теперь нам необходимо настроить конфигурационный файл для клетки нашего пакета.
Типичный конфиг с минимальными настройками выглядит следующим образом
host.hostname = "<packet_name>";
path = " /mnt/packets/<packet_name>";
interface = em0;
ip4.addr = 192.168.0.1;
allow.raw_sockets = 1;
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown;";
exec.clean;
enforce_statfs = 0;
mount.devfs;
mount.fstab = /usr/local/etc/packets/<packet_name>/<packet_name>.fstab;


В качестве интерфейса указываем одну из сетевых карт в системе. Присваиваем ему ip-адрес, который будет доступен снаружи. Также, для работы сетевых функций, таких как ping или traceroute разрешаем опцию allow.raw_sockets.

Раздел команд exec указывает какие команды выполнять для запуска и отключения системы внутри клетки. Поскольку это стандартный мир FreeBSD, то указываем базовые пути для включения и выключения системы.

Последними тремя опциями мы разрешаем монтирование внешних систем, подключаем физические устройства и монтируем во внешнюю папку нашу папку с данными внутри клетки согласно созданному файлу fstab.

Сохраняем наш файл в удобном месте, например в той же директории /usr/local/etc/packets/<packet_name>/<packet_name>.jail.conf

Монтирование пакета и запуск клетки



У нас все готово, чтобы запустить пакет в клетке. Начнем с того, что примонтируем датасет пакета:

mount -t zfs zp000111/packets/<packet_name> /mnt/packets/<packet_name>

Теперь можем запускать клетку:

jail -f /usr/local/etc/packets/<packet_name>/<packet_name>.jail.conf -c <packet_name>

Опцией -r мы указываем путь до конфигурационного файла, а -c что необходимо создать новую клетку с указанным именем.

Наша клетка запустилась, можем проверить это:

jls -j <packet_name>

Будет выведена строка с именем клетки и ее id в системе jailов.

Теперь, если нам необходимо войти в рабочую систему пакета, выполним команду:

jexec <jail_id> /bin/tcsh

Для остановки клетки и отмонтирования пакета выполняем обратные команды:

jail -f /usr/local/etc/packets/<packet_name>/<packet_name>.jail.conf -r <packet_name>

umount /mnt/packets/<packet_name>


Притом все данные, которые были сохранены пакетом в папке /usr/local/share/data своей файловой системы, будут доступны по пути /usr/local/share/packets/<packet_name> во внешней системе.

Итого



Таким образом, мы создали изолированную среду для нашего пакета.
Клетка снаружи будет пинговаться по указанному адресу, и, соответственно, все сетевые сервисы, поднятые в ней будут по нему также доступны. А поскольку пользовательские данные хранятся во внешней папке рабочей системы, то их легко хранить и резервировать.

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

На этом на сегодня все, спасибо за внимание!
Подробнее..

Облачное хранилище компонентов новый этап в развитии Интернет-шлюза ИКС

24.02.2021 16:18:54 | Автор: admin

Начало


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

Наше решение, Интернет Контроль Сервер, также относится к классу UTM-продуктов, объединяя в себе функции защиты сети, управления Интернет-подключениями, контентной фильтрации и многие другие.

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

С этой точки зрения более интересным продуктом является шлюз UTM, который может быть построен самим пользователем, исходя из реальных задач компании.

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

Исторически сложившаяся экосистема FreeBSD нас устраивала и была достаточно хорошо изучена. В качестве наиболее подходящего метода дистрибуции пакетов мы выбрали традиционную утилиту установки пакетов во FreeBSD pkg. В нашем облаке собирается репозиторий, а на клиенте прописывается доступ к нему в традиционном конфиге pkg.

Что мы планировали сделать


Система должна представлять собой конструктор в виде ядра, на которое нанизываются компоненты. Пользователь получает дистрибутив, в который входят только минимально необходимые плагины (router, fierwall и т.д.). Остальное устанавливается по мере необходимости.

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

  • система модифицированное ядро FreeBSD
  • core специальный пакет с настройками продукта, являющийся родительской зависимостью для всех плагинов
  • плагины каждый из них это собранный пакет, который штатным образом устанавливается утилитой pkg

Структура плагинов


Как уже говорилось, плагин это обычный пакет. Он может быть установлен утилитой pkg.
Для того, чтобы поддерживать обратную совместимость, решили, что новая мажорная версия плагина будет отдельным пакетом, не связанным с предыдущей мажорной версией. Например plugin-v1 и plugin-v2. Причина у них могут быть совершенно разные зависимости, возможно даже противоречащие друг другу.

У минорной версии просто меняется номер версии пакета, тут все как обычно.

Работа с плагинами


Для того, чтобы система работала так, как нам нужно, была создана утилита-надстройка над pkg.

Основным инструментом для работы будут команды pkg query и pkg rquery.

Первая собирает информацию по установленным в системе плагинам, вторая обращается к репозиторию.

Для того, чтобы утилита контролировала корректность выполнения команд в системе, выполнение каждой команды проверяется на код возврата. Если получен код возврата 0, значит команда выполнилась, если что-то иное, значит возникла ошибка. Таким образом можно отследить, к примеру, проблемы с сетевым соединением.

Здесь возник интересный нюанс. Если выполнить например поиск всех пакетов по шаблону:



то в случае, если ни один установленный пакет не попадает под условие шаблона, возвращается ошибка 69 без сообщения об ошибке. Разработчики утилиты посчитали, что если поиск ничего не вернул, то это ненормальное поведение. Ну, ОК. Пришлось обрабатывать такой случай специальным образом.

Проблемы обновления


Дальше начинаются проблемы с версионностью при обновлении плагинов.

Во-первых, утилита pkg при выполнении команды pkg upgrade <pkg_name> также обновляет ВСЕ прямые зависимости пакета, такое поведение заложено разработчиками. Но в нашем случае это включает в себя обновление и core, если для него вышла минорная версия, что нежелательно, поскольку core меняет системные параметры, а также требует перезагрузки системы после обновления.

То есть, если у нас в системе установлены 2 пакета pkg-1 и pkg-2, и в зависимостях pkg-1 указан pkg-2, то если мы выполним команду pkg upgrade pkg-1, то обновится также и pkg-2.

Пойдем другим путем.

Построим дерево зависимостей пакета вверх и вниз.
Находим имена всех пакетов, от которых зависит наш пакет:

pkg rquery %rn <pkg_name>

Теперь всех пакетов, которые зависят от нашего пакета:

pkg rquery %dn <pkg_name>

Удалим все зависимые от пакета сущности. Далее начнем удалять сущности, от которых зависит наш пакет вверх по дереву до тех пор, пока не окажемся в core (его удалять не будем конечно). Теперь можно восстановить дерево, установив последние минорные версии всех пакетов в нем.



Например, на рисунке выше мы видим, что пакет ics-plugin-a-v1 зависит от плагина ics-plugin-b-v1. Если нам нужно обновить пакет до версии ics-plugin-v2 то это повлечет и обновление пакета ics-plugin-b-v1, для которого существуют 2 мажорные версии ics-plugin-b-v2 и ics-plugin-b-v3. При этом ни одна из них не поддерживает плагин ics-plugin-c-v1. То есть, при обновлении сначала будет установлен пакет ics-plugin-b-v2 или -v3, затем ics-plugin-a-v1, а ics-plugin-c будет удален и установлен не будет.

Кроме того мажорная версия может быть у core, в этом случае весь состав плагинов должен быть обновлен для соответствия.



Для установки пакета ics-plugin-a, который зависит от ics-core-v2 нужно обновить ics-core, после чего установятся только пакеты мажорных версий, которые зависят от ics-core-v2.

Резервное копирование базы


При работе с репозиторием возможна ситуация, когда во время обновления пропадет связь (возникает системная ошибка код 70 или 3). Кроме того, для того, чтобы корректно удалить плагин, системе необходима действующая база pkg в системе. При выполнении команды pkg update, если нет связи, база сообщает об ошибке и даже локально не выполняет свои функции пока апдейт не выполнится корректно.

Чтобы избежать таких ситуаций, воспользуемся утилитой pkg backup. Перед любой операцией, в процессе которой есть вероятность не получить нужный результат, сохраняем базу:

pkg backup -d <backup_dir>

Если операция завершилась некорректно, возвращаем базу на место:

pkg backup -r <backup_dir>

Ядро


Тут пока все достаточно просто. Если вышло обновление для ядра, то:

  1. скачиваем новый образ ядра в архиве
  2. создаем новый датасет в zfs
  3. монтируем датасет в систему
  4. распаковываем образ
  5. устанавливаем специальный пакет с нужными опциями ядра для нормальной работы
  6. прописываем новый образ как загрузочный
  7. ???

Profit?


Пока еще нет.

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

Но (как всегда) есть нюанс.
У нас еще не установлено ядро будущей версии, а установить плагин для другой версии мы не можем. Если же мы загрузим систему с нового образа, то не сможем получить доступ к репозиторию (по исторически-техническим причинам маршрутизация это тоже плагин).

Что делать?

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

Затем наша утилита сгенерирует файл конфига репозитория с указанием пути до папки с распакованным архивом.

Для FreeBSD это будет файл <repo_name>.conf в папке /usr/local/etc/repos или /etc/repos. Здесь стоит обратить внимание, что путь пишется следующим образом: url: file:///path_to_repo (3 слэша!)

Сохраняем данные об установленных плагинах и проверяем их совместимость с будущей версией ядра (если есть несовместимые, сообщим об этом пользователю).

Вот теперь можно перезагружаться.

Последнее


После обновления системы утилите pkg требуется инициализация (bootstrap). Поэтому если выполнить любую команду, возникнет запрос на нее. В нашем случае, утилита-обвязка не поймет, что он нее хотят и посчитает что операция выполнена некорректно.

Для этого мы сделали обработчик ошибки инициализации, к счастью, у нее есть отдельный системный код операции 1. Поэтому если утилита встречает такой код, то просто выполняет

pkg -y

Pkg делает бутстрап, и дальше можно работать в штатном режиме.

Итого


Вот так мы построили наш репозиторий. Пока это прототип, и в будущем он, скорее всего, будет изменяться и усложняться, но проектная база заложена и останется неизменной.

Описанная технология будет применяться в новых разработках Интернет Контроль Сервер, делая его еще более удобным для использования в корпоративных сетях компаний. Скачать и протестировать самую актуальную версию ИКС можно по ссылке.

Триал-период, бесплатная версия на 9 пользователей, демо-онлайн и отзывчивая техподдержка.

Следите за новостями и оставайтесь с нами!
Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru