Xgu.ru теперь в Контакте  — приходите и подключайтесь.
Пока мы работаем над следующими видео, вы можете подключиться в Контакте. Познакомимся и обсудим новые страницы и ролики.

Vk-big.pngYoutube-big.jpeg

Два шлюза в Интернет и OpenVPN

Материал из Xgu.ru

Перейти к: навигация, поиск

ОС и ПО: Linux, OpenVPN

Автор: Игорь Чубин
Короткий URL: openvpn/gw

На странице описывается как с помощью OpenVPN и двух независимых интернет-каналов низкой надёжности организовать надёжное подключение удалённой сети (или системы) к центральной сети.

Приведённое решение может быть полезно и без использования VPN — для решения задачи автоматического выбора основного шлюза.



Содержание

[править] Задача

Есть два канала связи с Интернетом, через двух независимых провайдеров. Один из каналов является основным, второй — резервным. Резервный канал нужно использовать только тогда, когда недоступен основной.

Нужно сделать так, что бы как только Интернет становится недоступным через одно из соединений (не имеет значения из-за того что пропала связь с провайдером, или потому что проблемы у провайдера), автоматически менять маршрут по умолчанию и использовать другой канал.

Как только связь через основной канал восстанавливается, необходимо возвращаться на его использование.

Рассмотреть ситуацию, когда компьютер входит в другую сеть через OpenVPN:

  • OpenVPN должен перестартовывать после смены маршрута;
  • (Важно!) когда OpenVPN-соединение установлено, маршрут по умолчанию направлен в частную сеть через OpenVPN.

Если OpenVPN не используется, ничего страшного — задача только упрощается.

[править] Схема

Схема включения шлюза gw в Интернет и локальную сеть (LAN), показана ниже.

            +-------+
            |CENTRAL|
            |  VPN  |
            |  HUB  |
            +---+---+
                |
           _____._
      ____/       \___
   __/                \
  /                    \__
 |                        \
  \       Internet         |
   |                       |
    \ _                   .
       \              ___/
        \___         _/
            \_______/
            GW1   GW2
             *     *
             ||    |
         IP1 ||    | IP2
      [eth1] ||    | [eth2]
            +-------+
            |       |
            |  gw   |
            |       |
            +---+---+
                | [eth0]
                |-
               LAN

  • CENTRAL VPN HUB — центральный VPN-концентратор, на который нужно держать VPN-канал;
  • GW1 — шлюз первого провайдера, на интерфейсе с нашей стороны установлен IP-адрес IP1 (предпочитаемый канал, отмечен двойной линией);
  • GW2 — шлюз второго провайдера, на интерфейсе с нашей стороны установлен IP-адрес IP2.

На шлюзе gw работает VPN-клиент, который через Интернет должен устанавливать туннель на центральный VPN-сервер (CENTRAL VPN HUB).

[править] Файл /etc/network/gateways

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

  • IP1 — адрес первого интерфейса (eth1);
  • IP2 — адрес второго интерфейса (eth2);
  • GW1 — адрес шлюза, доступного через первый интерфейс;
  • GW2 — адрес шлюза, доступного через второй интерфейс;
  • DEFAULTGW — адрес предпочитаемого (один из GW1 и GW2) шлюза (если он работает, лучше использовать его).

Пример файла /etc/network/gateways

#### ISP 1
IP1=10.0.1.1
GW1=10.0.1.2

#### ISP2
IP2=10.0.4.1
GW2=10.0.4.4

### Let ISP1 be default
DEFAULTGW=${GW1}

Для того чтобы избежать случайного внутреннего противоречия в настройках, стоит использовать эти адреса и при настройке интерфейсов. Например, для Debian GNU/Linux, если настройка интерфейса выполняется статически через файл /etc/network/interfaces:

iface eth1 inet manual
    up sh -c '. /etc/network/gateways ; ifconfig eth1 $IP1'
iface eth2 inet manual
    up sh -c '. /etc/network/gateways ; ifconfig eth2 $IP2'

(если при настройке интерфейса нужно указывать и маску, следует добавить соответствующую переменную в файл /etc/network/gateways и использовать её при настройке интерфейса).

[править] Автоматическая правка файла /etc/network/gateways

IP-адреса сетевых интерфейсов и шлюзов, указанные в файле /etc/network/gateways, могут изменяться, при условии, что они назначаются динамически при конфигурировании интерфейса. В этом случае необходимо чтобы файл отражал изменения адресов.

Автоматическая правка файла /etc/network/gateways возможно с помощью скрипта, который вызывается при поднятии соответствующего интерфейса.

Для Debian GNU/Linux это можно сделать или указав скрипт в директиве up в файле /etc/network/interfaces или разместив в соответствующем каталоге, например /etc/ppp/ip-up.d/.

Пример скрипта который вызывается при поднятии интерфейса показан ниже. Здесь интерфейс соответствует каналу 1, и поэтому он правит переменные GW1 и IP1.

Пример. Скрипт автоматической правки файла /etc/network/gateways, вызываемый при подъёме интерфейса.

