Часть 1. Обзор возможностей, установка.
Что умеет PF :
- - транслировать адреса Network Address Translation (NAT);
- позволяет строить отказоустойчивый кластер фаерволлов с использованием 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 (смотрим тут http://www.benzedrine.cx/pfstat.html какая красота получается);
- нормализовать пакеты. Полезно от некоторых видов 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
Код: Выделить всё
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
Код: Выделить всё
pf_enable="NO"
pf_rules="/etc/pf.conf"
pf_program="/sbin/pfctl"
pf_flags=""
pflog_enable="NO"
pflog_logfile="/var/log/pf.log"
pflog_program="/sbin/pflogd"
pflog_flags=""
pfsync_enable="NO"
pfsync_syncdev=""
pfsync_ifconfig=""
Код: Выделить всё
pf_enable="YES"
Код: Выделить всё
pflog_enable="YES"
Код: Выделить всё
# pfctl -e
NetBSD
О том, как заставить PF работать в NetBSD читайте в [1].
Часть 2. Разбор конфигурационного файла.
Вообще говоря, конфиг делится на несколько секций, которые должны иметь строгий порядок и не должны пересекаться. Порядок следующий :
- 1) макросы
2) таблицы
3) опции
4) параметры нормализации
5) приоретизация и очереди ALTQ
6) правила трансляции
7) правила фильтра
Код: Выделить всё
##### 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
Код: Выделить всё
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. Начиная с 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
воспримет это правило как :
Код: Выделить всё
pass out on $ext_if_expensive from $trusted_lan to $untrusted_lan no 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) отправлять с быстрого канала пакеты с исходным адресом безлимита (тогда пакеты пройдут именно тот путь что отмечен красными стрелками на рисунке);
Код: Выделить всё
nat on $ext_if_expensive from $trusted_lan to any -> $spoofed_addr
ext_if_expensive -- внешний быстрый интерфейс;
trusted_lan -- доверенная сеть, это моя внутренняя сеть, что находится за шлюзом
spoofed_addr -- какой адрес отправителя писать. В моем случае, это адрес безлимитного интерфейса.
Настроив маршрутизацию, применив правило и запустив tcpdump на быстром интерфейсе я убедился что пакеты отправляются с подмененным адресом. Правда на цисках провайдера, как оказалось, была включена опция rp_filter, запрещающая спуфинг, поэтому из моей задумки ничего не вышло. Но, говорят, есть еще провайдеры где эта схема работает.
Список литературы :
- [1] The book of pf (лежит на фтп, называется the.book.of.pf.pdf)
[2] OpenBSD PF FAQ (http://www.openbsd.org/faq/pf/)
[3] Vingrad forum, network technologies. (http://forum.vingrad.ru/forum/network_technologies.html)