Linux firewall своими руками
автор: gapsf@yandex.ru
начато: 25.07.2010
последнее изменение: 01.09.2013
http://handmade-linux-firewall.narod.ru
http://hlf.netii.net

Содержание

Вступление

Часть 1. Концепции
Что мы хотим
Схема сети
Почему рассматривается именно этот вариант схемы?
Проблемы
Основные концепции: потоки, линки, цепочки
Листинг правил iptables для рассматриваемой схемы сети
Конфигурация интерфейсов шлюза
Фильтрация
Сетевые сервисы или чего не должно быть на шлюзе/firewall'е
Контроль доступа
Цепочки фильтрации
Локальная сеть <-> шлюз
Интернет <-> шлюз
Локальная сеть <-> Интернет
Интернет <-> DMZ
Локальная сеть <-> DMZ
Фильтрация и отслеживание соединений (connection tracking)
Маршрутизация (routing)
Один провайдер
Два провайдера
Policy routing
fwmark: NETFILTER+routing
Этапы прохождения пакета через NETFILTER
Входящий трафик через двух провайдеров
Резервирование и переключение: динамическая маршрутизация?
Доступ из внутренних сетей к сети Интернет через один публичный адрес (NAT - трансляция адресов)
Управление трафиком (traffic control: shaping, policing и т.д.)
Управление исходящим трафиком: shaping, scheduling
Входящий трафик и почему им невозможно управлять. Policing
Управление трафиком в рассматриваемом примере
Объединённое управление трафиком на нескольких интерфейсах (IFB, IMQ - одна дисциплина на несколько интерфейсов)
Управление трафиком в downstream-канале
Обьединение трафика на виртуальном IFB-интерфейсе
Настройка дисциплины IFB-интерфейса и создание иерархии классов
Иерархия классов для двух каналов/провайдеров
Классификация трафика при помощи NETFILTER
Управление трафиком и SQUID
Вариант №1: использовать возможности SQUID (версии 2.x) по управлению трафиком
Вариант №2: косвенное влияние на использование канала SQUID'ом плюс частичная "интеграция" с traffic control
Учет (подсчет) трафика (traffic accounting)
Подсчет трафика с помощью conntrackd
Подсчет трафика с помощью ulogd
Учет трафика и SQUID
Roadwarriors: мобильные клиенты. Доступ к внутренним сетям и VPN.
Функционирование VPN с точки зрения фаервола
VPN и маршрутизация
Выбор VPN
Использование ipset. Оптимизация набора правил
iptables-restore: атомарная загрузка набора правил
nftables: преемник iptables и будущее Linux firewall


Часть 2. Реализация
Чем не устроил Shorewall
Предыдущие варианты реализации или "как не надо делать"
Исходные тексты фаервола
Почему perl, а не bash, python и т.д.?
Почему конфигурация не хранится в СУБД или LDAP
Устройство фаервола
Структура каталогов
Поддержка нескольких конфигураций
Общие модули
Конфигурационные файлы
Файл описания шлюза и сетей
Синтаксис файла
Файл описания правил для хостов и сетей
Синтаксис файла
Конфигурация main
Процесс загрузки конфигурация main
Подробнее о функциях firewall.pm
Вопросы без ответов
TODO


Вступление

В данном тексте показан один из возможных вариантов построения интернет-шлюза (небольшой организации) на базе GNU/Linux с функциями фаервола, маршрутизатора. В качестве дистрибутива используется Debian GNU/Linux.

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

Также рассмотрены: маршрутизация (напр. работа через двух провайдеров), управление трафиком, учет трафика, разъяснены некоторые неочевидные моменты, с которыми пришлось столкнуться на практике.

Текст ориентирован на тех, кто хочет увидеть пример реально эксплуатирумого набора правил iptabels/tc (плох он или хорош - это другой вопрос), разобраться в работе и использовании подсистем фаервола и управления трафиком в Linux с целью самостоятельного конфигурирования. Подразумевается, что вы представляете, как продвинутый пользователь/администратор, что такое iptables, знаете синтаксис команд iptables, в общих чертах возможности NETFILTER (подсистема ядра для перехвата пакетов и выполнения манипуляций над ними), сетевого стека Linux в целом, знаете о IP, UDP/TCP и т.д. и т.п.
Т.е. у вас есть знания о составных частях, но вы смутно представляете себе, как из этого всего собрать, нечто, решающее сетевые задачи в комплексе.

Текст состоит из двух частей.
В первой части описаны предлагаемые принципы организации правил для фаервола (NETFILTER) и управления трафиком (TRAFFIC CONTROL), а также взаимодействие этих подсистем.

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

Если необходимо готовое, широко используемое решение - смотрите Shorewall http://www.shorewall.net (есть в большинстве дистрибутивов) или специализированные дистрибутивы-фаерволы/маршрутизаторы: IPCop, IPFire и т.п. (List of router or firewall distributions)

Если вы недостаточно понимаете возможности iptables - есть отличная документация по возможностям и синтаксису, правда на английском -
"Iptables Tutorial", http://www.frozentux.net/iptables-tutorial/iptables-tutorial.html.

Есть русский перевод устаревшей версии: http://www.opennet.ru/docs/RUS/iptables/



Часть 1. Концепции

Что мы хотим

Условимся подконтрольные нам сети называть "внутренними сетями", а сети, которые не находятся под нашим управлением - "внешними сетями". В простейшем случае: все что в офисе - "внутренняя сеть", Интернет - "большая внешняя сеть".

В рассматриваемом случае шлюз выполняет роль фаервола, маршрутизатора и, возможно, на нём функционируют какие-либо сетевые сервисы.

Итак, есть общая задача - организовать одновременный доступ нескольких узлов (хостов, компьютеров, устройств) из внутренних сетей к сети Интернет через один публичный IP-адрес. Также, необходимо организовать инфраструктуру под собственные web/ftp/smtp/pop-сервера с возможностью доступа к ним из внешних сетей. Дополнительно, небходимо обеспечить возможность доступа к другим внешним сетям через дополнительные интерфейсы шлюза и доступ в сеть Интернет через второго провайдера.

Уточним требования к шлюзу и задачи которые мы хотим решить:



Схема сети

Ниже приведена схема сети, которая будет рассматривать в дальнейшем:


Рис. 1 Схема сети

В общем, схема представляет собой классический "three-legged" ("Т-образный") firewall с выделением сервисов, доступных извне в отдельную сеть DMZ (от "Демилитаризованная зона"), которая подключена к отдельному интерфейсу (т.е. сетевой карте).

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


Рис. 2 Упрощенная схема сети

Теперь рассмотрим схему сети на Рис.1 подробнее:

Территориально в Здании 1 расположены:

Территориально в Здании 2 расположены:

Почему рассматривается именно этот вариант схемы?

Естественно реальная сеть, являющася прототипом рассматриваемой здесь сети, развивалась от простого к более сложному. Сначала была сеть LAN и один провайдер ISP1. Далее потребовалось контролировать доступ из сети LAN в Интернет - был выбран PPTP и появились pppN интерфейсы. Затем появилась необходимость в размещении собственных сервисов - для этого появилась сеть DMZ. Позже появилось подключение к сети LAN2 и выход через неё на второго провайдера ISP2. Соответственно, росло и количество интерфейсов.

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


Проблемы

Как организовать последовательность правил для фаервола? По какому принципу? Чтобы это было удобно и наглядно. В NETFILTER есть пять встроенных цепочек:

Чаще всего, в примерах и HOWTO встречается такой подход: правила фильтрации помещаются в эти цепочки "все в кучу" - одно за одним: все правила для фильтрации входящих пакетов на все интерфейсы - в INPUT, все правила для фильтрации исходящих пакетов из всех интерфейсов - в OUTPUT. Или: все разрешающие правила - в одну пользовательскую цепочку, все запрещающие - в другую цепочку. Или: все правила для tcp - в одну цепочку, для udp - в другую, для icmp - в третью и т.п. (см. тот же "Iptables Tutorial"). Изредка вводятся какие-то цепочки для решения каких-то частных задач и только. Достаточно универсальная методика не предлагается - в основном какие-то фрагменты, из которых трудно "сложить" что-то "внятное".

Такой подход к организации последовательности правил приводит к ряду сложностей:

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



Основные концепции: потоки, линки, цепочки

За основу взято предположение о симметричной маршрутизации. Для шлюза это значит, что ответные IP-датаграммы приходят на тот же интерфейс, с которого ушли "прямые". В случае форвардинга - что ответные IP-датаграммы проходят через ту же пару интерфейсов, что и IP-датаграммы в прямом направлении.

Косвенно о применимости данного подхода намекает cуществование "Strict Reverse Path Forwarding" в RFC 3704 - Ingress Filtering for Multihomed Networks:

Strict Reverse Path Forwarding is a very reasonable approach in front
of any kind of edge network; 
...
First, the test is only applicable in places where routing is
symmetrical - where IP datagrams in one direction and responses from
the other deterministically follow the same path.  While this is
common at edge network interfaces to their ISP, it is in no sense
common between ISPs, which normally use asymmetrical "hot potato"
routing.

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

Потоком (flow)
условимся называть однонаправленный логический канал передачи данных между двумя сетями или сетью и шлюзом. С учетом того, что:
  • физически трафик между сетями проходит не напрямую, а через шлюз;
  • в одну и ту же сеть (напр. INET) трафик может идти через разные интерфейсы шлюза;
  • трафик в разные сети может идти через один и тот же интерфейс шлюза (напр. в LAN2 и INET через eth3),
поток будет идентифицироваться, в общем случае четырья элементами:
  1. cеть источник,
  2. входной интерфейс на шлюзе,
  3. выходной интерфейс на шлюзе,
  4. сеть приёмник.
При этом сетью может быть совокупность нескольких IP-сетей. В рассматриваемой схеме, IP-сети 192.168.0.0/24 и 192.168.0.1/24 трактуются как одна сеть LAN, а не как две разные сети. Так как по смыслу трафик этих сетей необходимо фильтровать и маршрутизировать по единым правилам, то в терминах потоков удобнее рассматривать эти две сети как единое целое.
Далее по тексту потоки будут обозначаться следующим образом:
  • сеть_источник>вх_интерфейс-исх_интерфейс>сеть_приёмник обозначает один конкретный поток. Например LAN>eth0-eth3>INET, LAN>eth0-GW;
  • сеть_источник>сеть_приёмник обозначает все потоки (через все интерфейсы) между двумя сетями, идущие в одном направлении. Например LAN>INET, INET>GW;
  • сеть_источник-сеть_приёмник обозначает все потоки (в прямом и обратном направлениях) между двумя сетями; Например LAN-INET, INET-GW.
Из двух потоков, поток считающийся прямым, выбирается произвольно. Обратным же для прямого, будет поток, в идентификаторе которого входящие и исходящие сеть и интерфейс зеркально переставлены местами.
Линком, связью (link)
в данном тексте условимся называть совокупность всех потоков (прямых и обратных через все интерфейсы) между двумя сетями или сетью и шлюзом.
Цепочкой потока (flow chain)
будем называть пользовательскую цепочку, в которой сгруппированы правила, предназначенные для управления пакетами данного потока.

На рис.3 показаны основные потоки. Потоки относящиеся к одному линку имеют один цвет. Неиспользуемые или второстепенные потоки не показаны, чтобы не загромождать схему. Потоки между VPN и другими сетями показаны в разделе Функционирование VPN с точки зрения фаервола. Каждая стрелка соответствует потоку, а название стрелки - имени цепочки в правилах фильтрации.


Рис. 3 Потоки (основные)

Из схемы видно, что, например, линк LAN-DMZ состоит из двух потоков:

  1. LAN>eth0-eth2>DMZ - прямого и
  2. DMZ>eth2-eth0>LAN - обратного.
Линк LAN-INET состоит из восьми потоков:
  1. LAN>eth0-eth1>INET - прямой,
  2. LAN>eth0-eth3>INET - прямой,
  3. INET>eth1-eth0>LAN - обратный,
  4. INET>eth3-eth0>LAN - обратный,
  5. LAN>ppp+-eth1>INET - прямой,
  6. LAN>ppp+-eth3>INET - прямой,
  7. INET>eth1-ppp+>LAN - обратный,
  8. INET>eth3-ppp+>LAN - обратный.

В случаях когда сетью источником/приемником является сам шлюз,то входной/выходной интерфейсы не имеют смысла, например для линка LAN-GW:

  1. LAN>eth0-GW - прямой поток,
  2. GW-eth0>LAN - обратный.

Выше была показана только часть существующих между сетями потоков. Посмотрим, сколько всего потоков в рассматриваемой конфигурации. Представим схему сети в виде графа (рис. 4), в котором шлюз и сети представлены вершинами. Соединим каждую вершину со всеми остальными - получим полный граф со всеми возможными линками (ребра графа) между пятью сетями. Цвет линков на рис. 4 соответствует цветам на рис. 3. Серым цветом показаны неиспользуемые линки - трафик между этими сетями нам не нужен и поэтому не разрешён. Кол-во линков будет равно N*(N-1)/2, где N - кол-во вершин в графе. Для рассматриваемой сети N=6 и кол-во линков равно 15.
Т.к. линк состоит как минимум из двух потоков - прямого и обратного, то минимальное кол-во всех потоков будет равно 30.


Рис. 4 Полный ориентированный граф сети - потоки и связи

Однако это еще не всё. Сеть LAN у нас "синтетическая" и фактически объединяет две разных IP-сети на двух разных интерфесах - eth0 и ppp+. С сетью INET похожая ситуация - доступ в сеть INET тоже возможен через два интерфейса - eth1 и eth3. Поэтому если мы хотим посчитать абсолютно все варианты обмена трафиком, то граф должен содержать все сочетания сеть-интерфейс: LAN-eth0, LAN-ppp+, INET-eth1, INET-eth3, LAN2-eth3, VPN-tun1, DMZ-eth2, GW или что то же самое все интерфейсы шлюза плюс сам шлюз. Получаем 7 интерфейсов плюс сам шлюз и полное кол-во потоков 7*8=56. Максимальное же кол-во потоков для графа на рис. 3 будет равно 52, т.е. меньше на 4 потока: LAN>eth0-ppp+>LAN, LAN>ppp+-eth0>LAN и INET>eth1-eth3+>INET, INET>eth3-eth1>INET.

Теперь переходим к предлагаемому приниципу организации правил.

Основной подход в организации правил - вместо линейной последовательности правил использовать ветвление: каждому потоку - своя пользовательская цепочка (цепочка потока). Пакет "направляется на проверку" в ту или иную пользовательскую цепочку на основании принадлежности к одному из потоков.

Схематично это можно изобразить следующим образом:



Рис. 5 Линейная организация правил (слева) и организация правил с ветвлениями на основе интрефейсов и адресов сетей (справа).

Т.о. краткий алгоритм организации правил таков:
  • идентифицируем все возможные связи между сетями и между сетями и шлюзом
  • для каждого реально используемого соединения создаем по две (для прямого и обратного потока) отдельных цепочки с информативными именами
  • правила, относящиеся к соответствующему потоку помещаем в соответствующую цепочку (т.е. в цепочку потока)
  • во встроенных цепочках INPUT, OUTPUT, FORWARD создаем только правила переходов в соответствующие цепочки потоков
  • используем данный подоход как для фильтрации (в filter table), так и модификации (напр. MARK, CLASSIFY) пакетов (mangle table)

В случае необходимости иметь единый набор правил для разных потоков достаточно направить пакеты, принадлежащие разным потокам, в одну и ту же цепочку потока. Например, в рассматриваемом примере, правила фильтрации одинаковы для трафика между сетями LAN и Интернет вне зависимости от того каким путём он идет: через eth0 или ppp+ и eth1 или eth3. Или, например, трафик между сетями LAN, PPTP и шлюзом GW будет обрабатываться одинаково, как будто LAN, PPTP - одна сеть.

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

Конфигурация интерфейсов из файла /etc/network/interfaces

/etc/network/interfaces

allow-hotplug eth0 eth1 eth2 eth3

### LAN
auto eth0
iface eth0 inet static
	address 192.168.0.1
	netmask 255.255.255.0
	network 192.168.0.0
	broadcast 192.168.0.255

### INET
auto eth1
iface eth1 inet static
	address 198.51.100.2
	netmask 255.255.255.252
	network 198.51.100.0
	broadcast 198.51.100.3
	gateway 198.51.100.1

### DMZ
auto eth2
iface eth2 inet static
	address 203.0.113.1
	netmask 255.255.255.248
	network 203.0.113.0
	broadcast 203.0.113.7
	
### LAN2
auto eth3
iface eth3 inet static
	address 192.168.5.1
	netmask 255.255.255.0
	network 192.168.5.0
	broadcast 192.168.5.255

Какие плюсы и минусы имеет такой принцип организации правил? На мой взгляд следующие:



Фильтрация

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

Сетевые сервисы или чего не должно быть на шлюзе

В норме на шлюзе не должно быть ни одного сетевого сервиса и соответственно открытых портов! Из этого принципа надо исходить изначально.

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

На шлюзе правила фильтрации настраивают таким образом, что подключения, инициируемые из сети LAN в DMZ - разрешены, а вот соединения инициируемые из сети DMZ в LAN - блокируются! Таким образом, при взломе сервиса, работающего на сервере в DMZ, уменьшаются последствия взлома - напрямую со взломанного сервера невозможно будет подключиться к изолированой сети LAN. Это и есть функция сети DMZ.

Однако в реальности, по разным организационно-финансовым причинам, не всегда есть возможность вынести сетевые сервисы (web/ftp/smtp/pop3/dns и т.п.) в отдельную сеть. Поэтому не остается ничего другого, как ставить их на единственном сервере - шлюзе.

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

Конкретно в нашем примере, сервисы расположены на шлюзе и только web-сервер вынесен в DMZ. Поэтому на шлюзе есть открытые порты.

Контроль доступа

Для контроля доступа на уровне адресов хостов, протоколов, tcp/udp-портов достаточно возможностей одного NETFILTER. Контроль сводится к фильтрации (блокированию) нежелательного трафика по IP и/или MAC адресам.

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

PPTP VPN

В качестве VPN довольно удобно использовать PPTP: сервер есть под Linux, а клиент встроен во все версии Windows.

Для выхода в контролируемую сеть (например из LAN в Internet), пользователь должен создать туннель между своим устройством (компьютером) и сервером PPTP на шлюзе. Для этого он должен пройти аутентификацию по логину/паролю: получить доступ в контролируемую сеть без аккаунта невозможно. Естественно на шлюзе правила фаервола запрещают прохождение пакетов c адресов 192.168.0.0/24 сети LAN в контролируемую сеть. Зато там есть разрешающие правила для клиентских адресов pptp-тунеллей 192.168.1.xxx. Если пользователь пройдет процесс аутентификации, то будет создан туннель между его компьютером и шлюзом. На стороне шлюза туннелю будет присвоен адрес 192.168.3.1, а клиентской части туннеля - некий адрес, привязанный к логину и задаваемый в например /etc/ppp/chap-config. Т.о. создается устойчивое соотвествие "пользователь" - "IP адрес туннеля" и на шлюзе появляется возможность управлять доступом и другими параметрами подключения на основе IP-адреса клиентской стороны туннеля с помощю NETFILTER. Т.е. контроль на шлюзе и в этом случае происходит "естественным способом" - по IP-адресам. Только в этом случае адрес "защищен от подмены" логином/паролем.

OpenVPN

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

NuFW

Работа NuFW основана на другом принципе. На firewall устанавливается Nufw-сервер, который принимает пакеты от клиентов. На этом же хосте (или на другом) ставится сервер аутентификации Nuauth. На каждом клиентском хосте (компьютере) ставится Nufw-клиент. Каждый пакет исходящий от клиента принимается Nufw-сервером и ставится в очередь в ожидании аутентификации. Об этом пакете информируется Nuauth, который, в свою очередь, запращивает у соответствующего Nufw-клиента аутентификационные данные. Nufw-клиент запрашивает прохождение данного пакета у Nuauth и если пакет авторизован - передает разрешение на Nufw-сервер, который и "пропускает" пакет.

Nufw для своей работы использует NETFILTER: пакеты, подлежащие авторизации, просто направляются в определенную userspace (пространство пользователя) очередь при помощи target NFQUEUE, т.е. в очередь которой управляет Nufw-сервер. В случае удочной авторизации пакета, Nufw-сервер просто реинжектит пакет из очереди обратно в сетевой стек. В реальности достатчно отправлять на на авторизацию только NEW-пакеты, а остальные будут контролироваться в рамках сединения с помощью conntrack.

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

В 2012 году проект был в "дауне". На начало 2013 похоже проект "восстал из пепла" с новым именем - UFWI

В нашем примере одновременно используются аутентификация по IP-адресам устройств и PPTP VPN.

NuFW более громоздок и на момент написания первоначального варианта фаервола не было бесплатного NuFW-клиента под Windows, поэтому в свое время был выбран вариант с PPTP VPN.

Цепочки фильтрации

Теперь рассмотрим непосредственно сами цепочки фильтрации и правила в них. Однако, в отличии от большинства руководств, вместо анализа последовательности команд iptables, мы будем анализировать непосредственно результат их работы, т.е. вывод команды iptables -nvL.

Как видно илз листинга, цепочки имеют "говорящие" имена и пронумерованы. Это позволяет легко ориентироваться в листинге таблицы filter. К тому же цепочки сгруппированы по потокам и всегда выводятся в предсказуемом порядке, что удобно. Рассмотрим эти цепочки.

INPUT, OUTPUT, FORWARD

