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

Опубликовано admin в Сб, 23/05/2009 - 19:15

Статья взята с wiki.bsdportal.ru (_http://wiki.bsdportal.ru/doc:netgraph_ng_nat).

Довольно часто на разнообразных “сетевых” форумах всплывает тема учета трафика и сбора статистики. Причем в качестве самого сенсора используют много разных программ, но как правило, по своей природе они ненадежны и (или) достаточно требовательны к ресурсам. Т.к. вполне логично пользовать плюсами, а не недостатками конкретной ОС рассмотрим возможность получения информации о трафике в ОС 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 нет необходимости.

Ссылки:

man 4 ng_netflow

man 4 ng_nat

man 4 ng_split

man 4 ng_ipfw

man 8 ipfw

“Все о Netgraph” http://citrin.ru/netgraph/ (_http://citrin.ru/netgraph/)

“12000 слов о netgraph, ng_netflow и FreeBSD” http://www.unix.lviv.ua/index_rus.html?art/nf.html (_http://www.unix.lviv.ua/index_rus.html?art/nf.html)







“A practical guide to BSD rc.d scripting” http://people.freebsd.org/~yar/rcng/article.html (_http://people.freebsd.org/~yar/rcng/article.html)

“MPD: FreeBSD PPP daemon” http://sourceforge.net/projects/mpd/ (_http://sourceforge.net/projects/mpd/)