FreeBSD: два канала в интернет и ipfw forward

В этой статье говорится о ситуации, когда хост имеет два канала для выхода в Интернет и о том как настроить хост, чтобы сервисы, принимающие запросы на внешних интерфейсах, работали корректно.

В чем собственно проблема? В случае когда запросы из-вне к нашим сервисам приходят по основному каналу связи (на который настроен наш defaul gateway), то проблем никаких и не возникает: откуда пришел пакет-запрос, туда же отправится и пакет-ответ. Однако, что будет, если запрос к сервису придет на второй интерфейс - по "запасному" каналу связи? Обратимся к рисунку внизу.

Неправильное хождение пакетиков-ответов в multihomed конфигруации

Рассмотрим левый рисунок. Представим, что некий компьютер пытается "пропинговать" наш "запасной" интерфейс. Что происходит...

  1. ICMP эхо-запрос (SrcIP=RemoteHost, DstIP=rl1_IP) приходит на интерфейс rl1;
  2. ОС отвечает на него ICMP эхо-ответом с SrcIP=rl1_IP и DstIP=RemoteHost;
  3. Анализируется таблица маршрутизации, наш DefaultGateway находится у провайдера ISP1 и достижим через интерфейс rl0;
  4. Пакет покидает наш хост с интерфейса rl0.
  5. Провайдер ISP1 сильно дивится тому, что к нему приходит пакет с SrcIP из не принадлежащей ему сети, совершенно логично его блокирует и даже подозревает нас в спуфинге;
  6. В итоге пакет с ICMP эхо-ответом до RemoteHost не доходит.

Рассмотрим правый рисунок: на "основном" интерфейсе крутится NAT (есть ли NAT на втором интерфейсе в данном случае не важно). Представим опять, что некий компьютер пытается "пропинговать" наш "запасной" интерфейс. Что происходит в этом случае...

  1. ICMP эхо-запрос (SrcIP=RemoteHost, DstIP=rl1_IP) приходит на интерфейс rl1;
  2. ОС отвечает на него ICMP эхо-ответом с SrcIP=rl1_IP и DstIP=RemoteHost;
  3. Анализируется таблица маршрутизации, наш DefaultGateway находится у провайдера ISP1 и достижим через интерфейс rl0;
  4. Пакет пытается покинуть наш хост с интерфейса rl0, но попадает в NAT, где ему заменяется SrcIP.
  5. Пакет покидает наш хост с интерфейса rl0, но уже с новым SrcIP=rl0_IP.
  6. ICMP эхо-ответ доходит до RemoteHost, но на это раз уже сам RemoteHost очень сильно удивлен тому факту, что посылал он запрос на один адрес, а получил ответ на свой запрос с другого адреса. Для команды ping такой фокус может и пройдет, но вот для других сервисов совсем даже не обязательно.

Решение

Для того чтобы это "победить" будем использовать правила forward в фаерволе ipfw.

kernel

К сожалению, подгружаемого модуля, отвечающего за forward в ipfw, насколько мне известно, нет, поэтому придется пересобрать ядро :(.

  1. Устанавливаем sysinstall → distribution → src → base(top-level files), sys (FreeBSD kernel).
  2. Создадим файл /root/etc/kernel/KMN
    include		GENERIC
    ident		KMN-GENERIC
    options		IPFIREWALL
    options		IPFIREWALL_VERBOSE
    options		IPFIREWALL_VERBOSE_LIMIT=10
    options		IPFIREWALL_DEFAULT_TO_DENY
    options		IPDIVERT
    options		IPFIREWALL_FORWARD
    #	options	IPFIREWALL_FORWARD_EXTENDED
    
  3. Собираем и инсталлируем ядро:
    cd /sys/i386/conf
    ln -s /root/etc/kernel/KMN
    cd /usr/src
    make buildkernel KERNCONF=KMN
    make installkernel KERNCONF=KMN
    

ipfw.conf

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

#LoopBack
setup_loopback

#Правила FORWARD. Эти правила следует помещать до NAT.
${fwcmd} add fwd ${rl0_gw} all from ${rl0_ip} to any out xmit rl1
${fwcmd} add fwd ${rl1_gw} all from ${rl1_ip} to any out xmit rl0

#NAT на интерфейсе rl0
case ${natd1_enable} in
[Yy][Ee][Ss])
	if [ -n "${natd1_interface}" ]; then
		${fwcmd} add divert natd1 all from any to any via rl0
	fi
	;;
esac
#NAT на интерфейсе rl1
case ${natd2_enable} in
[Yy][Ee][Ss])
	if [ -n "${natd2_interface}" ]; then
		${fwcmd} add divert natd2 all from any to any via rl1
	fi
	;;
esac

${fwcmd} add 65000 pass all from any to any

Дата документа: 09.08.09