позволяет строить отказоустойчивый кластер фаерволлов с использованием Common Address Redundancy Protocol (CARP);
поддерживает несколько видов приоритезации трафика (ALTernate Queuing);
пробрасывать определенный порт на определенный комп в локальной сети (Port Forwarding);
выступать в качестве прокси для входящих соединений, защищая внутренний сервер от атак SYN-flood (SYN-proxy);
передавать нагрузку на пул серверов, отслеживая их состояние. Получается, опять же, отказоустойчивый кластер + балансировка нагрузки несколькими способами (Load balancing);
фильтровать пакеты по типам (tcp, udp, icmp) и подтипам (icmp echoreq, icmp unreach);
поддерживает макросы и таблицы. Например, можно завести таблицу брутфорсеров и динамически ее пополнять;
выступать в роли низкоуровнего Ethernet моста, не имеющего собственного IP адреса (Bridging Firewall);
метить пакеты специальными метками. Полезно когда нельзя принять решение о судьбе пакета основываясь только лишь на адресе подсети и номере порта (tags);
фиксировать любую сетевую активность и рисовать графики в различных разрезах с помощью утилиты pfstat (смотрим тут какая красота получается);
нормализовать пакеты. Полезно от некоторых видов dos атак; (scrub). Вроде все, ничего не забыл.
Установка :
OpenBSD. OpenBSD является материнской платформой для PF (именно на ней он появился впервые, а потом был портирован на остальные BSD), поэтому он присутствует во всех ее версиях. Для включения PF в OpenBSD требуется :
/etc/rc.conf.local
pf=YES
и можно еще указать конфиг файл
pf_rules=/etc/pf.conf
FreeBSD. Во всех свежих версиях FreeBSD он уже присутствует в качестве загружаемого модуля. Но я включил его в ядро явно (это необязательно) :
device pf
device pflog
device pfsync
и ОБЯЗАТЕЛЬНО включите следующие опции в ядро Вы хотите использовать приоритезацию и очереди ALTQ
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
и ведение лога (как смотреть лог - файл читайте в конце 2 части)
pflog_enable="YES"
Изменения вступят после перезагрузки. Чтобы не перезагружать компьютер можно дать команду (требует рутовых прав)
# pfctl -e
должно появиться pf enabled. А после перезагрузки он будет запускаться согласно rc.conf
NetBSD О том, как заставить PF работать в NetBSD читайте в [1].
Часть 2. Разбор конфигурационного файла.
Вообще говоря, конфиг делится на несколько секций, которые должны иметь строгий порядок и не должны пересекаться. Порядок следующий :
1) макросы
2) таблицы
3) опции
4) параметры нормализации
5) приоретизация и очереди ALTQ
6) правила трансляции
7) правила фильтра
Ниже приведен конфиг с моего домашнего шлюза, не претендует на полноту, т.к. здесь нет таблиц и очередей ALTQ, но в качестве примера сойдет, тем более что я буду комментировать :
##### BEGIN CONFIG
###############################################################################
## The gateway1's pf ruleset BASIC v. 0.02a ##
###############################################################################
# СЕКЦИЯ макросы, (фактически макросами называется то же самое что в языках
## программирования называется переменной)
## интерфейсы
int_if="xl0" # внутренний интерфейс, который "смотрит" ко мне в домашнюю
## локалку.
ext_if_cheap="tun0" ## виртуальный pppoe интерфейс, безлимит.
## Здесь берется адрес интерфейса tun0.
## Это удобно, т.к. провайдер может назначать динамический адрес.
## Используя этот макрос нам не придется корректировать файл правил pf когда
## провайдер по какой - то причине станет выдавать нам другой IP адрес.
ext_if_expensive="tun1" ## виртуальный pppoe интерфейс, помегабайтная оплата
game_ports="7777" ## порт игры Unreal Tournament
icmp_types="{ echoreq, unreach}" ## разрешенные типы icmp сообщений.
## Первое необходимо для работы UNIX traceroute (для win tracert этого
## не требуется), а второе нужно когда пытаемся коннектиться
## к хосту, который в дауне.
udp_services="{ domain, ntp }" ## разрешенные типы udp сервисов
# сети и хосты
trusted_lan="192.168.0.0/24" ## доверенная домашняя локалка
untrusted_lan="10.0.0.0/8" ## внешняя локалка провайдера,
## будем называть ее WAN
localnet="127.0.0.0/8" ## сеть интерфейса петли, ну это понятно
game_server="192.168.0.15/32" ## адрес игрового сервера в
## домашней локалке
wan_services="{ 85.113.63.251/32, 85.113.59.8/32, 85.113.62.200/32 }"
## серверы в WAN провайдера (игровые, файловые).
## Если мы захотим добавить в этот список еще несколько серверов,
## например 1.1.1.1 и 2.2.2.2, то список будет выглядеть так
## wan_services="{ 85.113.63.251/32, 85.113.59.8/32,
## 85.113.62.200/32, 1.1.1.1/32, 2.2.2.2/32}"
## и править больше ничего не потребуется. Вот оно, удобство макросов.
# СЕКЦИЯ глобальные опции
set block-policy return ## сбрасываем соединение вежливо,
## по умолчанию стоит агрессивное drop
set skip on lo0 ## полностью пропускаем проверку на петле
## (повзоляет сэкономить немного вычислительных ресурсов)
set skip on $int_if ## полностью пропускаем проверку на интерфейсе,
## к которому подключена домашняя локалка.
## Вредителей в ней у меня нет. (также повзоляет сэкономить немного
## вычислительных ресурсов)
# СЕКЦИЯ параметры нормализации
scrub in all ## нормализуем все входящие пакеты на всех
## интерфейсах. Что это значит?
## Пакеты приходят частями (фрагментами). scrub сначала собирает
## пакет у себя на шлюзе и только потом передает его дальше,
## во внутреннюю сеть. Позволяет защититься от некоторого
## вида dos атак сбрасывая фрагменты TCP с некорректно
## установленными флагами.
# СЕКЦИЯ приоретизация и очереди ALTQ
## у меня пустая, т.к. не использую шейпинг и очереди.
## Всем компам домашней сети доступна
## полная ширина канала без ограничений.
# СЕКЦИЯ port forwarding & NAT
rdr on $ext_if_expensive proto { tcp, udp } from $untrusted_lan to \
$ext_if_expensive port $game_ports -> $game_server port $game_ports
## Здесь пробрасываем всех кто коннектится по интерфейсу
## ext_if_expensive на порт
## game_ports НА game_server порт game_ports, т.е. :
## from 10.0.0.0/8 to ext_if_expensive:7777 ПРОБРОСИТЬ НА
## 192.168.0.15:7777
## вот что получается если посмотреть с точки зрения PF
## (команда pfctl -sa)
## rdr on tun1 inet proto tcp from 10.0.0.0/8 to 10.[censored]
## port = 7777 -> 192.168.0.15 port 7777
## rdr on tun1 inet proto udp from 10.0.0.0/8 to 10.[censored]
## port = 7777 -> 192.168.0.15 port 7777
## И....
## По просьбам трудящихся добавляю нат средствами PF
nat on $ext_if_cheap from $trusted_lan to any -> ($ext_if_cheap)
## делаем нат НА внешнем интерфейсе (интерфейс с инетом) от
## доверенной сети (192.168.0.0/24) ко всем.
## Параметр после знака -> позволяет подписывать у
## отправляемых в инет пакетов любой адрес отправителя,
## но здесь нам нет смысла его менять, поэтому ставим
## адрес внешнего интерфейса. Т.е. ($ext_if_cheap).
# СЕКЦИЯ правила фильтрации
antispoof quick for $ext_if_cheap # включает простейший
## антиспуфинг, который превращается в 2 правила :
## block drop in quick on ! tun0 inet from tun0 to any
## block drop in quick inet from tun0 to any
## Что означает : сбрасывать все входящие пакеты с
## адресом отправителя равным нашему
## Самое время рассказать о порядке срабатывания фильтрующих
## правил и о ключе quick.
## Все правила обрабатываются сверху вниз и приоритет имеет
## последнее правило, т.е. если написать сначала
## block all ## а потом
## pass all
## то сработает
## pass all ## как если бы мы кроме него ничего не писали
## Ключ quick указывает на то, то нужно применить текущее
## правило и прекратить дальнейшую обработку.
antispoof quick for $ext_if_expensive ## тоже самое и
## для второго интерфейса
## Честно говоря, полноценным антиспуфингом это сложно
## назвать, от части спуферов, конечно спасает
## но для реализации путевого антиспуфинга надо
## смотреть RFC 1918 и 3330 и явно запрещать пакеты с
## левых сетей типа 127.0.0.0, 240.0.0.0/4, 169.254.0.0/16 и т.д.
block all ## запрет всего по-умолчанию
##--everithing LOCAL
pass out on $ext_if_cheap from $ext_if_cheap to any keep state
## разрешаем весь исходящий трафик с
## локальной машины (на ней у меня клиент торент сетей)
##--everithing LOCAL
##--everithing for 192.168.0.0/24 LAN
pass out on $ext_if_cheap from $trusted_lan to any keep state
## разрешаем весь трафик от нашей домашней сети
## здесь хочу сказать, что если бы мы включили фильтрацию для
## интерфейса xl0, то пришлось бы писать ДВА правила :
## одно на разрешение ВХОДящего трафика по xl0 от сети 192.168.0.0/24
## другое на разрешение ИСХОДящего трафика по ext_if_cheap
## от сети 192.168.0.0/24
##--everithing for 192.168.0.0/24 LAN
##--direct connections from 192.168.0.0/24 LAN to 10.0.0.0/8 WAN
pass out on $ext_if_expensive from $trusted_lan to \
$untrusted_lan keep state ## разрешаем трафик от
## локалки 192.168.0.0/24 к сети провайдера по быстрому соединению
##--direct connections from 192.168.0.0/24 LAN to 10.0.0.0/8 WAN
##--WAN services
## out from 192.168.0.0/24 LAN to all WAN servers VIA fast connection
pass out on $ext_if_expensive from $trusted_lan to \
$wan_services keep state ## разрешаем трафик от
## локалки 192.168.0.0/24 к серверам провайдера по быстрому соединению
##--WAN services
## и теперь разрешаем входящий трафик по быстрому соединению,
## которые мы будем пробрасывать на 192.168.0.15:7777
pass in log on $ext_if_expensive proto { tcp, udp } from $untrusted_lan to \
$game_server port $game_ports keep state
## это локальный трафик, стоит недорого, а для игр очень важна
## скорость и ширина канала, поэтому
## трафик пущен именно по быстрому соединению
## ВНИМАНИЕ! Долго ломал голову почему ко мне не могут присоединиться
## ребята из локальной сети, чтобы
## поиграть в UT. Методом многочисленных проб удалось установить
## что в этом правиле требуется указать
## разрешение трафика к game_server, а не к ext_if_expensive,
## как сначала может показаться. Убил кучу
## времени на это, благо товарищ из локалки помог продиагностировать.
## Спасибо GrayCat ;-)
pass log inet proto icmp all icmp-type $icmp_types
## разрешаем icmp трафик установленных типов.
##### END CONFIG
И пара слов о keep state. Когда программа (например, браузер из доверенной сети) лезет в интернет — разрешение на исходящий от нас трафик мы указываем явно правилом :
pass out on $ext_if_cheap from $trusted_lan to any keep state
мы набираем в строке обозревателя какой - то сайт и запрос от обозревателя поступает на этот сайт. В нашем файрволле устанавливается состояние, означающее что мы ожидаем данных от этого сайта. Так вот если написать
pass out on $ext_if_cheap from $trusted_lan to any
БЕЗ keep state (или указав no state), то запрос на сайт поступит, а ответ наш файрвол зарежет. Вот зачем нужен keep state. И еще одна особенность keep state. Начиная с FreeBSD версии 7.0 (аналогичная версия pf входит в поставку OpenBSD 4.1) keep state применяется ко всем правилам по умолчанию. Если вы не хотите чтобы к некоторым (или ко всем) правилам применялось keep state — пишите явно no state.
Пример из конфига выше.
pass out on $ext_if_expensive from $trusted_lan to $untrusted_lan
FreeBSD версии 7.0 и выше (или OpenBSD версии 4.1 и выше) воспримет правило как :
pass out on $ext_if_expensive from $trusted_lan to $untrusted_lan keep state
а FreeBSD ниже версии 7.0 (или OpenBSD ниже версии 4.1) воспримет это правило как :
pass out on $ext_if_expensive from $trusted_lan to $untrusted_lan no state
в новых версиях BSD (FreeBSD 7.0 и OpenBSD 4.1) лично я предпочитаю все равно явно писать keep state (хотя это необязательно и принимается по умолчанию), потому что особого труда это не составляет, а обратная совместимость со "старыми" версиями PF в наличии. Например, конфиг из пункта 2 взят с сервера под управлением ОС FreeBSD 7.0, но у меня есть сервер и на 6.3, на котором сейчас IPFW в качестве файрвола, потом когда буду переводить все на PF меньше шансов запутаться на такой мелочи как состояния keep state.
По поводу логов. PF пишет лог в бинарном формате, поэтому если смотреть его обычным просмотрщиком Вы увидите сплошные крякозябры. Читать его можно с помощью tcpdump, командой
# tcpdump -n -e -ttt -r /var/log/pf.log
Часть 3. IP spoofing. Несколько месяцев назад у меня была задача организовать ip spoofing под FreeBSD; коротко, ip spoofing — это подмена ip адреса отправителя. Спуфинг надо сказать планировался в мирных целях, имелось 2 канала :
1) быстрый и помегабайтный (оплата за входящий трафик);
2) медленный и безлимитный (абонемент на 1 месяц);
хотелось получить один гибридный ассиметричный канал с большой скоростью на отдачу и при этом платить только за безлимит. Погуглив чутка я выяснил что это можно реализовать с помощью ip spoofing'a сделав следующее :
1) сделать дефолтовым маршрутом интерфейс с быстрым инетом;
2) отправлять с быстрого канала пакеты с исходным адресом безлимита (тогда пакеты пройдут именно тот путь что отмечен красными стрелками на рисунке);
Мне подсказали что это умеет pf. Вот эта строчка в конфиге позволяет подменить адрес отправителя :
nat on $ext_if_expensive from $trusted_lan to any -> $spoofed_addr
где ext_if_expensive — внешний быстрый интерфейс; trusted_lan — доверенная сеть, это моя внутренняя сеть, что находится за шлюзом spoofed_addr — какой адрес отправителя писать. В моем случае, это адрес безлимитного интерфейса.
Настроив маршрутизацию, применив правило и запустив tcpdump на быстром интерфейсе я убедился что пакеты отправляются с подмененным адресом. Правда на цисках провайдера, как оказалось, была включена опция rp_filter, запрещающая спуфинг, поэтому из моей задумки ничего не вышло. Но, говорят, есть еще провайдеры где эта схема работает.
В следующей статье рассмотрим более сложный конфиг с очередями ALTQ, серверами в DMZ, требующими доступ снаружи, и ограниченным списком сервисов, к которым могут обращаться пользователи нашей сети. Ну и если получится, завернем трафик на squid.
Список литературы :
[1] The book of pf (лежит на фтп, называется the.book.of.pf.pdf)