Современные информационные технологии/Вычислительная техника и программирование

Мясищев А.А.

Хмельницкий национальный университет, Украина

Резервирование каналов Интернет с помощью ipfw nat на маршрутизаторах под UNIX FreeBSD

         В локальных сетях организаций, которые имеют выход в Internet через несколько внешних каналов, иногда требуется такое подключение нескольких «важных» серверов, при котором в случае исчезновения связи по одному из каналов сервера переподключаются автоматически к другому каналу. Рассмотрим решение этой задачи, когда в качестве маршрутизатора используется компьютер с ОС Unix FreeBSD 7.3 и установленным там ipfw nat(«ядерным» nat, который появился в FreeBSD с версии 7.0).

         Однако перед решением задачи рассмотрим более детально, как происходит движение трафика в стеке FreeBSD (рис.1).

Рис.1.

Простейший маршрутизатор обычно использует два интерфейса, через который проходит трафик. Через каждый интерфейс трафик идет как на вход, так и на выход. Поэтому каждый интерфейс логически имеет два прохода out и in. Проходы  in и out важны тем, что при поступлении в них сетевого трафика, этот трафик попадает в фаервол, где проходит через правила фильтрации ipfw. Таким образом, трафик, проходящий через маршрутизатор, проходит через правила ipfw два раза - на проходе IN сетевого адаптера через который он был получен, и на проходе out сетевого адаптера через который он будет передан.

         Рассмотрим пример [1].  Допустим, что имеем следующий набор правил:

ipfw add 1    deny  tcp from any to any 135,445
ipfw add 2    divert 8668  all from any to any via ext0
ipfw add 3    count icmp from any to any
ipfw add 4    allow  all from any to any
#
в 65535 по умолчанию deny

Когда пакет проходит через машину, к нему системой прикрепляется дополнительная информация, помимо собственно его содержимого, видного в tcpdump. Например, на каком интерфейсе он был получен, через какой отправляется, и т.п. Эту информацию можно проверять соответствующими опциями в правилах ipfw. На рис.1 показаны места, где будут срабатывать соответствующие указания in/out и recv/xmit для сетевых адаптеров.

В соответствии с рис.1 рассмотрим более подробно прохождение пакетов через интерфейс, предполагая, что они предварительно обработаны подсистемой Ethernet и попали на интерфейс как ip пакеты.  IP пакет на входе попадает в функцию ip_input(), в точку (2), где выполняются базовые проверки на корректность пакета, после чего пакет приходит в ipfw - функция ipfw_chk(). Допустим, правила были простые, без использования других подсистем. Тогда, вернувшись из ipfw, пакет продолжает движение по ip_input(), которая смотрит, предназначен ли пакет нашей машине либо кому-то другому. Если нам, то пакет уходит в точку (3), где решится, в какую программу его отправить.

Если же пакет был предназначен не нам, то он из ip_input() направится в точку (4), где ip_forward() проверит, установлен ли sysctl, разрешающий форвардинг, произведет уменьшение на единицу TTL и другие действия. После этого пакет придет в точку (6) - функцию ip_output(). Туда же он попадет напрямую, когда какая-нибудь программа решит что-то отправить в сеть.  Функция ip_output() вначале смотрит в таблицу маршрутизации, определяя, на какой интерфейс и на какой шлюз надо направить пакет. С этой информацией пакет вновь передается в ipfw, в котором опять пробегается по всем правилам. После выхода из ipfw_chk() в ip_output(), если ядро было скомпилировано с соответствующей опцией, проверяется, не был ли применен ipfw fwd - если да, то просмотр  таблицы маршрутизации выполняется заново с целью получить MAC-адрес нового шлюза. Затем пакет в точке (7) покидает ip_output() и передается дальше, на 2-й уровень модели OSI и к драйверам интерфейсов.

Теперь, предположим, что в правилах файрвола появляется divert(смотри приведенные выше правила). Предположим, что пакет из внутренней локальной сети должен пойти в Интернет на порт 80. Тогда путь следования будет таким. Пакет войдет на внутреннем интерфейсе в точку (1), пройдет начальные проверки в функции ip_input() на корректность в (2), будет передан в ipfw_chk() и начнет проходить по правилам. Под правило 1 пакет  не подпадает, под 2 тоже, так как имя интерфейса сейчас int0(интерфейс внутренней сети).  Правило 3 не срабатывает, а под правило 4 подходят все пакеты, и он выходит из ipfw_chk() дальше в ip_input().  В этой функции выясняется, что предназначен он для сети Интернет, поэтому пакет попадает в точку (4) и затем в (6), где ip_output() определяет адрес выходящего интерфейса и  шлюза. Интерфейсом будет ext0, с чем пакет и попадает опять в ipfw_chk() и снова идет по правилам. Правило 1 снова не подходит, но условие правила 2 срабатывает - " from any to any via ext0”, соответствующее проходу через интерфейс ext0 в любом направлении. Здесь срабатывает divert - пакет из ipfw_chk() передается в точку (8), в подсистему divert, при этом к нему предварительно прикрепляется метаинформация с направлением (out), интерфейсом (ext0) и номером правила 2. Подсистема divert передает этот пакет в указанный в правиле порт (8668), на котором работает natd. Он обрабатывает пакет, метаинформацию не трогает, и возвращает подсистеме divert измененный пакет. Подсистема divert выводит полученный из natd пакет из точки (9) в точку (6). Далее пакет попадает в ip_output() еще раз. Потом пакет опять входит в  ipfw_chk() и в нем проверяется  номер правила, которое соответствует 2. Далее ipfw_chk() пропускает правила номер 1 и 2, и начинает с номера 3. (в противном случае пакет снова попал бы в divert, и получился бы бесконечный цикл). На этом месте пакет продолжит движение с правила номер 3, под которое он не попадет, а по правилу 4 уйдет в точку 7 и далее в сеть. Таким образом, несмотря на то, что пакет попадал в ip_output() два раза, с точки зрения пользователя это выглядит так, как если бы он  был там один раз и никуда из файрвола не уходил - просто на правиле 2 в пакете поменялись адреса и порты в соответствии с настройками nat.

