Кроме высокой надежности и производительности, операционная система
FreeBSD славится и своим firewall'ом - удобным и понятным средством
работы с протоколом TCP/IP. Далее по тексту я буду довольно свободно
кидаться словами "адрес", "порт", "icmp" и т.д., поэтому тем, для кого
эти слова звучат уж очень непонятно, рекомендую сначала ознакомиться с
TCP/IP (http://book.itep.ru/4/44/inter_44.htm).
Для нас же будет важно, в основном, только то, что в
заголовке каждого ip-пакета имеются 32-битные адреса источника и
назначения, а в пакетах протоколов UDP и TCP так же номера портов
источника и назначения пакета. Из вышеперечисленных только TCP
является протоколом с установлением соединения, и требует пересылки
нескольких специальных пакетов для установления соединения перед
началом полезной передачи данных.

Firewall - это система, управляющая прохождением пакетов данных через
систему на основе заданных администратором правил и информации,
содержащейся в заголовках пакетов. Обычное использование firewall -
запрещение прохождения нежелательных пакетов, например, отключение
абонента, не оплатившего услуги связи, или закрытие части сервисов
локальной сети от внешнего мира. В системе FreeBSD firewall - это
гораздо больше, чем просто турникет в метро - это мощный инструмент
управления сетью, позволяющий, например, подсчитывать трафик по любым
разумным правилам, основывающимся на данных заголовков пакетов
протоколов стека TCP/IP, обрабатывать пакеты внешними программами,
прятать за одним компьютером целую сеть и т.п.

В отличие от разнообразных систем firewall для операционных систем
семейства Windows, во FreeBSD firewall является частью самой
операционной системы, поэтому работает значительно быстрее и надежнее
своих не-UNIX конкурентов (исключая, конечно, системы, созданные
специально для обработки сетевого трафика, такие как Cisco IOS). Но
т.к. firewall нужен далеко не на каждом компьютере с FreeBSD (ведь
FreeBSD - это еще и высокопроизводительный файл-сервер, быстрый и
надежный сервер приложений, мощная рабочая станция), то поддержка
firewall не включена в ядро FreeBSD по умолчанию.

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

IPFIREWALL Включение поддержки firewall в ядро FreeBSD. Если нужны
только простейшие функции, вроде запрета или разрешения прохождения
пакетов, то эту опцию можно не включать - FreeBSD загрузит
соответствующий модуль автоматически, если в /etc/rc.conf разрешено
использование firewall, но поддержка возможностей будет минимальной.
IPFIREWALL_VERBOSE При включении данной опции firewall может
записывать все "происшествия" в лог-файл. Полезно для анализа работы
системы и слежения за попытками взлома. Некоторые особо одаренные
личности даже делают на этом системы учета трафика, хотя для этого
есть и менее винчестероемкие технологии.
IPFIREWALL_VERBOSE_LIMIT= Ограничение числа попадающих в лог
сообщений. При отсутствии лимита хакер или просто сбойный компьютер в
сети способны в считанные минуты сгенерировать столько пакетов, что
лог-файл firewall'а займет все имеющееся дисковое пространство.
IPFIREWALL_DEFAULT_TO_ACCEPT По умолчанию при загрузке системы
firewall находится в режиме "никому ничего нельзя", т.е. весь трафик
блокируется. Данная опция заставляет firewall загружаться в режиме
"всем можно все". Не рекомендуется, если Вы точно не знаете, зачем оно
Вам нужно, и не можете без этой опции обойтись. Примером необходимости
такой опции может быть использование FreeBSD как операционной системы
для бездисковых рабочих станций - в этом случае при отсутствии
указанной опции в ядре FreeBSD весь трафик будет заблокирован до того,
как FreeBSD сможет подмонтировать сетевые диски и загрузить с них
настоящий набор правил для firewall.
IPDIVERT Поддержка возможности передать пакет на обработку внешней
программе. Примером такой программы служит natd - демон, маскирующий
локальную сеть и переписывающий заголовки IP-пакетов. Программа
обработки обязательно должна быть запущена на этой-же машине.
IPFIREWALL_FORWARD Возможность перенаправления пакетов другому
адресату. С помощью этой возможности можно организовать, например,
transparent proxy (http-прокси сервер, работающий для всех клиентов в
принудительном режиме в не зависимости от того, обращаются они к
какому-то сайту напрямую, или "просят" его у прокси-сервера), или
перенести web-сервер внутрь закрытой nat'ом сети так, чтобы он был
доступен из внешнего мира по адресу сервера доступа.
DUMMYNET Система ограничения пропускной способности определенных
соединенй, основанная на задержке прохождения пакетов через роутер.
Для многих системных администраторов именно эта возможность стала
определяющей при выборе операционной системы - форвардинг пакетов и
nat можно организовать на практически любой сетевой операционной
системе, но с ограничением пропускной способности мало кто справится
так же хорошо, как FreeBSD.
HZ= dummynet работает, просматривая очередь поставленных на ожидание
пакетов с некоторой частотой (по умолчанию - 100 раз в секунду). Вы
можете увеличить или уменьшить этот параметр, соответственно повысив
точность ограничения скорости ценой повышения нагрузки на процессор,
либо уменьшить нагрузку на процессор за счет уменьшения точности
ограничения пропускной способности. Имеет смысл менять этот параметр в
сторону увеличения только если при нормальной работе Ваш процессор
нагружен не более чем на 10%. Уменьшение этого параметра обычно не
дает сильного выигрыша при перегруженном процессоре, разве что
ограничение полосы пропускания - это основная выполняемая сервером
задача. Увеличение этого параметра, кстати, увеличивает нагрев
процессора. Чрезмерное увеличение может наоборот приводить к понижению
точности работы dummynet, т.к. процессор может не успевать
отрабатывать сигналы от таймера.

И несколько опций, не относящихся к firewall напрямую, но чтобы уже
больше не возвращаться:

TCP_DROP_SYNFIN Отбрасывать редко встречающиеся при нормальной работе
TCP-пакеты с одновременно установленными флагами начала и завершения
соединения. Польза от таких пакетов крайне мала (есть всего несколько
случаев, когда такие пакеты реально используются), но такими пакетами
часто пользуются хакеры в своих гнусных целях.
ICMP_BANDLIM Ограничение числа генерируемых машиной сообщений об
ошибках TCP/IP. На нормальную работу не влияет, но может помочь в
отбивании некоторых DoS-атак.
BRIDGE Превращает Ваш сервер в обычный Ethernet-коммутатор, только
очень хорошо управляемый.

Как работает firewall

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

Проверка пакета производится по упорядоченному списку правил, которые
задаются администратором. Каждому правилу присваивается номер (либо
вручную администратором, либо автоматически), и правила проверяются
строго в порядке возрастания номеров. Несколько правил могут иметь
один и тот же номер - в этом случае они проверяются в том порядке, в
котором они были занесны в список. Каждое правило содержит условие и
действие. Вот общий вид правила firewall:
[prob ] [log [loamount ]] from to

где:
- целое число в диапазоне от 1 до 65535. Правило с номером 65535 вседа
существует, его нельзя удалить, и оно определяется параметром ядра
IPFIREWALL_DEFAULT_TO_ACCEPT, разрешая или запрещая весь IP-трафик в
зависимости от наличия этого параметра. Правила с другими номерами
полностью управляются администратором.
prob - Действие применяется с некоторой вероятностью. Данная
возможность используется весьма редко. С помощью этого указания можно
симулировать, например, нестабильную линию, или разное время
прохождения пакетов. в данном случае - вещественное, в диапазоне от 0
до 1 (0-правило не исполняется никогда, 1 - всегда).
- одна из следующих команд:

allow, pass, accept, permit Синонимы. Разрешение дальнейшего
прохождения пакета. Последующие правила не рассматриваются, т.е. пакет
выходит из firewall (пакет, проходящий компьютер "насквозь", может
вновь попасть в firewall на "выходе" из системы)
unreach Запрещение прохождения пакета. Отправителю пакета отправляется
уведомление (по протоколу ICMP) о неудаче. Вид уведомления задается
параметром "тип":
* net - сеть адресата не доступна
* host - адресат не доступен
* needfrag - необходима фрагментация пакета
* host-unknown - нет такого адреса
* и др.

Подробнее см. man ipfw
После этого действия другие правила не рассматриваются, и пакет
прекращает свое существование.
reject Действие аналогично unreach host
deny, drop Пакет отбрасывается без уведомления отправителя.
reset Запрещение прохождения пакета. Действует только для протокола
TCP. По адресу отправителя пакета посылается сообщение TCP RST,
информирующее отправителя о закрытии TCP-сессии - в данном случае
удаленный абонент сразу "понимает", что ему отказали, и может
корректно завершить соединение без лишнего ожидания и сообщений об
ошибках. На практике применяется довольно редко.
skipto Перейти к рассмотрению правила с заданным номером. Все правила,
находящиеся между текущим и целевым не проверяются. Пакет не покидает
пределов firewall и продолжает проверяться другими правилами.
count Не производит никаких действий. Но, как и для любого правила в
firewall, в счетчики заносится количество и суммарный объем пакетов,
удовлетворяющих этому правилу, поэтому единственное логичное
применение этому правилу - подсчет трафика, что соответствует и
названию действия - count. Пакет не выходит из firewall и переходит
под власть следующего по порядку правила.
fwd, forward Перенаправление пакета по указанному адресу. Заголовок
пакета изменяется. Если подразумевается, что на данный пакет ожидается
ответ, то он придет уже с "нового" адреса - это может вызвать проблему
в работе некоторых программ, которые проверяют адрес отправителя в
ответном пакете. Тем не менее, это очень полезное свойство часто
используется для организации "transparent-proxy" или при переносе
каких-то сервисов на другой компьютер, при этом обращаться к этим
сервисам можно и по старому адресу.
divert Перенаправление пакета на дополнительную обработку программе,
запущенной на том же компьютере, что и firewall. Внешняя программа
может изменить пакет по своему разумению, уничтожить его, вернуть в
firewall для продолжения обработки со следующего правила или ввести
его в firewall заново для проверки с самого первого правила. Используя
данную возможность firewall'а работают такие программы, как natd
(который мы уже настраивали в первой главе) и ipacctd (программа
"интеллектуального" подсчета трафика).
tee Аналогичен действию divert, но на обработку посылается копия
пакета. Сам пакет продолжает движение по списку правил. Может
применятья для подсчета трафика, анализа состояния сети,
протоколирования и т.п. Возврат пакета в firewall при такой обработке
не желателен, но возможен.
pipe , queue Прохождение пакета через "канал" или "очередь" dummynet.
Используется для ограничения пропускной способности, внесения задержек
в прохождение пакетов. После "выхода" из dummynet пакет может
продолжить путешествие по списку правил, или выйти из firewall в
зависимости от установок sysclt net.inet.ip.fw.one_pass (подробнее о
sysctl - несколько позже). По умолчанию пакет выходит из firewall
(т.е. действует как allow).

- если ядро скомпилировано в опцией IPFIREWALL_VERBOSE, то в системный
журнал будет записан отчет о прохождении пакетом этого правила. Данная
возможность может оказаться весьма полезной, если Вы ожидаете атаки
хакера или отлаживаете сложный набор правил, разобраться в котором
вручную не хватает сил. В журнал записывается время, номер правила и
адреса источника и назначения пакета. logamount - максимальное
количество пакетов, запись о которых попадает в журнал. По умолчанию
устанавливается равным значению опции IPFIREWALL_VERBOSE_LIMIT в ядре
операционной системы.
Действие будет предпринято, если пакет удовлетворяет следующим
условиям:
- название протокола, к которому относится данный пакет. Возможные
значения - ip или all (для всех протоколов стека TCP/IP), udp, tcp,
icmp, igmp и т.д. Полный список доступных для указания протоколов Вы
можете посмотреть в файле /etc/protocols.
from - IP-адрес источника пакета. Для протоколов TCP и UDP так же
может быть указан и порт. Может быть указан IP-адрес, доменное имя
компьютера (типа wwwhub.ru), или целая подсеть в формате IP:MASK или
IP/LEN, например 192.168.0.0:255.255.255.0 или 192.168.0.0/24. Есть
так же два специальных слова - any, означающее любой адрес (аналогично
0.0.0.0/0) и me, означающее любой из адресов, принадлежащих локальной
системе. Номер порта указывается после адреса через пробел. Несколько
номеров портов можно указать через запятую. Перед адресом или номером
порта может стоять слово "not", инвертирующее значение адреса или
порта, т.е. from not 192.168.0.0/24 означает "все пакеты, пришедшие не
из сети 192.168.0.0/24".
to - Адрес назначения пакета. Формат адреса аналогичен предыдущему.

Из дополнительных условий самыми нужными и часто используемыми
являются направление пакета (входящий или исходящий) и сетевой
интерфейс, при прохождении через который был "пойман" пакет. Формат
указания направления и интерфейса следующий:
[in|out] [via ]

где in или out - направление пакета (входящий и исходящий) по
отношению к операционной системе, интерфейс - название устройства
(rl0, lo0, etc) или IP-адрес этого устройства (192.168.0.1, 127.0.0.1,
etc).

Управление firewall производится с помощью программы /sbin/ipfw,
позволяющей удалять и добавлять правила, управлять настройками
dummynet, снимать и обнулять статистику. Команда может вызываться со
следующими наборами параметров:

/sbin/ipfw [-adetN] list [число...] - показать текущий список правил.
Ключи команды имеют следующие значения:

-a Показывать текущие значения счетчиков пакетов. Более короткое и
осмысленное выражение для команды /sbin/ipfw -a list выглядит как
/sbin/ipfw show.
-d Показывать динамические правила. Программы типа natd могут
создавать дополнительные правила в firewall, имеющие ограниченное
время жизни, и не выводящиеся при обычном просмотре. Данная опция
позволяет разобраться в причинах неожиданной неработоспособности на
первый взляд нормального набора правил, или глубже вникнуть в суть
работы некоторых "инерционных" правил, о которых будет рассказано
дальше.
-de Опция -d показывает только действующие динамические правила. При
добавлении опции -e показываются также и устаревшие динамические
правила, еще не удаленные из списка.
-N При выводе списка правил ipfw пытается найти доменные имена для
IP-адресов, и дать символические имена протоколам, указанным через
номер порта. Например, правило allow tcp from any to 195.54.192.86 80
будет выведено в виде allow tcp from ant to jubjub.rinet.ru http
-t Показывать время последнего срабатывания правила. Иногда помогает
при отладке firewall.

/sbin/ipfw [-q] add правило - добавляет правило в список. Если в теле
правила не указан его номер, то он будет вычислен как номер последнего
правила в списке + 100. Флаг -q отключает вывод на экран
подтверждающего сообщения, что весьма полезно при задании большого
количества правил из скрипта (например, при старте системы).

/sbin/ipfw delete номер правила - удалить правило с заданным номером.
Если в списке существует несколько правил с данным номером - будут
удалены все.

/sbin/ipfw [-q] zero [номер правила] - обнуляет счетчики статистики
для выбранного правила, или для всех правил, если номер не указан.

/sbin/ipfw [-i] resetlog [номер правила] - обнуляет счетчик попавших в
журнал записей о срабатывании правила (если включена опция
IPFIREWALL_VERBOSE_LIMIT=XXX). Если журнал ведется для мониторинга
попыток хакерских атак, то полезно периодически выполнять эту команду
для продолжения ведения журнала.

/sbin/ipfw [-f | -q] flush - полная очистка списка правил. Опция -f
избавляет от вопроса "Действительно ли Вы хотите все удалить", и опция
-q, кроме того, подавляет и вывод на экран подтверждающего сообщения.

/sbin/ipfw { pipe | queue } номер config опции - конфигрирование
канала или очереди dummynet. О конфигурировании dummynet - попозже.

Вышеперечисленных возможностей достаточно для конфигурирования
работоспособного firewall'а (но посмотреть man ipfw все-же рекомендую
:) ), поэтому предлагаю перейти к рассмотрению примера,
конфигурирующего наш сервер для предоставления пользователям сети
192.168.0.0/24 доступа в сеть Интернет, защищающего сервер от
нежелательных соединений из внешнего мира, и предоставляющего доступ
администратору сервера для конфигурирования системы из любой точки
мира. Итак, попробуем переписать содержимое нашего файла
/usr/local/billing/rc.firewall пользуясь имеющимися знаниями:

