Пример использования ng_nat, ng_netflow и ng_ipfw ОС FreeBSD 6.0.


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

В ОС FreeBSD достаточно давно существует система Netgraph. Что такое Netgraph, и почему это очень удобно и быстро можно выяснить из ссылок внизу. Относительно недавно (если я не ошибаюсь с версии 5.4) в базовую систему входит узел ng_netflow (4), способный получать информацию о проходящем через него трафике и экспортировать ее в виде совместимым с протоколом NetFlow фирмы Cisco, что дает возможность использовать достаточно большое число коллекторов поддерживающих этот протокол. Кроме того, одними из важных нововведений в FreeBSD 6.0 были усовершенствование библиотеки libalias(3), что дало возможность ее использования на уровне ядра, и реализация узла ng_nat (4) собственно и являющимся "ядерным" аналогом natd(8). Также появился узел ng_ipfw (4) - интерфейс между ipfw и системой netgraph.

Я рассмотрю вполне реальную задачу: шлюз в Internet, к которому подключены пользователи через интерфейсы ng* (например в случае mpd). У части клиентов реальные адреса, а у части "серые". Необходимо учитывать трафик от и к пользователям, выполнять преобразование адресов для пользователей с "серыми" адресами. Что бы жизнь не казалась медом, рассмотрим возможность подключение к Internet более чем через одного провайдера, каналы правда балансировать не будем, а ограничимся простой маршрутизацией по источнику.
192.168.11.42 netmask 255.255.255.0 - адрес первой сетевки смотрящей во внешний мир
192.168.255.1 netmask 255.255.255.0 - адрес второй "внешней" сетевки

192.168.11.1 - адрес первого шлюза
192.168.255.254 - адрес второго шлюза

10.0.0.0/16 и 10.100.0.0/16 "серые" подсети
192.168.11.0/24{43,211-254} - группа реальных адресов

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

В случае FreeBSD 6.0 задача решается довольно просто с помощью компонентов системы netgraph: нам понадобятся узлы ng_netflow для сбора сведений о трафике и ng_nat для преобразования адресов, ng_ipfw для взаимодействия с пакетным фильтром, также пригодится узел ng_split. Дело в том, что на сегодняшний момент (FreeBSD 6.0) ng_netflow, с точки зрения админа-обывателя, работает следующим образом: пакет, принятый на ifaceX, будет обработан и послан на соответствующий outX. Пакет, полученный на outX, будет оправлен на соответствующий ifaceX БЕЗ какой либо обработки. Для того чтобы "развернуть" все пакеты в одном направлении, т.е. чтобы и посланные и принятые системой пакеты всегда входили в netflow через ifaceX, я использовал ng_split. В сущности ng_split это "половинка ng_tee", но пакеты при этом не дублируются. Собственно говоря это все, что нужно в данной ситуации для счастья :). Все необходимые узлы являются частью базовой системы, осталось соединить их между собой. Если до сего момента у Вас не было времени ознакомиться с Netgraph, то лучше прямо сейчас перейти к статье "Все о Netgraph", тогда будет легче понять написанное ниже.

Подготавливаем систему:

Подгружаем основные модули:
kldload netgraph.ko
kldload ng_ipfw.ko

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

Для загрузки модулей во время старта системы нужно добавить следующие строки в /boot/loader.conf
netgraph_load="YES"
ng_ipfw_load="YES"

или собрать ядро со следующими опциями:
options NETGRAPH
options NETGRAPH_IPFW

options LIBALIAS
options NETGRAPH_NAT
options NETGRAPH_NETFLOW
options NETGRAPH_SPLIT
options NETGRAPH_KSOCKET

options NETGRAPH_SOCKET
options NETGRAPH_BPF
options NETGRAPH_IFACE
options NETGRAPH_MPPC_ENCRYPTION
options NETGRAPH_PPP
options NETGRAPH_PPTPGRE
options NETGRAPH_TCPMSS
options NETGRAPH_VJC

Первые две опции собственно нам нужны для начала работы, но при этом надо учитывать одну особенность NETGRAPH_IPFW: как говорит man 4 ng_ipfw, единственный на данный момент способ создать заново ноду ng_ipfw, это выгрузить и загрузить снова соответствующий модуль. Т.е. если сделать shutdown ipfw: в ngctl, а NETGRAPH_IPFW "вкомпилен" в ядро, то придется перезагружать ядро :) (поправьте меня если ошибаюсь).

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

Ну и кроме того на роуторе не помешает еще несколько опций:
options IPFIREWALL
options IPFIREWALL_FORWARD
options IPFIREWALL_FORWARD_EXTENDED

Строим необходимые цепочки в netgraph при помощи вот такого rc.d скрипта:

#!/bin/sh
. /etc/rc.subr