Аналогичным образом, ответный пакет, возвращающийся из Интернета на интерфейс ext0, пройдет через машину по пути (1) - (2). Функцией ip_input() пакет будет передан  на ipfw_chk() и пойдет по правилу 1, правилу 2, которое передаст его в точку (8).  Дальше он попадает в divert, natd, divert и выйдет в точку (9) и далее в  (2). Снова вернется на ipfw_chk() но на правило 3, затем на правило 4. После него в точку (4), потом в  (6) на интерфейс int0. Функция ip_output() передаст пакет на  ipfw_chk(), который пойдет по правилам 1, 2 , 3. После правила 4 – пакет попадет в точку (7) которая соответствует его отправке через интерфейс int0.

Подобное выведение пакета из обработки ipfw в другую подсистему характерно не толька для divert, но и для pipe и queue в dummynet, передача пакета в netgraph, а также и для  ipfw nat, который появился в FreeBSD 7.0. Отличие в том, что в последних подсистемах пакет остается внутри ядра, никакому демону не передается (т.е. natd). Поэтому, во-первых, подсистемы вместо номера правила сохраняют на пакет полный указатель, и он вернется непосредственно в следующее правило, даже если оно имеет тот же номер. Во-вторых, для таких подсистем действует настройка one_pass в sysctl. Если net.inet.ip.fw.one_pass=1, то при повторном входе пакета в ipfw после возврата из подсистемы dummynet (netgraph), ipfw_chk() сразу вернет пакет в ip_output() (ip_inputt() ) без прохода по правилам, как если бы к пакету был применен allow. Это поведение позволяет упростить правила файрвола, когда известно, что если пакет попал в трубу, то он уже точно отправляется дальше без возврата на ipfw, и не требуется после каждого pipe вставлять allow (чтобы пакет не попал в следующие правила и следующие pipe/queue). Если же конфигурация требует сначала ограничить трафик, а потом уже разбираться по правилам, что разрешить, а что запретить, то упрощению правил будет способствовать отключенный one_pass (net.inet.ip.fw.one_pass=0).

            Таким образом, пакет проходит по списку правил последовательно, в порядке возрастания номеров правил. Список правил можно рассматривать как таблицу с тремя столбцами: номер, действие (allow, deny, nat, pipe) и условия (from any to 192.168.0.0/16 in via em0). Таблица просматривается сверху вниз, пакет сравнивается с условиями. На первом совпавшем условии система смотрит в столбец действий, выполняет действия, прекращает просмотр и ... выполняет дальнейшие действия.
            Механизм ipfw nat можно рассматривать как своеобразный фильтр-преобразователь трафика. В зависимости от того, как настроен nat, и от того какие IP адреса отправителя и получателя есть в проходящем через него пакете, nat будет преобразовывать в пакете IP адрес отправителя, либо IP адрес получателя, или же просто отвергать пакет. Важно знать, что nat разделяет трафик на исходящий (тот который надо маскировать) и входящий (тот который надо демаскировать) в зависимости от того, на каком проходе(out или in)трафик был получен. Так, трафик, попавший в nat из прохода out, требуется маскировать (заменить IP адрес отправителя на IP адрес nat машины), а трафик пришедший из прохода in демаскировать. На поведение механизма nat влияет настройка параметра one_pass. В зависимости от ее значения трафик прошедший через nat может либо вернуться в проход фаервола из которого он был направлен в nat (net.inet.ip.fw.one_pass=0), либо выйти из фаервола (net.inet.ip.fw.one_pass=1). Такая гибкость дает возможность делать с трафиком много полезных вещей. ipfw nat позволяет запускать множество своих экземпляров, каждый из которых имеет свою nat-таблицу и настройки.
            Рассмотрим некоторые возможности ipwf nat, которые необходимы для решения поставленной задачи и некоторые параметры конфигурации.
            Некоторые параметры конфигурации ipfw nat
if nic -  Параметр предписывает ip адрес используемый при маскировании и демаскировании, на основе имеющихся настроек для сетевого интерфейса с именем "nic".
deni_in  - запрещает пропускать через nat входящие пакеты для которых нет совпадения во внутренней таблице активных соединений, или же нет совпадения с другими правилами демаскировки. Если данный параметр не установлен то для входящих соединений будет создаваться новая запись в таблице активных соединений и пакет будет пропускаться.
same_ports - параметр предлагает механизму nat поведение при котором тот будет стараться сохранять оригинальные номера портов в уходящих(маскируемых) пакетах.
reset - предписывает очищать внутреннюю таблицу активных соединений при изменениях параметра "ip", указывающего адрес используемый при маскировании и демаскировании.
redirect_addr localIP publicIP - параметр предписывает проводить перенаправление трафика поступающего на внешний IP адрес (publicIP), на машину с IP адресом во внутренней сети (localIP). Пример:
redirect_addr 192.168.1.36 212.111.198.77
redirect_port proto targetIP:targetPORT[-targetPORT][aliasIP:]aliasPORT[-aliasPORT][remoteIP[:remotePORT[-remotePORT]]] - параметр предписывает проводить перенаправление входящих соединений поступающих на указанный номер порта(ов), на другой хост и порт(ы). Аргумент "proto" – tcp либо udp. Аргумент "targetIP" указывает IP адрес, на который должна происходить пересылка трафика, а аргумент "targetPORT" порт (или диапазон портов) на который будет перенаправлен трафик. Аргумент "aliasIP" - IP адрес входящих соединений, "aliasPORT" - номер порта входящих соединений. Аргументы "remoteIP" и "remotePORT" –указывают удаленные источники/назначения трафика. Пример:
redirect_port tcp 192.168.100.65:telnet 8088 - означает, что входящие TCP пакеты, направляемые на порт 8088 nat машины, будут перенаправлены на порт 23 (telnet) машины с ip адресом 192.168.100.65
            Для включения ipfw, nat, forwarding,divert и Dummynet необходима пересборка ядра в нашем случае для системы Unix FreeBSD 7.3 со следующими параметрами:
options         IPFIREWALL                                      #собственно файрвол
options         IPFIREWALL_VERBOSE                               #логгинг пакетов
options         IPFIREWALL_VERBOSE_LIMIT=10  #ограничение логов
options         IPFIREWALL_DEFAULT_TO_ACCEPT #разрешающее правило
options         IPFIREWALL_NAT            #разрешение “ядерного” nat
options         IPFIREWALL_FORWARD # перенаправление пакетов
options         LIBALIAS  #библиотека nat
options         IPDIVERT  #если нужен “обычный” NAT (трансляция адресов)
options         ROUTETABLES=2 #включение 2-х таблиц маршрутизации
options         DUMMYNET          #включение ограничения скорости 
options         HZ="1000"
Рис.2
            На рис.2 представлена схема фрагмента подключения двух локальных сетей к routerNAT через два сетевых адаптера fxp0(сеть 172.20.0.0/16) и rl0(172.21.0.0/16). С другой стороны этот router подключен к Интернет через два адаптера – re0(78.152.183.32/27 - Infocom) и re1(212.111.198.32/28 - URAN). “Важный” сервер находится в локальной сети 172.20.0.0/16 и имеет адрес 172.20.6.37. Требуется, чтобы по умолчанию он работал в Интернет через сеть  212.111.198.32/28 и был виден с Интернет по адресу 212.111.198.37. В случае обрыва связи работа с сервером переключалась на канал Интернет по сети 78.152.183.32/27, а с Интернет он был бы виден по адресу 78.152.183.35. С сети Internet  необходимо разрешить telnet, т.е. по команде
telnet 78.152.183.34 8023
подключение к routerNAT, а по команде
telnet 78.152.183.34 8024 
подключение к локальному компьютеру 172.21.8.2. Аналогично по команде
telnet 212.111.198.39 8023
подключение по telnet к NAT, а по команде 
telnet 212.111.198.39 8024 
подключение к локальному компьютеру 172.20.5.3
            Необходимо также обеспечить прохождение пакетов для DNS запросов (порт 53) через интерфейс re0, так как router NAT является также DNS сервером. Сервер (172.20.6.37) должен иметь внешнее имя ed.tup.km.ua и отзываться по нему  в случае обрыва канала. Узлы сети 172.20.0.0/16 должны разделять между собой один канал pipe 1 с пропускной способностью 3024KBit/s. Также должны выполняться следующие скоростные условия: узел 172.21.8.1 имеет выход в Internet со скоростью pipe 5 - 3024KBit/s,  узел 172.21.8.2 - pipe 10 - 2024KBit/s, узел 172.21.8.3 - pipe 15 - 4096KBit/s, узел 172.21.8.4 - pipe 20 - 3024KBit/s и узел 172.21.8.200 разделяет с узлом 172.21.8.3 канал   pipe 15 со скоростью  4096KBit/s. 
            Так как ядро routerNAT собрано с опцией ROUTETABLES=2, то он поддерживает работу с двумя таблицами маршрутизации. Примем, что трафику, исходящему из сети 172.20.0.0/16 будет назначена таблица маршрутизации 0 (таблица по умолчанию со шлюзом 212.111.198.33), а трафику из сети 172.21.0.0/16 – таблица маршрутизации 1 (для нее шлюз по умолчанию 78.152.183.33). 
            Тогда /etc/rc.conf будет выглядеть следующим образом:

gateway_enable="YES"

hostname="nat.tup"

ifconfig_rl0="inet 172.21.88.1  netmask 255.255.0.0"

ifconfig_fxp0="inet 172.20.50.2  netmask 255.255.0.0"

ifconfig_re0="inet 78.152.183.34  netmask 255.255.255.224"

ifconfig_re0_alias0="inet 78.152.183.35  netmask 255.255.255.224"

ifconfig_re1="inet 212.111.198.39  netmask 255.255.255.240"

ifconfig_re1_alias0="inet 212.111.198.37  netmask 255.255.255.240"

inetd_enable="YES"

sshd_enable="YES"

firewall_enable="YES"

firewall_script="/etc/firewall"

         Файл /etc/sysctl.conf описывает вариант, когда пакет после захода в nat, pipe  возвращается опять в правила ipfw:

net.inet.ip.fw.one_pass=0

            Файл /etc/rc.local

setfib 0 route add default 212.111.198.33

setfib 1 route add default 78.152.183.33

/usr/sbin/named -c /etc/namedb/named.conf

/usr/home/alex/ru.exe &

Первая и вторая строки (setlib) назначают для 0-й и 1-й таблиц маршрутизации шлюзы по умолчанию. В четвертой строке запускается скрипт, необходимый для  мониторинга основного канала через re1(URAN). В 3-й строке запускается кэширующий DNS сервер.

Скрипт /usr/home/alex/ru.exe:

sleep 60

/usr/home/alex/monit.exe &

sleep 10

echo Load ROUT

/usr/home/alex/rout.exe &

Задержка 60 секунд необходима для гарантированного поднятия интерфейсов routerNAT. Скрипт /usr/home/alex/monit.exe, используя команду ping, проверяет работоспособность канала URAN. Если пакеты теряются на 100% при 5-и пингах, то формируется “непустой”файл /usr/home/alex/los.dat.

/usr/home/alex/monit.exe:

#!/bin/sh

while [ 1 ]

 do

   sleep 5

   /sbin/ping -c 5 212.111.195.121 > /usr/home/alex/pin.dat

   cat /usr/home/alex/pin.dat | grep "100.0% packet loss" > /usr/home/alex/los.dat

 done

         Скрипт /usr/home/alex/rout.exe проверяет файл /usr/home/alex/los.dat. Если он ‘непустой”, то удаляется 0-я таблица маршрутизации и вместо нее записывается таблица со шлюзом по умолчанию 78.152.183.33. Таким образом, устанавливаются две одинаковых таблицы. Далее идет задержка на 3600 секунд, соответствующая ожиданию восстановления линии через URAN, и таблицы маршрутизации возвращаются к исходному виду. Потом опять проверяется файл /usr/home/alex/los.dat. Если он пустой, тогда линия восстановилась и никаких изменений  по переназначению таблиц маршрутизации не происходит. В противном случае снова происходит изменение 0-й таблицы.

/usr/home/alex/rout.exe:

#!/bin/sh

while [ 1 ]