Листинг начинается со встроенных стандартных цепочек INPUT, OUTPUT, FORWARD. В эти цепочки помещены правила, которые "определяют принадлежность" (по интерфейсам и адресам сетей) пакета к какому-либо логическому потоку и переключают логику дальнейших проверок на последовательность правил в пользовательской цепочке, соответствующей потоку. Пакеты, не соответствующие ни одному из правил в этих цепочках (пакеты не соответствующие ни одному из логических потоков) игнорируются. Достигается это установкой политик по умолчанию (см. Chain XXXX (policy DROP...) Порядок, в котором размещены правила в цепочках INPUT, OUTPUT, FORWARD, косвенно влияет на скорость обработки пакетов: первыми желательно размещать правила, под которые будет, предположительно, подходить основная масса пакетов.

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

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

Таким образом, в дальнейшем (после правил в цепочках INPUT, OUTPUT, FORWARD ), пакет проверяется на соответствие только "своим" (относящимся только к данному потоку) правилам, а не всем подряд правилам, имеющимся в firewall'е. Это и даёт преимущества, указанные выше.

Примечание. В англоязычных источниках процесс проверки пакета на соответствие какому-то из правил так и описывается: будто бы пакет "движется" по набору правил, от правилу к правилу, "прыгает" в другие цепочки, "выпрыгивает" из них обратно и т.д. Насколько я понимаю, это условность - в реальности пакет никуда не "движется" (по набору правил), но так очень удобно описывать процесс, в котором ядро, для каждого пакета, перебирает правила, имеющиеся в firewall'e, проверяет, соответствует ли пакет данному правилу и решает что с эти пакетом делать: проверить следующее правило, переключиться (-j ИМЯ_ЦЕПОЧКИ) на другую последовательность (цепочку) правил проверки, принять пакет (-j ACCEPT) или отвергнуть его (-j REJECT/DENY) и т.п. Конечно, взаимосвязь между этапами обработки пакетов в сетевом стеке и таблицами/встроенными цепочками есть, но не более того. Я по тексту буду тоже "грешить" таким подходом, чтобы не ломать язык.

Далее рассмотрим цепочки потоков и наборы правил в них подробнее, ориентируясь по схеме на рис. 3.
Расцветка правил, соответствует цветам потоков на схеме.

01_LAN_GW, 02_GW_LAN: Локальная сеть - шлюз

Начнём со взаимодействия локальной сети LAN и шлюза Gateway. Для цепочки 01_LAN_GW мы "отбираем" только пакеты пришедшие из сети 192.168.0.0/24 на адрес 192.168.0.1 интерфейса eth0, а также пакеты пришедшие из сети 192.168.1.0/24 на адрес 192.168.3.1 любого ppp-интерфейса. Это соответственно правила №2 и №3 в цепочке INPUT. 192.168.3.1 - это адрес серверных концов всех ppp-туннелей, он одинаков для всех и устанавливается в /etc/pptpd.conf.
Для цепочки 02_GW_LAN всё наоборот: пакеты уходящие с 192.168.0.1 eth0 в сеть 192.168.0.0/24 и пакеты уходящие со 192.168.3.1 любого ppp-интерфейса в сеть 192.168.1.0/24. Это и есть потоки между локальной сетью и шлюзом.

Примечание. Возможно, вам покажется, излишним указывать в правилах одновременно интерфейс и его адрес (или адрес сети), но это не так. Дело в том, что на любой интерфейс может прийти пакет с абсолютно любым адресом назначения (и отправителя тоже). Если вы укажите только имя интерфейса и не укажете IP-адрес(а), то даже пакет с адресом получателя, не совпадающим с адресом интерфейса, будет принят на дальнейшую обработку и может "проскочить" ваш firewall. Поэтому ни в коем случае нельзя полагаться на то что, раз у вас внутренняя сеть имеет адрес 192.168.0.0/24, то в ней не могут появиться пакеты с адресами принадлежащими другим IP-сетям. Такие ситуации могут возникнуть как в результате попыток обойти firewall так и в штатных режимах, например при маршрутизации (policy routing) или использовании туннелей (IPSec/NETKEY). Тоже самое относится и к отправляемым пакетам: например eth1 имеет адрес 198.51.100.2 но это не значит, что все пакеты отсылаемые провайдеру ISP1 через этот интерфейс обязательно будут иметь адрес отправителя 198.51.100.2. И уж тем более это очевидно в случае, когда интерфейс имеет несколько IP-адресов (да, интерфейс может иметь несолько IP-адресов одновременно).

Итак мы рассмотрели, какие пакеты попадают на проверку в цепочки 01_LAN_GW, 02_GW_LAN. Теперь посмотрим, что у нас в цепочке 01_LAN_GW. Первым же правилом мы отправляем пакет в цепочку со счетчиками 01_LAN_GW_CNT, в которой просто перечислены интересующие нас правила с пустым target (в соответствующенй команде нет ключа -j). Это правило идет самым первым, потому что иначе не будут считаться пакеты уже установленных соединений.

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

В 01_LAN_GW_CNT, у подходящих правил, счетчики увеличиваются на размер пакета и мы снова возвращаемся в 01_LAN_GW на второе правило. Все "счетчики" являются необязательными и если они вам не нужны - просты выкиньте соответствующие правила и цепочки. Вторым правилом мы принимаем все пакеты, относящиеся к уже установленным соединениям (т.е. те о которых уже "знает" conntrack). Это правило идет вторым и перед правилами, разрешающими подключения на определенные порты, потому что основная масса пакетов будет относиться к уже установленным соединениям. И поэтому нет смысла пакеты "прогонять" сначала через кучу правил для новых соединений и только потом ставить правило RELATED,ESTABLISHED. Следующее правило перекидывает нас в цепочку 01_LAN_GW_SKIP_OR_DENY, в которой мы помещаем правила, запрещающие прохождение каких-либо пакетов и правила, позволяющие сделать исключения из этих запретов. Такая комбинация практически универсальна и позволяет реализовать большинство вариантов запретов и исключений. Остановимся на этом моменте подробнее.

Рассмотрим пример, правда несколько искусственный. Итак, вам надо открыть доступ к порту tcp 873 для всей сети 192.168.0.0/24, за исключением хостов с адресами из диапазона 192.168.0.10-192.168.0.20, но некий узел 192.168.0.13, из этого запрещенного диапазона, тоже должен иметь доступ на tcp 873.

Ясно, что запрещающее правило (192.168.0.10-192.168.0.20 tcp dport 873 DROP) должно идти перед разрешающим (192.168.0.0/24 tcp 873 tcp dport 873 ACCEPT). А теперь, как из запретного диапазона исключить 192.168.0.13 tcp dport 873? Надо поставить это правило перед правилом 192.168.0.10-192.168.0.20 tcp dport 873 DROP. А если у нас будет много разных запрещающих условий и исключений из них? Надо будет следить за порядком следования этих правил и это будет слишком громоздко.
Поэтому удобнее разнести запрещающие и исключающие правила в разные цепочки по нижеизложенному принципу.

Как мы уже видели, в основной цепочке потока 01_LAN_GW первым же делом мы принимаем (ACCEPT) все пакеты, относящиеся к уже установленным (RELATED,ESTABLISHED) соединениям. Таким образом, после этого правила, все проверки будут проводиться только над пакетами "в состоянии NEW" (ну еще может быть INVALID), что, по идее, экономит ресурсы. Поэтому далее идет "работа" только с "NEW-пакетами".
Далее проверка из 01_LAN_GW переходит в цепочку 01_LAN_GW_SKIP_OR_DENY, где первыми размещены все исключающие правила с target -j RETURN, а самым последним стоит правило направляющее проверку в список запрещающих правил 01_LAN_GW_DENY. Таким образом пакеты, подпадающие под исключения из запретов, "выскочат сухими" из 01_LAN_GW_SKIP_OR_DENY обратно в 01_LAN_GW, так и не дойдя до цепочки 01_LAN_GW_DENY. А пакеты, не соответствующие исключениям, уйдут на проверку в 01_LAN_GW_DENY и, возможно там и "умрут" (DROP). "Выжившие" (не подошедшие ни под один DROP) "выскочат" сначала в 01_LAN_GW_SKIP_OR_DENY, так как цепочка 01_LAN_GW_DENY закончилась, а затем и в 01_LAN_GW. И если эти пакеты не будут соответствовать разрешающим правилам (ля-ля-ля state NEW -j ACCEPT) в этой цепочке, они будут "убиты" самым последним правилом в цепочке 01_LAN_GW (DROP), которое, в некторой степени, является перестраховочным, но полезным. Оно скорейшим образом убъет "неугодный" пакет, не давая ему "выпрыгнуть" обратно в INPUT и "гулять" по остальным правилам пока его не DROP'нут по default'у.

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


Рис. 6 Логика "Разрешения+исключения из разрешений (запрет)+исключения из запретов"

Т.е. разрешающие правила располагаются после всех запрещающе-исключающих правил.

По только что описанному принципу (рис. 6) построены цепочки и правила фильтрации для всех потоков!
Да, конечно можно, вместо использования цепочек LAN_GW_DENY и LAN_GW_ALLOW, просто выстроить правила в нужном порядке: сначала RELATED/ESTABLISHED, затем все запрещающие и затем все разрешающие - эффект будет (почти) тот же. "Почти" потому что, теряется возможность вносить исключения в запреты посредством добавления правил в LAN_GW_SKIP_OR_DENY - без использования цепочки подобный "пропуск" группы правил невозможен. Также теряется простой (путем очистки соответствующей цепочки) способ обновить часть правил: только разрешающие или только запрещающие. Т.е. возможности оптимизации зависят от наличия или отсутствия тех или иных правил в конкретном потоке. Эту задачу можно решить с помощью скриптов.

Назначение большей части открытых портов в 01_LAN_GW думаю очевидна. Специально поясню только два правила:

Теперь о цепочке 02_GW_LAN. Здесь мы описываем, что сам шлюз может посылать в сеть LAN. Здесь все тоже построено по вышеописанному принципу. Так же, вторым правилом после счетчиков, идет ... state RELATED,ESTABLISHED...ACCEPT, для того чтобы принимать пакеты ранее установленных соединенений.

Порты в 02_GW_LAN.

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

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


Рис. 7 Connection tracking: почему RELATED,ESTABLISHED нужен в обоих направлениях

03_INET_GW, 04_GW_INET: Интернет - шлюз

Это потоки между самим шлюзом и сетью Интернет. Как видно из схемы, существует два пути для трафика между шлюзом и Интернетом: через провайдера ISP1 и через провайдера ISP2. Соответственно для проверок, в эту цепочку отбираются следующие пакеты (смотрим цепочки INPUT, OUTPUT): Каким же образом определяется, по какому пути пойдет со шлюза конкретный пакет в данный момент? Этот вопрос решается с помощью средств маршрутизации. Фильтрация, как таковая, здесь может лишь выполнять вспомогательную функцию - маркировку пакетов (напр. MARK). Поэтому ответ на этот вопрос будет дан в разделе Маршрутизация (routing), а сейчас рассмотрим только фильтрацию.

Выданный провайдером ISP1, адрес 198.51.100.2 на eth1, является статическим, поэтому внешние сервисы (ftp/smtp/dns/pop3) "слушают" на этом адресе и интерфейсе. Именно этот адрес является "адресом шлюза в сети Интернет". А вот адрес 192.168.5.1 на интерфейсе eth3 является внутренним, а внешний адрес присваивается внешнему интерфейсу модема ADSL №2 динамически. Т.е. шлюз своим интерфейсом eth3 непосредственно в Интернете "не виден". Поэтому организовать на нем внешние сервисы возможно, но на практике это связано с различными проблемами, связанными как с динамическим адресом модема (обновление DNS, RR в обратной зоне для SMTP-сервера и т.п.), так и необходимостью использования DNAT на модеме для осуществления проброса портов (port forwarding) от модема на eth3 шлюза. Т.о., в нашем примере, для интерфейса eth1 надо открыть порты внешних сервисов, а вот для eth3 это делать не надо.

Поэтому, специально для трафика, приходящего из Интернета на eth3 сделана отдельная цепочка 03_INET_GW_ISP2, которая фактичеcки является копией 03_INET_GW, но без "NEW-правил". В ней мы только считаем и разрешаем трафик от уже установленных соединений.

Почему в INPUT в правиле для 03_INET_GW_ISP2 мы не указали destination 192.168.5.1? Это связано с тем опять же с маршрутизацией через двух провайдеров: на интерфейс eth3 могут приходить и пакеты с адресом назначения 198.51.100.2, а не только 192.168.5.1. Причины этого рассмотрены в разделе "Этапы прохождения пакета через NETFILTER"

Схема же построения правил в цепочках 03_INET_GW, 04_GW_INET совпадает со схемой правил в цепочках 01_LAN_GW, 02_GW_LAN и не имеет каких-то отличительных особенностей.


05_LAN_INET, 06_INET_LAN: Локальная сеть - Интернет

Переходим к рассмотрению несколько более интересной ситуации: трафик между локальной сетью LAN и сетью Интернет. Здесь также два пути прохода трафика: через провайдера ISP1 и провайдера ISP2.

В данном примере цепочки 05_LAN_INET, 06_INET_LAN универсальны и служат для фильтрации по обоим маршрутам, т.е. правила фильтрации одни и те же для трафика через обоих провайдеров.

Эти цепочки используются для проверки следующих пакетов (смотрим схему и правила):

и в обратном направлении соответственно (просто меняем source и destination местами).

Смотрим как устроена цепочка 05_LAN_INET. Принцип тот же, что и в цепочках, рассмотренных выше. Но есть некоторые отличия.

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

Если в сети LAN появятся зараженные компьютеры, то с высокой вероятностью они станут рассылать спам и прочую ерунду посредством SMTP-протокола, напрямую подключаясь к различным SMTP-серверам в Интернете. Не полагайтесь на наличие антивируса: всегда найдется машина, не защищенная в данный момент антивирусом (сотрудник или клиент принес свой ноутбук) либо найдется вирус, который не дететктируется установленным антивирусом. 100%-ый выход один - заблокировать подключение на tcp 25 на сетевом уровне. Однако при этом пользователи сети теряют возможность отправлять почту через внешние SMTP-сервера посредством почтовых агентов. Если это не проблема для сотрудников и/или у вас в сети есть свой собственный SMTP-сервер (естественно с SMTP-аутентификацией для защиты от неавторизованной рассылки), то можно решить эту проблему и так. А можно поступить чуть менее жестко.

Можно вместо жесткого запрета, сделать исключения для некоторых, нужных вам серверов, например популярных почтовых сервисов. Как раз правила в 05_LAN_INET_SKIP_OR_DENY и разрешают доступ к 25 tcp портам некоторых почтовых сервисов. А в 05_LAN_INET_DENY есть запрещающее правило на dst tcp 25. Таким образом мы запрещаем подключения на 25 tcp порт всех хостов в Интернете за исключением перечисленных в 05_LAN_INET_SKIP_OR_DENY.

Второй вариант - лимитировать кол-во подключений на 25 tcp в единицу времени. Так как задача вируса или бота - разослать как можно быстрее и больше, то можно заблокировать слишком часто идущие попытки подключения на 25 tcp. Правда это подразумевает, что бот остылает одно (или мало) сообщений через одно подключение (SMTP-сессию).

Для этого можно воспользоваться модулями iptables limit, hashlimit или recent.

В нашем примере использован limit - разберем сначала его. В 05_LAN_INET_LIMIT первое правило говорит: "Разрешить новые подключения на tcp порт 25 не чаще 2-х раз в минуту для каждой комбинации "адрес источника-адрес приемника" и допускается "сжечь"(использовать) до пяти подключений без контроля (промежутка времени)".

Перескажу своими словами, как работает этот алгоритм: в некий "счетчик" ("корзину" - bucket) емкостью (burst) 5, с постоянной частотой 2 раза в минуту "капают" (поступают) разрешения ("билеты") на прохождение пакета (т.е. чтобы "корзина" заполнилась "до краев" надо около 2,5 минут). После заполнения "до верху" разрешения уже не капают, а ждут пока в "корзине" появится свободное место. Свободное место появляется при выдаче разрешения на прохождение одного пакета: одно разрешение - один пакет. Теперь, пусть клиент из сети пытается подключиться к 25-му порту 10 раз в течении 10 секунд (отправить 10 "NEW-пакетов" за 10 секунд). Произойдет следующее: накопившиеся в корзине 5 разрешений будут "мгновенно" использованы и 5 подключений будут сделаны в кратчайшие сроки и корзина опустошится: разрешениий на прохождение пакетов больше нет. Теперь оставшиеся 5 пакетов смогут уйти, только если в корзине появятся разрешения для них (по одному на пакет): поэтому клиенту для отправки следующего (6-го) пакета придется ждать пока в корзину "капнет" очередное разрешение. А так как капают они по 2 в минуту, то чаще двух подключений в минуту клиент уже сделать не сможет. Если он снизит частоту своих подключений ниже 2/мин, то разрешения снова смогут накапливаться до 5 (емкости корзины).
http://en.wikipedia.org/wiki/Token_bucket

Подключения, идущие чаще 2/минуту, не будут разрешены первым правилом и проверка "проваливается" дальше: на второе и третье правила. Ну а третье правило просто "убивает" пакет, который бы привысил заданную частоту. А вот второе правило записывает в системный лог (и возможно выдает на консоль) сообщение, но тоже с частотой не превышающей определенной заданной. Дело в том что, если пакеты, не подпадающие под лимит будут идти с большой частотой, то в лог (и на консоль!) будут, с этой же частотой, добавляться записи о каждом пакете (а их может быть ОЧЕНЬ много). Для ограничения записи в лог используется тот же модуль limit. Т.е. в логе мы увидим запись не о каждом пакет, превысившим частоту предыдущего правила, а только о части, не превысившей ограничений команды ...-j LOG Таким образом мы можем ограничить частоту подключений до желаемой величины, практически не даваю боту из сети забивать ваш канал и "сильно беспокоить" SMTP-сервера. Однако соединения хоть и редко, но будут осуществляться. Вообще этот модуль служит для защиты от DOS-атак, когда не надо полностью блокировать доступ.

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

Обратите внимаение, что лимитровать надо только частоту NEW-пакетов (они же SYN для tcp), а не частоту пакетов уже установленных соединений!

Вместо модуля limit можно использовать использовать модуль recent. Для этого вместо трех команд, приведенных в примере, можно использовать такие:

iptables -A 05_LAN_INET_LIMIT -p tcp --dport 25 -m recent --update --seconds 120 --hitcount 5 --name LAN_INET_25 --rdest \
  -m limit --limit 1/min --limit-burst 5 -j LOG --log-prefix `LAN->INET_SMTP_limit: '  
iptables -A 05_LAN_INET_LIMIT -p tcp --dport 25 -m recent --update --seconds 120 --hitcount 5 --name LAN_INET_25 --rdest -j DROP 
iptables -A 05_LAN_INET_LIMIT -p tcp --dport 25 -m recent --set --name LAN_INET_25 --rsource -j ACCEPT
что даст нам следующее
0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:25 recent: UPDATE seconds: 120 hit_count: 5 name: LAN_INET_25_LOG side: dest limit: avg 6/hour burst 5 LOG flags 0 level 4 prefix `LAN->INET_SMTP_limit: '
0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:25 recent: UPDATE seconds: 120 hit_count: 5 name: LAN_INET_25 side: dest
0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:25 recent: SET name: LAN_INET_25 side: source  
и означает: "если с определённого адреса за 120 секунд или менее будет происходить более 5 попыток подключения" - "убивать" пакеты с этого адреса в дальнейшем до тех пор, пока частота не снизится до "не более 5-ти пакетов за 120 секунд". Это условие описывается двумя правилами, а не одним (подробнее читайте документацию recent)

Какой эфект даст recent в сравнении с limit? После момента превышения частоты и до её спада ниже заданного уровня recent полность болкирует трафик, а limit "цедит в час по чайной ложке" как указано в параметре --limit. Но в limit можно указать отслеживание лимита по комбинации "источник-приёмник", а у recent либо только "источник", либо только "примёник": c limit можно ограничить каждую пару IP (source-destination) в отдельности, а с recent - только всю сеть, как единое целое.

07_INET_DMZ, 08_DMZ_INET: Интернет - DMZ

Как уже говорилось ранее сеть DMZ предназначена для размещения внешних сервисов. В нашем примере часть сервисов расположена на самом шлюзе (хотя это и является плохой практикой!), а в DMZ расположены два web-сервера: один с адресом 203.0.113.2 и другой с адресом 203.0.113.3. Предполагается, что доступ к серверам в DMZ извне осуществляется только через провайдера ISP1 по внешнему адресу 198.51.100.2, по причинам, описанным в разделе Интернет - шлюз. Рассмотрим соответствующие цепочки.

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

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

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

Так же как и в цепочках сединения Интернет - шлюз, для входящих на eth3 через ISP2 пакетов создана специальная цепочка 07_INET_DMZ_ISP2, в которой нет разрешений для установления новых соединений из сети Интернет.

09_LAN_DMZ, 10_DMZ_LAN: Локальная сеть - DMZ

Основная особенность у этих цепочек - однонаправленность для новых соединений: хосты из сети LAN должны иметь возможность подключаться (устанавливать новые соединения) к хостам сети DMZ, а вот наборот: хосты из сети DMZ к хостам сети LAN - нет.

Для этих цепочек мы отбираем пакеты:

и в обратном направлении, соответственно.

Обратите внимание на цепочку 10_DMZ_LAN - видно, что там разрешены только RELATED,ESTABLISHED-пакеты, т.е. из DMZ невозможно установить новое соединение с хостами сети LAN, что и требуется.

Второй момент, на который можно обратить внимания - правила, где есть tcp spts:60000:61000. Они связаны с протоколом FTP через SSL (как и tcp spt:20 разделе Локальная сеть - шлюз). Только приём с открытием 20 порта здесь "не пройдет" из-за active mode протокола FTP!
Ведь в этом случае мы будем вынуждены разрешить подключения из DMZ в LAN, чего мы крайне не хотим!

Выход один - использовать пассивный режим (passive mode) в котором все соединения инициирует только клиент (см. Active FTP vs. Passive FTP, a Definitive Explanation). Для этого на серверах в DMZ надо сконфигурировать ftp-сервер на прослушивание фиксированного диапазона портов, на которые клиент будет подключаться для передачи данных. Вот как раз прохождение пакетов на этот диапазон портов и нужно будет разрешить на шлюзе в потоках LAN->DMZ.

Фильтрация и отслеживание соединений (connection tracking)

В рассматриваемом варианте организации правил используются возможности NETFILTER по отслеживанию состояния соединений (stateful packet inspection), что необходимо учитывать в ситуциях, подобной описанной ниже.

Пусть для некоего хоста в данный момент разрешено прохождение пакетов: установление новых соединений разрешают правила типа -m state NEW, а прохождение остальных пакетов этих соединений разрешаются правилами типа -m state RELATED, ESTABLISHED. Пусть, начиная с какого-то момента исчезает необходимость в разрешении трафика данному хосту: для него планируется полностью удалить все правила (удалить хост из конфигурации), а не просто запретить трафик, явно добавив запрещающие правила. При этом подразумевается, что после удаления всех, в т.ч. разрешающих, правил, обмен пакетами для данного хоста тут же станет невозможным.

Однако только удаления рарзрешающих правил в такой ситуации недостаточно. Т.к. остаются правила типа -m state RELATED, ESTABLISHED, то хост сможет получать/отправлять пакеты в рамках ранее установленных соединений до момента их разрыва: явного или по таймауту. Например, если в момент удаления из конфигурации хост загружал некий файл, то и после удаления правил хост сможет продолжать загружать данный файл, пока он полностью не будет загружен.

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

Для удаления отслеживаемых соединений а также других манипуляций с соединениями необходима утилита conntrack из пакета conntrack-tools.

Для работы утилиты conntrack необходимо предварительно загрузить модуль ядра ip_conntrack.

modprobe ip_conntrack # загружаем модуль ядра
И далее, например, чтобы удалить все соединения, установленные хостом с адресом 192.168.1.3 надо выполнить команду:
conntrack -D -s 192.168.1.3 

По этой же причине, для того чтобы запрещающие правила действовали и на уже установленные соединения, их необходимо вводить перед RELATED, ESTABLISHED-правилами. См. цепочку 05_LAN_INET в листинге таблицы filter.

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



Маршрутизация (routing)

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

Маршрутизацию осуществляет ядро Linux. Настраивается же маршрутизация командой ip из пакета iproute или коммандой route из пакета net-tools. route не позволяет управлять всеми аспектами маршрутизации в Linux и является, в принципе, устаревшей. Поэтому рассматривается управление только через команду ip.

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

Один провайдер

Сначала, рассмотрим как происходит маршрутизация, без учета того факта, что в Интернет можно "попасть" и через ISP2 (eth3). Рассмотрим таблицу маршрутизации для нашего примера:

#ip route show
192.168.1.1 dev ppp1  proto kernel  scope link  src 192.168.3.1.
192.168.1.3 dev ppp2  proto kernel  scope link  src 192.168.3.1.
192.168.255.2 dev tun1  proto kernel  scope link  src 192.168.255.1.
198.51.100.0/30 dev eth1  proto kernel  scope link  src 198.51.100.2.
203.0.113.0/29 dev eth2  proto kernel  scope link  src 203.0.113.1.
192.168.5.0/24 dev eth3  proto kernel  scope link  src 192.168.5.1.
192.168.0.0/24 dev eth0  proto kernel  scope link  src 192.168.0.1.
192.168.255.0/24 via 192.168.255.2 dev tun1.
default via 198.51.100.1 dev eth1.

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

При поиске маршрута используется Longest prefix match (соответствие по самому длинному префиксу). Т.е. если в таблице будут строки:
192.168.0.0/16
192.168.5.0/24
то адрес 192.168.5.1 соответствует обоим сетям, но выбран будет маршрут для 192.168.5.0/24 так как здесь префикс (сети) длиннее.

Итак, первые два маршрута в таблице говорят о том, что пакеты для хоста с адресом 192.168.1.1 надо доставлять через интерфейс ppp1, а для хоста 192.168.1.3 - через ppp2.
Это PPTP-клиенты подключившиеся из сети LAN.

3-ий маршрут говорит что пакеты для хоста с адресом 192.168.255.2 надо доставлять через через интерфейс tun1.
Это виртуальный интерфейс сервера OpenVPN.

4-ый маршрут говорит что пакеты для сети с адресом 198.51.100.0/30 надо доставлять через интерфейс eth1.
Это glue-сеть между шлюзом и щлюзом провайдера с адресом 198.51.100.1.

5-ый маршрут говорит что пакеты для сети с адресом 198.51.100.0/30 надо доставлять через интерфейс eth2.
Это сеть DMZ, 203.0.113.1 - адрес интерфеса eth2 в этой сети.

6-ой маршрут говорит что пакеты для сети с адресом 192.168.5.0/24 надо доставлять через интерфейс eth3.
Это сеть LAN2, 192.168.5.1 - адрес интерфеса eth3 в этой сети.

7-ой маршрут говорит что пакеты для сети с адресом 192.168.0.0/24 надо доставлять через интерфейс eth1.
Это сеть LAN, 192.168.0.1 - адрес интерфеса eth1 в этой сети.

8-ой маршрут говорит что пакеты для сети с адресом 192.168.255.0/24 надо доставлять через шлюз (в сеть OpenVPN) 192.168.255.2 через интерфейс tun1.

И наконец 9-ый маршрут говорит что пакеты для которых не было найдено подходящго маршрута (т.е. хоста/сети назначения) доставлять через через интерфейс eth1 на шлюз ("via" - "через") 198.51.100.1.
Это шлюз провайдера ISP1, через который пакеты далее попадут в Интернет.
Это и есть маршрут "по-умолчанию" (default).

По этому маршруту будет направлен любой пакет, для которого ядро не найдет подходящего маршрута!

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

Все эти маршруты были добавлены в таблицу маршрутизации автоматически при инициализации интерфейсов на основании конфигурационной информации. В Debian это файл /etc/network/interfaces.

Поэтому, в случае одного провайдера вручную делать особо ничего и не надо, кроме как правильно сконфигурировать интерфейсы. При указанной выше конфигурации таблицы маршрутизации весь "Интернет-трафик" идет только через провайдера ISP1.

Два провайдера

В этом случае возникает несколько вариантов того какой именно трафик куда и когда направить:

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

default via 198.51.100.1 dev eth1
на
default via 192.168.5.10 dev eth3
где 192.168.5.10 адрес модема ADSL модем №2 в сети LAN2, который выполняет роль шлюза в сеть провадера ISP2 и далее в Интернет.

А что делать если мы хотим использовать оба канала одновременно?

Использовать то, что называется "policy routing" (марщрутизация по политикам) совместно с несколькими таблицами маршрутизации (множественные таблицы маршрутизации).

Policy routing

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

Взглянем на всю картину в целом. В действительности, та таблица маршрутизации, которая была показана выше является всего лишь одной из существующих таблиц маршрутизации и называется она main. Есть еще две "встроенные" таблицы: local и default. И, к тому же, можно создавать дополнительные таблицы маршрутизации - до 252 таблиц. Итак можно создать несколько таблиц с разными наборами маршрутов. А вот для того чтобы, управлять, тем по какой из этих таблиц будет определятся маршрут для того или иного пакета и нужна routing policy database (RPDB) (база политик маршрутизации). Используя только команду route невозможно управлять всем этим механизмом - она позволяет манипулировать только маршрутами в одной определенной таблице - main. Для полноценного управления мрашрутизацией служит команда ip из пакета iproute2

RPDB имеет несколько вариантов селекторов (selector) и действий (action), которые позволяют нам указать ядру: вот c такими пакетами делай указанное действие. Например: пакеты, идущие с такого-то адреса маршрутизируй (т.е. определяй куда его послать) с использование вот этой таблицы или пакеты помеченные так-то - блокируй и т.д. Селекторы можно комбинировать. Ниже приведен пример RPDB:

#ip rule show
0:      from all lookup local
32763:  from all fwmark 0x4 lookup isp2
32766:  from all lookup main
32767:  from all lookup default

Номер до двоеточия - приоритет записи в базе , все что до слова "lookup" - селектор(ы) и последнее - имя таблицы маршрутизации. Например запись 32763 значит: для пакетов с любым ("from all") адресом источника и у которых отметка MARK равна 0x4 (просто число, которым мы помечаем определенные пакеты в NETFILTER), маршрут определять по таблице с именем "isp2". Т.е. в этой записи скомбинированы два селектора: "from" и "fwmark".

Алгоритм поиска в RPDB следующий:
  • записи (правила) в RPDB перебираются в порядке их приоритета (первое число в записи RPDB)
  • если данный пакет соответствует селектору правила, то выполняется действие указанное в правиле
  • если указанное в правиле действие окончательно определяет маршрут пакета, то правила, следующие за "сработавшим" перебираться для данного пакета уже не будут, иначе перебор правил продолжается

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

Например если в базу, показанную выше, добавить такое правило
#ip rule del from all fwmark 0x4  priority 32700 prohibit
то мы получим вот такую таблицу
#ip rule show
0:      from all lookup local
32700:  from all fwmark 0x4 prohibit
32763:  from all fwmark 0x4 lookup isp2
32766:  from all lookup main
32767:  from all lookup default

В ней для пакетов с одной и той же отметкой MARK сначала идет правило с запретом (prohibit), поэтому все пакеты, имеющие отметку MARK=0x4 будут запрещены. Для таких пакетов правило с приоритетом 32763 уже никогда срабатывать не будет, так как в правиле 32700 маршрут пакета однозначно будет определен (запрет) в процессе маршрутизации!

Или другой пример. Например если в базу добавить такое правило с таблице маршрутизации:
#ip rule add from all fwmark 0x4 priority 32700 table newtable
то мы получим вот такую таблицу
#ip rule show
0:      from all lookup local
32700:  from all fwmark 0x4 lookup newtable
32763:  from all fwmark 0x4 lookup isp2
32766:  from all lookup main
32767:  from all lookup default

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

Видов селекторов не так уж много (смотрите документацию), но один из них "убойный" - это fwmark.

fwmark: NETFILTER+routing

Селектор fwmark позволяет сделать одну, но очень мощную вещь: отбирать в правилах RPDB пакеты на основе меток, которые можно с помощью NETFILTER ставить в пакетах.

С помощью iptables мы можем "помечать" пакеты в таблице mangle. Тут важно знать что эти метки вставляются не внутрь пакета, а ассоциируются с пакетом, до тех пор пока пакет не ушел в сеть. Т.е. эти метки привязаны к пакету, но хранятся вне его - в памяти ОС и по сети не передаются. Метки существуют пока пакет "крутится" внутри операционной системы данного хоста (компьютера).

Еще важно помнить про разные типы меток, которые могут быть привязан к пакету одновременно и которые не надо путать между собой (CONNMARK, CONNSECMARK, MARK, SECMARK). Также важно не путать установку отметки (это определённые виды target'ов в ключе '-j') c проверкой на соответствие (match) между отметкой в пакете и заданной в правиле. Сейчас речь пойдет о отметках, которые проставляются у пакетов target'ом MARK.

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

Теперь уточним, чего именно хотим добиться:

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

#ip route show table main
192.168.1.1 dev ppp1  proto kernel  scope link  src 192.168.3.1.
192.168.1.3 dev ppp2  proto kernel  scope link  src 192.168.3.1.
192.168.255.2 dev tun1  proto kernel  scope link  src 192.168.255.1.
198.51.100.0/30 dev eth1  proto kernel  scope link  src 198.51.100.2.
203.0.113.0/29 dev eth2  proto kernel  scope link  src 203.0.113.1.
192.168.5.0/24 dev eth3  proto kernel  scope link  src 192.168.5.1.
192.168.0.0/24 dev eth0  proto kernel  scope link  src 192.168.0.1.
192.168.255.0/24 via 192.168.255.2 dev tun1.
default via 198.51.100.1 dev eth1.

Теперь подготовим альтернативную таблицу маршрутизации, которую назовем "isp2". У этой таблицы только одна роль - определить другой "маршрут по умолчанию", отличный от того, который указан в таблице маршрутизации "main".

В файле /etc/iproute2/rt_tables описываем новую таблицу
#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1     inr.ruhep
1      isp2 # таблица которую мы добавили
выполняем команды
#ip route flush table isp2
#route add default via 192.168.5.10 dev eth3 table isp2
 
проверяем, что получилось
#ip route show table isp2
default via 192.168.5.7 dev eth3

Добавим новую политику в RPDB

#ip rule add from all fwmark 0x4 table isp2
проверяем, что получилось
#ip rule show
0:      from all lookup local
32763:  from all fwmark 0x4 lookup isp2
32766:  from all lookup main
32767:  from all lookup default

Осталось сделать один шаг - пометить те пакеты, которые будут маршрутизироваться по таблице "isp2". Для этого надо разобраться с еще одним важным вопросом - где и каким образом помечать нужные пакеты.

Этапы прохождения пакета через NETFILTER


Открываем листинг команды 'iptables -t mangle -nvL', чтобы было удобнее следить за дальнейшими пояснениями.

Ниже приведена схема этапов (таблицы и цепочки) которые проходит пакет. Информация о этапах взята отсюда: http://www.frozentux.net/iptables-tutorial/iptables-tutorial.html#TRAVERSINGOFTABLES

На схеме показаны таблицы и цепочки, которые проходит трафик, маршрутизируемый с помощью таблиц "main" и "isp2" (пунктирное ответвление), точки в которых осуществляется пометка пакетов (MARK) и преобразование адресов (SNAT).


Рис. 8 Прохождение пакетов из сети LAN в Internet

Помечать пакеты с помощью target MARK можно только в таблице mangle. Делать преобразование адресов SNAT можно только в таблице nat в цепочке POSTROUTING.

Поток GW->INET (цепочка 04_GW_INET_MARK в mangle)
Исходя из сказанного, помечаем нужные нам пакеты трафика Gateway<->Internet в mangle OUTPUT. Почему, например, не в mangle POSTROUTING? Отметку надо проставить еще до процесса маршрутизации (routing decision), чтобы на момент осуществления маршрутизации нужные пакеты уже имели нужную метку. Пометка в mangle POSTROUTING просто не будет иметь никакого эффекта.

При этом пакеты трафика Gateway<->Internet будут иметь адрес источника 198.51.100.2, присвоенный еще на первом этапе маршрутизации, сразу после Local process, аж до самого SNAT to:192.168.5.1. Обратите внимание: в filter OUTPUT выходным интерфейсом для всех пакетов всё еще будет eth1. Он сменится на eth3 только после второго routing decision.

Таким образом,согласно правилу from all fwmark 0x4 lookup isp2 в RPDB, трафик c отметкой 0x4, на финальном этапе маршрутизации будет направлен в eth3. По пути будет сделано преобразование адресов (SNAT to:192.168.5.1) и после этого трафик через eth3 уйдет в сеть LAN2 на шлюз 192.168.5.10. При этом пакет будет иметь: адрес источника 192.168.5.1, адрес назначения - адрес в Интернете и MAC-адрес назначения - MAC-адрес внутреннего порта ADSL модема №2 Таким образом, пакет будет доставлен в ADSL модем №2, откуда он, согласно маршрута по умолчанию модема, отправится к провайдеру ISP2.

Посмотрим на соответствующие правила (помечены красным) в листинге таблицы mangle.
В OUTPUT для eth1 есть переход в цепочку 04_GW_INET_MARK. Там первые три правила, исключают из маркировки исходящий SMTP и DNS трафик. Это сделано для того чтобы трафик почтового сервера всегда уходил со статичного адреса, для которого прописана адрес в обратной зоне (.in-addr.arpa), так как очень часто принимающий SMTP-сервер использует обратную зону для проверки истинности адреса принимающего SMTP-сервера (Forward-confirmed reverse DNS). DNS тоже не помечается, чтобы идти через более надежного провайдера ISP1.

Предпоследнее правило в цепочке помечает все новые исходящие от шлюза соеденения (CONNMARK) неким произвольным числом (0x500). Ну и последнее правило уже все пакеты соединений, помеченных этим числом (match 0x500), помечает числом 4 (MARK xset 0x4).

Просто помечать все пакеты, без использования предварительного CONNMARK будет неверно! По рис. 8 видно, что без предварительной пометки исходящих соединений будет поголовно помечен (и далее отправлен через eth3, а не через eth1) ответный трафик всех входящих на eth1 соединений!
Т.е. помечать надо только трафик, относящийся к исходящим со шлюза соединениям, а весь остальной трафик через eth1 должен остаться нетронутым.

Поток LAN->INET (цепочка 05_LAN_INET_MARK в mangle)
С трафиком LAN<->Internet поступаем также, только в этом случае существует возможность пометить трафик раньше - еще до первого решения о маршрутизации - в mangle PREROUTING. Т.о. уже в цепочке FORWARD у помеченных пакетов будет "правильный" выходной интерфейс источника eth3, а не eth1.

Смотрим листинг (правила помечены синим): там тоже есть исключения.
Первое исключение для подсети 198.51.100.0/30 - эта сеть подключена напрямую к eth1 и трафик в нее всегда должен идти через eth1.
Второе исключение для пользвателя в сети LAN с адресом 192.168.1.3 - ему надо "ходить" через ISP1.
Третье исключение для сети DMZ - ответный трафик из DMZ должен всегда уходить через eth1, так как входящие соединения приходят на eth1 (публичный статический адрес).

Хотя сеть DMZ никак не относится к потоку LAN->INET, но данное исключение необходимо просто потому, что согласно правилам перехода из PREROUTING в цепочку 05_LAN_INET_MARK, сеть DMZ тоже относится к "Интернету" (просто подпадает под условие !192.168.0.0/16) и для обхода этого приходится вставлять данное исключение.
Если бы можно было (без ipset) задать условие типа "!192.168.0.0/16 and !203.0.113.0/29" то все было бы проще...
Четвертое исключение для подсети 192.168.255.0/24, выделенной для клиентов OpenVPN - ответный трафик также должен всегда уходить через eth1, так как openvpn принимает входящие соединения на eth1.
Пятое исключение для RDP (tcp 3389) трафика.
Далее, как и в случае 04_GW_INET_MARK помечаем новые исходящие соединения и уже только помеченные соединенения метим числом для дальнейшей маршрутизации по таблице isp2.

Далее смотрим как идет обратный трафик.

Потоки INET->GW, INET->LAN (цепочки 03_INET_GW, 06_INET_LAN в mangle)


Рис. 9 Прохождение пакетов в обратном направлении: из сети Internet в LAN

Пожалуй самое примечательное здесь это то, что в filter INPUT могут появляться пакеты вот с такими адресами назначения:

host [361.745891] IN=eth3 OUT= SRC=213.180.204.190 DST=198.51.100.2 LEN=52 TOS=0x08 PREC=0xC0 TTL=55 ID=6076 DF PROTO=TCP SPT=80 DPT=44909 WINDOW=13387 RES=0x00 ACK FIN URGP=0 
host [361.917213] IN=eth3 OUT= SRC=217.14.203.229 DST=192.168.5.1 LEN=40 TOS=0x08 PREC=0xC0 TTL=55 ID=38988 DF PROTO=TCP SPT=80 DPT=54684 WINDOW=6456 RES=0x00 ACK FIN URGP=0  
Т.е. в filter INPUT eth3 приходят пакеты с адресом назначения 198.51.100.2, который вообще-то привязан к eth1! Происходит следующее (рис. 8): при отправке пакетов от локальных процессов (Local Process) сразу идет маршрутизация и согласно таблице "main" пакетам назначается выходной интерфейс eth1 и адрес источника 198.51.100.2. После этого, в mangle OUTPUT, идет пометка части пакетов и повторная маршрутизация с учетом отметки. И вот тут помеченным пакетам переназначается выходной интерфейс с eth1 на eth3, а адрес источника (почему-то) остается исходным - 198.51.100.2. Т.е. уже после второй маршрутизации и до SNAT to: 192.168.5.1 пакеты с отметкой MARK=0x4 имеют out=eth3 и src=198.51.100.2.

Ответные пакеты (рис. 9), приходящие на eth3 подвергаются обратной трансляции адресов и уже после nat PREROUTING и до самых Local Process пакеты будут иметь in=eth3 dst=198.51.100.2.

Вот почему в цепочке INPUT есть правило с in=eth3 и dst=198.51.100.2: на eth3 сюда могут приходить пакеты как с destination 198.51.100.2 так и 192.168.5.1!

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

Входящий же через eth1 и eth3 трафик "не сливается" после маршрутизации, так как входные интерфейсы у пакетов остаются разными на всем пути до интерфейса eth0.

С трафиком Internet<->DMZ (на рис. 8 и рис. 9 не показан) происходит то же, что и с трафиком LAN<->Internet, за исключением отсутствия преобразования адресов.

Так как в сети DMZ у нас публичные ("реальные, белые") Интернет-адреса, то не надо делать SNAT для трафика Internet<->DMZ.

Входящий трафик через двух провайдеров

Рассмотрим для нашей схемы случай, в котором мы хотим принимать входящие соединения на веб-сервер в сети DMZ через два подключения ISP1 и ISP2 одновременно (не путайте этот случай со случаем использования двух каналов (основной+резервный) к одному провайдеру). Это возможно в случае, если клиенты будут подключаться на два разных IP-адреса, принадлежащих разным провайдерам. Пакеты от клиентов, подключающихся на адрес 203.0.113.2, будут проходить через провайдера ISP1 на eth1 и далее через наш шлюз в сеть DMZ. Пакеты от клиентов, подключающихся на внешний адрес модема ADLS №2,будут проходить через провайдера ISP2.

Так как на модеме ADLS №2 включен NAT и публичный адрес присваивается интерфейсу самого моддема, то клиенты, подключающиеся на этот адрес, будут соединяться с модемом, а не с веб-сервером в сети DMZ. Для того чтобы "пробросить" входящие соединение через модем до веб-сервера в сети DMZ, надо на модеме включить и настроить "проброс портов" (port forwarding). С этой функцией входящие соединение на определённый tcp или udp порт модема, будут перенаправляться далее на IP-адрес, в указанный настройке port forwarding'а. Дополнительно в таблице маршрутизации модема надо прописать путь до сети DMZ через 192.168.5.1, иначе "проброшенный" трафик модем будет направлять обратно в Интернет, так как у него этот трафик будет маршрутизироваться согласно маршруту по умолчанию, т.е. обратно через ISP2. Однако одного этого недостаточно для чтобы сервер в сети DMZ мог работать с клиентами, подключившимися через разных провайдеров.

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

Это обусловлено тем, что клиент будет посылать пакеты серверу на один IP-адрес (в данном случае на внешний адрес ADLS-модема №2), а получать ответные пакеты с совершенно другого IP-адреса (в данном случае с адреса eth1). Большинство же реальных программ не рассчитаны на это, так как написаны с использованием такой абстракции транспортного уровня как internet socket.

В этой абстракции рассматривается передача трафика между двумя конечными точками соединения - сокетами, каждый из которых характеризуется комбинацией IP-адреса, порта и протокола (напр. TCP или UDP), где порт фактически идентифицирует приложение, создавшее сокет. Абстракция сокетов реализована в большинстве операционных систем и является стандартом де факто для написания приложений работающих с сетью Интернет. Т.о. именно ОС "занимается" учётом и идентификацией сокетов и соединений. Для сокетов с поддержкой соединения (TCP/SCTP), соединение однозначно идентифицируется парой сокетов: серверным и клиентским. Для сокетов без соединений (UDP), соединение однозначно идентифицируется локальным сокетом.

Из этого следует, что при использовании протоколов с поддержкой соединений, операционная система будет всегда ожидать прихода ответных пакетов с того сокета (т.е. IP-адреса:порта), с которым соединение было установлено изначально. Т.о. ответные пакеты, проходящие к клиенту с другого IP-адреса, ОС клиента не сможет отнести ни к одному из уже существующих сокетов и установленных соединений и просто их проигнорирует. Это справедливо для любых протоколов, работающих поверх TCP.

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

http://tools.ietf.org/html/rfc3704#page-5

Т.к. протокол HTTP реализован поверх TCP, HTTP-клиент не сможет работать с веб-сервером, который принимает входящие пакеты на один IP-адрес, а ответные пакеты отправляет этому же клиенту с друого IP.

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

Пока я вижу только один универсальный способ добиться этого - использовать модуль conntrack. Надо пометить все входящие в DMZ соединения с помощью CONNMARK: пакетам, входящим в разные интерфейсы, надо присваивать разные отметки.

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

Резервирование и переключение: динамическая маршрутизация?

todo

Доступ из внутренних сетей к сети Интернет через один публичный адрес (NAT - трансляция адресов)

При прохождении трафика через маршрутизатор из одной сети в другую могут иметь место два случая:

Например (см. рис. 3) пакеты из сети LAN в сеть DMZ проходят шлюз без преобразования адресов: пакет от хоста 192.168.0.1 доходит до хоста 203.0.113.2 с неизменённым адресом отправителя. Т.е. узел 203.0.113.2 "видит", что пакет пришел от хоста адреса 192.168.0.1, который, естественно, находится в другой IP-сети. Поэтому, когда узел 203.0.113.2 отошлет (ответный) пакет, то в качестве адреса получателя будет указан адрес 192.168.0.1 - пакет будет смаршрутизирован на нужный интерфейс (eth0) и попадет на узел 192.168.0.1.

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

Т.о. для того чтобы хосты из сети с приватными адресами могли обмениваться трафиком с хостами сети Интернет нужны: хотя бы один публичный ("белый","реальный") адрес "от имени" которого вся приватная сеть сможет обмениваться трафиком с Интернетом и механизм трасляции адресов. Также подразумевается что на шлюзе включен ip forwarding.

В нашем случае, для трафика между сетями LAN и Internet необходимо применить NAT, а точнее Source NAT: адрес-источник во всех пакетах (например src 192.168.0.1), перед "выходом" через eth1 должнен быть заменен на адрес 198.51.100.2 (публичный адрес выданный нам провайдером ISP1). При этом все пакеты, отправляемые из сети LAN "будут выглядеть для Интернета" как отправленные одним хостом с публичным адресом 198.51.100.2.

Когда некий сервер, в ответ на приход такого пакета, ответит своим пакетом, то в адресе назначения будет указан 198.51.100.2 (а не, например, 192.168.0.1). Как же он, в конце концов, дойдет до хоста 192.168.0.1, ведь в ответ на пакеты с разными адресами (192.168.0.1,192.168.0.138 и т.д.) сервер будет отвечать на один и тот же адрес 198.51.100.2?

Для этого NETFILTER, помимо смены адреса источника, также меняет некоторые параметры отправляемых пакетов и ведет таблицы соответствия параметров исходных и модифицированных пакетов. Поэтому любой приходящий на 198.51.100.2 пакет NETFILTER может проверить на соответствие в этой таблице и "выяснить" относится ли пришедший пакет к соединению, установленному с хоста сети LAN (здесь также задействован модуль conntrack). Для TCP/UDP пакетов NETFILTER кроме адреса источника дополнительно заменяет порт источника на определённое значение, что и позволяет транслировать номер порта назначения в ответном пакете в адрес сети LAN. В случае протоколов, не имеющих портов, NETFILTER применяет (какие-то) другие (отличные от замены номеров портов) методики сопоставления адресов.

Также, есть ещё один путь прохождения трафика между сетями LAN и Internet - через eth3. Здесь преобразование адресов не является обязательным, так как оно все равно обязательно произойдет позже - в ADSL модеме №2. Хотя наличие NAT для этого трафика имеет свои преимущества: например не надо будет в таблицах маршрутизации хостов сети LAN2 (того же ADSL модема №2 или например принтеров) прописывать маршруты в сети 192.168.0.1 и 192.168.1.1 что облегчает адинистрирование и позволяет пользоваться устройствами сети LAN2, не поддерживающими настройку маршрутов, из сети LAN.

Теперь посмотрим как это сделано в нашей схеме.
Открываем листинг команды 'iptables -t nat -nvL'. Как видно из рис.6 SNAT удобнее всего сделать в nat POSTROUTING. В листинге это первое правило в цепочке POSTROUTING и оно означает: у всех пакетов, выходящие через eth1, кроме идущих из сети 203.0.113.0/29 (т.е. из DMZ) заменить адрес источника на адрес 198.51.100.2, что и требуется.

Обратное преобразование для приходящих пакетов происходит автоматически, т.е. никаких правил для обратного преобразования в NEТFILTER задавать не надо!

Второе правило в цепочке POSTROUTING задает трансляцию адресов для трафика, идущего через eth3: здесь мы заменяем адрес источника на адрес интерфейса eth3 только у тех пакетов, которые должны были идти на eth1, а мы "завернули" их при помощи policy routing на eth3. Как минимум часть таких пакетов (например, от локальных процессов) будут иметь адрес 198.51.100.2, а не нужный нам 192.168.5.1. Хотя, повторюсь, можно сделать преобразование и у абсолютно всех исходящих пакетов или только у пакетов у которых адрес источника не равен 192.168.5.1 - для решения задачи "расшаривания Интернета" эти варианты эквивалентны.

Однако, при необходимости надо учитывать влияние преобразования на связь между другими сетями: выборочная или поголовная трансляция пакетов перед выходом через eth3, будет влиять на доступность хостов сетей LAN и LAN2 между собой. Например, если будут транслироваться все пакеты, то связь из сети LAN с устройствами сети LAN2 будет работать независимо от наличия маршрутов в сети 192.168.0.1 и 192.168.1.1 в таблицах маршрутизации устройств сети LAN2. При этом, в обратном направлении (из LAN2 в LAN), без специальных ухищрений (напр. port forwarding ), невозможно будет "достучаться". Если же NAT будет выборочный (напр. как в нашем случае), то с устройствами в LAN2, у которых не заданы маршруты в сети 192.168.0.1 и 192.168.1.1 из сети LAN работать будет невозможно.

Вообщем с помощью этих двух правил мы и добъемся нужного эффекта: хосты из сети LAN смогут "лазить в Интернет через один белый адрес".

Управление трафиком (traffic control: shaping, policing и т.д.)

Раcсмотрим основные концепции управления трафиком (traffic control) в Linux.

Traffic control в Linux включает в себя три главных компонента:

Управление исходящим трафиком: shaping, scheduling

Начнём с исходящего трафика. В случае шлюза, исходящий трафик может быть сгенерирован как локальными процессами так и в результате прохождения трафика (forwarding) через шлюз, от одного сетевого интерфейса к другому. С каждым сетевым интерфейсом связана очередь (queue), в которую пакеты ставятся перед отправкой: пакет, который должен быть отправлен, отправляется в сеть не сразу, а ставится в очередь ("привязанную" к исходящему интерфейсу) и затем извлекается из очереди планировщиком для отправки в сеть согласно некому принципу (алгоритму).

Алгоритм, согласно которому пакеты извлекаются из очереди и отправляются в сеть и есть queuing discipline (qdisc). При этом (внутренне) используются два механизма:
  • shaping - задержка отправки (определённых) пакетов из очереди в сеть для ограничения скорости передачи или сглаживания неравномерности передачи
  • и
  • scheduling - реорганизация порядка (приоретизиация) пакетов в очереди, т.е. управление порядком обработки (отправки в сеть) пакетов из очереди.
В опеределённом смысле, любая queuing discipline является scheduler'ом (планировщиком).

В traffic control для обозначения исходящяго трафика или исходящей qdisc используется термин egress (например egress qdisc). В команде же tc для задания исходящей дисциплины используется ключевое слово root, а не egress; например:

tc qdisc add dev eth0 root handle 6: htb r2q 5   

Например, пусть в нашей схеме, два хоста сети LAN получают пакеты из сети Internet (что-то качают). Для определенности пусть пакеты проходят через eth1 далее они форвардятся на eth0 - естественно сначала они попадают в очередь этого интерфейса. В этой очереди единовременно могут находиться пакеты предназначенные для обоих качающих хостов.

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

Естественно, чаще всего, интересуют формулировки вида:

а не какие-то там "очереди" и прочие сложности.

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

Например, на рассматриваемой схеме (пока отбросим провайдера ISP2) пропускная способность подключения к ISP1 (пусть 1 Мбит/с=125 кбайт/с) распределяется между всеми хостами сетей LAN и DMZ, а также самим шлюзом. Допустим пользователей в сети LAN хотя бы 10 - на каждого придется по 12,5 кбайт/с чего явно мало даже для комфортного серфинга по Инету. А ведь есть еще сервера в сети DMZ и сам шлюз, которым тоже надо что-то выделить из 1 Мбит/с. Если все это распределить и установить вычисленные жёсткие лимиты, то каждому хосту достанется мизерные пару кбайт/с, которые ни один узел не сможет привысить, даже если остальные хосты не качают (и как следствие - канал свободен). Поэтому, возникает желание установить некие более высокие лимиты, но в то же время они не должны давать возможность какому-либо хосту единолично загрузить канал и не давать качать остальным. И вот тут, прежде чем что-то делать, надо уже придумать схему (политику), согласно которой различные хосты (группы хостов) смогут делить пропускную способность общего канала, а так же, как и в каком приоритете одни хосты будут "отбирать" пропускную способность у других хостов. Например, пользователи с выскоим приоритетом хотят чтобы, когда они качают "нечто важное", им отдавалась большая часть пропускной способности канала - естественно за счет "ущемления" других потребителей трафика. И даже между ними (пользователями с высоким приоритетом) желательно как-то явно поделить канал (поровну или опять же - по приоритету). Вот эту задачу (придумать политику использования канала) и надо решить перед тем как что-то делать. И это мы еще не учли влияние входящяго трафика!

После такого вводного отступления вернёмся к traffic control.

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

Дисциплины (qdisc) могут быть двух видов:

Classful qdisc позволяют определить классы. К каждому из классов можно привязать:

Ниже приведен абстрактный пример со всеми четырьмя случаями:

(1) tc qdisc del dev eth0 root
(2) tc qdisc add dev eth0 root handle 6: htb r2q 5
(3)     tc class add dev eth0  parent 6: classid 6:1 htb rate 48Kbit ceil 48Kbit
(4)         tc qdisc add dev eth0 parent 6:1 handle 7: prio
(5)             tc qdisc add dev eth0 parent 7:1 handle 10: sfq
(6)             tc qdisc add dev eth0 parent 7:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
(7)             tc qdisc add dev eth0 parent 7:3 handle 30: pfifo
(8)     tc class add dev eth0 parent  6: classid 6:2 htb rate 48Kbit ceil 48Kbit
(9)         tc class add dev eth0 parent 6:2 classid 6:3 htb rate 24Kbit ceil 48Kbit
(10)        tc class add dev eth0 parent 6:2 classid 6:4 htb rate 24Kbit ceil 48Kbit

В данном примере к egress (root) интерфейса присоединена classful HTB дисциплина (команда 2). К дисциплине присоединены два класса: 6:1 и 6:2 с гарантированной полосой 48 кбит (команды 3 и 8). Обмена токенами (свободной полосой пропускания) между этими классами не будет, так как у них нет общего родительского класса. Далее, к классу 6:1 присоединена дисциплина classful PRIO в которой (автоматически созданы) три класса: 7:1, 7:2, 7:3, к каждому из которых, в свою очередь подключены clasless дисциплины sfq, tbf, pfifo. К классу 6:2 подключены два дочерних класса 6:3, 6:4. Каждому из них гарантированы 24 кбита из 48 кбит родительского класса 6:2. И каждый из 6:3 и 6:4 может позаимствовать у другого до 48 кбит, если в каком-то из них есть свободные токены (свободная пропускная способность).

Описанные команды задают только иерархию классов, но никак не определяют каким пакетам какой класс назначить (что влмяет на время и порядок отправки пакетов из очереди в физ. интерфейс). Чтобы указать какому трафику какой класс назначить надо воспользоваться фильтрами (tc filter ...) Фильтры (filter) позволяют классифицировать трафик: каким пакетам какой из классов классовой дисциплины назначить.

Фильтры могут существовать только в классовых (classful) дисциплинах и всегда привязываются к определенному классу (без опции parent фильтр добавляется в корневой класс дисциплины, а если дисциплина безклассовая то фильтр просто не добавится - будет выдана ошибка...). Это значит что фильтры не являются глобальными сущностями - фильтр, привязанный к данному классу, используется только для классификации того трафика, которому назначен этот класс ("который попал в этот класс").

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

Входящий трафик и почему им невозможно управлять. Policing

Всё вышесказанное относилось только к исходящему трафику. С входящим трафиком несколько иная ситуация. Строго говоря, управлять тем, как откуда-то нам посылают пакеты (и как следствие тем, что приходит на "наш" интерфейс) принципиально невозможно. Рассмотрим почему.

Рассмотрим один единственный интерфейс, который обменивается трафиком с неким другим, удаленным интерфейсом.

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

В случае некоторого типа трафика возможно косвенное управление входящим трафиком, но это ничего не гарантирует. Например протокол TCP имеет механизм "управление заторами" (congestion control), который позволяют не допускать превышения пропускной способности всей цепи (сети, маршрутизаторы) между клиентом и сервером. Для этого, в частности, используется анализ прихода подтверждающих пакетов от противоположной стороны (пакеты с установленным флагом ACK). И, например, клиент, манипулируя отправкой этих пакетов (или игнорируя часть входящих), может косвенно вынудить сервер уменьшить скорость передачи - в надежде, что сервер подстроится под эти условия. Казалось бы - вот оно! Но это работает, только если сервер будет "подчиняться правилам хорошего тона" и только в случае TCP (или каких-то других протоколов с поддержкой congestion control).

Например в "голом" IP ничего такого нет, и в UDP тоже. Т.е. на исходящем интерфейсе мы можем принудить трафик к определённому поведению, а вот другую, удалённую сторону соединения мы не можем в принципе принудить - только в некторых случаях "вежливо попросить". Т.о. мы можем явно влиять на загрузку канала в направлении "от нас" и не можем в направлении "к нам".

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

Поэтому для входящего (ingress) трафика (в Linux) применяется другой подход - policing. На практике, разница заключается в том, что для входящего трафика нельзя использовать дисциплины (команда tc просто не даст прикрепить дисциплину к ingress). Для управления (в смысле вышесказанного) входящим трафиком надо применять, так называемый policer. Его использование возможно только в составе фильтра (tc filter...).

Обратите внимание, что собственно policing может применяться как для входящего так и для исходящего трафика. Просто входящим можно управлять только с помощью policing'а, а исходящим - всеми доступными средствами: и с помощью qdisc и с помощью policer. И это может приводить к некоторым двухсмысленным ситуациям (см. Вопросы без ответов).

На примере последовательности команд рассмотрим в общих чертах, возможности policer и представим, что можно сделать с входящим трафиком.

(1) tc qdisc add dev eth1 handle ffff:0 ingress
(2) tc filter add dev eth1 parent ffff:0 protocol ip prio 1 u32 match ip dport 110 ffff \
      police rate 256kbit burst 10k continue flowid 44:1
(3) tc filter add dev eth1 parent ffff:0 protocol ip prio 2 u32 match ip dport 110 ffff \
      police rate 128kbit burst 5k drop flowid 44:2

В первой (1) команде мы добавляем дисциплину для входящего трафика (ingress) для интерфейса eth1. Указан handle ffff:0 так как именно ffff:0 зарезервирован для ingress.

Тип дисциплины для входящего трафика указать нельзя, так как фактически есть только одна, специальная дисциплина, которую допустимо подключить к ingress, которая (возможно) называется ingress. По крайней мере в тексте дисциплина для входящего трафика будет называться ingress (qdisc). Дисциплина ingress является классовой, несмотря на то, что в ней нельзя создавать классы. Зато к ней можно прикреплять фильтры, а фильтры прикрепляются только к classful дисциплинам.

Во второй (2) команде к ingress дисциплине прикрепляется фильтр с приоритетом равным 1 и содержащий policer. В третьей (3) команде также подключается фильтр с приоритетом равным 2 и содержащий policer. Приоритет будет определять порядок перебора фильтров, о чём ниже.

Команда (2) добавляет в ingress eth1 фильтр, ограничивающий входящий на tcp порт 110 трафик величиной 256 kbit. Трафик, укладывающийся в 256 kbit, будет классифицироваться как 44:1. Так как в policer'е 2-ой команды указано continue, то трафик превышающий 256 kbit (те пакеты, при прохождении которых уже будет превышен лимит в 256 kbit) будет подпадать под действие следующего по порядку фильтра, заданным в команде (3). Т.о. 2-ая команда фактически ничего не ограничивает, а только вычленяет из трафика "полосу" в 256 кбит и назначает ей определенный класс. Третья команда добавляет фильтр, который "подхватывает" трафик, превысивший лимит, заданный в преыдущем фильтре и вычленяет из него полосу в 128 кбит и назначает ему класс 44:2, а трафик превысивший 128 кбит он просто игнорирует (т.к. указан drop).

Т.о. оба фильтра, суммарно, ограничивают входящий трафик величиной 256+128=384 кбита. Если же, например первый же фильтр имел бы drop в policer'е, то ограничение накладывал бы только он (величиной в 256 кбит), и второй бы фильтр вообще не имел бы смысла: трафик, превышающий 256 кбит, был бы "сдропан" первым фильтром и до второго бы никогда не дошел.

Осталось разобраться что даёт возможность присваивать классы в фильтрах для ingress.

В документации tc и во всех примерах упоминается что в команде 'tc policer...' параметр flowid является обязательным. Если при использовании policer в egress виден смысл и назначение параметра flowid, каковы его функции при использовании policer в ingress (ведь это обязательный параметр) - пока загадка! Ведь в ingress не может быть классов! Например здесь в командах и описании к ним есть параметр flowid, указывающий какой класс назначить трафику, но вот что за классы подразумеваются!?

Управление трафиком в рассматриваемом примере

Рассмотрим управление трафиком в нашем примере. Имеются два подключения (не важно, к разным или к одному и тому же провайдеру). Каждое подключение это полнодуплексных (full-duplex) канал до провайдера и его пропускная способность в нисходящем (downstream) направлении (от провайдера) не зависит от загрузки канала в восходящем (upstream) направлении (к провайдеру).

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

  1. Разделять пропускную способность каждого канала в отдельности между сетями DMZ, LAN в определенных пропорциях.
  2. Задавать приоритеты для различного типа трафика (SSH, RDP, DNS, HTTP, p2p и т.д.).
  3. Равномерно разделять пропускную способность между хостами сетей DMZ, LAN, не позволяя какому-либо из хостов этих сетей занимать канал.
  4. Использовать свободную пропускную способность хостами сетей DMZ, LAN в случае, если одна из этих сетей не использует выделенную ей полосу полностью.
  5. Задавать максимальную скорость скачивания для отдельных хостов.

Из рис. 3 видно, что для того чтобы полностью контролировать канал (например от ISP1) в нисходящем направлении, надо одновременно управлять трафиком следующих потоков:

так как все три потока одновременно "конкурируют" за пропускную способность downstream-канала. Без некоего "объединения" дисциплин интерфейсов eth0, eth1, eth2, pppX этого сделать не получится. Поэтому далее, на примере ppp-интерфейсов рассмотрим варианты управления трафиком нескольких интерфейсов в рамках одной дисциплины.

Т.о. управление трафиком в нисходящем канале (от провайдеров к нам) будет осуществляться только косвенно - управлением отдачей трафика на исходящих интерфейсах eth0 и eth2, т.к. это единственный реалистичный вариант.

Объединённое управление трафиком на нескольких интерфейсах (IFB, IMQ - одна дисциплина на несколько интерфейсов)

Так как одну (и ту же) дисциплину нельзя привязать к нескольким интерфейсам, то невозможно согласованно управлять трафиком на нескольких интерфейсах. Ниже показаны как раз такие ситуации для рассматриваемой нами схемы. На первой схеме показана ситуация, когда весь трафик во внутренние сети (LAN и DMZ) поступает через провайдера ISP1. Второй рисунок иллюстрирует ситуацию, когда трафик в LAN поступает через ISP2, а трафик в DMZ - через ISP1.



Рис. 10 Весь входящий трафик идет через ISP1.



Рис. 11 Входящий трафик идет через двух провайдеров.


В первом случае видно, что загрузка канала к провайдеру ISP1 зависит от суммарного потребления трафика клиентами в сети LAN, pptp-клиентами в сети LAN и серверами сети DMZ. Таким образом, если, например, pptp-клиент подключенный, через (виртуальный) интерфейс ppp1 начнет качать, со скоростью равной пропускной способности канала до ISP1, то другим хостам "ничего не достанется". Можно, например, задать каждому ppp-интерфейсу свою индивидуальную дисциплину с нужными ограничениями, однако это не решит проблему эффективного распределения свободной пропускной способности канала. Сказанное относится и к интерфейсам eth0, eth1, eth2 через которые внутренние сети получают трафик.

Вообще-то эту задачу можно решить установкой еще одного шлюза перед описываемым. Через этот шлюз надо пропустить весь трафик и на его исходящем интерфейсе создать одну дисциплину и уже без всяких ухищрений управлять всем проходящим трафиком. Ранее для этого требовался отдельный компьютер, но сейчас можно обойтись контейнером типа OpenVZ, LXC или виртуальной машиной. Фактически это эквивалентно "рассечению" описываемого шлюза на две независимые части, содединеные между собой сетью. При этом интерфейсы внутренних сетей (eth0, eth2, pppX) остаются на одной машине, а интерфейсы внешних сетей (eth1 и eth3) "перемещаются" на другую машину. Правда для этого требуется настройка и адимнистрирование еще одной физической машины или контейнера. Мы же рассматриваем решение в рамках одного экземпляра ОС.

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

На данный момент для ядра Linux cуществуют два таких уcтройства:

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

IFB-интерфейс - узкоспециализированное устройство и оно не является полноценным сетевым интерфейсом, как например виртуальные ppp или tun. Интерфейсы IFB не могут иметь IP-адрес, они не "видны" как устройства в iptables, но на них можно перехватывать трафик с помощью tcpdump. Исходящим интерфейсом всегда будет тот, с которого трафик перенаправлен на IFB, а не сам IFB-интерфейс.

Итак, на схемах показан IFB-интерфейс ifb0, условно "объединяющий" трафик с разных интерфейсов, что и даёт возможность управлять им как единым целым в рамках одной дисциплины, прикрепленной к ifb0.

После этого отступления переходим непосредственно к рассмотрению конфигурирования управления трафиком в нисходящем канале.

Управление трафиком в downstream-канале

С учетом вышеизложенного конфигурирование будет состоять из следующих шагов:

Обьединение трафика на виртуальном IFB-интерфейсе

Рассмотрим команды, необходимые для конфигурирования IFB-интерфейса и перенаправления на него трафика.

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

modprobe ifb numifbs=4

Затем активируем интерфейсы, как и обычные:

ifconfig ifb0 up
ifconfig ifb1 up
ifconfig ifb2 up
ifconfig ifb3 up

А далее надо перенаправить трафик с нужных нам интерфейсов на один из IFB-интерфейсов. В нашем примере для управления нисходящим трафиком будем использовать ifb0.

Перенаправляем трафик c eth0 (LAN) на ifb0.

(1) tc qdisc del dev eth0 root
(2) tc qdisc del dev eth0 ingress
(3) tc qdisc add dev eth0 root handle 1: prio
(4) tc filter add dev eth0 parent 1: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0
Первые две команды удаляют прикрепленные (если они были) дисциплины у egress (1) и ingress (2). Далее, к egress прикрепляем какую-нибудь, обязательно классовую дисциплину (3), например самую "простую" - PRIO.

К egress надо прикреплять именно classful дисциплину, так как только к классовой дисциплине можно прикрепить любой, в данном случае "перенаправляющий", фильтр!

И командой (4) производится собственно перенаправление - используются так называемые actions.

Перенаправляем трафик c eth2 (DMZ) и на ifb0.

(1) tc qdisc del dev eth2 root
(2) tc qdisc del dev eth2 ingress
(3) tc qdisc add dev eth2 root handle 1: prio
(4) tc filter add dev eth2 parent 1: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0

Для ethernet-интерфейсов эти команды можно выполнить один раз, в момент инициализации интерфейса.

В случае ppp-интерфейсов ситуация осложняется тем, что они создаются и уничтожаются динамически. Поэтому схожую последовательность команд, надо выполнять при создании каждого ppp-интерфейса, в скрипте, выполняющемся при активации ppp-интерфейса. В Debian это будет некий скрипт, расположенный в специальном каталоге /etc/ppp/ip-up.d/. Пусть в нашем случае этот скрипт называется tc-ppp.

/etc/ppp/ip-up.d/tc-ppp:

tc qdisc del dev $PPP_IFACE root
tc qdisc del dev $PPP_IFACE ingress
tc qdisc add dev $PPP_IFACE root handle 1: prio
tc filter add dev $PPP_IFACE parent 1: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0
где в переменной PPP_IFACE будет имя активированного интерфейса. Значение переменной PPP_IFACE будет передано демоном pppd через скрипт /etc/ppp/ip-up.

Таким образом, при подключении каждого pptp-клиента, автоматически будет выполняться этот скрипт и исходящий трафик с ppp-интерфейса этого клиента будет перенаправлен на ifb0.

Теперь весь трафик (кроме входящего на eth1, который пока опускаем для простоты!) "собран" на ifb0 и всё должно работать как и ранее, до перенаправления. Далее рассмотрим схему классов для HTB-дисциплины, которую мы будем использовать для управления трафиком на ifb0.

Настройка дисциплины IFB-интерфейса и создание иерархии классов

Сначала рассмотрим схему классов для управления трафиком только в одном из каналов - к ISP2.


Рис. 12 Иерархия классов для управления загрузкой канала к ISP2.

К ifb0 прикрепляем HTB дисциплину и создаем "корневой" класс, чтобы все подчиненные классы могли занимать (borrow) друг у друга свободную пропускную способность (п.4 списка). У данного класса rate и ceil равны пропускной способности канала в нисходящем направлении - 10 Мбит/с (10240 Кбит/с).

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

Для обеспечения приоритетов (п.2 списка) различных типов трафика, у классов LAN и DMZ создаем по три подчиненных класса (на схеме - LANp2, LANp3, LANp4, DMZp2, DMZp3, DMZp4) с различным значениями приоритета и rate/ceil. Приоритезация трафика полезна вот для чего. например, если в сети LAN хосты начнут загружать канал второстепенным трафиком, не столь важным для рабочих целей, то прохождение более важного трафика замедлится, начнут расти задержки и падать его скорость. Классический пример трафика, который часто "мешает" в офисной сети - p2p-трафик (LANp2), а к самым приоритетным типам трафика можно отнести SSH, RDP, DNS, ACK-пакеты, VoIP (LANp2). HTTP и FTP трафик можно в этом случае отнести к классу LANp3. Кроме приоритета, каждому из трех классов назначается свои значения rate/ceil, аналогично тому, как это сделано для классов LAN и DMZ. Это позволит задать некий гарантированный для каждого типа трафика минимум (rate) и максимум (ceil) скорости.

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

HTB-класс позволяет задать только 8 значений приоритетов: от 0 до 7.

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

Т.о. данной иерархией классов мы добились решения всех задач, поставленных в начале, кроме последней - "задавать максимальную скорость скачивания для части хостов сетей LAN и DMZ". Переходим к решению данной задачи.

Для задания индивидуальных скоростных ограничений необходимо для каждого ограничиваемого хоста добавить группу классов с минимально возможным значением rate, и ceil, равным величине требуемого ограничения. На схеме ниже показана схема классов, реализующая ограничение скорости скачивания для двух хостов из сети LAN: host1 и host2.


Рис. 13 Иерархия классов для управления загрузкой канала к ISP2 с индивидуальными параметрами для двух хостов.

Собственно классы host1 и host2 необходимы для возможности задать индивидуальное ограничение для хостов (ceil 200Kbit и ceil 500Kbit сооответственно). Так как в HTB-классе параметр rate является необходимым (т.е. rate не может быть равен 0), то каждому хосту, которому мы захотим задать индивидуальные настройки, придется дать некий гарантированный минимум, который бессмысленно делать меньше MTU (1500 байт для стандартного Ethernet). В нашей схеме это 4500bps (три класса по 1500bps). Заметьте, что пока узел не был выделен в отдельный класс, ему (и другим) по сути никакого минимума не гарантировалось. Выделяя же узел в отдельный класс приходится дать гарантированый минимум, отбирая тем самым часть rate у подчиненных классов класса LAN: у LANp2 отобрали 32Kbit (4500 байт/с * 8) и у LANp3 тоже 32Kbit. Т.е. на каждый узел, которому мы захотим дать индивидуальные настройки, придется отдать как минимум гарантированные 32Kbit (3*8*1500 байт/с).

Посмотрим зачем нужны подчиненные классы приоритетов для классов host1 и host2. Если бы подчиненных классов не было и приоритеты были бы указаны прямо у классов host1 и host2, то тем самым была бы нарушена логика работы данной иерархии классов. Т.е. весь трафик для такого хоста подпадал бы под один заданный класс и приоретизровался не так, как трафик других хостов. Например, если бы классу host1 дали prio 2, то его трафик стал бы приоритетнее трафика всех остальных хостов, а если бы prio 4 - то наборот. Это как раз то что треубется, если наша задача - дать преимущество какому-нибудь хосту. Мы же хотим всего лишь задать верхнюю границу скорости скачивания для выбранного хоста и чтобы при этом правила приоритезации оставались едиными и неизменными для всех хостов сети LAN.

Поэтому необходимо к классу host1 (и к каждому классу, играющему роль ограничителя) прикрепить иерархию классов с приоритетами, аналогичную той, что используется и для всех остальных, но естественно со своими собственными значениями rate и ceil. Так как к классам host1p2/3/4 будет относится трафик только одного хоста, а не нескольких как у классов LANp2, LANp3, LANp4, то нет необходимости присоединять к ним дсициплину SFQ. Естественно, всё вышесказанное справедливо и для сети DMZ, для хостов которой, по тому же принципу, можно задвать индивидуальные настройки.

Приведенная схема классов позволяет реализовать все указанные выше требования, а также в общем задавать индивидульные настройки для отдельных хостов. Например, устанавливая определенную величину rate можно выделить гарантированную полосу пропускания, а устанавливая меньшие/большие приоритеты, можно давать преимущество на скачивание одним хостам по отношению ко всем остальным. Например, если необхоидмо дать преимущество хосту host1, по отношению ко всем остальным хостам, то достаточно установить у класса host1 prio 2, а подчиненные классы не создавать вовсе, так как они в этом случае становятся излишними, и трафик к хосту host1 будет обслуживаться наравне с трафиком класса LANp2. Если, например необходимо дать классу host1 абсолютное преимущество, то можно установить величину его prio меньше, чем у любого из классов, например prio 1.

Недостаток у данной схемы следующий: с ростом кол-ва хостов, которым необходимо задать индивидуальные настройки быстро растёт и общее количество классов и иерархия классов становится очень громоздкой. Например, в показаном на схеме варианте с делением трафика на три типа (с тремя приоритетами), каждый узел с индивидуальными настройками требует создания четырех классов (класс для хоста + три подчиненных). Если мы захотим более детально делить трафик на типы, например на пять типов, то это уже даст по шесть классов на каждый узел с индивидуальными настройками. Мне неизвестно какова реальная масштабируемость подсистемы traffic control по кол-ву классов и как большое кол-во классов сказывается на производительности. Кроме того каждому такому хосту необходимо выделить некий минимум пропускной способности, кратный MTU помноженному на количество подчиненных классов для типов трафика, что тоже ограничивает разумное кол-во таких хостов. Из конечной ширины канала можно получить конечное число полос с гарантированной полосой. В нашем примере для сети LAN (8192 Кбит/с) можно "нарезать" 256 полос по 32 Кбит, а ведь еще надо что-то оставить для остальных.

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

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


Рис. 14 Вариант управления с использование промежуточного интерфейса.

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

К сожалению, выстроить цепочку из IFB-интерфейсов не получится. Т.е. перенаправить трафик с ethX или pppX на IFB можно, а вот далее с этого IFB на другой IFB-интерфейс - нельзя.
Из файла doc\actions\mirred-usage в исходниках iproute2: "Do not redirect from one IFB device to another... Redirecting from ifbX->ifbY will actually not crash your machine but your packets will all be dropped" Проверено - всё именно так.

Поэтому нужнен "нормальный" промежуточный интерфейс, а не IFB. И решить эту задачу в одном экземпляре ОС с помощью, например TUN тоже не получится, ибо нам необходимо, чтобы в конце концов трафик "уходил" со своего "родного" интерфейса: в случае (eth0,eth2) -mirred redirect-> ifb0 это работает, так как на самом деле IFB просто помещает пакет в то же место сетевого стека, откуда он был "подобран". Очевидно в цепочке (eth0,eth2) -mirred redirect-> tunX -mirred redirect-> ifb0 это работать не будет.
Возможность использования TUN как некоего абстрактного интерфеса, на который можно перенаправить (mirred action) траффик с eth0, eth2 и eth1, потом с этого tun перенаправить трафик на ifb0 не изучен на практике - проверить. (Проверил - не работает).

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

Иерархия классов для двух каналов/провайдеров

Описанная выше схема классов подходит для случая, когда весь входящий трафик поступает через одно подключение - именно загрузкой этого канала мы и управляем. При наличии двух каналов с разными публичными IP (к одному или разным провайдерам) расмотренной выше схемы управления трафиком становится недостаточно в силу того, что она задает правила только для одного из каналов, в то время как второй остается без управления. В случае одновременного использования двух подключений, трафик даже к одному и тому же хосту в сети, например LAN, может одновременно поступать через два канала за счет применения policy routing. И даже если два подключения не используются одновременно, то при переключении на другой канал (предположим именно он не сконфигурирован), возникнет ситуация неуправляемой загрузки этого канала, особенно если его пропускная способность меньше основного. Люди же привыкают к "толстому" каналу, загружают его "по полной" и при переключении менее "толстый" канал "ложится". Поэтому, чтобы управлять загрузкой двух каналов в нисходящем направлении необходимо на интерфейсе ifb0 сконфигурировать две иерархии - каждая со своими характеристиками, подходящими для пропускной способности "своего" канала. Естественно это повлияет и на классификацию трафика. В случае использования двух каналов уже будет нельзя использовать один набор правил классификации для всего трафика из Интернета, как это делалось в случае одного канала. Трафик из сети Интернет в сеть LAN (а равно и DMZ) необходимо будет классифицировать по разному, в зависимости от того через какой канал (интерфейс eth1 или eth3) он пришёл. Для такой классификации в таблице mangle для трафика Internet -> LAN/DMZ будет использоватья две цепочки, а не одна.

Посмотрим на схему классов для двух каналов с пропускной способностью в downstream 10 Мбит/с и 512 Кбит/с.


Рис. 15 Иерархия классов для управления трафиком в двух каналах - ISP1 и ISP2.

К описанной ранее иерархии классов (правая часть диаграммы) в корень HTB-дисциплины добавлен второй класс ISP1 с rate и ceil равными пропускной способности другого канала. Классы ISP1 и ISP2 не имеют общего родительского класса и прикреплены напрямую к HTB-дисциплине, поэтому не могут обмениваться между собой свободной пропускной способностью. Именно это и требуется, так как у нас два независимых канала, пропусная способность которых просто физически не может быть "передаваться" от одного другому.

К классу ISP1 прикреплена иерархия классов аналогичная иерархии классов в ISP2, только с величинами rate/ceil уменьшенными пропорционально меньшей пропускной способности второго канала. Т.о. иерархия класса ISP1 будет регламентировать загрузку канала до провайдера ISP1, а класса ISP2 - до провайдера ISP2. При этом в иерархии классов ISP1 индивидуальные настройки тоже реализуются созданием отдельных подклассов для отдельных хостов, аналогично тому, как это было показано для класса ISP2.

Классификация трафика при помощи NETFILTER

Для классификации трафика можно воспользоваться возможностями tc filter classifier, а можно использовать возможностями NETFILTER. Для классификакции трафика в NETFILTER есть target CLASSIFY, с помощью которой можно проставлять пакету класс, заданный в подсистеме traffic control (классифицировать пакет). Классификация возможна только в таблице mangle NETFILTER.

В рассматриваемой конфигурации используется классификация с помощью NETFILTER, так как для меня проще отбирать нужные пакеты с помощью NETFILTER, чем возиться с классификаторами tc filter. В таблице mangle у меня уже формируются цепочки по потокам - остается только вставить нужное правило классификации в нужную цепочку. Возможно классификация при помощи tc filter быстрее, особенно для большого числа правил (см. tc hashing filters). Пока не ясно как в классификаторе tc filter отбирать указывать условия эквивалентные возможностям ipset.

Трафик будем классифицировать с помощью NETFILTER, а не с помощью tс-фильтров.

Классификация возможна только в таблице mangle. Правила в таблице mangle организуем по тому же принципу, что и в таблице filter: каждому потоку свою цепочку с правилами плюс переходы на них со встроенных цепочек согласно адресам сетей. Рассмотрим листинг команды iptables -nvL -t mangle: нас интересуют цепочки 06_INET_LAN и 07_INET_DMZ.

В цепочке 06_INET_LAN мы классифицируем трафик для всех четырех хостов (boss, admin, user, server) в сети LAN. Правила (выделены синим) для перехода в цепочку 06_INET_LAN находятся во встроенной цепочке FORWARD.

Правила для классификации трафика для хостов в сети DMZ расположены в цепочке 07_INET_DMZ. Правила (выделены желтым) для переходов в цепочки 07_INET_DMZ расположены во встроенной цепочке FORWARD.

Управление трафиком и SQUID

Как видно из рис. 10 и 11 хосты сетей LAN и DMZ могут использовать канал до провайдера не только напрямую, но и опосредованно - через прокси-сервера, если таковые работают на шлюзе. В рассматриваемом примере это SQUID - http/ftp прокси.

Исходящий трафик от хоста сети LAN к какому-либо http-серверу может быть явно направлен через прокси, либо принудительно перенаправлен на локальный прокси средствами NETFILTER (transparent proxy, "прозрачный прокси"). В результате узел сети LAN уже не устанавливает прямое соединение с целевым сервером: сначала трафик идет на прокси-сервер, а уже прокси-сервер создает новое соединение с целевым сервером и шлёт пакеты "от своего имени". В обратном направлении происходит тоже самое: целевой сервер отвечает прокси-серверу, а прокси-сервер передает данные клиенту. Дополнительная сложность возникает из-за того, что прокси-сервер может отдать данные клиенту взяв их из кэша и при этом канал до провайдера не будет занят передачей этих данных.

Это осложняет задачу управления трафиком, так как трафик, проходящий через прокси не является "непрерывным": он разбит на два этапа. При этом потребителем трафика http-сервер -> прокси, как видно из рис. 10 и 11, является сам шлюз, а не конечный клиент.

Поэтому, строго говоря, для такого трафика исчезает возможность управлять им на исходящих (eth0, pppN, ifb0) интерфейсах, а управление входящим трафиком (до SQUID'а, на eth1) малореалистично: надо анализировать пакеты на уровнях выше транспортного, так как "где чей пакет" уже невозможно понять на транспортном уровне (по IP источника - источник один - локальный процесс squid'a) а также "увязывать" входящую дисциплину для eth1 и исходящих (eth0, pppN, ifb0) интерфесов.

Есть два пути решения задачи управления трафиком при наличии прокси на шлюзе:

Оба решения имеют свои плюсы и минусы. Рассмотрим их.

Вариант №1: использовать возможности SQUID (версии 2.x) по управлению трафиком

Первый вариант - воспользоваться возможностями самого SQUID'а по управлению трафиком - delay pools ("пулы задержек").

Минусы:

Плюсы:

Итак основными недостатками управления тарфиком в SQUID с помощью delay_pools являются: отсутствие интеграции с подсистемой управления трафиком ядра (traffic control) и как следствие - невозможность гибкого распределения неиспользуемой пропускной способности между нуждающимися в ней хостами, невозможность управления трафиком рамках единой системы и связанная с этим громоздкость конфигурирования и администрирования.

Вариант №2: косвенное влияние на использование канала SQUID'ом плюс частичная "интеграция" с traffic control

В этом варианте с помощью tc ограничивается скорость, с которой SQUID отдает трафик хостам внутренних сетей (зелёные стрелки на рис. 10 и 11). Это косвенно влияет на то, с какой скоростью squid загружает себе данные для каждого клиента.

Для этого трафик от SQUID в сЕти LAN и PPTP (цепочка 02_GW_LAN) надо проклассифицировать с помощью iptables (-j CLASSIFY) аналогично цепочке 06_INET_LAN (таблица mangle). И, естественно, этот трафик должен быть пернаправлен на интерфейс ifb0, что мы уже рассмотрели ранее. Т.о. трафик от SQUID будет проклассифицирован так же как и трафик, идущий к пользователям напрямую, "мимо" SQUID'а, и управление этим трафиком будет происходить согласно правилам для "прямого" трафика. Т.е. для целей управления трафиком, трафик идущий от SQUID во внутренние сети, так же, как интерпретируется как прямой трафик из Интернета.

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

Трафик от SQUID во внутренние сети пометим (CONNMARK) в цепочке 01_LAN_GW_ALLOW таблицы filter:

CONNMARK   tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:3128 state NEW CONNMARK xset 0x3128/0xffffffff 
CONNMARK   tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:3129 state NEW CONNMARK xset 0x3128/0xffffffff 

Далее, помеченный трафик из цепочки OUTPUT таблицы mangle отправим на классификацию в цепочку 06_INET_LAN таблицы же mangle:

06_INET_LAN  all  --  *      eth0    0.0.0.0/0            192.168.0.0/24      connmark match 0x3128 
06_INET_LAN  all  --  *      ppp+    0.0.0.0/0            192.168.1.0/24      connmark match 0x3128
Как результат: пакеты от SQUID к внутренним хостам будут проклассифицированы также, как и пакеты приходящим им напрямую из Интернета.

Таким образом мы получаем возможность (косвенно) управлять трафиком, который идет пользователям через SQUID, средствами traffic control.

Однако, при этом, возникает следующий минус: контент, который SQUID возьмет из своего кэша, будет отдаваться не на максимально возможной скорости, а с ограничением, установленным для данного клиентского хоста. Это происходит потому, что подсистема traffic control, естественно, не может "понять" откуда SQUID получил передаваемый контент - из своего кэша или из Интернета.

От этого недостатка можно избавиться следующим образом. Начиная с версии SQUID 2.7 появилась возможность пометить трафик, который squid берет из кэша. Для "отметки" можно использовать поле TOS заголовка IPv4 пакета.

Т.о. можно настроить SQUID так, что контент взятый из кэша, будет иметь одно значение поля TOS, а контент которого нет в кэше SQUID'а (и который поэтому загружен SQUID'ом из Интернета) будет иметь другое значение поля TOS.

Ниже показаны две дерективы, которые надо использовать в конфигурационном файле SQUID 2.7:

zph_mode tos
zph_local 0x88
Первая (zph_mode tos) включает пометку пакетов в зависимости от попадания/промаха (HIT/MISS) в кэше SQUID.
Вторая (zph_local 0x88) - задает (произвольное) значение поля TOS IP-пакета для контента взятого из локального кэша SQUID'а.

С этими директивами, IP - пакеты контента, взятого из кэша (HIT), будут иметь поле TOS равное 0x88, а пакеты контента взятого не из кэша (MISS) - 0x00.

На данный момент предыдущие варианты использования поля TOS аннулированы (RFC 3168, section22) и сейчас биты 0-5 это DSCP, а 6-ой и 7-ой - ECN (RFC 3168, page 8), то число, которое вы выбираете для zph_local, в двоичной форме должно иметь нули в 6-ом и 7-ом битах. Например: 0x88 - 10001000 (136 дес.), 0x58 - 01011000 (88 дес.), 0x20 - 00100000 (32 дес.).

http://bytesolutions.com/Support/Knowledgebase/KB_Viewer/ArticleId/34/DSCP-TOS-CoS-Presidence-conversion-chart.aspx

В SQUID 3.1 принцип тот же, но директива называется по другому (qos_flows) и имеет другой синтаксис: (http://wiki.squid-cache.org/Features/QualityOfService)

Как результат - исчезают все минусы, указанные в Варианте №1: появляется возможность целостного управления входящим трафиком из сети Интернет во внутренние сети, упрощается админстрирование, так как не надо управлять скоростями в двух разных системах (ядре и SQUID).


Roadwarriors: мобильные клиенты. Доступ к внутренним сетям и VPN.

Со временем у сотрудников появляется необходимость получить доступ к ресурсам внутренних сетей извне. Например, сотруднику надо из Интернета (дом, командировка) подключиться удаленным рабочим столом к своему компьютер, стоящему в офисе и что-то на нём посмотреть или поработать с него с какими-либо проложениями. Фактически, это позволяет находясь вне офиса, работать за своим офисным компьютером почти также как если бы сотрудник лично сидел за ним в офисе: получать тот же доступ к внутренним ресурсам и т.п. Таким же образом можно организовать прямой доступ к серверам и приложениям, работающим на хостах внутри офисной сети, не выставляя их в публичный доступ. Или, например, маршрутизировать трафик от удаленного работника через офисный шлюз, создавая для ресурсов Интернета иллюзию того, что трафик идет непосредственно из офиса. Это полезно, если нужные сотруднику ресурсы разрешают доступ только с адресов офисного шлюза.

"Напрямую" такое сделать невозможно: и из-за фаервола и из-за частных адресов, используемых во внутренних сетях. Непосредственно можно подключиться только хостам, имеющим публичные IP-адреса (например сам шлюз и сеть DMZ) и только если к ним разрешён доступ в фаерволе.

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

Естественно, реализация такой возможности, потенциально открывает для посторонних ещё одну возможность получить доступ к внутренней сети. В случае утечки аутентификационных данных (ключей, паролей) легитимных пользователей, посторонний сможет подключиться к VPN-серверу и получить доступ ко всему, к чему имеет доступ реальный пользователь. Из-за неправильного конфигурирования VPN-сервера или наличия уязвимостей в сервере/клиенте/протоколе VPN, возможны неавторизованные подключение, взлом сервера VPN или шлюза или всей сети в целом.

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

Функционирование VPN с точки зрения фаервола

Как уже говорилось выше, VPN всего лишь создает туннель между VPN-сервером и мобильным клиентом. Поэтому всех подключенных клиентов VPN (или их часть) можно рассматривать как просто еще одну IP-сеть, подключенную к шлюзу через (виртуальный) интерфейс. С точки зрения маршрутизации и фаервола это такая же сеть, как и например LAN, DMZ. Опять, же таких VPN сетей можно организовать несколько, выделив каждой группе пользователей по отдельной IP-сети.

В зависимости от конкретной реализации, сервер VPN либо может создавать виртуальный интерфейс, являющиеся серверным концом этого туннеля, либо работать без создания виртуальных интерфейсов. В случае, если используемый сервер VPN создает для своей работы виртуальные интерфейсы (напр. OpenVPN, PPTP), эти интерфейсы будут входящими/исходящими в iptables для туннелируемых пакетов (payload protocol).

Если используемый сервер VPN не создает виртуальные интерфейсы в своей работе (напр. IPSec/NETKEY), то в цепочках iptables будут "видны" уже распакованные из туннеля и заново инжектированные в сетевой стек пакеты VPN-протокола (payload protocol), естественно уже с частными адресами VPN-клиентов. При этом входящим/исходящими интерфейсом в iptables для этих пакетов будет тот физический интерфейс, на который подключились VPN-клиенты. Поэтому на этом интерфейсе будут одновременно "видны" как пакеты c частными адреcами VPN-клиентов, так и пакеты других интернет-соединений с публичными адресами. При этом пакетов самого туннеля (delivery protocol) в iptables не "видно". (см. Примечание)

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


Рис. 16 Потоки VPN-LAN инкапсулированные в VPN-туннель.

Как показано на рис. трафик между VPN-клиентом 192.168.255.5 и хостами сети LAN (payload protocol) трактуется не как Интернет-трафик, хотя VPN-клиент для нас "находится в Интернете", а как отдельный трафик между сетями VPN (192.168.255.0/24) и LAN. В свою очередь этот трафик инкапсулирован в VPN-туннель (delivery protocol) и трактуется с точки зрения фаервола как трафик Интерент-шлюз и относится к потокам INET>eth1-GW и GW>eth1-INET (цепочки потоков 18_VPN_LAN и 19_LAN_VPN).

В рассматриваемом примере VPN-клиентам выделен диапазон адресов 192.168.255.0/24. В цепочке FORWARD имеем следующие цепочки для трафика между VPN-клиентом и сетью LAN и между VPN-клиентом и Интернетом.

18_VPN_LAN  all  --  tun1   eth0    192.168.255.0/24     192.168.0.0/24
19_LAN_VPN  all  --  eth0   tun1    192.168.0.0/24       192.168.255.0/24
20_VPN_INET  all  --  tun1   eth1    192.168.255.0/24    !192.168.0.0/16
21_INET_VPN  all  --  eth1   tun1   !192.168.0.0/16       192.168.255.0/24
Так как у нас нет необходимости в обмене трафиком между VPN-клиентами и внутренними PPTP-клиентами, то этот трафик и не разрешён. Цепочки же 20_VPN_INET, 21_INET_VPN как раз и нужны для случаев когда VPN-клиенту необходимо свой трафик "пропустить" не напрямую через своё подключение к Интернету, а "в обход" - через офисный шлюз, чтобы исходящие пакеты в результате NAT имели адрес шлюза. Это надо, например, если на фаерволе клиентов разрешёно подключение по RDP-протоколу только с офисного IP-адреса, а сотруднику надо подключиться к клиенту из дома. Он подключается к офисному шлюзу посредством VPN и на своём компьютере добавляет маршрут, указывающий, что к такому-то IP отправлять пакеты не через шлюз по умолчанию, а через туннель и пакеты к этому адресу начинают уходить в туннель и далее через офисный шлюз к адресу назначения.

Вкратце рассмотрим правила в цепочках 18_VPN_LAN и 19_LAN_VPN.
Видно что в цепочке 18_VPN_LAN_ALLOW у нас правила для двух VPN-клиентов: с адресом 192.168.255.5 и с адресом 192.168.255.9. Клиенту 192.168.255.5 разрешён доступ к своему офисному компьютеру по RDP-протоколу (tcp dpt:3389) и напрямую к серверу по любому протоколу.
Клиенту 192.168.255.9 также разрешён доступ к своему офисному компьютеру по RDP-протоколу и он может пинговать свой компьютер (icmp). В цепочке же 19_LAN_VPN есть только ACCEPT...state RELATED,ESTABLISHED. Это позволяет VPN-клиентам инициировать новые соединения на разрешённые адреса и порты, а вот к ним из сети LAN (и других) никто подключится не сможет - хосты сетей LAN, PPTP, DMZ не будут их "видеть". Это полезно для того, чтобы пользователи внутри офиса не могли случайно или умышленно негативно воздействовать на подключённых VPN-клиентов (например, взломать или заразить их компьютеры).

VPN и маршрутизация

Маршрутизация с VPN работает также как и с "обычными" сетями. Дополнительно можно заострить внимание на VPN-клиенте.

К VPN-серверу мобильный клиент подключается с помощью специального ПО - VPN-клиента, который либо уже входит в состав ОС, либо устанавливается отдельно. Само мобильное устройство, естественно, должно быть подключено к сети Интернет или иметь выход в Интернет через другие сети. Обычно выход в Интернет описывается маршрутом по умолчанию, что означает, что все пакеты для неизвестных (не описанных в таблице маршрутизиации) сетей будут уходить туда - в Интернет. В случае же использования VPN, мобильному клиенту надо выборочно направлять трафик: пакеты к определённым хостам надо направлять в VPN-туннель, а не напрямую в Интерент. Достигается это добавлением соответствующих маршрутов на компьютере мобильного клиента. При этом возможны ситуации, когда подключение к VPN-серверу прошло нормально, "всё должно работать, но не работает". Это может быть связано с вопросами маршрутизации. Рассмотрим пример.

Пусть наш сотрудник, находится в некоей организации (или домашней сети за роутером) со своим ноутбуком. Диапазон адресов сети этой организации совпадает с диапазоном адресов офисной сети - и там и там 192.168.0.0/24. Сотрудник подключает свой ноутбук к сети организации и ноутбуку присваивается адрес, допустим, 192.168.0.17. Сотрудник подключается к офисному VPN-серверу и пытается удалённо через VPN подключиться к своему компьютеру внутри офиса с адресом 192.168.0.10. Естественно ничего не получается, потому что в таблице маршрутизации его компьютера при подключении к сети, автоматически был прописан маршрут, говорящий что сеть 192.168.0.0/24 подключена непосредственно к его ноутбуку. Другими словами попытка подключения происходит к компьютеру с адресом 192.168.0.10 внутри той сети, где находится сотрудник, а не к компьютеру находящемуся в нашей офисной сети (пакеты маршрутизируются в прямо "сетевуху", а не в туннель). Т.е. надо прописать дополнительный маршрут в таблицу маршрутизации компьютера сотрудника.

Однако, если указать маршрут ко всей сети 192.168.0.0/24 через туннель, то работать ничего не будет, потому что физический шлюз организации, через который сотрудник "выходит" в Интернет и далее к VPN-серверу перестанет быть доступен. Перестанет быть доступным вообще всё: и сеть организации в которой находится сотрудник и Интернет. Ведь компьютер начнёт "пытаться пихать" в туннель любые пакеты из-за того, что шлюз для выхода в Интернет (тот который в маршруте по умолчанию) тоже находится в местной сети 192.168.0.0/24!

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

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

Выбор VPN

Выбирать будем из следующих вариантов: PPTP, L2TP, SSTP, IPSec, OpenVPN

Необходимы:

PPTP

Слабая аутентификация. Потенциальные проблемы с прохождением протокола GRE через "кривые" маршрутизаторы (напр. Eltex NTE-RG-1402G-W с прошивками до 04.02.2011). Демон не расчитан на прослушивание разных интерфейсов с разными настройками (в т.ч. разными базами пользователей). Используя xinetd в качестве костыля, можно получить два pptp-демона с разными настройками - один слушает в LAN другой в Интернет. Однако при перезапусках такой конфигурации бывали сбои - зависело от того, к какому из них подключится первый клиент. Если в настройках pptp-демона, слушающего на Интернет-интерфейсе включить обязательное шифрование, то у pptp-клиентов в сети LAN, которые используют PPTP без шифрования, случайным образом становились недоступны различные серверы в Интернете. Причина сразу была неясна - разбираться не стал, выяснил только, что если все клиенты обоих демонов работают с одинаковыми настройками шифрования, то работает без сюрпризов. Возможно проблема связана с MTU. Макс. скорость передачи PPTP-клиента (Windows 7) по 100 Мбит ethernet-каналу чуть меньше (8-9 Мбайт/с), чем у IPSec, но с бОльшей загрузкой CPU (10-15%). Задержки (RTT) в ping больше в 3-4 раза, чем в IPSec и OpenVPN и "плавают".

L2TP

L2TP - протокол туннелирования. Не обеспечивает шифрование. Обычно в L2TP-туннель инкапсулируется PPP-трафик. В свою очередь, для защиты самого L2TP туннеля его туннелируют через IPSec. Т.е. для организации VPN на основе L2TP надо конфигурировать в три раза больше сущностей чем в IPSec: L2TP, PPP и IPSec. Уж проще тогда использовать "голый" IPSec или OpenVPN.

SSTP

Протокол от Microsoft для туннелирования PPP или L2TP через SSL: IP(TCP(SSL(SSTP(PPP-payload)))). Во время аутентификации payload-протокол еще и в HTTP "заворачивается". SSTP-сервера для Linux не существует. Т.о. пользоваться этим "чудом" реально только на продукции Microsoft.

IPSec

KLIPS - первый стек IPSec проекта FreeS/WAN для Linux версий 2.4 и 2.6.
NETKEY - "родной" IPSec стек ядра Linux версий 2.6 и 3.
Для userspace (IKE-демоны, библиотеки и утилиты) существуют:

Для подключения к IPSec-серверу нужен клиент под Windows (ясно что под Linux всё родное). Мне известен только один бесплатный IPSec клиент - Shrew Soft VPN Client. В текущей версии 2.x поддерживается только IKEv1. Разработка версии с поддержкой IKEv2 не предвидится. Shrew Soft VPN Client хорошо взаимодействует с ipsec-tools и strongSWAN.

В Windows 7 есть встроенная поддержка IKEv2, но установить туннель из Windows 7 с ipsec-tools или strongSWAN так и не удалось предположительно из-за сертификатов. Правда с этими же сертификатами Shrew Soft VPN Client успешно подключался к ipsec-tools (IKEv1-демон racoon) и к strongSWAN (IKEv1-демон pluto), но, видимо, у Microsоft свои недокументированные особенности.

Вот что показала проверка реальной работы.

У ipsec-tools обнаружился недостаток - IKE-демон racoon не позволяет задать определённому клиенту определённый виртуальный IP (адрес клиентской части туннеля). Из-за этого невозможно указать правила фаервола для конкретных клиентов (сотрудников). Выснилось, что этот функционал можно реализовать с использованием RADIUS-сервера, однако, как случайно выяснилось в переписке, в Debian IKE-демон racoon скомпилирован без поддержки RADIUS-сервера, и изменений не предвидится (см. здесь и здесь) Работа через NAT не проверялась.

Сколько нибудь внятную документацию OpenSWAN и информацию о совместимости с другими реализациями на сайте www.openswan.org найти не удалось.

strongSWAN лучше документирован авторами, имеет более зрелую реализацию IKEv2 и активнее развивается. И из рассматриваемых реализаций IPSec, только strongSWAN формально отвечает всем требованиям, перечисленным в начале раздела. Однако от strongSWAN (и от IPSec) пришлось отказаться, так как в результате тестов с подключением клиента из-за NAT (Linux box и DLink DIR-300) выяснилось, что туннель создаётся, но через пару секунд "отваливается". При "прямом" (без NAT) подключении к strongSWAN всё отлично работало. Eстественно NAT-Traversal и всё что надо для его работы (открытые порты) при этом было включено и настроено. Короткая переписка с авторами strongSWAN и Shrew Soft VPN Client (см. ниже) ничего так и не прояснила. Позже появилась непроверенная догадка, что проблема как-то связана с MTU.

В ходе настройки, выяснилось, что настройка IKEv1 демона для IPSec довольно муторное дело, так как без опыта, по логам трудно понять что не так и в чём именно ошибка настройки. У strongSWAN (pluto) довольно информативные логи в сравнении с net-tools (racoon). Основные проблемы возникали из-за ошибок при генерации сертификатов.

Производительность IPSec оказалась самой высокой: около 10 Мбайт/с на 100 Мбит ethernet-канале при самой низкой (2-3%) загрузке CPU на клиенте.

http://tiebing.blogspot.com/2011/06/fairly-new-comparison-of-openswan-and.html
http://www.installationwiki.org/Openswan#Choosing_the_Kernel_IPsec_Stack

OpenVPN

Так как мне не удалось заставить IPSec работать в случае нахождения мобильного клиента за NAT'ом, то остался последний вариант - OpenVPN, который собственно и используется.

Всех вышеперечисленных недостатков у OpenVPN нет, но есть свои собственные. В ОС Windows подключиться из под пользователя без административных прав, из идущего в комплекте GUI-клиента не получится - OpenVPN не сможет создать TAP-устройство и прописать новые маршруты. Для подключения из под пользователя без административных прав предлагается запускать сервис (службу OpenVPN Service). Однако при этом теряетcя возможность управления openvpn через штатный GUI-клиент - ни отключиться, ни подключиться, ни посмотреть статус. Кроме того, при старте службы opevpn автоматически подключается ко всем сконфигурированным серверам, что не всегда удобно. Неудобная схема присвоения адресов концам туннеля, используемая для совместимости с TAP-Win32 драйвером. Производительность ниже чем у PPTP и IPSec: около 5-6 Мбайт/c при 20% загрузке CPU клиента (Celeron 2.4). Наблюдалось полное блокирование (непрохождение) трафика через туннель при 100% загрузке CPU (Celeron 2.4) другими процессами, чего не наблюдалось с PPTP (у PPTP просто раза в 4 увеличивался "пинг").



Учет (подсчет) трафика (traffic accounting)

Был выбран вариант, использующий счетчики NETFILTER. В нужные цепочки вносятся правила для счетчиков. С помощью cron, каждую минуту счетчики в этих цепочках сохраняются в файлы перенаправлением вывода команды iptables -nvxL цепочка в файл в имени которого содержится имя потока, дата и время. Таким образом за сутки создается 1440 файлов с поминутной историей значений счетчиков. Файлы сгруппированы в каталоги по годам, месяцам и числам.

Вместо хранения значений счетчиков в текстовых файлах можно использовать RRDtool или другие БД.

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

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

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

Несмотря на то, что есть возможность сохранить правила фаервола вместе с показаниями счетчиков в файл, а затем восстановить эти правила, также, вместе со значениями счетчика. Однако этим крайне нетривиально воспользоваться в случае изменения набора правил. См. скрипт conf.d/main/ta/print_inet_lan_counters в исходниках.

ДОПИСАТЬ

Подсчет трафика с помощью conntrackd

На момент начала созадния данного текста conntrack-tools не были доступны в стабильной версии в Debian 5 Lenny. Изучить данный вопрос. http://conntrack-tools.netfilter.org/manual.html#what.

Подсчет трафика с помощью ulogd

Когда ulogd 2-ой версии будет в Debian.

Учет трафика и SQUID

Ситуация с учетом трафика при использовании прокси-сервера на шлюзе схожа с ситуацией управлением трафиком: клиенты внутренних сети получают трафик не напрямую, а опосредованно - через squid, что затрудняет подсчет трафика средствами NETFILTER.

Без веб-прокси трафик между сетями мы подсчитываем в цепочке FORWARD, так как он проходит через шлюз от одного интерфейса к другому. В случае же использования веб-прокси, уже squid будет получать трафик из Интернета (цепочка INPUT) и затем отдавать его клиентам (цепочка OTUPUT).

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

В случае, если хотим поставить счётчики NETFILTER в INPUT eth1, eth3, то встает проблема определения адреса клиента, для которого squid скачал пакет. Так как пакеты приходят на адрес шлюза, а не напрямую на адрес клиента, то определить адрес истинного получателя трафика можно только анализируя HTTP-заголовки, что затруднительно на практике и негативно влияет на производительность.

Существует Application Layer Packet Classifier for Linux (L7-filter), позволяющий с помощью шаблонов из регулярных выражений с довольно высокой степенью вероятности определить к какому прикладному протоколу (уровень 7 модели OSI) относится трафик. Однако этого недостаточно, ведь надо определить не просто протокол, а выделить из каждого HTTP-заголовка IP-адрес клиента. Лобовым решением было бы создание индивидуального шаблона, включающего сам IP-адрес для каждого клиента, но очевидно, что это крайне неэффективный вариант. Опять же использование данного фильтра требует патчить и компилировать ядро и iptables. Т.о. использование L7-filter не подходит для решения данной задачи. Остаются два рабочих варианта подсчета трафика: подсчет трафика отдаваемого squid'ом и подсчет трафика пол логам squid'а.

Для подсчета трафика, который squid отдаёт клиентам внутренних сетей, счетчики можно разместить OUTPUT eth0, ppp+. В этом случае необходимо отличать трафик взятый squid'ом из своего кэша, от трафика полученного из Интернета. Сделать это можно с помощью поля TOS IP заголовка, это описано в разделе "Вариант №2: косвенное влияние на использование канала SQUID'ом плюс частичная "интеграция" с traffic control". Кроме того, подсчёт в этом варианте будет давать слегка завышенные результаты, из-за того что squid будет добавлять в HTTP-заголовки передаваемых клиентам пакетов дополнительны данные.

Вариант с подсчетом трафика по логам squid'а сводится к парсингу access-логов с информацией о каждом HTTP-ответе (адрес клиента и размер). Далее полученная таким образом информация о количестве трафика для каждого клиентского IP суммируется с данными, полученными с помощью счётчиков iptables. В результате получается суммарный (через squid + весь остальной) трафик для каждого IP.

В данном примереи спользуется вариант с подсчетом трафика по логам squid'а.
См. скрипт conf.d/main/ta/print_inet_lan_counters в исходниках.



Использование ipset. Оптимизация набора правил

Модуль ipset позволяет в одном правиле iptables эффективно выполнить проверку не по одному адресу и/ или порту (как в -s/-d и --dport/--sport), а по (большому) списку адресов и/или портов. Для этого создается нужное кол-во таблиц ipset, которые заполняются проверяемыми IP адресами и/или портами. Далее созданные таблицы используются в командах iptables для проверки соответствия с помощью '-m set --match-set setname', где setname - имя таблицы. В рассматриваемом примере с помощью ipset можно сделать следующие оптимизации.

В INPUT заменить два правила

03_INET_GW_ISP2  all  --  eth3   *      !192.168.0.0/16       192.168.5.1
03_INET_GW_ISP2  all  --  eth3   *      !192.168.0.0/16       198.51.100.2
на одно, предварительно создав таблицу и заполнив её dst-адресами 192.168.5.1 и 198.51.100.2.

В рассматриваемом примере используется такое понятие как "адреса сети Интернет" которыми в данном случае удобно считать все остальные адреса кроме, адресов локальных сетей. Т.о. "Интернетом считается" всё что НЕ 192.168.0.0/16. Однако, если в локальных сетях одновременно будут использоваться адреса из разных диапазонов (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), то такой простой вариант указания того, что такое "Интернет" в правилах iptables не пройдет. Надо будет либо делать отдельную цепочку со списком сетей, считающихся локальными, и делать все проверки по этой цепочке, либо делать список этих сетей в таблице ipset и вставлять проверку по этой таблице в нужные правила вместо ! -d/s 192.168.0.0/16.

Далее, можно во всех цепочках *_ALLOW,*_SKIP_OR_DENY, *_SKIP_OR_DENY заменить последовательности правил с проверкой IP-адресов и портов на правила с проверкой по таблицам ipset. Фактически, c использованием таблиц ipset становятся лишними сами цепочки *_ALLOW,*_SKIP_OR_DENY, *_SKIP_OR_DENY: переходы в соответствующие цепочки можно заменить на правила с проверкой по соответствующим таблицам ipset. Такой вариант приведет к значительному уменьшению общего количества правил. При этом изменение настроек фаервола будет сводиться, в основном, к изменению ipset-таблиц, а не к удалению/добавлению правил iptables.

В рассматриваемом варианте ipset не используется в связи с тем, что в Debian модуль ipset не входит в стандартное ядро.

Для установки модулей ядра ipset можно воспользоваться пакетом module-assistant и с помощью него автоматически скомпилировать и установить пакет xtables-addons-source, в который входит и модуль ipset.

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

Использование -m multiport для нескольких tcp/udp портов.



iptables-restore: атомарная загрузка набора правил

Одна из особенностей NETFILTER и iptables заключается в следующем: при внесении любых изменений в активный (находящийся в ядре) набор правил с помощью команды iptables происходит копирование существующего набора правил из kernelspace в userspace, далее вносятся изменения и измененный набор загружается обратно в ядро. Поэтому при таких, инкрементальных обновлениях правил, возможны следующие негативные эффекты.

Первый эффект заключается в замедлении загрузке новых и обновлении уже существующих правил: 1-ая команда iptables обрабатывается и загружается в ядро. При выполнении второй команды, сначала из ядра загружается существующий набор правил (состоящий пока из одного правила), этот набор парсится, в него добаляется второе правило и новый набор из двух правил загружается в ядро. При выполнении 3-ей команды происходит то же, но из ядра уже загружается и обрабатывается больше информации: два правила. Далее, к этим двум правилам добавляется третье и новый набор правил снова загружается в ядро. И с каждой следующей командой между kernelspace и userspace надо прогнать и пропарсить всё больше информации. Это занимает какое-то время. Поэтому когда правил становится много, то замедление загрузки и обновления правил (время выполнения всего набора команд iptables) становится ощутимым.

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

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

Однако надо не забывать, что iptables-restore атомарно загружает правила только в одну таблицу!
В NETFILTER четыре таблицы: raw, filter, mangle, nat и все они загружаются независимо друг от друга. Возникновение ошибки при загрузке в одну из них не вызывает отката изменений в остальных.

Синтаксис команд, которые принимает на вход iptables-restore слегка отличается от синтаксиса iptables.

Структура входных данных для iptables-restore:

*таблица #1
команда #1
...
команда #N
COMMIT
*таблица #2
команда #2
...
команда #N
COMMIT
EOF
Сначала указывается таблица, затем перечисляются правила, вносимые в эту таблицу и завершается блок командой COMMIT.

Команда создания новых цепочек выглядит так:

:имя_цепочки - [кол-во пакетов:кол-во байт]
Остальные команды (добавление, удаление, вставка и т.п.) идентичны по синтаксису параметрам команды iptables. Т.о. файл с командами для iptables-restore может выглядеть так:
*filter
:DMZ_INET - [0:0]
-A DMZ_INET -m state --state RELATED,ESTABLISHED -j ACCEPT
-A DMZ_INET -p tcp --dport 80 -m state --state NEW -j ACCEPT
COMMIT
*nat
:LAN_INET - [0:0]
-A LAN_INET -o eth1 -j SNAT --to-source 44.44.44.44
COMMIT

По умолчанию iptables-restore полностью очищает указанные таблицы от имеющихся там правил, однако если нам надо внести изменения, не очищая существующие в ядре правила, то надо указать ключ --noflush.



nftables: преемник iptables и будущее Linux firewall

nftables - написанная "с нуля" система управления фильтрацией и классификацией трафика, призванная заменить iptables. Изначально nftables разрабатывал Patrick McHardy, сопровождающий подситему NETFILTER. Презентация проекта (включая реализацию подсистемы ядра) состоялась на Netfilter Workshop в сентябре 2008 года. 18 марта 2009 Patrick анонсировал первый публичный релиз nftables (http://marc.info/?l=linux-netdev&m=123735060618579), содержащий реализацию пространства ядра (kernelspace) и утилиту пространства пользователя для администрирования. Официальная страница проекта http://netfilter.org/projects/nftables/index.html По логу git'а (http://git.netfilter.org/nftables) можно судить о том, что релизов не было уже 4 года, но работа над проектом продолжается. На данный момент ведущим разработчиком, продолжившим развитие nftables является Pablo Neira Ayuso. Уже начата интеграция с кодом ядра в отдельном репозитарии (см. http://git.kernel.org/cgit/linux/kernel/git/pablo/nftables.git). Xtables2 vs. nftables

При разработке nftables учтены недостатки iptables. Например, "одно правило - одна цель", когда для логирования (-j LOG) и последующего игнорирования (-j DROP) пакета надо создавать два отдельных идентичных правила. При добавлении новых расширений в iptables их надо реализовать и в userspace и в коде ядра. Код ядра, отвечающий за фильтрацию содержит много дублирущегося функционала. Неэффективность инкрементальных изменений: iptables загружает из ядра весь набор правил в userspace, модифицирует его, и затем загружает весь набор обратно в ядро. Это происходит при каждом вызове iptables: в скрипте с 300-ми вызовами команды iptables описаная операция будет выполнена 300 раз, при этом с каждым разом будет расти количество информации, передаваемое между ядром и утилитой iptables. Для решения этих и множества других проблем nftables имеет следующую архитектуру.

В ядре реализована простая виртуальная машина (интерпретатор), которая имеет инструкции для загрузки данных из пакета, поддерживает выражения для сравнения различных частей пакета (по смещениям) с чем-либо, инструкции отслеживания состояний (limit, quota, ...) и т.д. Это освобождает от "зашитой" в код ядра информации о протоколах и о том как их анализировать: мы просто программируем ВМ ядра на выполнение тех или иных программ по обработке пакетов прямо из userspace. Подобным же образом в BSD-системах давно и успешно реализован BSD Packet Filter. Это позволит, не затрагивая ядро Linux, разрабатывать новые (исправлять существующие) правила обработки для новых протоколов, что очень сильно упрощает процесс и избавляет от дублирования кода в kernelspace и userspace.

nftables поддерживает такие структуры данных как множества и словари, что позволяет в одном правиле выполнять различные проверки по спискам и диапазонам значений (аналогично ipset, но с любыми данными). Код ядра полностью свободен от блокировок (locks). Счетчики пакетов и байтов, в отличие от iptables, опциональны, т.е. их нет по умолчанию в каждом правиле, их надо создавать явно. Это положительно влияет на производительность. Нет встроенных таблиц - все таблицы надо явно объявлять. Ядро выполняет минимальные проверки и не проверяет (смысловую) корректность загружаемых в ВМ программ, лишь бы программа не повредила ядро: все синтаксические и семантические проверки дожны проводиться утилитами userspace.

Соответственно, вместо последовательности команд iptables с ключами, для nftables существует полноценный высокоуровневый язык, который компилируется в низкуровневые инструкции для ВМ ядра. С помощью userspace утилиты nft можно выполнять как отдельные команды этого языка, для инкрементальных изменений, так и целые программы, написанные на этом языке. Т.о. сложной последовательности команд iptables по созданию цепочек и правил в этих цепочках, в nftables будет соответствовать программа состоящая из одного или нескольких файлов. Утилита nft проверит синтаксис этой программы, сообщит об ошибках, откомплирует её в инструкции ВМ и загрузит откомпилированую программу в ВМ ядра, используя протокол netlink.

Т.о. образом, если и когда проект созреет и будет включен в ядро, в Linux появится гибкая и мощная система фильтрации. Она во многом перекроет возможности, предоставляемые ipset и такими надстройками над iptables как shorewall, ferm, ufw, uif и т.п.

Часть 2. Реализация

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

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

При реализации фаервола возникли вопросы вроде:

Будет рассмотрено как эти вопросы были решены в данной реализации.


Чем не устроил Shorewall

На момент проб Shorewall был 3-ей версии. Пробовал я его на "Web server #1" в DMZ. В частности необходимо было задать различные наборы правил для клиентов из сети INET и cети LAN2, подключенных через eth3. Из INET надо было ничего не разрешать, а из LAN2 дать доступ к DMZ. Но я так и не смог настроить Shorewall так, чтобы он "понимал" что за интерфейсом eth3 у меня две сети, для каждой из которых я хочу настроить разные правила. На все мои варианты Shorewall сообщал об ошибке в конфигурации: что именно было уже не помню - что-то про нестыковку в сетях (файл hosts) и интерфейсах (файл interfaces). Т.е. добиться того что мне надо от Shorewall я так и не смог, и чем мучаться и догадываться что там не так я вручную написал эти несчастные несколько нужных мне правил.

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

Потом я поверхностно посмотрел, какие правила и цепочки генерировал Shorewall, в надежде понять как же добиться от него того, что мне надо, однако никакой системы на тот моментя там не уловил - все правила "валились в кучу": в INPUT, FORWARD, OUTPUT. Помню была некая специальная цепочка "all2all" и всё.

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

На 2013 год Shorewall имеет 4 версию, которая переписана на Perl вместо Bash, но синтаксис конфигурационных файлов остался прежним. Интересно, изменилось ли что-то в логике построения цепочек и правил.


Предыдущие варианты реализации или "как не надо делать"

В своё время, я не знал о существовании, например, shorewall и поэтому "изобретал велосипед" заново, не зная на каких принципах построены другие firewall'ы. Позже я попробовал использовать shorewall 3-ей версии на веб-сервере в DMZ, но так и не добился того, что мне было нужно и не стал разбираться - возможно ли это в приниципе. Поэтому изобретение велосипеда продолжилось.

Ниже описаны варианты фаервола, предшествововашие рассматриваемому варианту. Это сделано с целью поделиться опытом и показать "как не надо делать". Условно, рассматриваемый в данном тексте вариант фаервола можно считать "четвертой версией". А вот предыдущие три:

"Версия №1" - это просто bash-скрипт, содержащий линейную последовательность команд iptables..., выполнение которых и конфигурировало NETFILTER. Скрипт не имел настроек или параметров и для вненсения изменений, надо было исправлять непосредственно сам скрипт. По мере добавления новых интерфейсов и расширения функционала, рос и объем скрипта. Скрипт становился плохо управляемым и нечитаемым. Управления трафиком не было в принципе - вообще непонятно было как это делается. Назрела переделка.

"Версия №2". Первоначальный bash-скрипт был переписан: за конфигурацию правил каждого из потоков теперь отвечала отдельная функция в этом скрипте. Имелся файл настройки: отдельный bash-скрипт, в котором были описаны одномерные массивы в которых указывались списки (через пробелы) разрешённых IP- адресов и/или портов. Массивов было много - для каждого из потоков и для каждого типа задач (глобальные/индивидульные/адреса/порты/протоколы разрешения, запреты, исключения) приходилось заводить свой массив. Индексами в этих массивах были последние октеты IP-адреса хостов внутренней сети (напр. LAN_IP_INET_IP_ALLOW[131]="0.0.0.0/0" описывал разрешение хосту 192.168.0.131 на выход в Интернет). В рабочем скрипте использовалось множество однотипных циклов по этим массивам: так строились наборы правил для каждого из потоков. В скрипте, в достаточном количестве, присутствовали "ручные вставки кода" для "для решения особых случаев". Такая система была неуниверсальна, привязана к сети класса С и громоздка (для каждой из сетей приходилось заводить свои наборы массивов).

На этом этапе возникла необходимость в управлении трафиком и отчетности. Т.е. в конфигурации надо было увязать IP-адреса и некие метаданные (ФИО сотрудников, названия серверов) и как-то интегрировать это с управлением трафиком. Вопрос интеграции метаданных был решен примитивно - так как срипт отчетности был написан на perl'е, то для него просто вёлся отдельный perl-скрипт c хэшем IP-адресов, в котором хранились нужные данные. Ясно что возникло дублирование данных (IP-адресов) в двух файлах: в конфигурационном bash-скрипте, предназначенном для фаервола и в конфигурационном perl-срипте предназначенном для отчетности.

Управление трафиком было реализовано также примитивно: отдельный фиксированный bash-скрипт, в котором указывалось download-скорость из Интернета в LAN. Для ppp-клиентов был отдельный скрипт. Дублирование конфигурационной информации нарастало. Позже эта версия была слегка модифицирована: один единый файл конфигурации (bash-скрипт) был разбит на отдельные файлы (по кол-ву потоков), в каждом из которых содержались настройки, относящиеся только к данномоу потоку. Единый основной скрипт также был "расчленён" по тому же принципу - "каждому потоку - свой скрипт", в каждом файле две функции: для правил трафика "туда" и для правил "в обратном направлении". В результате получилась довольно негибкая "смесь" из различных подходов, которая не обладала нужной "полноценностью" (управление трафиком, ау!) и снова была слабоуправляемой. Необходимо было превратить эту "кашу" в нечто более стройное и единообразное. Основные направления: унификация формата настройки, его расширямость, читаемость и некая универсальность. И как следствие - неизбежный уход от bash, в сторону языка, с поддержкой болеес сложных структур данных.

"Версия №3". В этой версии файлы настроек были уже написаны на perl как и сам firewall. Однако ж снова, на каждую пару потоков было по своему собственному скрипту и для каждого потока был свой настроечный файл. Предполагалось, что при необходимости, нужные скрипты и конфигурационные файлы будут дописаны, используя существующие, как шаблоны.

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

Что должны описывать конфигурационные файлы.

Поэтому встал вопрос о том, как организовать информацию в конфигурационных файлах. Здесь усматривается два типа информации, которую надо хранить:


Рис. 17 Конфигурационные файлы

Следующий вопрос: в какое количество файлов и каким именно образом сгруппировать данную информацию? Варианты:

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

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

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

Т.о. версия №3 фаервола просуществовала недолго и оказалась опытной площадкой для перехода на следующую, четвертую версию с унифицированным форматом конфигурационных файлов, который и рассматривается далее.


Исходные тексты фаервола

Исходный код hlf.tar.gz


Почему perl, а не bash, python и т.д.?

Для написания фаервола рассматривались языки bash, perl и python. Для описания конфигурации и собственно кодинга выбран язык perl и вот почему.

bash
В bash 3.x нет сложных структур данных, а именно хэшей (словарей, ассоциативных массивов). И хотя в bash 4.x словари появились, но этого недостаточно, потому как невозможно создавать вложенные струтктуры данных: многомерные массивы, массивы хэшей, хэши массивов и т.п.
python
Python не является обязательным для Linux и не устанавливается по умолчанию (как минимум в Debian без GUI). Использование Python приводит к зависимости фаервола от необязательного компонента системы и необходимости устанавливать Python, потенциально, только ради работоспособности фаервола.
perl
Является неотьемлемой частью любого (серверного) дистрибутива. Мало того, без него невозможно скомпилировать Linux систему из исходных текстов (смотри напр. Linux From Scratch). Т.е. perl обязательно будет в любом десктопном или серверном дистрибутиве по умолчанию. В perl, в отличии от bash, есть удобные структуры данных и их легко сделать вложенными.

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

Почему конфигурация не хранится в СУБД или LDAP

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


Устройство фаервола

Общие замечания

  1. Код и настройки фаервола имеют модульную структуру. Идея заключается в том, чтобы иметь в наличии некий набор базовых действий (функций), используя которые, можно "собирать" более высокоуровневые скрипты, выполняющие уже какие-то конкретные задачи. Конечно, это далеко не абсолютно универсальные функции, позволяющие строить фаервол произвольной структуры, а функции, реализующие вполне определенный подход, описанный в первой части. (С течением времени идея выродилась, так как набора слабосвязанных скриптов сделать не получилось и все превратилось в набор функций в модуле firewall.pm)
  2. Код написан, а настройки расположены в файловой системе таким образом, чтобы легко было иметь несколько различный конфигураций одновременно и переключаться между ними одной командой. К тому же за счёт того, что есть возможность разбивать конфигурацию на произвольное кол-во файлов, при продуманном подходе к их написанию, возможно их повторное использование в различный конфигурациях. Например, если с 9 до 18 часов, должны действовать какие-то глобальные запреты, а в другое время эти запреты не действуют, то в основной (с 9 до 18) конфигурации можно вычленить в отдельный файл эту запрещающую часть. Далее создать новую конфигурацию, в которую слинковать часть файлов из основной конфигурации и убрать запуск обработки конфигурационного файла с запретами. И, наконец, перезапускать фаервол в нужное время с помощью crontab, указывая нужную конфигурацию.
  3. Не все команды для конфигурации фаервола создаются автоматически на основании файлов конфигурации. Часть команд содержится в явном виде непосредственно скриптах. Это связанно с тем что не для всех случаев легко и очевидно на данный момент или целесообразно придумывать синтаксис конфигурации и писать её обработку - иногда проще написать несколько команд, выполняющих нужные действия. В рассматриваемом варианте это относится, например, к правилам NAT: их немного, меняются они нечасто и "городить" ради них настройку и её обработку просто сложнее чем написать скрипт с несколькими командами iptables который и будет запускаться в нужный момент. Пометка трафика для целей policy routing также сделана отдельным скриптом с нужным набором правил, так как на данный момент для меня неочевидно как это гибко "вписать" в выбранную концепцию конфигурационных файлов. Но и данный вариант более чем жизнеспособен.
  4. Фаервол не сохраняет какой-либо метаинформации о своём текущем состоянии на диске или в памяти. После каждого изменения конфигурации требуется полная обработка конфигурации, построение структур в памяти и формирование нового набора правил.
  5. Сначала на основании конфигурации генерируется полная последовательность команд iptables и tc, и только затем эти команды выполняются. При возникновении оишибок во время генерации команд они просто не выполняются.
  6. Фаервол позволяет описать настройку как шлюза с произвольным кол-вом интерфесов и сетей к ним подключенным, так и, например, сервер или рабочую станцию, не выполняющие роль шлюза.
  7. Фаервол не проверяет смысловую корректность файлов настройки и конфигурации в целом. Реализация таких проверок в полном объеме - сложная и трудоёмкая задача и для проекта, используемого только в личных целях, не приоритетная. В данном случае, с помощью возможностей интерпретатора perl, проверяется только синтаксическая корректность файлов конфигурации. По этой же причине выдача различных дигностических сообщений минимальна.
  8. По умолчанию для каждой связи трафик потоков идущих в одном направлении, проверяется по одной и той же пользовательской цепочке. Например потоки INET>eth1-eth0>LAN, INET>eth3-eth0>LAN, INET>eth1-pppX>LAN, INET>eth3-pppX>LAN по умолчанию будут проходить проверку по одной и той же цепочке. Данное поведение изменяется доп. конфигурированием.
  9. Фаервол работает только с IPv4. Для IPv6 необходима соответствующая доработка (как минимум, для IPv6 надо использовать ip6tables).

Структура каталогов

В приведенных выше исходных файлах примере весь код и настройки фаервола расположены в каталоге /etc/hlf.
Код написан так, что работает по относительным путям и поэтому его можно размещать где угодно в файловой системе.
Поэтому далее по тексту каталог в котором расположены файлы фаервола будет обозначаться как /FW. Если для файлов и каталогов не указан полный путь, то подразумевается их размещение относительно каталога /FW.
Итак, в каталоге /FW находятся:

Поддержка нескольких конфигураций (/FW/conf.d)

В каталоге conf.d может находиться произвольное количество различных конфигураций фаервола - каждая в своем отдельном подкаталоге. При этом имя подкаталога фактически является именем расположенной в нём конфигурации. Также в conf.d может находиться символическая ссылка conf.d/default, указывающая на каталог, содержащий конфигурацию фаервола, используемую по умолчанию.

Для запуска фаервола предназначен скрипт /FW/start. Если в качестве параметра скрипту start передано имя подкаталога из каталоге conf.d (напр. outbound_only), то будет запущен скрипт start, находящийся в указанном каталоге.
В свою очередь, запускаемый скрипт conf.d/имя_конфигурации/start и должен выполнить всю последовательность действий по конфигурированию фаервола для выбранного варианта конфигурации. Если скрипту start не передано имя конфигурации, то этот скрипт пытается запустить скрипт conf.d/default/start, где default может быть как обычным подкаталогом с конфигурацией по умолчанию, так и символической ссылкой на некий существующий подкаталог conf.d/имя_конфигурации.

Данный механизм жёстко задан и не может быть изменён без правки кода в /FW/start.

Общие модули (/FW/modules)

Собственно здесь находятся общие "строительные блоки" - файлы с кодом, которые можно использовать в скриптах каждой из конфигураций. Основной из них - perl-модуль firewall.pm. Именно в нём реализованы почти все функции по конфигурированию фаервола. Его внутреннее устройство будет рассмотрено ниже.

Конфигурационные файлы

Как уже говорилось выше, фаервол поддерживает произвольное кол-во независимых конфигураций.
Минимальная конфигурация (каталог conf.d/имя_конфигурации) включает в себя три элемента:

В конфигурации фиксированное имя имеет только скрипт start. Так как содержимое скрипта start пишется индивидуально для каждой конфигруации, то и для файлов networks.conf и hosts.conf можно выбрать любые имена и расположение внутри каталога конфигурации. Кроме того, не обязательно иметь один файл типа hosts.conf - конфигурацию правил для хостов/сетей можно разбить произвольным образом на любое кол-во файлов, естественно с разными именами.

В случае необходимости используются как конфигурационные файлы, так и вспомогательные скрипты. Т.е. конфигруация может придставлять собой произвольный набор скриптов и конфигурационных файлов определенной структуры. Скрипты, в определенной последовательности, запускают какие-либо команды ОС, вызывают вспомогательные функции из общих модулей conf.d/modules, которые в свою очередь разбирают файлы конфигурации и формируют на их основе последовательность команд iptables, tc и т.п. для создания нужных цепочек, правил, задания правил управления трафиком и т.д.

Файл описания шлюза и сетей (аkа networks.conf)

Назначение данного файла - описать интерфейсы шлюза и сЕти, подключенные к этим интерфейсам непосредственно или которые доступны через эти сети. В конфигурации этот файл должен быть в единственном экземпляре. На основании этого файла определяются все возможные соединения между каждой парой сетей, между шлюзом и сетями; для соединений определяются прямые и обратные потоки, именуются цепочки и строится общий "каркас" цепочек NETFILTER (т.е. набор цепочек и переходов между ними). Также, на основании информации из этого файла строятся "головные" правила для цепочек INPUT, OUTPUT, FORWARD, с помощью которых, на основании входящих и исходящих интерфейсов и сетей, трафик относится к тому или иному потоку.

С точки зрения синтаксиса данный файл представляет собой Perl-скрипт, настройки в котором описаны в виде хэша %networks. Использование конфигурационных файлов, которые по сути являются фрагментами исполняемого языка программирования имеет недостатки:

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

В данном варианте подразумевается, что администратор шлюза (root):
  • владелец каталога /FW и всего его содержимого;
  • только root может изменять и запускать файлы в /FW;
  • и что он достаточно разумен, чтобы не вписать в файлы конфигурации что-то вроде `unlink '/etc/passwd'` или `system('rm -R /')`.

Далее описан синтаксис файла описания шлюза и сетей.
В качестве примера смотри файл /etc/hlf/conf.d/main/conf/networks.conf в исходниках.

Файл должен иметь синтаксис perl-скрипта. В файле должен быть один раз описан хэш с зарезервированным именем %networks. Каждый элемент хэша %networks описывает одну сеть. Ключ в %networks является именем сети и на него возможны ссылки из этого и других конфигурационных файлов. Имя сети выбирается произвольно и должно быть уникальным. В %networks должны быть описаны все сети, трафик которых проходит через шлюз и трафиком которых вы хотите управлять. Сам шлюз синтаксически трактуется как сеть и должен быть описан в данном файле.

%networks = (
    сеть№1 => описание_сети,
    сеть№2 => описание_сети,
    сеть№3 => описание_сети,
    ...
    сеть№N => описание_сети,
}

Описание_сети представляет собой хэш из четырех элементов со следующими ключами:

%networks = (
    сеть№1 => {
        name => наименование_сети, type => тип_сети,
        ifaces => описание_интерфейсов,
        flows_options => опции_потоков
    }
)

Описание_интерфейсов представляет собой хэш в котором каждый элемент описывает один сетевой интерфейс. Ключом хэша является имя интерфейса в формате команд iptables. В описании шлюза (тип "host") в ifaces должны быть перечислены сетевые интерфейсы шлюза. В описании сети (тип "net") должны быть перечислены сетевые интерфейсы, через который данная сеть доступна непосредственно или далее через маршрутизаторы (next hops). При использовании алиасов (интерфейс с несколькими ip-адресами) алиас описывается как отдельный интерфейс.

    ifaces => {
        'eth0'   => параметры_интерфейса,
        'eth0:0' => параметры_интерфейса,
        'eth1'   => параметры_интерфейса,
        'ppp+'   => параметры_интерфейса,
        ...
    },

Параметры_интерфейса представляют собой хэш из элементов со следующими ключами.

Обязателен только параметр addr.

gw => {
    ...
    ifaces => {
        'eth0'   => {addr => '192.168.0.1', tc_iface => 'ifb0', tc_root_handle => '0'},
        'eth0:0' => {addr => '192.168.4.1'},
        'ppp+'   => {addr => '192.168.3.1', tc_iface => 'ifb0', tc_root_handle => '3', qd_start_minor => '30'}
    }
},
lan => {
    ...
    ifaces => {
        'eth0'   => {addr => '192.168.0.0/24'}
        'ppp+'   => {addr => '192.168.1.0/24'}
    }
}
Как видно из примера, для туннеля адрес интерфейса не обязан совпадать с адресом сети - поэтому адрес сети не вычисляется на основании адреса интерфейса, а задается явно. На факт "подключена" сети lan к шлюзу gw указывает только наличие "общего" интерфейса ppp+. Фактически описывается через какие интерфейсы шлюза доступны те или иные сети.

Опции_потоков (ключ flows_options) представляет собой хэш в котором каждый элемент описывает параметры одного потока или группы потоков между данной сетью и другими сетями. Понятие потока смотрите в разделе Основные концепции: потоки, линки, цепочки, в частности рис. 3.

сеть№1 => {
    ifaces => {
        ...
        flows_options => {
            'сеть№2'           => параметры_потока,
            'сеть№3/ppp+/tun+' => параметры_потока,
            'сеть№4'           => параметры_потока,
            'сеть№4//eth3'     => параметры_потока,
            'сеть№5/ppp+'      => параметры_потока,
        }
    }
}

Так как правила и имена цепочек генерируются автоматически, то необходимо иметь некий мехнизм управления параметрами этой генерации - опции_потоков.
Основное использование опций потоков:

Ключ в опции потока идентифицирует поток или группу потоков согласно следующему синтаксису:
сеть_назначения/входящий_интерфейс/исходящий_интерфейс. Указание сети_назначения обязательно. Отсутствие в ключе интерфейса означает "любой интерфейс" из тех, что "связывает" две сети.

Пример для потоков INET->LAN на рис. 3. Их будет четыре: INET>eth1-eth0>LAN, INET>eth1-ppp+>LAN, INET>eth3-eth0>LAN, INET>eth1-ppp+>LAN. Смотрим, как можно указать эти потоки:

inet => {
    name = "INET",
    ...
    ifaces => {
        ...
        flows_options => {
(1)         'lan'              => параметры_потока,
(2)         'lan/eth1'         => параметры_потока,
(3)         'lan//ppp+'        => параметры_потока,
(4)         'lan/eth3/eth0'    => параметры_потока,
        }
    }
}

Вариант (1) задаст параметры для всех четырёх потоков. Вариант (2) задаст параметры для двух из четырёх потоков: INET>eth1-eth0>LAN, INET>eth1-ppp+>LAN. Вариант (3) задаст параметры для других двух потоков, у которых выходной интерфейс ppp+: INET>eth1-ppp+>LAN, INET>eth3-ppp+>LAN. Вариант (4) задаст параметры для только для одного потока: INET>eth3-eth0>LAN.

Параметры_потока представляют собой хэш из элементов со следующими возможными ключами:

Пример, чтобы было понятнее про параметр count.
В сети LAN есть хост с адресом 192.168.0.13 и ему разрешён доступ в сеть INET (Интернет). Для этого в цепочку потока 05_LAN_INET добавляется ALLOW-правило с адресом источника 192.168.0.13.

Теперь необходимо посчитать сколько данный хост получает трафика из сети Интернет (входящий трафик). Трафик к этому хосту из сети Интернет идет в обратном направлении и проверяется по цепочке 06_INET_LAN обратной для 05_LAN_INET. Естественно правила-счетчики для подсчёта необходимо вставить в цепочку 06_INET_LAN (а не 05_LAN_INET), да еще и адрес 192.168.0.13 указать в назначении, а не в источнике. Вот такие правила и генерируются при count => 'back'. Другими словами: "сгенерируй для адреса, которому мы разрешили выход в сеть, правила для подсчета трафика, получаемого им из этой сети".

Если необходимо подсчитать исходящий трафик для 192.168.0.13, то правила естественно надо размещать в 05_LAN_INET, т.е. в той же цепочке, где и само разрешающее правило. Для этого используется count => 'direct'.

И наконец, если для хоста 192.168.0.13 необходимо подсчитать и исходящий и входящий трафик, то ставим count => 'both' и счетчики для 192.168.0.13 генерируются для 192.168.0.13 в обоих цепочках: в 05_LAN_INET с src 192.168.0.13, а в 06_INET_LAN с dst 192.168.0.13.


Файл описания правил для хостов и сетей (аkа hosts.conf)

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

Например, можно использовать один файл net.conf, в котором будут описаны абсолютно все правила, можно использовать несколько файлов net.conf - по одному на сеть, можно сгруппировать правила в разные net.conf по отделам, кабинетам или по как-либо другим признакам.

С точки зрения синтаксиса данный файл также представляет собой Perl-скрипт, настройки в котором описаны в массиве @items. О плюсах и минусах такого подхода см. выше.


Далее описан синтаксис файла описания правил для хостов и сетей.
В качестве примера смотри файл /etc/hlf/conf.d/main/conf/lan.conf в исходниках.

Файл должен иметь синтаксис perl-скрипта. В файле должен быть один раз описан массив с зарезервированным именем @items. Каждый элемент массива @items описывает метаданные, правила фильтрации и управления трафиком для хоста или некоей логически связанной группы ip-адресов одной из сетей, описанных в networks.conf. Порядок элементов в @items важен и напрямую влияет на порядок генерируемых правил.

@items = (
    описание_хоста,
    описание_хоста,
    ...
    описание_хоста
}

Описание_хоста представляют собой хэш из элементов со следующими возможными ключами:

  • name, ou, ln, fn, mn ,email - позволяют задать наименование правила/хоста, организацию, фамилию, имя, отчество, почтовый адрес пользователя хоста. Данный элементы используются только для информационных целей и в отчетах и их значения никак не влияют на конфигурацию фаервола;
  • rules - в этом элементе задется список правил в виде массива;
  • ipv4 - задает ip-адрес по умолчанию для данного хоста;
  • net - задает сеть, которой принадлежит ipv4-адрес.
  • Все элементы являются необязательными.

    {name => 'boss',
        ou    => 'Organization',
        ln    => 'LastName',
        fn    => 'FirstName',
        mn    => 'MiddleName',
        email => 'box\@host.domain',
        rules => список_правил
    }
    

    Список_правил представляет собой массив, в котором каждый элемент описывает набор правил для некоего ip-адреса или группы ip-адресов.

    {name => 'boss',
        ou    => 'Organization',
        ln    => 'LastName',
        fn    => 'FirstName',
        mn    => 'MiddleName',
        email => 'box\@host.domain',
        rules => [
          набор_правил_№1,
          набор_правил_№2,
          ...
          набор_правил_№N
        ]
    }
    

    Набор_правил представляют собой хэш из элементов со следующими возможными ключами:

        rules => [
            {srcnet => 'lan', dstnet => 'inet', srcip => '192.168.0.10', name => 'workstation',
                allow => список_разрешающих_правил,
                deny  => список_запрещающих_правил,
                limit => список_темп_правил,
                speed => список_скоростных_правил,
                classify => список_правил_классификации
            },
        ]
    

    Некоторые пояснения. Так как каждый элемент в @items можно рассматривать как описание хоста, а хост может иметь неcколько адресов, то наборов_правил может быть несколько - каждый набор описывает правила для одного из адресов хоста.
    Зачем необходимо явно задавать сети (srcnet, dstnet) если есть адреса srcip, dstip, по которым их можно вычислить? Потому что могут быть правила в которых нет явных IP-адресов как показано ниже.

    {name  => 'icmp allow all',
        rules => [
            {srcnet => 'dmz', dstnet => 'inet',
                allow => [{proto => 'icmp'}]
            },
            {srcnet => 'inet', dstnet => 'dmz',
                allow => [{proto => 'icmp'}]
            },
            {srcnet => 'lan', dstnet => 'dmz',
                allow => [{proto => 'icmp'}]
            }
        ]
    },
    

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

    Кроме того, не для всех случаев достаточно указать адрес сети. Например сеть INET (Интернет) в используемом здесь подходе невозможно описать каким-то одним конкретным адресом сети или группой адресов. Для шлюза Интернет можно описать как все не private-сети известные шлюзу и в том числе не DMZ. "Известные шлюзу" фактически значит все сети, указанные в таблицах маршрутизации шлюза. Т.о. все сети, которые не входят в таблицы маршрутизации шлюза и будут Интернетом. В таком случае надо будет указывать целый список адресов, что не очень практично и реализуемо разве что при помощи ipset, который пока в данном фаерволе не поддерживается. Несмотря на то что сеть DMZ имеет публичные адреса её мы тоже не относим к сети Интернет только потому, что для шлюза это особенная сеть - она маршрутизируется через сам шлюз, а не находится "где-то там шлюзом по умолчанию".

    Список_разрешающих_правил представляет собой массив из хэшей, в котором каждый хэш описывает одно разрешающее правило:

        allow => [
            разрешающее_правило_№1,
            разрешающее_правило_№2,
            ...
            разрешающее_правило_№N
        ]
    
    Разрешающее_правило позволяет разрешить создание новых соединений, ограничить темп создания новых соединений и включить лимитирование логирование пакетов соединений, превысивших заданный темп. По сути каждое правило разворачивается в одну или две команды вида:
    iptables -A...{условие_соответствия} -m hashlimit ... -j ALLOW
    iptables -A...{условие_соответствия} -m limit ... -j LOG
    
    Разрешающее_правило представляет собой хэш в котором допустимы следующие ключи:

    Список_запрещающих_правил представляет собой массив из хэшей, где каждый хэш описывает одно запрещающее правило:

        deny => [
            запрещающее_правило_№1,
            запрещающее_правило_№2,
            ...
            запрещающее_правило_№N
        ]
    
    Запрещающее_правило позволяет запретить те или иные пакеты. Запрещающие правила приоритетнее разрешающих правил. Поэтому для запрещающих правил возможно задание исключений. Запрещающее_правило представляет собой хэш в котором допустимы следующие ключи: Список исключений из запрещающего правила представляет собой массив из хэшей, в котором каждый хэш описывает одно исключающее правило.

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

  • srcip - ip-адрес источника (см. --source);
  • dstip - ip-адрес назначения (см. --destination);
  • proto - протокол (см. --protocol);
  • dport - порт назначения (см. --dport);
  • Список_темп_правил представляет собой массив из хэшей, в котором каждый хэш описывает одно правило, ограничивающее кол-во новых соединений в единицу времени:

        limit => [
            темп_правило_№1,
            темп_правило_№2,
            ...
            темп_правило_№N
        ]
    
    Темп_правило позволяет ограничить темп создания новых соединений и включить лимитированное логирование пакетов соединений, превысивших заданный темп. По сути каждое правило разворачивается в три команды команды вида:
    iptables -A...{условие_соответствия} -m hashlimit ... -j RETURN
    iptables -A...{условие_соответствия} -m limit ... -j LOG
    iptables -A...{условие_соответствия} ... -j DROP
    
    Темп_правило представляет собой хэш в котором допустимы следующие ключи:

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

        speed => [
            скоростное_правило_№1,
            скоростное_правило_№2,
            ...
            скоростное_правило_№N
        ]
    
    Скоростное_правило представляет собой хэш в котором допустимы следующие ключи:

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

    Каждое скоростное правило вызывает генерацию одного классифицирущего правила для для таблицы mangle iptables и одного правила создания класса для команды tc.

    Список_правил_классификации представляет собой массив хэшей, в котором каждый хэш позволяет задать классифицирующее правило в формате комнады iptables или имя пользовательского perl-скрипта, который генерирует нужный набор правил. в восходящем или нисходящем направлениях:

        classify => [
            правило_классификации_№1,
            правило_классификации_№2,
            ...
            правило_классификации_№N
        ]
    
    Правило_классификации представляет собой хэш в котором допустимы следующие ключи:

    Конфигурация main

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

    Основная конфигурация, рассматриваемая в данном тексте называется main и расположена она соответственно в /FW/conf.d/main. В этом же каталоге, наряду с обязательным скриптом start расположены и другие вспомогательные скрипты, задающие некоторые наборы правил, перезапускающие firewall по частям и т.д. Конфигурационные же файлы, для удобства, вынесены в отдельный подкаталог /FW/conf.d/main/conf.

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

    Конфигурация main расположена в каталоге /FW/conf.d/main и состоит из скриптов и конфигурационных файлов, расположенных в каталоге /FW/conf.d/main/conf .

    Процесс загрузки конфигурация main

    После загрузки ядра система инициализации выполняет скрипты для текущего runlevel. Для запуска фаервола при загрузке ОС добавляем символическую ссылку на /FW/modules/fw-sysv, например в /etc/rc3.d, который собственно и запускает скрипт /FW/start. Так как при этом скрипту /FW/start никакого имени конфигурации не передается, то /FW/start запускает скрипт /FW/conf.d/default/start. При этом ссылка default указывает на /FW/conf.d/main.

    Perl-скрипт /FW/conf.d/main/start уже является специфичным для конфигурации main и через вызовы вспомогательных функций конфигурирует фаервол под конкретные задачи. Если вы захотите написать свою собственную конфигурацию, отличную по количеству сетей, интерфейсов, конфигурационных файлов, то в этот файл надо будет вносить изменения.

    В скрипте /FW/conf.d/main/start, после подготовительных действий и объявления переменных идет вызов функции check_confs из /FW/modules/firewall.pm. Эта функция просто проверят правильность синтаксиса всех perl-скриптов, находящихся в /FW/conf.d/main/conf/. Файлы проверяются только на соответствие синтаксису языка - никакой смысловой проверки не проводится.

    Далее выполняется функция init из того же firewall.pm, которая должна инициализировать конфигурацию интерфейсов шлюза и сетей, подключенных к ним. Для этого ей передается имя файла конфигурации сетей, в данном случае /FW/conf.d/main/conf/networks.conf. Так как подразумевается что это просто perl-скрипт (см. Файл, описывающий шлюз и сети), то данный файл просто выполняется как обычный perl-скрипт. Т.о. создается и инициализиурется хэш %networks.

    Следующим выполняется bash-скрипт из другой конфигурации /FW/conf.d/outbound_only/start, который загружает временный набор правил, позволяющих шлюзу делать исходящие соединения. Это необходимо, например, для разрешения DNS-имён.

    Затем запускается скрипт /FW/modules/wait_dns, который задерживает дальнейшее выполнение кода до момента, когда станет доступен DNS-сервер провайдера. Подразумевается, что доступность DNS-сервера провайдера означает, что xDSL-модем №1 загрузился и установил соединение. Это необходимо в случае если запуск фаервола происходит после сбоя питания и модем включился одновременно с шлюзом (шлюз загружается быстрее, чем модем устанавливает соединение). После этого становится возможным разрешение имён хостов и дальнейшее выполнение скрипта фаервола. Если после некоторого числа попыток DNS-сервер так и остается недоступным выполнение скрипта продолжается.

    Так как фаервол конфигурируется с использованием iptables-restore, любая ошибка, возникшая в момент загрузки набора правил, приведет к отмене загрузки данного набора правил. В том числе и при ошибках (невозможности) разрешения доменных имён, если таковые есть в правилах вместо IP адресов. Т.е. недоступность DNS-сервера или какие-то другие ошибки разрешения имён (ошибки в имени, например) в момент загрузки правил, приведут к тому, что фаервол не будет сконфигурирован как положено, что чревато нерабоспособностью шлюза. Поэтому лучше не использовать в правилах доменные имена вместо IP адресов.

    Далее bash-скрипт /FW/modules/load_modules загружает необходимые модули ядра, а /FW/conf.d/main/sysctl устанавливает значения некоторых переменных ядра, необходимые для данной конфигурации.

    И затем выполняется bash-скрипт /FW/conf.d/main/via_isp, который настраивает маршрутизацию через первого или второго провайдера, в зависимости от значения переменной в /FW/conf.d/main/conf/via_isp.conf.

    Вслед за этим четыре раза вызывается функция flow - для каждого из имеющихся conf-файлов, описывающих хосты и правила для них. В результате вызовов данной функции в массивы @filter_rules, @mangle_rules, @nat_rules, @raw_rules сгенерированными строчками команд для iptables-restore.

    Затем, с помощью функции add_rules_from_file к списку уже сгенерированных на данном этапе правил добавляются правила из файлов /FW/conf.d/main/conf/rules_nat_redirect, rules_nat_snat и rules_route. Правила в этих файлах написаны вручную.

    Функция gen_main_rules генерирует все правила переходов в цепочки потоков из встроенных цепочек INPUT, OUNPUT, FORWARD для таблиц filter и mangle.

    После этого из файла rules_main_extra добавляются правила, написаные вручную для цепочек INPUT, OUNPUT, FORWARD таблиц filter и mangle.

    Далее, в фукнции finalize генерируются правила DROP, добавляемые в конец каждой ALLOW-цепочки и правила переходов в DROP-цепочки,добавляемые в конец каждой SKIP-цепочки. Отдельно генерируются переходы в цепочки счетчиков *_CNT.

    К этому моменту полный набор сгенерированных правил iptables находится в массивах @filter_rules, @mangle_rules, @nat_rules, @raw_rules. Вызов функции load_rules загружает эти правила в ядро при помощи команды iptables-restore.

    Далее вызов bash-скрипта tc-ifb0 конифигурирует HTB_дисциплину и основную иерархию классов на интерфейсе ifb0, где происходит управление трафиком в нисходящем направлении. Вызовы tc-eth0 и tc-eth2 перенаправляют трафик с интерфейсов eth0 и eth2 на ifb0. Следом, аналогичная последовательность вызовов tc-ifb1, tc-eth1, tc-eth3 конфигурирует ifb1, где происходит управление трафиком в восходящем направлении.

    Ранее, при вызовах flow генерировались не только правила для iptables, но и в массив @tc_rules генерировались tc-команды индивидуальных ограничений для хостов сетей. Теперь функция load_tc_rules() выполняет эти команды в дополнение к командам предыдущего пункта.

    Последняя функция generate_save_counters по настройкам в networks.conf генерирует из шаблона /FW/modules/save_counters скрипт сохранения значения счетчиков /FW/conf.d/main/var/save_counters, который далее регулярно вызывается через cron.

    Подробнее о функциях firewall.pm

    Вопросы без ответов

    * В команде tc filter ... police ... flowid major:minor параметр flowid по документации является обязательным (хотя на практике оказывается его можно не указывать). Для egress задание flowid имеет смысл - задаем класс для трафика, который соответствует этому фильтру. Но фильтр то можно привязать и к ingress! В чём смысл flowid в фильтре, созданном в ingress !?

    * Major во всех командах для одной qdisc должен быть одинаковым?

    * После tc filter add dev eth0...action mirred egress redirect dev ifb0 весь трафик из egress(?) eth0 перенаправляется на ingress(?) ifb0. А какой интерфейс в этом случае является "выходным", например для iptables? По логике ifb0, хотя все таки, похоже eth0.

    * Если посмотреть в исходники tc (m_police.c) то там можно увидеть следующее недокументированное actions:

    ...
    else if (matches(arg, "shot") == 0)
      res = TC_POLICE_SHOT;
    

    В m_mirred.c можно увидеть следующее:

    fprintf(stderr, "Usage: mirred   [index INDEX]  \n");
    fprintf(stderr, "where: \n");
    fprintf(stderr, "\tDIRECTION := \n");
    fprintf(stderr, "\tACTION := \n");
    fprintf(stderr, "\tINDEX  is the specific policy instance id\n");
    fprintf(stderr, "\tDEVICENAME is the devicename \n"); 
    
    Как это работает (т.е. смысл) нигде недокументировано. Есть только заученная мантра 'tc filter add dev ... action mirred egress redirect dev ifb0' для использования одной дисциплины на IFB. Это что, можно "полисить" входящий трафик с нескольких интерфейсов одной дисциплиной? И это реально работает?

    * Безклассовая (согласно любой документации) дисциплина TBF однако имеет класс:

    tc qdisc add dev ifb0 root handle 4: tbf limit 1500 rate 160kbps burst 1600
    tc qdisc show dev ifb0
    tc class show dev ifb0 
    
    получаем:
    
    qdisc tbf 4: root refcnt 2 rate 1280Kbit burst 1600b lat 4295.0s 
    class tbf 4:1 parent 4:  
    
    Что означает наличие класса в безклассовой дисциплине - непонятно. Например прикрепить к TBF-классу дочерние дисциплины PRIO (как в примере http://blog.edseek.com/~jasonb/articles/traffic_shaping/scenarios.html#guarprio ) или HTB не получается. Прикрепить дочерний класс тоже непонятно можно ли, какой и зачем.

    TODO

    Текст

    - описать из-за stateful уже созданные соединения будут существовать несмотря на последующие запреты и их надо явно удалять (conntrack -D);
    - упомянуть почему в реализации INET_SKIP_OR_DENY идут до RELATED,ESTABLISHED;
    - возможность подсчета трафика с помощью conntrackd?;
    - упомянуть про поток типа LAN>GW>LAN (маршрутизация через шлюз в одну и ту же сеть);
    - Резервирование и переключение - разаобраться, что можно сделать;
    - Policing закончить. Использование policing для входяещего трафика не дало нужных результатов;

    Код

    - удаление возможных существующих соединений (conntrack -D) при отсутствии хотя бы одного разрешающего правила;
    - добавить возможность использования ipset. В штатном ядре Debian 7 уже есть ipset, так что можно начинать прикручивать;
    - --multiport;
    - рефакторинг длинных процедур?;
    - реализовать _default;
    - в случае отсутствия DNS явно не загружать правила с именами вместо адресов - надо ли?;
    - откат к предыдущему состоянию фаервола в случае неудачной загрузки команд iptables; необходимо так как правила для отдельных таблиц загружаются независимо и возможна загрузка в таблицу filter и затем ошибка при загрузке в mangle, но filter так и останется в новом состоянии

    Оптимизация

    - оптимизация цепочек. По возможности вставлять allow-правила прямо в цепочу потока;