name="ngnat"
rcvar=`set_rcvar`
start_cmd="ngnat_start"
stop_cmd="ngnat_stop"
load_rc_config $name
eval "${rcvar}=${${rcvar}:-'NO'}"
ngnat_aliasaddr1=${ngnat_aliasaddr1:-"0.0.0.0"}
ngnat_aliasaddr2=${ngnat_aliasaddr2:-"0.0.0.0"}
ngnat_export=${ngnat_export:-"127.0.0.1:9999"}

ngnat_start() {
echo "Setup ng_nat and ng_netflow"
/usr/sbin/ngctl -f- <<-SEQ
mkpeer ipfw: netflow 1 iface0
name ipfw:1 netflow
mkpeer netflow: split out0 in
name netflow:out0 split1
mkpeer netflow: ksocket export inet/dgram/udp
msg netflow:export connect inet/$ngnat_export
connect split1: netflow: out iface1
connect ipfw: netflow: 4 out1
mkpeer split1: nat mixed out
name split1:mixed nat1
connect ipfw: nat1: 23 in
connect ipfw: netflow: 5 iface2
connect ipfw: netflow: 6 out2
msg nat1: setaliasaddr $ngnat_aliasaddr1
msg netflow: setdlt { iface=0 dlt=12 }
msg netflow: setifindex { iface=0 index=1000 }
msg netflow: setdlt { iface=1 dlt=12 }
msg netflow: setifindex { iface=1 index=1001 }
msg netflow: setdlt { iface=2 dlt=12 }
msg netflow: setifindex { iface=2 index=1002 }
connect ipfw: netflow: 7 iface3
mkpeer netflow: split out3 in
name netflow:out3 split2
mkpeer split2: nat mixed out
name split2:mixed nat2
connect ipfw: nat2: 89 in
connect split2: netflow: out iface4
connect ipfw: netflow: 40 out4
msg nat2: setaliasaddr $ngnat_aliasaddr2
msg netflow: setdlt { iface=3 dlt=12 }
msg netflow: setifindex { iface=3 index=1003 }
msg netflow: setdlt { iface=4 dlt=12 }
msg netflow: setifindex { iface=4 index=1004 }
SEQ
}

ngnat_stop() {
/usr/sbin/ngctl -f- <<-SEQ
shutdown nat1:
shutdown nat2:
shutdown split1:
shutdown split2:
shutdown netflow:
SEQ
}

run_rc_command "$1"

Скрип понимает команды start и stop. Сохраняем его в /usr/local/etc/rc.d/ngnat.sh, не забываем
# chmod 555 /usr/local/etc/rc.d/ngnat.sh

Пояснения:

Здесь и везде ниже я буду обозначать имена узлов как "имя:" т.е. так, как обозначаются абсолютные имена в netgraph. Например ipfw: будет означать ноду ng_ipfw, создающуюся при загрузке модуля ng_ipfw.ko, а ipfw - пакетный фильтр или программу управления фильтром в зависимости от контекста

Сама "схема" соединений довольно проста: я использовал единственный узел ng_netflow - netflow: и два узла ng_nat - nat1: и nat2: для преобразования адресов, как говорилось ранее, split1: и stplit2: "разворачивают" выходящие пакеты из хуков out узлов nat1: и nat2: на хуки iface1 и iface4 узла netflow: соответственно.

Хуки ipfw:
Хук 1 - пакеты полученные на этот крючок пройдут netflow: и попадут на nat1:, где над ними будет выполнена

операция преобразования адреса (masquerading), после чего пакеты вернутся в фильтр из крючка 23 ipfw: .
Хук 23 - адреса пакетов полученные на этот крючок будут будут преобразованы nat1 в "серые" адреса (dealiasing),

затем оправлены в netflow (iface1) и попадут обратно в пакетный фильтр через крюк 4.
Хук 5 - пакеты полученные на этот крючок пройдут netflow и попадут назад в пакетный фильтр через крюк 6.
Хук 7 - идентичен хуку 1, за тем исключением, что пакеты будут обрабатываться узлом nat2: а не nat1:

и пакеты будут возвращаться в пакетный фильтр через хук 89.
Хук 89 - пакеты полученные на это крюк пройдут операцию dealiasing в ноде nat2: и попадут в пакетный фильтр через крюк 40.

Вроде как все. Добавляем в /etc/rc.d следующие строчки:
ngnat_enable="YES"
ngnat_aliasaddr1="192.168.11.42"
ngnat_aliasaddr2="192.168.255.1"
ngnat_export="127.0.0.1:9996"

ngnat_aliasaddr1 и ngnat_aliasaddr2 - адрес, в который будет "сворачиваться" приватная сеть
ngnat_export - куда мы будем оправлять информацию от трафике
Все переменные выставлены, необходимые модули подгружены - пробуем запустить наш скрипт:
/usr/local/etc/rc.d/ngnat.sh start