do

 while [ 1 ]

  do

  sleep 1

   if [ -s /usr/home/alex/los.dat ]

    then route delete default 212.111.198.33; route add default 78.152.183.33; break

   fi

 done

#

sleep 3600

#

route delete default 78.152.183.33

route add default 212.111.198.33

done

 

Для решения поставленной задачи скрипт /etc/firewall имеет вид:

#!/bin/sh

ipfw pipe 1  config bw 3024KBit/s

ipfw pipe 5  config bw 3024KBit/s

ipfw pipe 10 config bw 2024KBit/s

ipfw pipe 15 config bw 4096KBit/s

ipfw pipe 20 config bw 3024KBit/s

ipfw add 5000 setfib 0 ip from any to any in recv fxp0

ipfw add 5010 setfib 1 ip from any to any in recv rl0

ipfw add 06000 setfib 0 ip from any to any in recv re1

ipfw add 06010 setfib 1 ip from any to any in recv re0

ipfw add 10040 pipe 1 ip from any to 172.20.0.0/16 out via fxp0

ipfw add 10045 pipe 1 ip from 172.20.0.0/16 to any in via fxp0

ipfw add 10050 pipe 5  ip from any to 172.21.8.1 out via rl0

ipfw add 10055 pipe 5  ip from 172.21.8.1 to any in via rl0

ipfw add 10060 pipe 10 ip from any to 172.21.8.2 out via rl0

ipfw add 10065 pipe 10 ip from 172.21.8.2 to any in via rl0

ipfw add 10070 pipe 15 ip from any to 172.21.8.3 out via rl0

ipfw add 10075 pipe 15 ip from 172.21.8.3 to any in via rl0

ipfw add 10080 pipe 20 ip from any to 172.21.8.4 out via rl0

ipfw add 10085 pipe 20 ip from 172.21.8.4 to any in via rl0

ipfw add 10380 pipe 15 ip from any to 172.21.8.200 out via rl0

ipfw add 10385 pipe 15 ip from 172.21.8.200 to any in via rl0

ipfw add 10400 skipto 30000 ip from any to 172.20.0.0/16 out via fxp0

ipfw add 10405 skipto 30000 ip from 172.20.0.0/16 to any in via fxp0

ipfw add 10410 skipto 30000 ip from any to 172.21.8.1 out via rl0

ipfw add 10415 skipto 30000 ip from 172.21.8.1 to any in via rl0

ipfw add 10420 skipto 30000 ip from any to 172.21.8.2 out via rl0

ipfw add 10425 skipto 30000 ip from 172.21.8.2 to any in via rl0

ipfw add 10430 skipto 30000 ip from any to 172.21.8.3 out via rl0

ipfw add 10435 skipto 30000 ip from 172.21.8.3 to any in via rl0

ipfw add 10440 skipto 30000 ip from any to 172.21.8.4 out via rl0

ipfw add 10445 skipto 30000 ip from 172.21.8.4 to any in via rl0

ipfw add 10450 skipto 30000 ip from any to 172.21.8.200 out via rl0

ipfw add 10455 skipto 30000 ip from 172.21.8.200 to any in via rl0

ipfw add 10620 deny ip from any to any via fxp0

ipfw add 10630 deny ip from any to any via rl0

ipfw nat 1 config if re0 reset same_ports deny_in redirect_port tcp 78.152.183.34:23 8023 redirect_port udp 78.152.183.34:53 53 redirect_port tcp 172.21.8.2:23 8024

ipfw nat 2 config if re1 reset same_ports deny_in redirect_port tcp 212.111.198.39:23 8023 redirect_port tcp 172.20.50.3:23 8024

ipfw nat 3 config if re1 reset same_ports redirect_addr 172.20.6.37 212.111.198.37

ipfw nat 4 config if re0 reset same_ports redirect_addr 172.20.6.37 78.152.183.35

ipfw nat 5 config if re0 reset same_ports deny_in redirect_port tcp 78.152.183.34:23 8023

ipfw add 10710 skipto 20100 ip from 172.20.6.37 to any out via re1

ipfw add 10720 skipto 20110 ip from any to 212.111.198.37 in via re1

ipfw add 10810 skipto 20200 ip from 172.20.6.37 to any out via re0

ipfw add 10820 skipto 20210 ip from any to 78.152.183.35 in via re0

#

ipfw add 10830 skipto 20300 ip from any to 78.152.183.34 8023 in via re0

ipfw add 10840 skipto 20310 ip from 78.152.183.34 23 to any out via re1

ipfw add 20000 nat 1 ip from any to any via re0

ipfw add 20010 nat 2 ip from any to any via re1

ipfw add 20020 allow ip from any to any

ipfw add 20100 nat 3 ip from 172.20.6.37 to any out via re1

ipfw add 20110 nat 3 ip from any to 212.111.198.37 in via re1

ipfw add 20120 allow ip from any to any

ipfw add 20200 nat 4 ip from 172.20.6.37 to any out via re0

ipfw add 20210 nat 4 ip from any to 78.152.183.35 in via re0

ipfw add 20220 allow ip from any to any

ipfw add 20300 nat 5 ip from any to 78.152.183.34 8023 in via re0

ipfw add 20310 nat 5 ip from 78.152.183.34 23 to any out via re1

ipfw add 20320 fwd 78.152.183.33 ip from 78.152.183.34 8023 to any out via re1

ipfw add 30000 allow ip from any to any

Во всех представленных выше скриптах команды пишутся в одну строчку.

Описание прохождения трафика через фаервол в представленной конфигурации.