#!/bin/sh
ipfw='/sbin/ipfw -q'
ournet='192.168.0.1/24'
uprefix='192.168.0'
ifout='rl0'
ifuser='rl1'
${ipfw} flush
${ipfw} add 100 check-state
${ipfw} add 200 deny icmp from any to any in icmptype
5,9,13,14,15,16,17
${ipfw} add 210 reject ip from ${ournet} to any in via ${ifout}
${ipfw} add 300 allow ip from any to any via lo
${ipfw} add 310 allow tcp from me to any keep-state via ${ifout}
${ipfw} add 320 allow icmp from any to any
${ipfw} add 330 allow udp from me to any domain keep-state
${ipfw} add 340 allow udp from any to me domain
${ipfw} add 350 allow ip from me to any
${ipfw} add 400 allow tcp from any to me http,https,ssh
${ipfw} add 410 allow tcp from not ${ournet} to me smtp
${ipfw} add 500 fwd 127.0.0.1,3128 tcp from ${ournet} to any http out
via ${ifout}
${ipfw} add 510 divert natd ip from ${ournet} to any out via ${ifout}
${ipfw} add 1002 allow ip from ${uprefix}.2 to any via ${ifuser}
${ipfw} add 1002 allow ip from any to ${uprefix}.2 via ${ifuser}
${ipfw} add 1003 allow ip from ${uprefix}.3 to any via ${ifuser}
${ipfw} add 1003 allow ip from any to ${uprefix}.3 via ${ifuser}
${ipfw} add 1004 allow ip from ${uprefix}.4 to any via ${ifuser}
${ipfw} add 1004 allow ip from any to ${uprefix}.4 via ${ifuser}
#${ipfw} add 65535 deny ip from any to any

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

