Два шлюза в Интернет и 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
|
Такая схема маршрутизации не будет правильно работать с 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 выполняется дальнейшая проброска внутрь сети.
[править] Дополнительная информация
- Default gateway
- Linux Advanced Routing & Traffic Control HOWTO — перевод руководства по iproute2 и управлению трафиком в Линукс
[править] Материалы по OpenVPN на xgu.ru
- OpenVPN
- Два шлюза в Интернет и OpenVPN
- OpenVPN в Windows
- OpenVPN Bridge — передача тегированного трафика через VPN
- OpenVPN Proxy ARP