Если все завершилось без ошибок убеждаемся, что нужная нам "цепь" действительно собралась в netgraph - воспользуемся ngctl (здесь "+" - приглашение ngctl ):
# ngctl
+ ls
There are 8 total nodes:
Name: ngctl2061 Type: socket ID: 00000077 Num hooks: 0
Name: nat2 Type: nat ID: 00000076 Num hooks: 2
Name: split2 Type: split ID: 00000075 Num hooks: 3
Name: nat1 Type: nat ID: 00000074 Num hooks: 2
Name: <unnamed> Type: ksocket ID: 00000073 Num hooks: 1
Name: split1 Type: split ID: 00000072 Num hooks: 3
Name: netflow Type: netflow ID: 00000071 Num hooks: 11
Name: ipfw Type: ipfw ID: 00000003 Num hooks: 8
+
+ show netflow:
Name: netflow Type: netflow ID: 00000071 Num hooks: 11
Local hook Peer name Peer type Peer ID Peer hook
---------- --------- --------- ------- ---------
out4 ipfw ipfw 00000003 40
iface4 split2 split 00000075 out
out3 split2 split 00000075 in
iface3 ipfw ipfw 00000003 7
out2 ipfw ipfw 00000003 6
iface2 ipfw ipfw 00000003 5
out1 ipfw ipfw 00000003 4
iface1 split1 split 00000072 out
export <unnamed> ksocket 00000073 inet/dgram/udp
out0 split1 split 00000072 in
iface0 ipfw ipfw 00000003 1
+

Если вывод такой, то все нормально, смело выходим из утилиты.

Теперь все готово, осталось только написать необходимые правила в ipfw. Трафик от серых сетей мы загоняем в хук 1 или 7, трафик от сети с реальными адресами загоняем в хук 5.

rc.conf может быть примерно таким:
gateway_enable="YES"

defaultrouter="192.168.11.42"

ifconfig_fxp0_name="inet1"
ifconfig_fxp1_name="inet2"
ifconfig_inet1="inet 192.168.11.42 netmask 255.255.255.0 link0"
ifconfig_inet2="inet 192.168.255.1 netmask 255.255.255.0 link0"
ifconfig_fxp2="inet 192.168.254.1 netmask 255.255.255.0 link0"

firewall_enable="YES"
firewall_script="/etc/fw_nat.sh"

ngnat_enable="yes"
ngnat_aliasaddr1="192.168.11.42"
ngnat_aliasaddr2="192.168.255.1"
ngnat_export="127.0.0.1:9996"

Чтобы было меньше путаницы, обозвал два внешних интерфейса inet1 и inet2.

сам /etc/fw_nat.sh может быть таким:
#!/bin/sh
sysctl net.inet.ip.fw.one_pass=0
ipfw="/sbin/ipfw"

real="192.168.11.0/24{43,211-254}"
# группа реальных адресов

vpn1="10.0.0.0/16"
vpn2="10.100.0.0/16"
# "серые" подсети

inet1="192.18.11.0/24"
inet2="192.168.255.0/24"
#подсеть интерфейса inet1 и inet2

aliasaddr1="192.168.11.42"
aliasaddr2="192.168.255.1"
#адреса NAT

gate1="192.168.11.1"
gate2="192.168.255.254"
#шлюзы во внешний мир

${ipfw} -f flush

${ipfw} add netgraph 5 all from ${real} to any via "ng*" in
${ipfw} add fwd ${gate1} all from ${real} to not ${inet1}
#заворачиваем трафик от реальных адресов в netgraph
#вышедшие из netgraph пакеты оправляем на роутер

${ipfw} add netgraph 1 all from ${vpn1} to any via "ng*" in
${ipfw} add fwd ${gate1} all from ${aliasaddr1} to not ${inet1}
#тоже самое, но пакеты попавшие в хук 1 проходять процедуру
#преобразования адреса в nat1:

${ipfw} add netgraph 7 all from ${vpn2} to any via "ng*" in
${ipfw} add fwd ${gate2} all from ${aliasaddr2} to not ${inet2}
#аналогично двум правилам выше, но преобразование пакетов
#производится в nat2:

${ipfw} add netgraph 23 all from any to any via inet1 in
#загоняем пришедшие пакеты в nat1:

${ipfw} add netgraph 89 all from any to any via inet2 in
#тоже самое для nat2:

${ipfw} add allow all from any to any


Ну вроде как все. Проверить работу системы можно с помощью любого netflow-коллектора например с помощью /usr/ports/net-mgmt/flow-tools/. Замечу еще, что пакеты при таком подходе нигде не дублируются ни в Netgraph ни в ipfw.

Однако во многих случаях использовать ng_ipfw не обязательно. ng_netflow можно подключить непосредственно к интерфейсу. Как это сделать очень хорошо описано в работе "12000 слов о netgraph, ng_netflow и FreeBSD". Помните только, что у современной реализации ng_netflow может быть большое число хуков, и использовать узел ng_one2many не нужно. Так же следует заметить, что современные версии mpd (4.X 5.X) имеют встроенную поддержку ng_netflow, так что если нужно только считать трафик использовать ng_ipfw нет необходимости.

http://wiki.bsdportal.ru/doc:netgraph_ng_nat

Обновлено: 12.03.2015