Организация доступности сервисов по двум внешним интерфейсам средствами natd и ipfw

На рисунке представлена типичная схема подключения небольших офисов к интернету.
Если основной канал выхода в интернет (ISP1) и резервный (ISP2), на который переключаются в случае проблем с основным каналом.
Переключение обычно проводится сменой шлюза по умолчанию на роутере.

А если на роутере есть сервисы которые должны быть доступны извне, то эти сервисы будут доступны только по внешнему адресу активного, на данный момент, канала в интернет, так как ответы на запросы, пришедшие с неактивного канала, будут отправятся через активный канал и успешно теряться. Налаживать BGP взаимодействие с провайдерами, как мне посоветовали на одном форуме — это редкостный маразм стрельба из пушки по воробьям, да и не каждый провайдер на это пойдет.

Я расскажу как сделать сервисы доступными по обоим внешним адресам одновременно, средствами ipfw и natd.


Исходные данные
Роутер с FREEBSD 6.3
Локальная сеть 192.168.1.0/24, которую будем натить.
Интерфейс re0 смотрящий в локальную сеть c ip адресом 192.168.1.1
Интерфейс xl0 смотрящий в интернет через ISP1 c ip адресом 111.111.111.1
Интерфейс xl1 смотрящий в интернет через ISP2 c ip адресом 222.222.222.1
Сервисы которые необходимо сделать доступными одновременно по 111.111.111.1 и по 222.222.222.1.
Шлюз первого провайдера: 111.111.111.2
Шлюз второго провайдера: 222.222.222.2

У PF есть волшебное reply-to() с помощью которого решается данная задача. На IPFW решение не столь тривиально, впрочем, начнем по порядку.

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

Настроим работу natd двумя инстансами
/etc/rc.conf

natd_program="/sbin/natd"
natd_enable="YES"
natd_flags="-f /etc/natd.conf"

Обратите внимание что мы не указываем интерфейс.

/etc/natd.conf
log
instance default
interface xl0
port 8668
use_sockets yes
same_ports yes

instance xl1
interface xl1
port 8669
use_sockets yes
same_ports yes

globalport 8670

instanse default указываем обязательно.
Если запускать две экземпляра natd вручную, как советуют в некоторых мануалах, ничего не выйдет, так как таблица транслаций у них будет у каждого своя.

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

/etc/rc.firewall
ipfw="/sbin/ipfw -q "

local="re0"
ISP1="xl0"
ISP2="xl1"

localnet="192.168.1.0/24"

ISP1_ip="111.111.111.1"
ISP2_ip="222.222.222.1"

ISP1_gw="111.111.111.2"
ISP2_gw="222.222.222.2"

nat_ISP1="8668"
nat_ISP2="8669"
nat_glob="8670"

${ipfw} -f flush

#Пример сервисов которые необходимо вывесить на оба внешних интерфейса.

# sshd
${ipfw} add 20 allow tcp from any to me 22

# Web Server
${ipfw} add 25 allow tcp from any to me 80

# MailServer
${ipfw} add 26 allow tcp from any to me 25
${ipfw} add 27 allow tcp from any to me 110

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

${ipfw} add 100 fwd ${ISP1_gw} ip from ${ISP1_ip} to not ${localnet}
${ipfw} add 200 fwd ${ISP2_gw} ip from ${ISP2_ip} to not ${localnet}

# Далее разделяем весь трафик по интерфейсам и направлениям, для удобства

# Локальный интерфейс
${ipfw} add 400 skipto 1000 all from any to any in recv ${local}
${ipfw} add 410 skipto 2000 all from any to any out xmit ${local}

# Внешние интерфейсы
${ipfw} add 500 skipto 3000 all from any to any in recv ${ISP1}
${ipfw} add 550 skipto 4000 all from any to any out xmit ${ISP1}
${ipfw} add 600 skipto 5000 all from any to any in recv ${ISP2}
${ipfw} add 650 skipto 6000 all from any to any out xmit ${ISP2}

# Если вдруг объявятся еще интерфейсы то рубим их. Все интерфейсы мы должны разрулить сами, иначе все пакеты с этих новых интерфейсов попадут под следующее разрешающее правило
${ipfw} add 900 deny all from any to any