#!/bin/sh
case $1 in
  ppp200)
    /bin/ip rule add from $4 lookup 3
    /bin/ip route add default via $5 table 3
    /usr/bin/perl -i -p -e 's/GW1=.*/GW1='"$5"/  /etc/network/gateways
    /usr/bin/perl -i -p -e 's/IP1=.*/IP1='"$4"/  /etc/network/gateways
;;
esac
exit 0

[править] Маршрутизация с двумя шлюзами

Маршрутизатор имеет два интерфейса, которые смотрят в Интернет через двух провайдеров (два другие независимые шлюза). При простой настройке маршрут по умолчанию может быть только один. Это означает, что весь трафик со шлюза (за исключением трафика, адресованного в непосредственно подключенные сети, и сети, маршрут в которые явно указан) будет уходить через это маршрут.

Нам необходимо сделать так, чтобы шлюз отправлял пакеты в интернет, в зависимости от того, какой у них обратный адрес:

  • трафик с интерфейса eth1 должен уходить через GW1;
  • трафик с интерфейса eth2 должен уходить через GW2.

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

Эту задачу можно решить с помощью policy routing, настройка которой в Linux возможна с помощью iproute2.

Например для шлюзов GW1 и GW2, которые описываются в /etc/network/gateways:

. /etc/network/gateways
ip rule add from $IP1 lookup 2
ip rule add from $IP2 lookup 3
ip route add default via $GW1 table 2
ip route add default via $GW2 table 3
ip route add default via $DEFAULTGW

Note-icon.gif

Такая схема маршрутизации не будет правильно работать с iptables DNAT. Другими словами, если с помощью iptables/netfilter пробрасывать обращения на какие-то порты внутрь сети, принцип «ответы уходят по тому каналу, по которому приходят запросы» работать не будет. Один из способов решения проблемы описан ниже, в разделе «Два шлюза в Интернет и NAT».

[править] Автоматическая смена маршрута по умолчанию

Основной маршрут нужно изменить, когда он перестаёт работать. Как проверить, что маршрут больше не работает?

Самое простое — проверять доступность ближайшего по этому маршруту шлюза, то есть шлюза провайдера. Это способ плох тем, что шлюз провайдера может быть доступен, но у самого провайдера могут быть проблемы со связью.

Лучший способ — проверять доступность сервера, на который должен быть установлен туннель через тот или иной канал. Это можно сделать путём отправки ICMP-запросов с того или иного интерфейса.

Скрипт использует второй способ. Каждые 30 секунд (CHECK_INTERVAL) выполняется проверка. Как только связь через приоритетный шлюз теряется, а связь через второй шлюз при этом есть, производится переход на использование второго шлюза в качестве основного.

При восстановлении связи через приоритетный шлюз, скрипт переводит систему на использование его в качестве основного (default gateway).

При любой смене шлюза (как с приоритетного на резервный, так и наоборот) в syslog попадает сообщение о том, что шлюз поменялся, и указывается причина этого изменения:

Sep  3 13:12:15 stab /usr/local/bin/change_default_route: Gateway 199.5.5.111 (199.5.5.112) is not usable. Trying 221.42.167.30 (221.
42.167.29)
Sep  3 13:12:16 stab /usr/local/bin/change_default_route: Changing default route from 199.5.5.111 to 212.42.167.30

[править] Скрипт change_default_route

Состояние шлюзов контролирует скрипт, change_default_route, работающий в соответствии с вышеописанным алгоритмом.

Переменные, которые должны быть заданы в скрипте:

  • GW_CONF — имя файла /etc/network/gateways (или другое, если он называется иначе)
  • OPENVPN_UPLINK — имя конфигурации OpenVPN;
  • CHECK_INTERVAL — периодичность проверки доступности удалённой точки через шлюзы, в секундах.

Имя конфигурации OPENVPN_UPLINK определяет имя конфигурационного файла OpenVPN и процесс openvpn, который должен быть перезапущен после смены маршрута. Например,

   OPENVPN_UPLINK=kiev

соответствует конфигурационный файл

   /etc/openvpn/kiev.conf

IP-адрес VPN-сервера, с которым должен устанавливаться туннель, и на возможность связи с которым постоянно проверяются шлюзы, определяется из этого конфигурационного файла, из директивы remote.


Скрипт может быть размещён, например, здесь:

   /usr/local/sbin/change_default_route

Скрипт должен вызываться автоматически при старте системы. Например, в /etc/rc.local:

nohup /usr/local/sbin/change_default_route &

или в /etc/network/interfaces

iface ...
 ....  
 up nohup /usr/local/sbin/change_default_route &

Скрипт /usr/local/sbin/change_default_route


#!/bin/sh

############################################################
# 
# Set the variables berfore starting the script:

GW_CONF=/etc/network/gateways
OPENVPN_UPLINK=kiev
CHECK_INTERVAL=30               #seconds between gw check

#############################################################

OPENVPN_UPLINK_CONFIG=/etc/openvpn/${OPENVPN_UPLINK}.conf

log()
{
  echo "$@" | logger -t $0 -p daemon.info
}

