Шлюз на FreeBSD за 20 минут

С каждым днем в интернете становится жить все страшнее и страшнее. Если раньше эпицентры этой помойки находились на отдаленных частях сети, то сейчас всякая зараза распространяется все ближе и ближе подступая к пользователю. Вобщем, если у вас есть сеть и у вас в ней есть IP-адрес, то, вероятно, обязательно найдется какой-нибудь умник, возомнивший себя кул-хацкером, который обязательно захочет эту сеть посканировать. Лечение от этого, разумеется, есть. Не панацея, но средство очень близкое к ней - фаервол. Доверять системам сетевой защиты, кои основаны на Windows, я считаю не слишком удачной идеей, поэтому предпочитаю путь джедая - UNIX. Исторически сложилось так, что linux я не слишком полюбил, а больше полюбил BSD. Так что будем с вами настраивать простой межсетевой экран с использованием FreeBSD и PacketFilter (пакетного фильтра портированного из OpenBSD). Преимущества этого фильтра я нахожу в том, что он умеет не только хорошо защищать систему, а так же пользоваться механизмами очередей (altq и все вытекающее отсюда, включая балансировки), но еще и являет в себе встроенный NAT. Сейчас мы решим следующую задачу: поставим между вами и интернет еще один компьютер - шлюз, который будет управлять сетевыми соединениями, раздавать интернет и защищать нашу подсеть.

И так, допустим, что у нас есть свежеустановленная FreeBSD версии 5.3 и старше (именно в 5.3 впервые появился PF) с настроенной сетью (шлюз, сетевые карты). У меня версия 6.2, но все написанное здесь прекрасно уживается с 7.0RC1. Так же не плохо было бы, чтобы вы представляли, что из себя представляет сама фряха. Так что за дело.

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

ПРИМЕЧАНИЕ: Если кому-то не удасться собрать ядро, то выполните комманду kldload pf, и модуль магическим образом загрузится, потом сделайте pfctl -e - это включит сам механизм фильтрации, хотя PF предварительно и выругается на отсутствие поддержки altq в ядре, но помните, что это не есть путь истинного самурая, поэтому попытайтесь ядро собрать, как это сказано ниже.

1. Сборка ядра.

Для пересборки ядра делаем следующее:

[root@home /]# cd /usr/src/sys/i386/conf/
[root@home /usr/src/sys/i386/conf]# cp GENERIC MYKERNEL

Первой строкой переходим в каталог с конфигурационным файлом ядра, второй - копируем рабочий и стандартный конфиг GENERIC в MYKERNEL. Открываем последний в любом текстовом редакторе (ee, vi, лично я люблю изврат и vi) и дописываем следующее:

#PacketFilter support
device pf
device pflog
device pfsync

#Queuing support (requied by PF)
options ALTQ
options ALTQ_RED
options ALTQ_CBQ
options ALTQ_RIO
options ALTQ_HFSC
options ALTQ_PRIQ
options ALTQ_NOPCC

Строки начинающиеся со знака решетки, как и принято, рассматриваются аки комментарий. Теперь подробнее о первых трех строках:

device pf включает в ядро поддержку сетевого экрана ''Packet Filter''. Собственно это то, чего мы добиваемся.
device pflog включает в ядро необязательное сетевое псевдоустройство pflog, которое может использоваться для протоколирования трафика через bpf (berkley packet filter). Даемон pflogd может использоваться для сохранения протоколируемой информации на диск. При желании все что попадает туда можно посмотреть tcpdump'ом. Однако, надо знать, как заставить PF логировать траффик. Об этом позднее.
device pfsync включает необязательное сетевое псевдоустройство pfsync, используемое для отслеживания ''изменений состояния''. Поскольку оно не входит в загружаемый модуль, для его использования необходимо собрать собственное ядро.

Дальше разбераем опции очередей (ALTQ - ALTernate Queuing).