Рассмотрим предложенный пример по порядку, опустив пока строки 100,310
и 330, содержащие пока не известные нам слова ckeck-state и
keep-state. В первых строках задаются значения переменных командного
интерпретатора, описывающие соответвенно, клиентскую сеть (ournet),
общую часть всех адресов наших клиентов (uprefix), название "внешнего"
и "внутреннего" интерфейсов (ifout и ifuser) Такие переменные позволят
избежать случайных опечаток, и позволят легко изменить настроку
firewall в случае, например, выхода из строя сетевой карты, и замены
ее на сетевую карту другого производителя (в этом случае название
внешнего интерфейса может смениться, например, на fxp0 для карт Intel
EtherExpress).

Правила с номерами 200 и 210 служат для повышения хакероустойчивости
системы: правило 200 запрещает появление пакетов с адресом,
принадлежащим внутреней сети, на "внешнем" интерфейсе, т.к. любимое
оружие хакера - представить свой "смертельный" пакет как-бы пришедшим
из локальной сети, для которой степень доверия выше. Правило 210
запретит прохождения некоторых ICMP-пакетов: icmptype 5 - это пакет
ICMP-REDIRECT, которй может быть использован при атаке типа "фальшивый
маршрутизатор", остальные icmptype - просто раскроют хакеру некоторую
"лишнюю" с нашей точки зрения информацию... Сюда же можно добавить
другие защитные правила - любой "гуру" насыпет их Вам с три короба.