current_uplink_gateway()
{
    uplink_addresses=`grep ^remote ${OPENVPN_UPLINK_CONFIG} | sed s/remote// | tr -d ' \t' | tr '\n' '|';echo default `
    ip route show | grep -v tun | egrep "^($uplink_addresses)" | awk '{print $3}' | tail -1
}

read_gateways()
{
    [ -f ${GW_CONF} ] && source ${GW_CONF}
    [ -f ${GW_CONF} ] || echo file ${GW_CONF} missing | logger -t $0 -p daemon.error
    [ -z "$GW" ] && GW=`current_uplink_gateway`
}

delete_uplink_gateway()
{
    [ -z "$GW" ] && return 
    for remote in $(grep ^remote ${OPENVPN_UPLINK_CONFIG} | sed s/remote// | tr -d ' \t')
    do
        ip route delete $remote via "${GW}"
    done
}

change_uplink_gateway()
{
    [ -z "$GW" ] && return 
    NEWGW=$1
    uplink_addresses=`grep ^remote ${OPENVPN_UPLINK_CONFIG} | sed s/remote// | tr -d ' \t' | tr '\n' '|';echo default `
    for remote in `ip route show | grep -v tun | egrep "^($uplink_addresses)" | awk '{print $3}'`
    do
        ip route change $remote via "${GW}"
    done
}
openvpn_uplink_pid()
{
#    uplink_addresses=`grep ^remote ${OPENVPN_UPLINK_CONFIG} | sed s/remote// | tr -d ' \t' | tr '\n' '|';echo FINAL_ITEM `
#    netstat -np 2> /dev/null  | grep openvpn | egrep $uplink_addresses | perl -n -e 's@/.*$@@; m@([0-9]+)$@; print $1."\n";'
    cat /var/run/openvpn.${OPENVPN_UPLINK}
}

ping_remote_site()
{
    for remote in $(grep ^remote ${OPENVPN_UPLINK_CONFIG} | sed s/remote// | tr -d ' \t')
    do
        ping -q -c 1 "$@" $remote >& /dev/null && return 0
    done
    return 1
}

set_default_route()
{
    NEWGW=$1
    [ "$GW" == "$NEWGW" ] && return 0
    log "Changing default route from $GW to $NEWGW"

#    openvpn_uplink_pid=`openvpn_uplink_pid`

    /etc/init.d/openvpn stop $OPENVPN_UPLINK

    route delete default gw "$GW"
    delete_uplink_gateway
    route add default gw "$NEWGW"

    /etc/init.d/openvpn start $OPENVPN_UPLINK

# Restarting openvpn to make him use new gw
#    [ -z "$openvpn_uplink_pid" ] && /etc/init.d/openvpn restart || kill -1 $openvpn_uplink_pid

    GW=${NEWGW}
}

while true
do
    read_gateways

    if [ "${DEFAULTGW}" = "${GW1}" ]
    then 
        if ping_remote_site -I ${IP1} 
        then 
            set_default_route ${GW1}
        else
            log "Gateway $GW1 ($IP1) is not usable. Trying $GW2 ($IP2)" 
            if ping_remote_site -I ${IP2} 
            then
                set_default_route ${GW2}
            else
                log "Uplink can't be reach by any path. Waiting" 
            fi
        fi
    else
        if ping_remote_site -I ${IP2} 
        then 
            set_default_route ${GW2}
        else
            log "Gateway $GW2 ($IP2) is not usable. Trying $GW1 ($IP1)" 
            if ping_remote_site -I ${IP1} 
            then
                set_default_route ${GW1}
            else
                log "Uplink can't be reach by any path. Waiting" 
            fi
        fi
    fi

    sleep ${CHECK_INTERVAL}
done

exit 0

[править] Дополнительные вопросы

[править] Два шлюза в Интернет и NAT

Вышеописанный способ маршрутизации в зависимости от источника (policy routing) не будет работать для систем внутри сети, доступных через NAT.

Другими словами, если с помощью iptables/netfilter пробрасывать обращения на какие-то порты внутрь сети, принцип «ответы уходят по тому каналу, по которому приходят запросы» работать не будет.

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

Один из способов решения проблемы описан ниже.

Для решения проблем с трансляцией соединений внутрь сети, необходимо использовать схему с промежуточным шлюзом:

            GW1   GW2
             *     *
             |     | 
         IP1 |     | IP2
      [eth1] |     | [eth2]
            +-------+
            |       |
            |  gw   |
            |       |
            +-------+
    10.0.3.250  |  10.0.3.254
        [eth0]  |  [eth0:1]
                | 
                |
    10.0.3.249  |  10.0.3.253
        [eth1]  |  [eth1:1]
            +-------+
            |       |
            |  pgw  |
            |       |
            +-------+
               | 10.0.3.6
               | [eth0]
               |

В этом случае:

  • на шлюзе gw выполняется проброска на один из внутренних адресов, в зависимости от того, куда пришёл запрос;
  • на шлюзе pgw выполняется дальнейшая проброска внутрь сети.

[править] Дополнительная информация

[править] Материалы по OpenVPN на xgu.ru