Изучаем интересные возможности сетевой подсистемы Netgraph
Сергей «grinder» Яремчук (grinder@ua.fm, )
Стандартные настройки сети в FreeBSD обычно не вызывают особых проблем. В интернет полно документации, где процесс конфигурирования описан вдоль и поперек. Однако при объединении нескольких сетей в одну, подсчете трафика и балансировке нагрузки придется изрядно попотеть. Но есть элегантный выход — использование netgraph.
Содержание:
- Введение
- Поддержка ядром
- Виртуальный свич
- Соединение нескольких сетей
- Подсчет трафика
- Балансировка нагрузки
- Заключение
- Боковые выносы
Сетевая подсистема netgraph была создана в 1996 году Джулианом Элисчером и Арчи Коббсом, как попытка решить ограниченную поддержку некоторого оборудования и протоколов в FreeBSD. Первые версии были доступны в специальной версии FreeBSD 2.2, собираемой для компании Whistle InterJet, а дебют в основном дереве состоялся в FreeBSD 3.4. Сегодня netgraph является частью ядра ОС. Основная идея этой подсистемы – модульность, комбинация простых инструментов для реализации более сложного решения.
Netgraph строится на взаимодействии узлов (nodes), которые посредством крючков (hooks) создают пару захвата (одну для каждого узла). Крючок узла формируется в момент связывания и определяет, как узел может быть подключен. Данные идут в двух направлениях вдоль ребер (edges) от узла к узлу. Когда узел получает пакет данных, он его обрабатывает и затем отправляет другому узлу. Узел может быть источником/потребителем данных, например, если он связан с аппаратной частью, или может просто добавлять/удалять заголовки, выполнять мультиплексирование и т.п.
Поначалу количество узлов было небольшим, но с каждой новой версией системы их количество увеличивалось. Так для FreeBSD 6.х уже были доступны модули, обеспечивающие поддержку: PPPoE, ATM, ISDN, Bluetooth, HDLC, EtherChannel, Frame Relay, L2TP и др. Система построена так, что добавить новый модуль очень легко. В новой версии FreeBSD 7.0 появились новые узлы: ng_car (алгоритмы ограничения трафика и rate-лимитов), ng_deflate (поддержка Deflate сжатия для PPP) и ng_pred1 (Predictor-1 сжатие для PPP).
Многие программы завязаны на netgraph. Например, реализация PPP для FreeBSD использует интерфейс netgraph, благодаря чему большинство операций выполняется на уровне ядра системы, и тем самым повышается скорость работы.
Не смотря на то, что сегодня насчитывается уже шесть десятков узлов, нормальная документация по использованию netgraph отсутствует. Можно отметить несколько устаревший документ «All about Netgraph» (русский перевод находится по адресу ) от разработчиков netgraph. В нем рассказано об общих принципах работы и дано несколько примеров, но его чтение хоть и дает некоторое понимание процесса, однако в конкретной ситуации помочь не сможет. В итоге остается справочная страница netgraph(4) и связанные с ним man’ы, описывающие особенности конкретного узла, плюс несколько примеров в /usr/share/examples/netgraph.
Как правило, специально включать поддержку netgraph в ядре не требуется. Все, что необходимо, доступно в виде модулей. В чем можно убедиться во время работы того же MPD при помощи команды /sbin/kldstat. Просмотреть все имеющиеся KLD модули узлов, можно введя команду:
# ls /boot/kernel/ng_*.ko
Плюс еще модуль netgraph.ko, который является основным. При пересборке ядра следует включить группу параметров, найти которую просто:
# grep NETGRAPH /usr/src/sys/conf/NOTES
options NETGRAPH # поддержка netgraph
options NETGRAPH_PPP # поддержка PPP в netgraph
options NETGRAPH_PPTPGRE # поддержка gre-туннелирования
…
Каждый узел netgraph может адресоваться при помощи абсолютного или относительного адреса. Абсолютный адрес получается из имени узла или его ID. Если узел сопоставлен с устройством, то он обычно имеет такое же имя, как и устройство. В этом случае для адресации используется адрес, состоящий из имени устройства и двоеточия. Например, устройству fxp0 будет сопоставлен узел fxp0, к которому следует обращаться, введя «fxp0:». К имени узла можно добавлять связанные крючки:
fxp0:hook1.hook2
Адрес вроде «.:» или «.» всегда показывает на локальный узел. Как в имени хука, так и в имени узла использовать знаки точки и двоеточия по понятным причинам запрещено. Если узел не получил имя, к нему можно обратиться, используя его уникальный номер ID, заключенный в квадратные скобки. Например, к ID 00000002 можно обратиться, используя «[00000002]:».
Каждый хук также имеет уникальное имя, отражающее его цель и использующееся только в контексте данного узла. Поэтому на разных узлах можно создавать крючки с одинаковым названием. Для некоторых типов (например, ksockets) формат имени не может быть произвольным и строго определен. Относительный адрес состоит только из имен хуков.
Еще один важный момент. На сегодня существует 15 типов узлов, имеющих свои характеристики и соответственно назначения. Изучив особенности каждого типа, легко выбрать узел для решения конкретной задачи. Например, тип узла echo всегда принимает подключения через любой хук. Полученный пакет просто отправляется назад в виде ответа. Такой тип узла полезен при отладке. Теперь смотрим описание узла ng_ether — «Ethernet netgraph node type». Тип Ethernet означает, что данный узел фактически является netgraph-копией физического интерфейса Ethernet с возможностью перехватывать и отправлять фреймы с этого интерфейса. Его можно использовать, например, для реализации PPP поверх Ethernet (PPPoE). Узел состоит из трех хуков: lower (или divert), upper и orphans, которые отвечают за определенный уровень работы. Или вот еще узел ng_bridge – «Ethernet bridging netgraph node type». Не трудно догадаться, что этот тип позволяет организовать сетевой мост на Ethernet интерфейсе.
Каждый тип и конкретный модуль может иметь большое количество параметров и настроек, поэтому реализовано управление при помощи специальных команд. Такие управляющие сообщения делятся на основные, поддерживаемые всеми модулями (определены в netgraph/ng_message.h), и индивидуальные, полное их описание есть только в конкретном man (кстати, иногда недостоверное).
Раз уже начали с узла ng_bridge, то разберем подробнее его в работе, соединив вместе несколько Ethernet сетей при помощи виртуального свича. В /usr/share/examples/netgraph/ether.bridge находится скрипт, который можно использовать в качестве примера, а после некоторой доводки и для организации подобной функции. Стоит отметить, если настройки выполняются удаленно при помощи SSH, то после выполнения ряда команд соединение может прерваться.
Для управления netgraph применяются две утилиты: ngctl и nghook. Первая более функциональна, поэтому в дальнейшем будем использовать именно ее. Для начала проверим, какие узлы уже созданы в системе:
# ngctl list
There are 1 total nodes:
Name: ngctl850 Type: socket ID: 00000001 Num hooks: 0
В списке нужных нам нет, поэтому загружаем модули вручную:
# kldload -v ng_ether
Loaded ng_ether, id=5
# kldload -v ng_bridge
Loaded ng_bridge, id=6
Проверяем:
# kldstat
Id Refs Address Size Name
…
3 1 0xc3574000 4000 ng_socket.ko
4 3 0xc3578000 b000 netgraph.ko
5 1 0xc35ad000 4000 ng_ether.ko
6 1 0xc35b1000 4000 ng_bridge.ko
Для их автоматической загрузки добавляем в /boot/loader.conf следующие строки:
$ sudo vi /boot/loader.conf
netgraph_load=»YES»
ng_ether_load=»YES»
ng_bridge_load=»YES»
Теперь смотрим список узлов еще раз:
# ngctl list
There are 3 total nodes:
Name: ngctl863 Type: socket ID: 00000004 Num hooks: 0
Name: le1 Type: ether ID: 00000003 Num hooks: 0
Name: le0 Type: ether ID: 00000002 Num hooks: 0
Если ввести «ifconfig | grep le», то обнаружим, что имена Ethernet-интерфейсов совпадают именем nodes, то есть le0 и le1. Создаем бридж и подключаем его хуком link0 к le0 на нижнем (lower) уровне.
# ngctl mkpeer le0: bridge lower link0
Параметр name команды ngctl позволяет дать имя созданному узлу:
# ngctl name le0:lower bnet0
Обрати внимание, что к узлу обращаемся по имени «le0:lower», а к созданному узлу bnet0 — «bnet0:». Вывод «ngctl list» должен показать наличие нового узла с именем bnet0 и типом bridge. Собираем все в кучу (le0 и link0 использовать нельзя, так как они уже задействованы):
# ngctl connect le1: bnet0: lower link1
Аналогично, используя крючки link2 и link3, соединяем le* с bnet0 по верхнему уровню:
# ngctl connect le0: bnet0: upper link2 # ngctl connect le1: bnet0: upper link3
Активируем на сетевых картах неразборчивый режим, отправив сообщение setpromisc с любым не нулевым значением (все сообщения описаны в ng_ether(4)):
# ngctl msg le0: setpromisc 1 # ngctl msg le1: setpromisc 1
Запрещаем модификацию исходного Ethernet адреса принятого фрейма, иначе исходный адрес будет переписан на адрес интерфейса, который его получил:
# ngctl msg le0: setautosrc 0 # ngctl msg le1: setautosrc 0
Мост готов. Для удобства все эти команды следует записать в файл, который выполняется во время загрузки системы. При желании также можно обеспечить фильтрацию пакетов. Для этого в netgraph есть два модуля: ng_bpf и ng_ipfw, реализующие возможности соответствующих пакетных фильтров.
В предыдущем примере мы получили аналог стандартного «bridge_load=»YES»". На вид все кажется сложнее, но возможности значительно шире. Например, попробуй в стандартном случае добавить (при помощи net.link.ether.bridge.config) третий Ethernet интерфейс или соединить удаленные сети в одну виртуальную, и ты столкнешься с кучей проблем. Может быть даже придется прибегнуть к построению целой схемы использования виртуальных интерфейсов, VPN сетей и прочее.
При использовании netgraph для подключения третьего Ethernet достаточно добавить несколько строчек, взяв за образец подключение le1 в нашем примере. Если же необходимо подключить удаленную сеть в единый виртуальный свич, то порядок чуть отличается. Как раз в этом случае применение netgraph уже не покажется таким сложным (возможно, несколько запутанным, но это только на первый взгляд).
Настроим узел ksocket (kernel socket), способный оперировать UDP туннелями, и подключим его к созданному ранее узлу bnet0 (модуль ядра ng_ksocket в этом случае стартует автоматически). Имя хука должно быть вида «family/type/proto» (значения параметров доступны в socket(2)):
# ngctl mkpeer bnet0: ksocket link4 inet/dgram/udp
Отправляем сообщение узлу bnet0:link4 (к которому подключен ksocket), чтобы он создал сокет, куда будут поступать данные с внешнего IP-адреса (пусть адрес будет 1.2.3.4, порт выберем произвольный выше 1024):
# ngctl msg bnet0:link4 bind inet/1.2.3.4:1111
На другом сервере повторяем эти настройки, только указываем свой IP-адрес (имена могут быть другими):
server2# ngctl msg bnet0:link0 bind inet/2.3.4.5:2222
И теперь подключаемся к удаленному сокету:
# ngctl msg bnet0:link4 connect inet/2.3.4.5:2222 server2# ngctl msg bnet0:link0 connect inet/1.2.3.4:1111
Теперь локальные и удаленная сеть соединены в одну при помощи виртуального свича, причем без использования VPN.
К netgraph часто обращаются именно из-за необходимости подсчета трафика. Получать статистику можно несколькими способами, задействуя различные модули. Например, «врежем» модуль ng_tee в le0 между lower и upper уровнями Ethernet-интерфейса. Этот модуль имеет такую же функциональность, как и стандартная утилита tee, дублирующая ввод. Вручную загружать его не требуется, ядро это делает автоматически при первом обращении к нему. Крючки right и left являются стандартными для этого модуля и показывают, с какой стороны мы его подключаем (подробности в «man 4 ng_tee»).
# ngctl mkpeer le0: tee lower right # ngctl name le0:lower le0_tee # ngctl connect le0: lower upper left
Проверяем настройки:
# ngctl show le0:
Name: le0 Type: ether ID: 00000003 Num hooks: 2
Local hook Peer name Peer type Peer ID Peer hook
———- ——— ——— ——- ———
upper le0_tee tee 00000006 left
lower le0_tee tee 00000006 right
Чтобы получить статистику, нужно отправить сообщение getstats:
# ngctl msg le0:lower getstats # ngctl msg le0:upper getstats
Более подробную информацию о проходящем трафике способен показать узел ng_netflow(4). Итоговая информация экспортируется в виде, совместимом с популярным протоколом NetFlow компании Cisco. Поэтому для ее анализа можно использовать любой коллектор, поддерживающий этот протокол. Кстати, кроме ng_netflow есть еще ng_ipacct, но он не входит в стандартную поставку, а последняя версия на сайте датирована декабрем 2006 года, поэтому не будем на нем останавливаться.
Существует несколько схем подключения ng_netflow, мы разберем только одну из них. Начинаем:
# ngctl mkpeer le0: tee lower left # ngctl connect le0: le0:lower upper right
Создаем many-узел при помощи ng_one2many (подробнее об этом типе узла читай ниже, в разделе «Балансировка нагрузки») и подключаемся к нему:
# ngctl mkpeer le0:lower one2many left2right many0 # ngctl connect le0:lower.left2right right2left many1
Даем имя o2m узлу le0:lower.right2left:
# ngctl name le0:lower.right2left o2m
Создаем на o2m узел типа netflow с именем netflow:
# ngctl mkpeer o2m: netflow one iface0 # ngctl name o2m:one netflow
И узел ksocket на «netflow:» для экспорта:
# ngctl mkpeer netflow: ksocket export inet/dgram/udp # ngctl msg netflow:export connect inet/192.168.1.100:1111
Теперь для проверки вводим «ngctl list». Если модули загружены, запрашиваем таблицу подключений netflow:
# ngctl show netflow
Далее устанавливаем одну из утилит-коллекторов. Список некоторых коллекторов приведен на странице . Поиск в портах FreeBSD также дает нужный результат:
$ cd /usr/ports/ $ make search key=netflow
Например, выбираем flow-tools:
# cd /usr/ports/net-mgmt/flow-tools # make install
Захватываем пакеты с созданного сокета (подробная документация о flow-tools есть на сайте :
# mkdir /var/netflows # /usr/local/bin/flow-capture -p /var/run/flow-capture.pid -n 24 -N 0 -w /var/netflows/ -S 0/192.168.1.100/1111
После этого в каталог /var/netflows будет складываться захваченная статистика, каждый час будет создаваться новый файл (-n 24). Файлы имеют бинарный формат, поэтому для просмотра используем специальные утилиты из комплекта flow-tools. Ключ
# /usr/local/bin/flow-cat -p /var/netflows/ | /usr/local/bin/flow-stat -f10 -S4 -P
Стоит упомянуть еще об одном интересном модуле — ng_one2many. Этот узел позволяет объединить несколько интерфейсов по принципу «один ко многим» (или многие к одному). На один из интерфейсов, который объявляется как one, перенаправляются все many-интерфейсы, входящие пакеты собираются в one. Подробности его настройки с примером приведены на man-странице ng_one2many(4), перевод лежит на .
С помощью ng_one2many можно объединить несколько сетевых интерфейсов (например подключенных к разным провайдерам) в один узел, трафик будет равномерно распределяться между интерфейсами. Для распределения нагрузки используется round-robin алгоритм, как вариант, предлагается дублирование пакетов от one на все many-интерфейсы. Следует отметить, пока он еще не достаточно хорошо определяет работоспособность канала, поэтому его следует использовать с осторожностью.
Объявляем le0 как one:
# ngctl mkpeer le0: one2many upper one # ngctl connect le0: le0:upper lower many0 # ngctl connect le1: le0:upper lower many1
И так далее все остальные интерфейсы. Переводим их в promiscuous и запрещаем модификацию:
# ngctl msg le1: setpromisc 1 # ngctl msg le1: setautosrc 0
Настраиваем все many интерфейсы, значение «1″ должно соответствовать числу интерфейсов:
# ngctl msg fxp0:upper setconfig "{ xmitAlg=1 failAlg=1
enabledLinks=[ 1 1 ] }"
И поднимаем le0:
# ifconfig fxp0 192.168.1.1 netmask 0xfffffffc
С этого момента ширина полосы, обеспечиваемая виртуальным интерфейсом, увеличилась.
Наличие большого количества модулей netgraph позволяет реализовывать действительно уникальные функции, складывая узлы как мозаику в любой комбинации, врезая при необходимости новые узлы или делая ответвления. Но, к сожалению, отсутствие полноценной документации существенно усложняет изучение netgraph.
INFO
-
Сегодня для netgraph существует около 60 готовых модулей, буквально на все случаи жизни.
-
Кроме FreeBSD, netgraph портирован в , есть коммерческий порт и для .
WWW
-
В качестве отправной точки для изучения netgraph следует использовать документ «All about Netgraph», перевод которого доступен по адресу .
-
Перевод man ng_one2many и некоторые примеры найдешь на сайте .
-
Подробная документация об утилитах, входящих в состав flow-tools, есть на сайте .
WARNING
-
При удаленной настройке некоторых узлов netgraph следует быть осторожным, так как соединение может прерваться.
-
При пересборке ядра не забудь включить параметры, начинающиеся с NETGRAPH.
Статья опубликована в сентябрьском номере журнала «Xakep» за 2008 год.