Правила 300-350 обеспечивают работоспособность самой системы. Правило
300 разрешает прохождение любых пакетов внутри системы (lo - локальный
интерфейс системы, имеющий адрес 127.0.0.1. Используется системой для
обращения к себе самой). 320 правило разрешает прохождение любых
ICMP-пакетов, т.к. их возникновение плохо предсказуемо, но их потеря
грозит сбоями в работе - например, icmp-пакет является единственным
способом сообщить "вызывающему" компьютеру, что "вызываемый" адрес не
доступен. Правило 340 разрешает прохождение пакетов DNS из внешнего
мира на сервер (me - все локальные адреса сервера). Это имеет смысл,
если сервер является DNS-сервером, поддерживающим одну или несколько
DNS-зон для сети Интенет - например, зону Вашей сети. Если Вы не
регистрировали для Вашей сети домен, то это правило можно убрать.
Правило 350 разрешает серверу посылать любые пакеты куда угодно - сами
себе то мы доверяем, не правда ли?

Правила 400 и 410 разрешают пользоваться некоторыми сервисами,
предоставляемым системой. Правило 400 разрешает всем (и пользователям
локальной сети, и Интернету) подключаться к Web-серверу (у Вас же есть
Web-сервер, рекламирующий Вашу сеть... кроме того, пользователь должен
иметь возможность узнать состояние своего счета вне зависимости от
того, оплачен у него "Интернет", или нет), и к службе SSH (Secure
SHell - защищенная консоль, используемая для удаленного управления
системой). Правило 410 разрешает прием входящей электронной почты,
если у Вас установлен почтовый сервер.

