dadv: (chuck)
Choose your future ([personal profile] dadv) wrote2014-12-10 01:42 pm
Entry tags:

IPSEC & NAT


При совмещении на одном маршрутизаторе функций NAT и IPSEC gateway администратор FreeBSD сталкивается с проблемой: для транзитного трафика на выходе из маршрутизатора сначала выполняется IPSEC-обработка и только потом пропуск трафика через пакетные фильтры. Поэтому невозможно сначала выполнить трансляцию (NAT), а затем зашифровать результат - после NAT трафик в IPSEC уже не направляется, и неважно, чем именно делается NAT: ipfw nat, pf, natd.

Решением для ipfw divert/natd является перенос трансляции трафика, идущего изнутри наружу, с традиционного второго прохода по списку правил ipfw, выполняемого после routing lookup, на первый проход. При этом в конфигурации natd вместо имени внешнего интерфейса нужно задавать непосредственно внешний alias_address и вместо единого divert socket для входящего/исходящего трафика использовать два отдельных сокета, указывая их ключами -in_port и -out_port. Входящий трафик из мира в локальную сеть, как и прежде, направляется в natd на входе через внешний интерфейс в in_port, а вот исходящий нужно направлять в out_port на входе в маршрутизатор через локальный интерфейс, а не на выходе через внешний.

Этим достигается требуемый результат: исходящий трафик из локальной сети транслируется на первом проходе по списку правил ipfw до routing lookup, а шифрование оттранслированных пакетов средствами IPSEC выполняется позже, после routing lookup.

К сожалению, реализовать то же самое средствами ipfw nat невозможно, так как аналога in_port/out_port у него нет ни в одной версии FreeBSD, включая 10.1 и текущую 11-CURRENT.

Сделал патч на ядерную часть ipfw nat для поддержки этой функциональности, что была в natd: патч позволяет явно задавать направление трансляции пакетов (in->out или out->in). Сейчас ipfw nat определяет это направление только автоматически: если пакет проходит по списку правил ipfw первый раз, на входе в маршрутизатор, он считается входящим и транслируется только как out->in. Если пакет проходит по правилам ipfw повторно и после выполнения routing lookup (на выходе и уже имеет атрибут xmit interface), то он транслируется только как in->out.

Патч http://www.grosbein.net/freebsd/patches/ip_fw_nat.c.diff не меняет это поведение по умолчанию: патченная система работает так же. Однако, патч вводит два новых sysctl с дефолтными нулевыми значениям:

net.inet.ip.fw.nat_tag_in
net.inet.ip.fw.nat_tag_out

Если пакет, попавший в правило ipfw nat, имеет прикрепленный тег с номером, равным ненулевому значению sysctl net.inet.ip.fw.nat_tag_out, он безусловно транслируется по схеме in->out, даже если обрабатывается на входе в маршрутизатор.

Аналогично, если пакет имеет тег, равный ненулевому net.inet.ip.fw.nat_tag_in, он транслируется как входящий снаружи по схеме out->in. Трансляция для пакетов, не имеющих таких тегов, выполняется как на непатченной системе. Для пакетов, имеющих теги, эти теги при трансляции снимаются (снимаются не все теги, а только равные nat_tag_in/nat_tag_out).

Теперь для решения исходной задачи можно задать sysctl net.inet.ip.fw.nat_tag_out=10 и заменить одно правило вида ipfw add nat 123 ip from any to any via $ext_if на три:

tag_out=$(sysctl -n net.inet.ip.fw.nat_tag_out)
# Трансляция трафика из мира в локалку
ipfw add 1000 nat 123 ip from any to any in recv $ext_if
# Тегирование пакетов для трансляции из локалки в мир
ipfw add 1010 count tag $tag_out ip from $LAN to not $LAN in recv $int_if
# Трансляция тегированных пакетов на ВХОДЕ в маршрутизатор
ipfw add 1020 nat 123 ip from any to any tagged $tag_out in

Таким образом, добиваемся необходимого эффекта: сначала трансляция исходящего
трафика, затем обработка IPSEC на выходе (между ними routing lookup).

Прикладывать патч: cd /usr/src && patch < /path/to/patch