# Локальный входящий
${ipfw} add 1000 allow all from any to any

# Локальный исходящий
${ipfw} add 2000 allow all from any to any

# ISP1 входящий (входящий трафик заворачиваем в нужный порт natd)
${ipfw} add 3000 divert ${nat_ISP1} ip from any to ${ISP1_ip}
${ipfw} add 3010 allow all from any to any

# ISP1 исходящий (аналогично для обоих провайдеров)
# Заворачиваем пакет в natd по порту globalport
${ipfw} add 4000 divert ${nat_global} ip from ${localnet} to any
# Если есть запись трянсляции подключения снаружи вовнутрь, то natd модифицирует пакет и ставит адресом источника тот внешний адрес, на который производилось подключение
# Если это адрес первого провайдера, то далее просто разрешаем пакет и на этом обработка заканчивается
${ipfw} add 4010 allow all from ${ISP1_ip} to any
# Если это адрес второго провайдера, то мы форвардим пакет на шлюз второго провайдера.
${ipfw} add 4020 fwd ${ISP2_gw} ip from ${ISP2_ip} to any
# Если же natd вернул неизмененный пакет, значит данный пакет не относится к подключению извне, тогда заворачиваем его в соответствующий порт natd
${ipfw} add 4030 divert ${nat_ISP1} ip from ${localnet} to any
# Разрешаем прохождение пакета.
${ipfw} add 4040 allow all from any to any

# Для второго провайдера все аналогично

# ISP2 входящий
${ipfw} add 5000 divert ${nat_ISP2} ip from any to ${ISP2_ip}
${ipfw} add 5010 allow all from any to any

# ISP2 исходящий
${ipfw} add 6000 divert ${nat_global} ip from ${localnet} to any
${ipfw} add 6010 allow all from ${ISP2_ip} to any
${ipfw} add 6020 fwd ${ISP1_gw} ip from ${ISP1_ip} to any
${ipfw} add 6030 divert ${nat_ISP2} ip from ${localnet} to any
${ipfw} add 6040 allow all from any to any

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

Для автоматизации можно создать статический маршрут до какого-нибудь ресурса через основной канал и мониторить его доступность. В случае его недоступности менять основной шлюз на шлюз резервного канала.

UPD

Скрипт переключения маршрутов:
#!/bin/sh
IP1=111.111.111.1
IP2=222.222.222.1
GW1=111.111.111.2
GW2=222.222.222.2
/sbin/ping -q -c 1 -S $IP1 yandex.ru > /dev/null 2>&1
if [ $? != 0 ]; then
/sbin/ping -q -c 1 -S $IP2 yandex.ru > /dev/null 2>&1
if [ $? = 0 ]; then
if [ ! -f /tmp/gw.changed ]; then
/sbin/route change default $GW2 && touch /tmp/gw.changed
fi
fi
else
if [ -f /tmp/gw.changed ]; then
/sbin/route change default $GW1 && rm /tmp/gw.changed
fi
fi

То есть мы просто «пингуем» ресурс через нужный интерфейс (флаг -S в команде ping)
Если ресурс недоступен через основной канал то:
— проверяем работоспособность резервного канала;
— перенастраиваем маршрут по умолчанию на шлюз резервного канала;
— оставляем «метку» /tmp/gw.changed, сигнализирующую о смене шлюза.

При следующем выполнении (например, скрипт можно запускать по cron раз в минуту), если GW1 в норме и есть «метка», то возвращаем основной шлюз на место. Если оба шлюза недоступны, текущее состояние не меняем.

Основным достоинством этого метода можно назвать прозрачность и простоту.
Недостатки — определённая инерционность, хоть и небольшие, но всё же непроизводственные затраты трафика, ненулевая вероятность ложных срабатываний, особенно при высокой загрузке канала.

Временная недоступность самого ресурса (в данном примере яндекс) при нормальной работе обоих каналов не приведет к переключению канал, поскольку его работоспособность также не будет подтверждена.

http://habrahabr.ru

Обновлено: 12.03.2015