Правила 500 и 510 обеспечивают дополнительную обработку
пользовательского трафика. Правило 500 "заворачивает" весь http-трафик
на локальный прокси-сервер (если он есть, если нет - то и правило не
нужно). Правило 510 отправляет весь исходящий трафик пользователей на
"переработку" системе NAT для трансляции адресов.

Правила 10ХХ управляют доступом в Интернет отдельных пользователей.
Пользователю с адресом 192.168.0.2 соответсвуют два правила с номером
1002, разрешающие прохождение любого трафика к пользователю и от
пользователя. Если Вы хотите "отключить" пльзователя (например, за
неуплату) - просто удалите эти правила (одной командой - /sbin/ipfw
delete 1002), и пользователь сможет работать только с вашим
собственным Web-сервером...

Теперь о правилах с загадочными словами keep-state. Мы, очевидно, не
хотим, чтобы хакеры из Интернета свободно подключались к любым портам
сервера и делали свое черное дело. С другой стороны, весьма
желательно, чтобы наш сервер мог соединяться с любой машиной в
Интернете. Однако, не сущесвует способа по содержимому одного
единственного IP-пакета определить, инициировано соединение нашей
системой, или "врагом". Правило с пометкой keep-state позволяет
запомнить удовлетворяющее правилу соединение на некоторое время: если
пакет соответствует правилу keep-state, то firewall создает
динамическое правило, разрешающее прохождение пакетов между адресами,
указанными в первом пакете. Последующие пакеты продляют жизнь
временного правила еще на некоторое время. Если активность соединения
прекращается - правило из списка исчезает, и соединение рвется.

Правило 310 разрешает прохождение TCP-пакетов с локальной машины на
любую другую машину, запоминая соединение во временном правиле. Таким
образом, разрешается прохождение и "обратных" пакетов от "вызванной"
машины. Временные правила проверяются firewall'ом при прохождении
через правило check-state - поэтому мы и поместили его на первое
место. Ознакомиться с текущим списком динамических правил Вы можете,
введя команду ipfw -d list.

Попробуем теперь рассмотреть прохождение нескольких пакетов через
firewall.

Пусть пользователь 192.168.0.2 желает посмотреть страницу wwwhub.ru
(считаем, что на сервере работет transparent-proxy и DNS). Первым
делом пользователь посылает DNS-запрос - UDP-пакет на адрес сервера
(192.168.0.1) на порт 53 (domain). Правила 100-330 пакет проходит не
задерживаяясь, т.к. он им не удовлетворяет. Правило 340 разрешает
прохождение пакета с любого адреса на локальный DNS, поэтому путь
запроса на этом оканчивается. Ответ локального DNS-сервера - UDP-пакет
с адреса 192.168.0.1, порт 53, на адрес 192.168.0.2 (порт - что-то
вроде 1025, первый попавшийся под руку компьютеру пользователя),
проходит по правилу 350, разрешающему прохождение любых пакетов
сервера куда угодно. Если в кэше локального DNS-сервера не найдется
записи о сервере wwwhub.ru, то он вынужден будет послать запрос
своему вышестоящему DNS-серверу - UDP-пакет с адресом источника
193.232.100.100 на адрес 195.34.32.10 (для примера взят DNS-сервер
MTU), который пройдет по правилу 330, породив временное правило,
разрешающее прохождение обратного пакета.

Следующим этапом открытия страницы будет TCP-соединение с сервером
wwwhub.ru на порт 80 (http). Пользователь, получивший от DNS-сервера
IP-адрес сервера wwwhub.ru (195.54.192.86) пошлет TCP-пакет с адресом
отправителя 192.168.0.2 (порт типа 1026) и адресом назначения
195.54.192.86, порт 80. Этот пакет пройдет по firewall до правила
1002, разрешающего пользователю любой трафик, почсле чего попадет в
систему маршрутизации, которая попытается отправить его через внешний
интерфейс - т.е. снова попадет в firewall, но уже как исходящий. В
этом своем втором путешествии он доберется до правила 500, где его
адрес назначения будет переправлен на 127.0.0.1, порт 3128, после чего
он попадет в proxy-сервер. Proxy-сервер сгенерирует ответный пакет, с
адресом назначения 192.168.0.1, который по правилу 350 будет отправлен
пользователю. После того, как proxy-сервер поймет, какую именно
страницу хочет получить пользователь (для этого пользователю и
proxy-серверу придется обменяться еще несколькими пакетами по той-же
схеме), proxy начнет собственное tcp-соединение с сервером wwwhub.ru
- отправит tcp-пакет с адреса 193.232.100.100 на адрес 195.54.192.86,
порт 80. Этот пакет пройдет по правилу 310, породив динамическое
правило. Ответ от wwwhub.ru будет пропущен этим динамическим правилом
при проверке правила с номером 100 - check-state.