Для routerNAT(работают 2 канала Интернет):
            Трафик, инициализированный от routerNAT из точки (5) рис.1 пойдет в точку (6). Попав в ip_output() трафику будет присвоен шлюз по умолчанию 212.111.198.33 и тег с сетевым интерфейсом re1, через который он должен выйти в Интернет(out via re1). Трафик войдет в фаервол и начнет проходить через цепочку правил. Дойдет до правила 20010 и будет передан в точку (8) т.е. на обработку в nat как выходящий трафик, так как он поступил из прохода out. В подсистеме nat для трафика в таблице соединений будет создана запись соответствия пар локальный_ip: порт < - > удаленный_ip: порт (например, 212.111.198.39:50318< - > 193.193.193.107:23). После этого в точке (9) трафик выйдет из подсистемы nat и опять будет передан в точку (6), попав опять в ip_output(). Трафик вновь войдет в фаервол но уже в правило 20020 и по нему уйдет в Интернет через re1.
            Трафик, возвращающийся в виде ответа, войдет в точку (1), попадет в проход in, получит тег re1(in via re1).  Затем трафик зайдет в (2) и попадет в функцию ip_input(). Далее он зайдет в фаервол  и пойдет по цепочке правил. Правило 6000 определит трафику таблицу маршрутизации 0 и пойдет по правилам дальше. В правиле 20010 он будет передан в точку (8) на обработку в nat, как входящий трафик. В подсистеме nat для трафика в таблице соединений будет найдена ранее созданная запись соответствия пар  локальный_ip: порт < - > удаленный_ip: порт(212.111.198.39:50318< - > 193.193.193.107:23) по установленному соединению 193.193.193.107:23 < -> 212.111.198.39:50318, после чего он будет демаскирован. Однако в данном случае, так как адрес маскировки/демаскировки один и тот же локальный 212.111.198.39, изменений не будет. После этого трафик из точки (9) выйдет из подсистемы nat и будет передан обратно в точку (2).  Т.е. попадет опять в  ip_input(). По правилу 20020 трафик выйдет из ipfw  и будет передан программе, которая ранее инициализировала соединение. 
            Если из сети  Интернет приходит трафик,  не имеющий в таблице соединений соответствия,  кроме запроса на соединение с адресами 212.111.198.37 и 78.152.183.35, то такой трафик при поступлении в nat дальше не пойдет, так как в настройках  nat  выставлен параметр deny_in.

         Если из сети  Интернет приходит трафик, адресованный на ip 212.111.198.37, то он войдет в точку (1), попадет в проход in, получит тег re1(in via re1).  Затем трафик зайдет в (2) и попадет в функцию ip_input(). Далее он зайдет в фаервол  и пойдет по цепочке правил. Правило 6000 определит трафику таблицу маршрутизации 0, и он пойдет по правилам дальше. Дойдя до правила 10720, он будет передан правилу 20110. Здесь трафик пойдет в точку (8) на обработку в nat, как входящий трафик. Там он будет демаскирован в локальный адрес 172.20.6.37 (по nat 3), далее выйдет в точку (9), перейдет в точку (2) и продолжит идти  по правилам фаервола. Сразу попав на правило 20120, трафик выходит из ipfw  и возвращается функции  ip_input(). Она определяет, что трафик предназначен не нам, передает его в точку (4), где функция ip_forward() произведет уменьшение на единицу TTL и после этого трафик будет передан в точку (6). Здесь он попадает в функцию in_output(), которая по ip 172.20.6.37 присвоит трафику выходящий шлюз 172.20.50.2 и интерфейс fxp0. Далее трафик попадает в ipfw и идет по правилам. По правилу 10040 трафик попадает в трубу pipe 1. Однако после этого он не покинет фаервола,  так как  net.inet.ip.fw.one_pass=0. Дойдя до правила 10400, трафик переходит к правилу 30000 и попадает в точку (7), затем в локальную сеть на узел 172.20.6.37.

         Ответный трафик с узла 172.20.6.37 войдет в точку (1), попадет в проход in, получит тег fxp0(in via fxp0).  Затем трафик зайдет в (2) и попадет в функцию ip_input(). Далее он зайдет в фаервол  и пойдет по цепочке правил. Правило 5000 определит трафику таблицу маршрутизации 0, и он пойдет по правилам дальше. По правилу 10045 трафик попадает в трубу pipe 1. Однако после этого он не покидает фаервола  так как  net.inet.ip.fw.one_pass=0. Дойдя до правила 10405, трафик  переходит к правилу 30000, попадает в точку (4) и управление передается функции  ip_forward(), которая произведет уменьшение на единицу TTL. После этого трафик будет передан в точку (6) – функцию in_output(). Она  по таблице маршрутизации 0, внешнего адреса узла, от которого шел запрос, назначит шлюз по умолчанию (212.111.198.33) и имя выходящего интерфейса re1(out via re1). Далее трафик передается функции  ipfw_chk() и идет по правилам фаервола. Дойдя до правила 10710, управление передается на правило 20100. По нему управление передается в точку (8), попадает в nat. Здесь по записи соответствия для nat 3 локальный адрес 172.20.6.37 будет заменен внешним адресом 212.111.198.37 и трафик попадет в точку (9), далее в точку (6) в функцию in_output(). Трафик вновь пойдет по правилам фаервола, дойдет до правила 20120 и выйдет через точку (7) в Интернет.

         Теперь предположим, что с Интернет приходит трафик, адресованный на ip 78.152.183.35. Тогда он войдет в точку (1), попадет в проход in, получит тег re0(in via re0).  Затем трафик зайдет в (2) и попадет в функцию ip_input(). Далее он зайдет в фаервол  и пойдет по цепочке правил. Дойдя до правила 6010, трафику будет назначена таблица маршрутизации 1. В 10820, он будет передан правилу 20210. Здесь трафик пойдет в точку (8) на обработку в nat (по nat 4), как входящий трафик. Там он будет демаскирован в локальный адрес 172.20.6.37 (по nat 4), далее выйдет в точку (9), перейдет в точку (2) и продолжит идти  по правилам фаервола. Сразу попав на правило 20220, трафик выходит из ipfw  и возвращается функции  ip_input(). Она определяет, что трафик предназначен не нам, передает его в точку (4), где функция ip_forward() произведет уменьшение на единицу TTL и после этого трафик будет передан в точку (6). Здесь он попадает в функцию in_output(), которая по ip 172.20.6.37 присвоит трафику выходящий шлюз 172.20.50.2 и интерфейс fxp0. Далее трафик попадает в ipfw и идет по правилам. По правилу 10040 трафик попадает в трубу pipe 1. Однако после этого он не покинет фаервола,  так как  net.inet.ip.fw.one_pass=0. Дойдя до правила 10400, трафик переходит к правилу 30000 и попадает в точку (7), затем в локальную сеть на узел 172.20.6.37.

         Ответный трафик с узла 172.20.6.37 войдет в точку (1), попадет в проход in, получит тег fxp0(in via fxp0).  Затем трафик зайдет в (2) и попадет в функцию ip_input(). Далее он зайдет в фаервол  и пойдет по цепочке правил. Правило 5000 определит трафику таблицу маршрутизации 0, и он пойдет по правилам дальше. По правилу 10045 трафик попадает в трубу pipe 1. Однако после этого он не покидает фаервола  так как  net.inet.ip.fw.one_pass=0. Дойдя до правила 10405, трафик  переходит к правилу 30000, выйдет из фаервола, попадает в точку (4) и управление передается функции  ip_forward(), которая произведет уменьшение на единицу TTL. После этого трафик будет передан в точку (6) – функцию in_output(). Она  по таблице маршрутизации 0, внешнего адреса узла, от которого шел запрос, назначит шлюз по умолчанию (212.111.198.33) и имя выходящего интерфейса re1(out via re1). Далее трафик передается функции  ipfw_chk() и идет по правилам фаервола. Дойдя до правила 10710, управление передается на правило 20100. По нему управление передается в точку (8), попадает в nat. Здесь, по записи соответствия для nat 3 локальный адрес 172.20.6.37 либо будет заменен внешним адресом 212.111.198.37, если она там  была, либо замены не произойдет вообще, так как запись соответствия была сформирована в nat 4. Далее трафик попадет в точку (9), затем в точку (6) в функцию in_output(). Снова пойдет по правилам фаервола, дойдет до правила 20120 и выйдет через точку (7) в Интернет. В этом случае соединения с удаленным узлом установлено не будет. Однако если в правила фаервола добавить строку

