Краткий обзор ядерного NAT-а в FreeBSD


Распространенных вариантов NAT-а под FreeBSD есть довольно много. Это и natd, ipnat, pfnat, ng_nat либо как вариант «купи ASA 5550 и не выделывайся».


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

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



Для начала объясню, почему именно ipfw nat
Во-первых, потому, что я сторонник простых и очевидных решений, поддерживаемость которых возможна практически с первого взгляда без длительных медитаций и размышлений на тему «а как это работает? Оо». К сожалению, я не могу отнести к таковым нетривиальные гибриды вида ipfw+pfnat.
Во-вторых документации по ng_nat и pfnat (nat on intif from somenet to any -> extif – Ы?) уже достаточно много.
В-третьих я использую ipfw, и люблю его – за простоту, наглядность, предсказуемость, dummynet а гибриды вида ipfw+pf+altq… в общем смотрим «Во-первых».

Матчасть.
Рассматривать ipfw nat мы будем на примере сферической конеобразной сети в вакууме изображенной чуть выше. Собственно по схемке все довольно понятно – оговоримся сразу что НАТить мы будем сеть 172.16.0.0/21, на em1 висит адрес, ну скажем 8.8.8.8 а внутренние ресурсы сети к которым нам не нужно осуществлять трансляцию адресов – такие как например, архивы порно корпоративные почтовые сервера и прочие нужные штуки будут жить с адресами скажем 8.8.8.9, 8.8.8.10
Функционал ipfw nat практически полностью дублирует функционал libalias, которую также используют natd, ng_nat.
Если очень грубо самоцель NAT-сервера сводится к подмене src-ip в пакетах, пришедших на внутренний интерфейс (в нашем случае em0) на адрес исходящего интерфейса (в нашем примере em1) и внесении в свою таблицу соответствий данных для обратного преобразования по приходу нужных пакетов в ответ. Далее все отрихтованые соответствующим образом пакеты улетают либо по дефолтрауту ну либо куда вам нужно (если, допустим, наворотили PBR).

Итого в базовом варианте нам нужно следующее

Перебрать ядро с поддержкой ipfw, ipfw nat, libalias ну и дальше по вкусу

# cd /sys/i386/conf/

# cp GENERIC NAT

# vim NAT



После чего вставляем нечто похожее на это:

ident NAT

options IPFIREWALL

options IPFIREWALL_DEFAULT_TO_ACCEPT

options IPFIREWALL_FORWARD

options IPFIREWALL_VERBOSE

options IPFIREWALL_VERBOSE_LIMIT=50

options IPFIREWALL_NAT

options LIBALIAS

options ROUTETABLES=2

options DUMMYNET

options HZ="1000"



Собираем и устанавливаем новое ядро предусмотрительно забекапив старое

# cp -R /boot/kernel /boot/good

# config NAT

# cd ../compile/NAT

# make depend

# make

# make install



В /etc/rc.conf добавляем
firewall_enable="YES"

firewall_nat_enable="YES"

dummynet_enable="YES"

firewall_type="/etc/firewall"



Что как бы должно намекать на то, что инициализацию ipfw мы будем проводить при помощи /etc/firewall который хочется чтобы в простейшем случае выглядел, например, так:

# наша сеть которую нужно выпустить в интернет

table 2 add 172.16.0.0/21



# внутренние ресурсы которые нету смысла выпускать сквозь nat

table 9 add 8.8.8.8

table 9 add 8.8.8.9

table 9 add 8.8.8.10



#следующие три строчки собственно и есть nat =)

nat 1 config log if em1 reset same_ports

add 1200 nat 1 ip from table(2) to not table(9) via em1

add 1300 nat 1 ip from any to 8.8.8.8 via em1



Для успокоения совести можно сделать еще в /etc/sysctl.conf что-то типа

net.inet.ip.fw.one_pass=1

net.inet.ip.fastforwarding=1

net.inet.tcp.maxtcptw=40960

kern.ipc.somaxconn=4096

kern.ipc.nmbclusters=65536

net.inet.tcp.nolocaltimewait=1