Следующий пример - обращение пользователя к внешнему ftp-серверу,
например - ftpfreebsd.org. DNS-запрос на разрешение доменного имени
пройдет аналогично предыдущему примеру, возвратив пользователю
IP-адрес 62.243.72.50. Получив адрес сервера пользователь пошлет на
него tcp-пакет, который будет соответствовать правилу 1002, и попадет
в маршрутизатор, который попытается отправить его через внешний
интерфейс.

При попытке "выбраться" из системы пакет дойдет до правила 510,
которое "завернет" пакет на обработку natd, и выйдет из системы c
адресом источника, равным нашему "внешнему" адресу - 193.232.100.100.
При этом natd запомнит во временном правиле настоящий источник пакета,
и пришедший ответный пакет будет прередан пользователю, пройдя через
резрешающее правило 1002.

Замечание для критиков - да, я знаю, что всё это можно организовать
немного эффективнее, в частности без динамических правил - но данный
пример создан в иллюстративных целях. Для желающих сделать всё
максимально эффективно в Интернете есть множество примеров настроек
(часто противоречащих друг-другу), однако динамические правила я
все-же счел нужным описать.

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

/sbin/ipfw add allow ip from 192.168.0.1/24 to any keep-state limit
src-addr 10

запретит каждому абоненту сети 192.168.0.1:255.255.255.0 устанавливать
более 10 соединений одновременно. Параметр src-addr указывает, что
ограничение считается по адресам источников пакетов (т.е. в нашем
примере - для каждого пользователя). Допустимые значения этого
параметра: dst-addr (ограничение подсчитывается по адресам
назначения), src-port (ограничение подсчитывается по портам
источника), dst-port (ограничение подсчитывается по портам
назначения), а также любые комбинации этих параметров, например, limit
dst-port dst-addr 1 позволит установить только одно соединение с любым
портом любого сервера, при этом можно будет установить несколько
соединений с одним сервером (например, HTTP, SMTP и POP3 одновременно)
и неколько соединений на один порт различных серверов (например,
одновременно скачивать wwwanekdot.ru и wwwhub.ru).
Ограничение скорости

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

Dummynet состоит из каналов (pipe, труба) и очередей (queue). Канал
характеризуется пропускной способностью (биты в секунду), задержкой
прохождения пакета (в секундах), размером очереди (сколько данных
может одновременно "находится" в канале), процентом потерь. Задать эти
значения Вы можете с помощью команды
/sbin/ipfw pipe config bw delay queue plr

, где - номер канала. Вибирается администратором произвольно из
диапазона 1-65534

- пропускная способность канала. Задается в виде числа,
интерпретируемого как биты в секунду. Возможно также задание и единицы
измерения из слоедующего набора: bit/s, Kbit/s, Mbit/s, Bytes/s,
KBytes/s,MBytes/s. Единицы измерения указываются после числа без
пробелов: 2MBytes/s, 64Kbit/s.

- время задержки пакета в миллисекундах, всегда прибавляется к времени
нахождения любого пакета в канале в не зависимости от текущей загрузке
канала.

- размер очереди в пакетах или в килобайтах (если указана единицы
измерения - Bytes или KBytes). Не поместившиеся в очередь пакеты
отбрасывабтся.

- процент потерянных пакетов. Обычно используется для эмуляции плохих
линий связи при проверке устойчивости сетевого программного
обеспечения к сбоям. Задается как вещественное число от 0 до 1 (0 -
потерь нет, 1 - теряются все пакеты).

Для управления каналом нужно представлять себе, как он работает -
иначе неминуемы нестыковки и неприятные разочарования. При поданании в
канал пакет "становится в хвост" очереди - совсем как в магазине.
Dummynet определенное количество раз в секунду (задается параметром HZ
при сборке ядра операционной системы) проверяет наличие пакетов в
очереди, и, если не превышен лимит скорости выхода данных из канала,
выпускает пакет. Считается именно скорость схода пакетов с канала -
поэтому если пакеты в очередь поступают с большей скоростью, чем
разрешенная для данного канала скорость выхода из очереди, то
"непоместившиеся" в очередь пакеты просто теряются (не встанете же Вы
в очередь за хлебом, если перед Вами уже 50 человек, а продавец
обслуживает клиентов очень медлено).

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

