- freebsd,
- ipfw,
- nat,
- networking
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).

no subject
Помню, что ты не очень любишь stateful firewalls, и однако ж, не пробовал ли ты написать высокоуровневый скрипт, который бы решал простую задачу, как в Cisco PIX. Назначаем каждому интерфейсу уровень security от 0 до 100, и ipfw разрешает трафик только с интерфейса с бОльшим уровнем security на интерфейс с меньшим уровнем, и соответствующий ему возвратный трафик.
no subject
no subject
А на нетграфе делать - это уровень еще ниже, чем ipfw.
no subject
Вот именно.
> А на нетграфе делать - это уровень еще ниже, чем ipfw.
На нетграфе делать надо реализацию логики со стейтами и уровнями. А читать пользовательский конфиг с соответствиями уровней и интерфейсов и создавать/настраивать ноды нужна, конечно, отдельная утилита по типу mpd, только гораздо проще.
no subject
no subject
no subject
no subject
no subject
no subject
Да, для pf наверняка нужен свой патч.
no subject