Затем пересобрать ядро либо только модуль ipfw_nat.ko (модуль ipfw.ko не требуется пересобирать), если используется модуль, а не кастомное ядро с FIREWALL_NAT:

cd /usr/src/sys/modules/ipfw_nat && make obj depend && make all install

Модуль можно затем выгрузить/загрузить на лету, только после этого обязательно сделать service ipfw start, иначе трансляция не заработает.

Патч сделан и проверен на 9.3 (вариант для 8.4: http://www.grosbein.net/freebsd/patches/ip_fw_nat.c.8.diff ). Для других версий, возможно, потребуется незначительная правка.

Update 07.09.2016: обновление патча для 11.0 (для 10.x работает первоначальный вариант от 9.3).

[identity profile] victor-sudakov.livejournal.com 2014-12-12 01:56 pm (UTC)(link)
Спасибо за информацию, возможно пригодится. Но возникает смешанное чувство. ipfw конечно вещь достаточно гибкая, но насколько же она низкоуровневая. Не хочу говорить за всех, но думаю, что большинству админов было бы удобнее иметь дело с более высокоуровневым языком.

Помню, что ты не очень любишь stateful firewalls, и однако ж, не пробовал ли ты написать высокоуровневый скрипт, который бы решал простую задачу, как в Cisco PIX. Назначаем каждому интерфейсу уровень security от 0 до 100, и ipfw разрешает трафик только с интерфейса с бОльшим уровнем security на интерфейс с меньшим уровнем, и соответствующий ему возвратный трафик.

[identity profile] dadv.livejournal.com 2014-12-12 02:05 pm (UTC)(link)
Я не пробовал, у меня нет таких задач. Думаю, что скриптом тут не обойтись, а вот netgraph-ноду такой "системы нипель" вполне можно было бы реализовать. Но я не умею писать такие ноды.

[identity profile] victor-sudakov.livejournal.com 2014-12-12 02:46 pm (UTC)(link)
На первый взгляд скриптом должно быть можно обойтись, т.е. в результате должен получиться набор простых правил вида "ipfw allow ip from any to any in via XXX out via YYY keep-state" в должном порядке. Но при попытке реализовать логику, оно оказывается сложнее, чем на первый взгляд.

А на нетграфе делать - это уровень еще ниже, чем ipfw.

[identity profile] dadv.livejournal.com 2014-12-12 02:54 pm (UTC)(link)
> Но при попытке реализовать логику, оно оказывается сложнее, чем на первый взгляд.

Вот именно.

> А на нетграфе делать - это уровень еще ниже, чем ipfw.

На нетграфе делать надо реализацию логики со стейтами и уровнями. А читать пользовательский конфиг с соответствиями уровней и интерфейсов и создавать/настраивать ноды нужна, конечно, отдельная утилита по типу mpd, только гораздо проще.

[identity profile] victor-sudakov.livejournal.com 2014-12-12 02:57 pm (UTC)(link)
Фактически это будет свой firewall с нуля написать.

[identity profile] dadv.livejournal.com 2014-12-13 08:55 am (UTC)(link)
В некотором смысле да. Или допиливать ядерную часть ipfw. Нужно делать расширять систему стейтов пониманием уровней security.

[identity profile] victor-sudakov.livejournal.com 2014-12-13 11:00 am (UTC)(link)
А ты не встречал случайно опенсорсных межсетевых экранов с таким функционалом?

[identity profile] dadv.livejournal.com 2014-12-13 11:54 am (UTC)(link)
Не интересовался, скажем так.

[identity profile] lg-skeletor.livejournal.com 2014-12-17 09:02 am (UTC)(link)
А в случае PF'a нужны какие-то патчи?

[identity profile] dadv.livejournal.com 2014-12-17 09:13 am (UTC)(link)
Тут была написана чушь (пишу одно, думаю другое - не надо делать столько дел разом), всё стёр.

Да, для pf наверняка нужен свой патч.
Edited 2014-12-17 12:09 (UTC)

[identity profile] zhestkach.livejournal.com 2016-12-20 09:06 pm (UTC)(link)
Очень помогло, спасибо!