ipfw add 5030 setfib 1 ip from 172.20.6.37 to any in recv fxp0

тогда для трафика узла 172.20.6.37 будет переопределена таблица маршрутизации 1, и после попадания в точку (6) функция in_output() назначит шлюз по умолчанию (78.152.183.33) и имя выходящего интерфейса re0(out via re0). В этом случае соединение с удаленным узлом будет установлено. Однако предыдущий случай, рассмотренный для установления соединения по адресу 212.111.198.37, работать не будет.

         Рассмотрим случай переназначения входящих соединений, поступающих на указанный порт, на другой узел, которым может быть либо узел в локальной сети, либо сам router NAT. Предположим, что с удаленного узла была введена команда на telnet соединение по порту 8024:

telnet 78.152.183.34  8024

В этом случае с Интернет приходит трафик, адресованный на ip 78.152.183.34 и порт 8024 по протоколу tcp. Тогда он войдет в точку (1), попадет в проход in, получит тег re0(in via re0).  Затем трафик зайдет в (2) и попадет в функцию ip_input(). Далее он зайдет в фаервол  и пойдет по цепочке правил. Дойдя до правила 6010, трафику будет назначена таблица маршрутизации 1. В правиле 20000 трафик пойдет в точку (8) на обработку в nat (по nat 1), как входящий трафик. Там он будет демаскирован по статической записи 172.21.8.2:23 8024 <-> любой_ip:любой_port. Далее трафик выйдет в точку (9), перейдет в точку (2) и продолжит идти  по правилам фаервола. Попав на правило 20020, трафик выходит из ipfw  и возвращается функции  ip_input(). Она определяет, что трафик предназначен не нам, передает его в точку (4), где функция ip_forward() произведет уменьшение на единицу TTL и после этого трафик будет передан в точку (6). Здесь он попадает в функцию in_output(), которая по ip 172.21.8.2 присвоит трафику выходящий шлюз 172.21.88.1 и интерфейс fxp0. Далее трафик попадает в ipfw и идет по правилам. По правилу 10060 трафик попадает в трубу pipe 10. Однако после этого он не покинет фаервола,  так как  net.inet.ip.fw.one_pass=0. Дойдя до правила 10420, трафик переходит к правилу 30000 и попадает в точку (7), затем в локальную сеть на узел 172.21.8.2, где запускается программа telnetd на порту 23.

         Ответный трафик от узла 172.21.8.2:23 войдет в точку (1), попадет в проход in, получит тег rl0(in via rl0).  Затем трафик зайдет в (2) и попадет в функцию ip_input(). Далее он зайдет в фаервол  и пойдет по цепочке правил. Правило 5010 определит трафику таблицу маршрутизации 1, и он пойдет по правилам дальше. По правилу 10065 трафик попадает в трубу pipe 10. Однако после этого он не покидает фаервола  так как  net.inet.ip.fw.one_pass=0. Дойдя до правила 10425, трафик  переходит к правилу 30000, выйдет из фаервола, попадает в точку (4) и управление передается функции  ip_forward(), которая произведет уменьшение на единицу TTL. После этого трафик будет передан в точку (6) – функцию in_output(). Она  по таблице маршрутизации 1, внешнего адреса узла, от которого шел запрос, назначит шлюз по умолчанию (78.152.183.33) и имя выходящего интерфейса re0(out via re0). Далее трафик передается функции  ipfw_chk() и идет по правилам фаервола. Дойдя до правила 20000, управление передается в точку (8), попадает в nat. Здесь, по записи соответствия для nat 1 локальный адрес 172.21.8.2:23 будет маскирован на 78.152.183.34:8024 в соответствии со статической записью в nat 1. Далее трафик попадет в точку (9), затем в точку (6) в функцию in_output(). Снова пойдет по правилам фаервола, дойдет до правила 20020 и выйдет через точку (7) в Интернет. Таким образом, соединение будет установлено.

         Аналогично будут срабатывать правило для nat 1 - redirect_port udp 78.152.183.34:53 53 и все правила в nat 2.

         Рассмотрим особый случай – правило redirect_port tcp 78.152.183.34:23 8023 для nat 1. Согласно нему, если на удаленном компьютере набрать команду

telnet 78.152.183.34  8023

