IPSEC & NAT
2014-12-10 13:42![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
При совмещении на одном маршрутизаторе функций 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).