Чтобы работа по протоколу TCP через канал dummynet происходила без
необходимости повторной пересылки пакета, необходимо чтобы все пакеты
"окна" могли поместиться в очереди. Стандартного размера очереди (50
пакетов) хватает для одновременной работы примерно 10 TCP-соединений
(это число очень сильно зависит от настроек протокола TCP на машинах
клиентов и на серверах, а так-же от среднего размера пакета,
генерируемого приложениями). При превышении этот числа пакеты начнут
теряться, что потребует их перепосылки заново. Если Вы платите за
трафик - эта особенность может больно ударить Вас по карману: Ваш
провайдер посчитает все переданные Вашей системе пакеты, в том числе и
потерянные в dummynet, однако Вы (или Ваш клиент), получат только
часть из них - поэтому, если Вы пускаете через один канал dummynet
большое количество соединений - пропорционально увеличивайте и размер
очереди. Причем подсчет пикового количества одновременных соединений
вовсе не так прост - пользователи с зажатой пропускной способностью
канала имеют обыкновение открывать граздо больше одновременных
соединений, чем обладатели высокоскоростных каналов: пока читают одну
страницу - запускают на скачивание еще несколько. Кроме того,
пользователи менеджеров загрузки типа GetRight быстро обнаружат, что
закачка файла в несколько потоков происходит быстрее, чем в один -
адресованные им пакеты, в виду большего их числа, бедет "вытеснять" из
очереди чужие соединения... да и продолжительность одного соединения
возрастет, что тоже приведет к увеличению их одновременного числа.

Ну ладно, попугали - и хватит. Решением этих проблем может быть как
ограничение числа соединений через pipe, так и грамотная настройка
всей системы ограничения, в частности с использованием приоритетов
трафика: каждый канал может иметь и больше одной очереди пакетов, при
этом пакеты из очередей "выходят" в соответствии с приоритетами,
заданными очередям. Для конфигурирования очереди используйте команду:

/sbin/ipfw queue config pipe weight

, где - произвольно выбранный администратором идентификатор из
диапазона 1-65534

- номер канала, частью которого становится эта очередь

- приоритет очереди, число из диапазона 1-100, где 100 - самый
приоритетный канал, 1 - самый бесправный. По умолчанию для каждой
очереди устанавливается приоритет 1.

Чтобы "пропустить" трафик через канал, воспользуйтесь командами:

/sbin/ipfw add pipe или
/sbin/ipfw add quqeue , например

/sbin/ipfw add pipe 1 ip from any to 192.168.0.2 загонит в канал номер
1 весь трафик, идущий к пользователю 192.168.0.2.

Если нужно выставить одинаковые ограничения для большого количества
пользователей, то вводить сотни правил, отличающихся только одним
адресом может оказаться весьма утомительным занятием. Для упрощения
таких задач FreeBSD предлагает дополнительный параметр mask,
позволяющий сгруппировать абонетов на основе их IP-адресов: адрес
комбинируется (неисключающее побитовое ИЛИ) с маской, и получившееся в
результате значение является идентификатором группы, например

/sbin/ipfw add pipe 1 ip from any to 192.168.0.1/24
/sbin/ipfw pipe 1 config bw 64Kbit/s mask dst-ip 0x00000001

Создаст отдельные каналы для "четных" и "нечетных" адресов, т.е.
адреса 192.168.0.2 и 192.168.0.4 будут делить один канал 64 кбит, а
адреса 192.168.0.3 и 192.168.0.5 - другой

/sbin/ipfw add 100 pipe 1 ip from anu to 192.168.0.1/24
/sbin/ipfw pipe 1 config bw 64Kbit/s mask dst-ip 0x00000010

объединит отнесет в одну группу адреса с нулевым пятым битом
(192.168.0.2 - 192.168.0.15, 192.168.0.32 - 192.168.0.47), а адреса с
единичным пятым битом (192.168.0.16-192.168.0.31,
192.168.0.48-192.168.0.64) в другую.

указание маски 0x00000000 создаст собственный канал для каждого
IP-адреса.

При указании маски в параметрах канала для каждой группы будет создан
одельный канал с теми же настройками пропускной способности, что и
базовый, т.е. для первого рассмотренного случая (с маской 0x00000001)
суммарная пропускная способность для пользователей будет равной 128
килобитам. При указании маски в параметрах очереди для каждой группы
адресов будет создана очередь с тем же приоритетом (весом), что и
базовая очередь, но все эти очереди попадут в один и тот-же канал,
т.е. суммарная пропускная способность канала не изменится.

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

/sbin/ipfw pipe 1 config bw 1000Kbit/s
/sbin/ipfw queue 1 config pipe 1 weight 50 mask dst-ip 0x00000000
/sbin/ipfw add queue 1 ip from any to 192.168.0.1/24

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

/sbin/ipfw pipe 1 config bw 1000Kbit/s
/sbin/ipfw queue 1 config pipe 1 weight 50 mask dst-ip 0x00000000
/sbin/ipfw queue 2 config pipe 2 weight 75 mask dst-ip 0x00000000
/sbin/ipfw add queue 1 ip from any to 192.168.0.1/25
/sbin/ipfw add queue 2 ip from any to 192.168.0.128/25