options ALTQ включает подсистему ALTQ.
options ALTQ_CBQ включает Class Based Queuing (CBQ). CBQ позволяет распределять пропускную способность соединений по классам или очередям для выставления приоритетов трафика на основе правил фильтрации.
options ALTQ_RED включает Random Early Detection (RED). RED используется для предотвращения перегрузки сети. RED вычисляет длину очереди и сравнивает ее с минимальной и максимальной границей очереди. Если очередь превышает максимум, все новые пакеты отбрасываются. В соответствии со своим названием, RED отбрасывает пакеты из различные соединений в произвольном порядке.
options ALTQ_RIO включает Random Early Detection In and Out.
options ALTQ_HFSC включает Hierarchical Fair Service Curve Packet Scheduler.
options ALTQ_PRIQ включает Priority Queuing (PRIQ). PRIQ всегда пропускает трафик из более высокой очереди первым.
options ALTQ_NOPCC включает поддержку SMP для ALTQ. Эта опция необходима для SMP систем.

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

[root@home /usr/src/sys/i386/conf]# config MYKERNEL
Kernel build directory is ../compile/MYKERNEL
Don't forget to do ``make cleandepend && make depend''

Пересобрать ядро можно и гораздо проще двумя командами

#cd /usr/src

#make kernel KERNKONF=MYKERNEL


Здесь config говорит нам, что он подготовил все файлы, сложил их в каталог ../compile/MYKERNEL, и что прежде чем набрать там make мы должны сделать make cleandepend и make depend, для очистки зависимостей и их сборки соответственно. Теперь, чтобы собрать ядро нужно сделать следующее:

[root@home /usr/src/sys/i386/conf]# cd ../compile/SLAVKAS/ && make cleandepend && make depend && make && make install && reboot

Думаю, что не стоит объяснять что сделает эта строка, но на всякий. Эта последовательность комманд перейдет в каталог ../compile/SLAVKAS, очистит зависимости от мусора, соберет зависимости, соберет ядро, проинсталлирует его в систему, а затем перезагрузит машину. Если по какой-то причине у вас новое ядро откажется загружаться, стоит при выборе загрузке явно указать ядро, которое мы хотим загружать, а именно ввести boot /boot/kernel.old/kernel. Надо заметить, что FreeBSD очень хитрая, и она не спешит класть новое ядро на место старого. Если вы проинсталлировали новое ядрышко, то старое у вас окажется в каталоге /boot/kernel.old, так, на всякий. Мало ли?

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

[root@home /usr/src/sys/i386/conf]# ifconfig

[...пропущенно...]

pfsync0: flags=0<> metric 0 mtu 1460
syncpeer: 224.0.0.240 maxupd: 128
pflog0: flags=0<> metric 0 mtu 33204

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

2. Настройка фаерволла.

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

Все, кто сидит в сети ИТК (за исключением тех, кто приобрел себе "белый" адрес) использует так называемые "серые" адреса. Это адреса из диапазонов 172.16.XXX.XXX-172.32.XXX.XXX, 192.168.XXX.XXX, 10.XXX.XXX.XXX. Другая вещь, о которой мы будем много говорить - это "внутренние" и "внешние" адреса, "маршрутизируемые" и "не маршрутизирумые". Этот вопрос не связан непосредственно с системами сетевой защиты и фильтрации пакетов, но мы вынуждены будем немного его коснуться. Все началось в начале 1990-ых, когда кто-то подсчитал, сколько всего возможно пользователей Интернета.

Во времена разработки системы адресации компьютеры были большими, очень дорогими и обычно обслуживали большое количество пользователей. Тогда подключенными к сети были лишь университеты и компании с заказами Пентагона. По сути дела 32 битная адресация в 4 (4 числа разделенных точкой в IP-адресе называются октетом, число от 0 до 255 занимает 8 бит, т.е. 32 / 4 = 8) октетах позволила бы подключить миллионы машин. Но тут случилась коммерциализация Интернет и в сеть попали сотни тысяч дешевых маленьких машин, в результате чего адресное пространство стало таять с катастрофической скоростью. Для решения этой проблемы был разработан протокол IP version 6, если коротко - IPv6, использующий 128 битную адресацию.

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

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

1. У нас есть сеть из 5 машин, одна из которых имеет доступ в интернет.
2. В сети машины имеют IP-адреса от 192.168.0.1 до 192.168.0.5.
3. На первой сетевой карте шлюза у нас адрес 192.168.0.1 (внутренний адрес).
4. На второй сетевой карте шлюза у нас адрес 80.70.96.130 (внешний адрес).