net.inet.ip.portrange.randomized=0



После перезагрузки все должно бы заработать. Проверяется работоспособность этого всего очень просто методом

ipfw nat 1 show

nat 1: icmp=271, udp=45462, tcp=61534, sctp=0, pptp=0, proto=1, frag_id=2 frag_ptr=0 / tot=107270



Вот такую вот скудную статистику в сравнении с pfctl –sa, при помощи параметра log собирает ipfw nat.

Кратко пробежимся по опциям скудные знания о которых мы можем почерпнуть из man:

if — интерфейс на котором будет производиться трансляция адресов
log — включает логирование «исчерпывающей» статистики по внутренним соединениям которую мы уже видели (ipfw nat1 show)
reset — намекает на то что при изменении интерфейса следует очищать внутреннюю табличку
deny_in — запрещает пропускать пакеты извне для которых не найдено соответствий во внутренней табличке.
same_ports — по возможности оставлять оригинальные порты в исходящих пакетах (рекомендуется включить)
unreg_only — проводить маскировку только пакетов пришедших из «нереальных» сетей.
redirect_addr intip extip — предписывает заворачивать трафик приходящий на extip к машине с intip. Сходным образом работают опции redirect_port и redirect_proto полезные для «вынесения» за NAT некоторых машин с нужными сервисами.

Собственно экземпляров nat мы можем наплодить сколько угодно и на каких угодно интерфейсах/ip (ipfw nat 2 config log if em2 reset same_ports, например) и заворачивать в них трафик как нам вздумается.
Память которую ipfw nat использует в своей работе является памятью доступной для ядра и смотрится при помощи
# sysctl -a | grep kmem

vm.kmem_size_scale: 3

vm.kmem_size_max: 335544320

vm.kmem_size_min: 0

vm.kmem_size: 335544320



Крутиться оная при помощи опции ядра KVA_PAGES (максимум 512 что соответствует 2Гб для i386).

Как-то субъективно, при хорошенькой нагрузке на канале (у меня вылазит при >350-400Mbit/s) может требоваться периодический рестарт экземпляра nat-а, в целях высвобождения памяти, которую съедают таблички оного. Думаю, собака порылась в медленных механизмах отстреливания просроченных сессий. Рестарт можно делать элементарным скриптиком вида:

#!/bin/sh

/sbin/ipfw nat 1 delete && /sbin/ipfw nat 1 config log if em1 reset same_ports



Также как водиться все наступают на грабли с тем, что сам по себе libalias категорически не дружит с аппаратными считалками контрольных сумм а также с tcp segmentation offload, засим конфигурить карточки изначально рекомендую похожим образом:

cat /etc/rc.conf | grep ifconfig

ifconfig_em1="inet 8.8.8.8 netmask 255.255.255.0 -rxcsum -txcsum -tso"



Вот собственно и все. Работает все прозрачно, не имеет изначально болезней с сервисами типа pptp, ftp (pfnat) и не страдает удручающей медлительностью за счет переключений в юзерспейс (natd). Единственное чего действительно может не хватать – это нормального способа nat-ить из под пула адресов. Единственное что в этом случае приходит в голову это наплодить множество экземпляров на алиасах, но выглядит, это не так красиво как скажем у pfnat.

Как-то сумбурно получилось но надеюсь наглядно.

На тему следующей статьи напрашивается вопрос – а кому-то было бы интересно увидеть пошаговое руководство по установке биллинга с разрулом трафика на удаленных NAS-ах и аккаунтингом на netflow? Ну собственно как решения свободно масштабируемого по горизонтали в отличии от всего того что я описывал в предыдущих статьях? Или написать о чем-то менее узко специфичном?

P.S. Мне тут намекнули что я запарил уже использовать в своих примерах под видом «реальной» айпишки dns google. Да я в курсе про RFC3330 где сказано что «192.0.2.0/24 is assigned as „TEST-NET“ for use in documentation and example code», но как-то не знаю, нагляднее для самого себя получается что-ли :)

Обновлено: 12.03.2015