даст некоторый проиоритет в использовании канала пользователям с
адресами, большими 192.168.0.128. Это неплохой инструмент для
VIP-обслуживания некоторых абонентов сети без "глобального" ущемления
прав рядовых пользователей: если никому из VIP-группы в данный момент
канал не нужен, то обычные пользователи делят между собой пропускную
способность поровну, но если VIP-абоненту понадобились услуги -
простые пользователи автоматически подвигаются ...(процент за
использование идеи можно присылать мне - о способе передачи
договоримся :) ладно, шучу - пользуйтесь за так) Аналогичным образом
можно, например, дать приоритет использования канала любителям
поиграть через Интернет (отправив через привелегированную очередь
пакеты с известных игровых серверов) за счет "качальщиков".

Если вышеприведенных сведений Вам не хватает - обратитесь к man ipfw.
Там Вы сможете узнать о более тонкой настройке firewall, а так-же
изучить возможности грядущей системы IPFIREWALL 2, позволяющий боле
гибко строить правила и работать с дополнительной информацией о
пакетах, такой как MAC-адреса пользователей и т.д.
Дополнительная настройка поведения системы

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

Для тонкой натройки поведения отдельных подсистем ядра используется
утилита sysctl, позволяющая задать значение некоторый переменных ядра.
Формат вызова утилиты sysctl следующий:

sysctl [опции] [имя переменной[=значение]] [...]

где имя переменной - имя переменной в объектной нотации вида
подсистема.[подсистема...].переменная

=значение - устанавливоемое для переменной значение. Может быть
строкой или числом. Не для каждой переменной можно установить
значение, например для переменной kern.ostype, содержащей тип
операционной системы, значение изменить нельзя (не поменяется же у Вас
операционная система "на ходу"), а для kern.hostname, содержащей
сетевое имя Вашего компьютера, значение установить можно.

опции - набор опций, изменяющих поведение утилиты sysctl. Вот самые,
на мой взгляд, интересные:

-a - вывести вписок всех имеющихся перменных и их значений

-N - выводить только имена переменных

-n - выводить только значения переменных, например для помещения
значения переменной ядра в переменную окружения (set
kbootfile=`sysctl -n hw.physmem` для помещения в переменную
окружения объема установленной в системе оперативной памяти)

-e - выводить переменный в формате имя=значение. Позволяет
сохранить текущее состояние в файле (sysctl -ae > current.sysctl)
перед изменением большого количества переменных, чтобы потом легко
"скормить" их обратно (sysctl `cat current.sysctl`).

С полным списком доступных переменных Вы можете ознакомиться,
посмотрев man sysctl, или введя команду sysctl -a, а мы рассмотрим
только имеющее непосредственное отношение к настройке firewall.

net.inet.ip.fw.enable - логическая (булева) переменная (значения 0 и
1). Показывает, включено ли использование firewall в текущий момент.
Позволяет включать и выключать firewall в любой момент времени.

net.inet.ip.fw.autoinc_step - целочисленная переменная. Задает шаг
автоматического приращения номеров правил firewall при вводе без
принудительного указания номера

net.inet.ip.fw.verbose и net.inet.ip.fw.verbose_limit - аналогичны
соответствующим опциям ядра

net.inet.ip.fw.one_pass - логическая переменная. При ее установки в 0
пакет, выходящий из dummynet, продолжит свое путешествие по правилам
firewall. В противном случае pipe действует, как allow. Значение по
умолчанию - 1.

net.inet.ip.dummynet.hash_size - целочисленная переменная.
Соответствует размеру хэш-таблицы, используемой dummynet для хранения
очередей. Увеличение этого значение ускоряет работу dummynet при
большом количестве очередей, естественно в обмен на оперативную
память. Значение по умолчанию - 64.

net.inet.ip.dummynet.expire - логическая переменная. При установке в 1
очереди dummynet удаляются через некоторое время после того, как через
них перестали "бегать" пакеты. В противном случае очереди удаляются
только при нехватке памяти для размещения новых. Значение по умолчанию
- 1. Имеет смысл выставлять в 0, если Ваш сервер обслуживает несколько
крупных потребителей трафика, постоянно находящихся в режиме on-line -
в этом случае кратковременное прекращение активности потребителя не
должно вызывать удаления его очереди, чтобы не тратить времени на ее
создание заново при появлении потребителя. В случае множества мелких
потребителей, подключающихся и отключающихся от сети на длительный
срок имеет смысл освобождать ресурсы, чтобы ускорить работу dummynet
за счет меньшей таблицы очередей.

net.inet.ip.dummynet.max_chain_len - целочисленная переменная,
значение по умолчанию - 16. Количество очередей, способных
одновременно храниться в одной ячейке хэш-таблицы. При превышении
этого значение пустые очереди удаляются. (или те, которым меньше всего
повезло).

net.inet.ip.fw.dyn_keepalive - булева переменная. Заставляет
генерировать "поддерживающие" пакеты (keep-alive) для tcp-соединений,
обрабатываемых динамическими правилами keep-state. Установка в 1
понижает вероятность прерывания tcp-соединения по таймауту, но
генерирует лишний трафик. Значение по умолчанию - 1

net.inet.ip.fw.dyn_max - целочисленная переменная. Максимальное
количество одновременно существующих динамических правил. Значение по
умолчанию - 8192.

О назначении других переменных Вы можете узнать в man ipfw.

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