Допустим, компьютер с адресом 192.168.0.1 (компьютер А) посылает на шлюз пакет (пользователь открыл браузер, набрал там yandex.ru, и браузер пошел обмениваться пакетами с сервероя Яндекса) инициализирующий подключение (более подробно про это можно прочитать здесь). Пакет приходит на шлюз, адрес отправителя пакета - адрес компьютера А. Задача NATа состоит в том, чтобы переправить пакет наружу, а потом обратно во внутреннюю сеть. Допустим, шлюз дальше пересылает пакет компьютера А к Яндексу. Яндекс обрабатывает пакет и, как порядочный сервер, отвечает на него, посылая ответный пакет на адрес отправителя. Только вот далеко ли уйдет пакет, если адрес отправителя - 192.168.0.2? Нет. Он вообще никуда не уйдет. Поэтому NAT поступает очень хитро: он запоминает уходящий от себя пакет, и меняет адрес отправителя с адреса компьютера А, на свой внешний адрес (80.70.96.130). В итоге, пакет благополучно добирается до Яндекса, обрабатывается, Яндекс отсылает этот пакет по адресу отправителя, пакет приходит к нам на шлюз, NAT смотрит на него, и определяет не проходил ли этот пакет через него раньше. Он видит, что проходил, и отправил его компьютер А, а итоге, он меняет адрес получателя со своего внешнего адреса, на адрес компьютера А и передает его этому самому компьютеру. В итоге получается, что одним адресом мозжет пользоваться очень многое количество людей.

Это - то, что называют "Трансляцией сетевых адресов" (NAT), иногда упоминается "подмена IP адресов", маскарадинг (линуксойды, которые имеют дело с iptables должны знать больше маскарадинг ;)) или нечто подобное.

ПРИМЕЧАНИЕ: почитать более подробно про диапазоны не маршрутизитуемых сетей можно в "RFC 1918 addresses".

Теперь, надо сказать системе, что мы хотим загружать наш фаерволл вместе со стартом системы. Сделать это можно в файле /etc/rc.conf. добавим в него следующие строки:

gateway_enable="YES" # Говорим, что наша машина будет выполнять функции шлюза
pf_enable="YES" # Включаем сам PF
pf_rules="/etc/pf.conf" # Указываем файл с набором правил
pflog_enable="YES" # Запускаем демон логирования pflogd
pflog_logfile="/var/log/pflog" # И говорим, куда надо складывать весь лог (лог в формате tcpdump, кстати)
Теперь давайте напишем правила. Допустим, что у нас внешнаяя сетевая карта называется fxp1, а внутренняя - fxp0. Открываем /etc/pf.conf и добавляем туда примерно следующее:

#Делаем две переменных (макросы), в которых прописываем наши интерфейсы. Это нужно для того, если у вас изменится сетевая карта, то чтобы поправить ее название в одном месте, а не по всем файле.
int_if="fxp0"
ext_if="fxp1"

#Вычищаем весь входящий траффик. Вредные кул-хацкеры могут посылать нам фрагментированные пакеты и прочие вещи, которые могут навредить системе (Windows раньше крайне плохо реагировала на слишком фрагментированные пакеты)
scrub in all

#Описываем правило для NAT. Этим правилом мы предписываем PF подвергать NATу все пакеты на внешний интерфейс на какие угодно адреса из нашей локальной сети (192.168.0.0-255)
nat on $ext_if from 192.168.0.0/24 to any -> ($ext_if)

#Блокируем весь входящий траффик на внешнем интерфейсе, ибо нех.
block in on $ext_if all
#Разрешаем весь траффик из локальной сети на внутренний интерфейс
pass on $int_if all
#Разрешаем весь уходящий траффик на внешний интерфейс по протоколу tcp (1-я строка) с включенными флагами S/SA, udp и icmp (вторая строка). В обоих правилах мы будем пропускать входящий трафик, являющийся ответным на наши запросы, т.е. без прохождения механизма фильтрации (это диррективы modulate state (только TCP/IP) и keep state - udp/icmp).
pass out on $ext_if proto tcp all modulate state flags S/SA
pass out on $ext_if proto { udp, icmp } all keep state

#Включаем PF, если еще это не сделали
[root@home /]# pfctl -e
#Загружаем наш набор правил
[root@home /]# pfctl -f /etc/pf.conf

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

В описанном использовался материал из handbook и некоторые вещи из статьи про PF на opennet.ru

http://www.lj.ivanovo.ru/community/unix/401.html

Обновлено: 12.03.2015