соединение по telnet должно установиться с router-ом NAT. Рассмотрим прохождение трафика в этом случае. С Интернет приходит трафик, адресованный на ip 78.152.183.34 и порт 8023 по протоколу tcp. Он войдет в точку (1), попадет в проход in, получит тег re0(in via re0).  Затем трафик зайдет в (2) и попадет в функцию ip_input(). Далее он зайдет в фаервол  и пойдет по цепочке правил. Дойдя до правила 6010, трафику будет назначена таблица маршрутизации 1. В правиле 10830 трафик будет переброшен на правило 20300, а потом пойдет в точку (8) на обработку в nat (по nat 5), как входящий трафик. Там он будет демаскирован по статической записи 78.152.183.34:23 8023 <-> удаленный_любой_ip:удаленный_любой_port. Далее трафик выйдет в точку (9), перейдет в точку (2) и продолжит идти  по правилам фаервола. Попав на правило 30000, трафик выходит из ipfw  и возвращается функции  ip_input(). Она определяет, что трафик предназначен нам, передает его в точку (3), где запустится программа telnetd:23 на обработку трафика с адресом 78.152.183.34:23, после чего он будет передан в точку (5) и потом в точку (6). Но  telnetd:23, запущенный на routerNAT сформирует трафик с исходящим адресом 212.111.198.39, а не 78.152.183.34, так как по умолчанию он работает с 0-й таблицей маршрутизации (иначе не объяснить наблюдаемый поток пакетов через интерфейсы с помощью программы trafshow). Попав в функцию ip_output() она по ip 212.111.198.39 присвоит трафику выходящий шлюз 212.111.198.33 и интерфейс re1. Далее трафик попадает в ipfw и идет по правилам. Дойдя до правила 10840, трафик переходит к правилу 20310 и попадает в точку (8) на обработку в nat (по nat 5), как исходящий трафик. Там он будет маскирован в адрес 78.152.183.34:8023 и перейдет в точку (9), (6) и вновь пойдет по правилам. Правило 20320 переназначит трафику шлюз по умолчанию с 212.111.198.33 на 78.152.183.33 и по правилу 30000 произойдет выход трафика из фаервола. Далее управление передается функции ip_output(), которая переназначит трафику выходящий интерфейс с  re1 на re0 и выведет его в Интернет.

Для сети 172.20.0.0/16(работают 2 канала Интернет):

Трафик, инициализированный, например, от 172.20.50.20 войдет router NAT через сетевой интерфейс fxp0, пройдет через точку (1), получит тег с сетевым интерфейсом fxp0 (in via fxp0). Далее трафик зайдет в точку (2) и попадет в функцию ip_input(). После этого он зайдет в фаервол и пойдет по правилам. Трафик дойдет до правила 5000 и ему будет назначена таблица маршрутизации 0. По правилу  10045 он войдет в трубу pipe 1, а по правилу 10405 будет передан на правило 30000, которое выведет трафик из ipfw. Затем он вновь попадает в функцию ip_input(), где принимается решение, что трафик принадлежит не нам. Поэтому он будет направлен  в функцию ip_forward(), точку (4). В ней у трафика будет уменьшено значение TTL, после чего он будет передан в точку (6) в функцию ip_output(). По таблице маршрутизации 0 трафику будет присвоен шлюз по умолчанию 212.111.198.33 и тег с исходящим интерфейсом re1 (out via re1). Трафик войдет в фаервол и начнет проходить по цепочке правил. Он дойдет до правила 20010 и будет передан в точку (8) на обработку в nat 2 как выходящий трафик. Для него в таблице соединений будет создана запись соответствия пар 172.20.50.20:порт<->удаленный_ip:порт и ip-адрес 172.20.50.20 в заголовке пакета будет заменен ip-адресом 212.111.198.39. В точке (9) трафик выйдет из nat 2 и в точке (6) зайдет заново в ip_output(). Однако после этого он не покинет фаервола,  так как  net.inet.ip.fw.one_pass=0. В правиле 20020 трафик выйдет из фаервола и уйдет в Интернет с адресом отправителя 212.111.198.39, а не 172.20.50.20 через интерфейс re1 (out via re1).

Трафик, возвращающийся в виде ответа, зайдет в точку (1) и попадет в проход in, получит тег с сетевым интерфейсом re1(input via re1), через который он вошел. Далее трафик зайдет в точку (2) и попадет в функцию ip_input() после чего войдет в фаервол и начнет проходить через цепочку правил. Трафик дойдет до правила 6000 и ему будет присвоена таблица маршрутизации 0. Пойдет по правилам далее, дойдет до 20010 и будет передан в точку (8) на обработку в nat 2 как входящий трафик, поступивший из прохода in. В подсистеме nat для трафика в таблице соединений будет найдена ранее созданная запись соответствия пар 172.20.50.20:порт <-> 212.111.198.39:порт <-> IP_удаленный:порт, после чего он адрес получателя в заголовке IP пакета будет заменен с 212.111.198.39 на 172.20.50.20. Далее  в точке (9) трафик выйдет из подсистемы nat и будет передан обратно в точку (2), попав ip_input(). Затем он вновь попадает в ipfw, доходит до правила 20020, по нему выходит из фаервола и попадет обратно в ip_input() где будет принято решение о том, что он предназначен не нам, а следовательно должен быть направлен в точку (4), в функцию ip_forward(). Здесь  у IP трафика будет уменьшено значение TTL, и он будет передан в точку (6) в функцию ip_output(). Попав ip_output() ему будет присвоен шлюз 172.20.50.2 и тег с сетевым интерфейсом fxp0, через который он должен выйти в локальную сеть (out via fxp0). Трафик войдет в фаервол и начнет проходить через цепочку правил. Он дойдет до правила 10040, попадет в трубу pipe 1, дойдет до правила 10400 по которому переместиться на 30000,и по нему выйдет из ipfw. После этого трафик уйдет в локальную сеть через fxp0, имея IP адрес получателя 172.20.50.20.

         Если с Интернет приходит трафик, не имеющий в таблице соединений записи соответствия, то такой трафик при поступлении в nat будет отвергнут, так как в настройках nat выставлен параметр deny_in.

            Для сети 172.21.0.0/16, когда  работают 2 канала Интернет, алгоритм прохождения трафика аналогичный. Только трафик попадает по правилам фаервола в таблицу маршрутизации 1 и выходит через интерфейс re0.
            Для сети 172.20.0.0/16(работает 1 канала Интернет на Infocom):
            Предположим, что канал на URAN отключен. Тогда скрипт /usr/home/alex/rout.exe удаляет для 0-й таблицы маршрутизации шлюз по умолчанию 212.111.198.33 и вместо него устанавливает 78.152.183.33:
route delete default 212.111.198.33
route add default 78.152.183.33
Тогда 0-я и 1-я таблицы маршрутизации становятся идентичны. Алгоритм прохождения трафика будет следующим.
            Трафик, инициализированный, например, от 172.20.50.20 войдет router NAT через сетевой интерфейс fxp0, пройдет через точку (1), получит тег с сетевым интерфейсом fxp0 (in via fxp0). Далее трафик зайдет в точку (2) и попадет в функцию ip_input(). После этого он зайдет в фаервол и пойдет по правилам. Трафик дойдет до правила 5000 и ему будет назначена таблица маршрутизации 0. По правилу  10045 он войдет в трубу pipe 1, а по правилу 10405 будет передан на правило 30000, которое выведет трафик из ipfw. Затем он вновь попадает в функцию ip_input(), где принимается решение, что трафик принадлежит не нам. Поэтому он будет направлен  в функцию ip_forward(), точку (4). В ней у трафика будет уменьшено значение TTL, после чего он будет передан в точку (6) в функцию ip_output(). По таблице маршрутизации 0 трафику будет присвоен шлюз по умолчанию 78.152.183.33 и тег с исходящим интерфейсом re0 (out via re0). Трафик войдет в фаервол и начнет проходить по цепочке правил. Он дойдет до правила 20000 и будет передан в точку (8) на обработку в nat 1 как выходящий трафик. Для него в таблице соединений будет создана запись соответствия пар 172.20.50.20:порт<->удаленный_ip:порт и ip-адрес 172.20.50.20 в заголовке пакета будет заменен ip-адресом 78.152.183.34. В точке (9) трафик выйдет из nat 1 и в точке (6) зайдет заново в ip_output(). Однако после этого он не покинет фаервола,  так как  net.inet.ip.fw.one_pass=0. В правиле 20020 трафик выйдет из фаервола и уйдет в Интернет с адресом отправителя 78.152.183.33, а не 172.20.50.20 через интерфейс re0 (out via re0).

         Трафик, возвращающийся в виде ответа, зайдет в точку (1) и попадет в проход in, получит тег с сетевым интерфейсом re0(input via re0), через который он вошел. Далее трафик зайдет в точку (2) и попадет в функцию ip_input() после чего войдет в фаервол и начнет проходить через цепочку правил. Трафик дойдет до правила 6010 и ему будет присвоена таблица маршрутизации 0. Пойдет по правилам далее, дойдет до 20000 и будет передан в точку (8) на обработку в nat 1 как входящий трафик, поступивший из прохода in. В подсистеме nat для трафика в таблице соединений будет найдена ранее созданная запись соответствия пар 172.20.50.20:порт <-> 78.152.183.34:порт <-> IP_удаленный:порт, после чего он адрес получателя в заголовке IP пакета будет заменен с 78.152.183.34 на 172.20.50.20. Далее  в точке (9) трафик выйдет из подсистемы nat и будет передан обратно в точку (2), попав ip_input(). Затем он вновь попадает в ipfw, доходит до правила 20020, по нему выходит из фаервола и попадет обратно в ip_input() где будет принято решение о том, что он предназначен не нам, а следовательно должен быть направлен в точку (4), в функцию ip_forward(). Здесь  у IP трафика будет уменьшено значение TTL, и он будет передан в точку (6) в функцию ip_output(). Попав ip_output() ему будет присвоен шлюз 172.20.50.2 и тег с сетевым интерфейсом fxp0, через который он должен выйти в локальную сеть (out via fxp0). Трафик войдет в фаервол и начнет проходить через цепочку правил. Он дойдет до правила 10040, попадет в трубу pipe 1, дойдет до правила 10400 по которому переместиться на 30000,и по нему выйдет из ipfw. После этого трафик уйдет в локальную сеть через fxp0, имея IP адрес получателя 172.20.50.20.

Для сети 172.21.0.0/16, когда  работает 1 канал Интернет, алгоритм прохождения трафика такой же, как и в случае работы 2-х каналов.

Предположим, что на компьютере 172.20.6.37 установлен WEB сервер. Для того чтобы он отзывался по имени, например ed.tup.km.ua в случае работы 2-х каналов связи и в случае обрыва канала URAN, необходимо в DNS сервере прописать следующие строки:

ed   0          IN      A       212.111.198.37

ed   0          IN      A       78.152.183.35

Таким образом, одно доменное имя имеет два ip адреса. Число 0 означает, сколько времени необходимо удерживать запись  соответствия имени ip-адресу до следующего возобновления в кеши компьютера. Чем меньше это время, тем быстрее компьютер подставит для запроса другой ip-адрес в случае невозможности доступа по предыдущему ip-адресу.

         Выводы.

1.Благодаря возможности FreeBSD 7.3-RELEASE поддерживать одновременно несколько таблиц маршрутизации удается просто перенаправлять трафик с разных локальных сетей на различные внешние каналы Интернет.

2.Переключение трафика на разные таблицы маршрутизации возможно лишь для входного трафика.  Например, работает команда

ipfw add 5000 setfib 0 ip from any to any in via fxp0

Команда

ipfw add 5000 setfib 0 ip from any to any out via fxp0

не работает.

3.Замечено, что в версии FreeBSD 8.1-RELEASE не работает параметр sysctl -

net.inet.ip.fw.one_pass.

4.Замечено, что если приложение запущено на NAT узле, то при внешнем запросе на него с интерфейса, принадлежащего не нулевой таблице маршрутизации, это приложение отсылает ответы на внешний интерфейс, описанный в нулевой таблице. Например, работа с telnet, ftp. Поэтому приходится в ipfw использовать fwd как в рассмотренном примере.

Литература.

1. Подробное руководство по ipfw nat. http://www.lissyara.su/articles/freebsd/tuning/ipfw_nat/, 2010

2.Гончаров В. ipfw: порядок прохождения пакетов, сложные случаи http://nuclight.livejournal.com/124348.html, 2008