Переключения между провайдерами интернета на Debian 7

от автора

В последних дистрибутивах Linux довольно много всяких полезных плюшек в папке /etc, однако мало кто ими грамотно пользуется. Здесь я расскажу про часть из них применительно к последней стабильной версии Debian, и приведу пример реализации переключения на резервный канал.
Все попытки найти банальную автопереключалку приводили к связке из 1-2 скриптов, написанным «под себя» и мало поддающимися настройке. К dhcp не был привязан ни один из них, а значит любые манипуляции на стороне провайдера требовали вмешательство в настройки. Сам писал такие в свое время, но вот теперь решил на новой системе оформить это красиво – так, как это задумывали разработчики Debian, а именно – меняем файлы конфигурации, добавляем свои скрипты и не трогаем те, что нам предоставила система.

Итак, имеем:
— два кабеля от двух провайдеров, оба выдают IP по dhcp
— свежесобранный сервер под управлением debian squeeze с тремя сетевухами (возможно потом добавлю еще)
— желание чтоб инет не пропадал (работа не ждет!). Балансировку и т.п. оставим на потом.

Логика на первый взгляд простая:
— пингуем какой-нибудь хост по очереди через разные интерфейсы
— если пинг нестабильный, переключаемся на резервный канал.
Вот только в реализации была поставлена цель сделать всё максимально гибко, например не ограничивать количество потенциальных провайдеров и впоследствии делать минимум телодвижений для перенастройки.
Для начала посмотрим какие плюшки уже есть в системе
В папке /etc/network нас интересует файл interfaces и папки if-down.d, if-post-down.d, if-pre-up.d, if-up.d.

root@ns:/etc/network# cat interfaces # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5).  # The loopback network interface iface lo inet loopback  # The primary network interface iface eth0 inet static     address 192.168.104.1     netmask 255.255.255.0  iface eth1 inet dhcp iface eth3 inet dhcp 

При манипуляциях с интерфейсами через ifup/ifdown на папочки натравливается run-parts, и скриптам в них доступны следующие переменные окружения: IFACE, LOGICAL, ADDRFAM, METHOD, MODE, PHASE, MODE, VERBOSITY, PATH
При старте системы сначала запускаются скрипты из папки if-pre-up.d (по одному разу для каждого интерфейса, но перед ними идет IFACE=”—all”, потом поднимаются интерфейсы и запускаются скрипты из папки if-up.d и IFACE=”—all” идет уже в конце. При ifup ethX запускается по одному разу только для ethX (без “–all”). Аналогично if-down.d и if-post-down.d при ifdown и выключении системы.
Система позволяет назначить для каждой операции и для каждого интерфейса свой скрипт, но вносить похожие изменения каждый раз в 10 из 20 скриптов в мои планы не входило, поэтому будем писать один большой скрипт и расставим на него симлинки изо всех четырех папок. Понять, откуда он запущен, можно по переменным окружения.

Однако нам еще надо узнать информацию о шлюзах, которая пришла по dhcp. На этот случай тоже есть папки со скриптами /etc/dhcp/dhclient-enter-hooks.d и /etc/dhcp/dhclient-exit-hooks.d
Последовательность запуска такая:
— опросили сервер dhcp
— запустили содержимое папки dhclient-enter-hooks.d
— настроили сетевые параметры (ip, DNS, шлюз,…)
— запустили содержимое папки dhclient-exit-hooks.d
Скриптам тоже доступны разные полезные переменные (параметры, которые пришли от dhcp сервера), часть из которых нам надо будет сохранять.

После нескольких вечеров получилось следующее:
Все скрипты лежат в папке /etc/network/scripts. Из разных папок туда ведут симлинки
Настройки — /etc/default/network-scripts
Временые файлы кладем /var/lib/dhcp
Логи пишутся в файл /var/log/network-scripts.log
Настройки

# cat /etc/default/network-scripts # Configuration file for /etc/network/scripts/*  # Host to ping for autoroute HOST_TO_PING="4.2.2.1"  # Number of pings to check the connection PING_COUNT=5  # list of LAN interfaces IFLAN="eth0"  # WAN prio from first to last IFWAN="eth1 eth3"  # open ports from WAN zone WAN_PORTS_OPEN="" 

Тут всё должно быть понятно. Интерфейсы LAN, WAN можно дописывать сколько угодно. В списке WAN первый – самый приоритетный, далее по убыванию.
Отдельно файл с функциями.

# cat /etc/network/scripts/functions #!/bin/sh  DHCPLIB="/var/lib/dhcp" LOGDIR="/var/log" LOGFILE="$LOGDIR/network-scripts.log"  HOST_TO_PING="4.2.2.1" PING_COUNT=3 SQUID_PORT="3128" IFLAN="" IFWAN="" WAN_PORTS_OPEN=""  . /etc/default/network-scripts  # Local variables DEFAULTWAN=${IFWAN% *}  log() {     DATE=`date`     echo "$DATE $@" >> $LOGFILE }  warn() {     log "WARNING: $@"     echo "WARNING: $@" }  cmd() {     $@     RES=$?     log "$RES - $@"     return $RES }  get_ip() {     IP=`ip addr list $1 | grep "  inet " | head -n 1 | cut -d " " -f 6 | cut -d / -f 1` }  update_local_redirect() {     for i in $IFLAN; do         cmd iptables -t nat $INS PREROUTING -i $i -p tcp --dport 80 -d $1 -j ACCEPT     done }  update_squid() {     case $1 in         start)             ADD="-A"             INS="-I"             ;;         stop)             ADD="-D"             INS="-D"             ;;         *)             ADD="-C"             INS="-C"     esac      for i in $IFLAN; do         # transparent proxy         cmd iptables -t nat $ADD PREROUTING -i $i -p tcp --dport 80 -j REDIRECT --to-port $SQUID_PORT     done } 

Тут мы имеем:
— импорт настроек из /etc/default/network-scripts
— ведение логов (log, warn),
— запуск команд с записью в лог параметров и результатов работы
— update_local_redirect() добавляет маршруты на 80 порт мимо transparent proxy
— update_squid() добавляет правило для самого transparent proxy (запускается в /etc/init.d/squid3 – это единственный системный скрипт, в который пришлось влезть)
Тут и далее используется технология, придуманная мной несколько лет назад с переменными $ADD и $INS для iptables. Позволяет писать правило только в одном месте, и потом его добавлять-удалять, изменяя только эти переменные.

# cat /etc/network/scripts/route-enter #!/bin/sh . /etc/network/scripts/functions log "$0 route-enter ${interface} ${reason} ${new_routers}"  # security bugfix new_host_name=${new_host_name//[^-.a-zA-Z0-9]/}  # save routers to special file echo -n ${new_routers} > $DHCPLIB/routers.${interface} echo -n ${new_ip_address} > $DHCPLIB/ip_address.${interface}  case ${interface} in    $DEFAULTWAN)     # by default enable routers only for first WAN interface     ;;   *)     # and clear it for others     unset new_routers     ;; esac 

— Сохраняем new_routers и new_ip_address в файл (потом понядобятся)
— default route разрешаем только для приоритетного интерфейса

# cat /etc/network/scripts/route-exit #!/bin/sh . /etc/network/scripts/functions log "$0 route-exit ${interface} ${reason}"  update_routes() {      cmd route $ADD -host $HOST_TO_PING gw ${routers}      # identyfy providers by DNS addresses     case $DNS in         *82.193.96*)             DESTIP=`resolveip -s stat.ipnet.ua`             cmd route $ADD -host $DESTIP gw ${routers}             ;;         *193.41.63*|*192.168.11.1*)             DESTIP=`resolveip -s my.kyivstar.ua`             cmd route $ADD -host $DESTIP gw ${routers}             ;;         *)             warn "route-exit - unknown DNS ${new_domain_name_servers} specified"             ;;     esac }  case ${reason} in     BOUND)         ADD="add"         DNS=${new_domain_name_servers}         # use saved-to-file value due to $old_routers can be cleared for some interfaces by other script         routers=`cat $DHCPLIB/routers.${interface}`         update_routes         ;;      RELEASE)         # No need to delete routes during release         # ADD="del"         # routers=${old_routers}`         # update_routes         ;;      PREINIT)         ;;      RENEW)         if [ "$old_routers" != "$new_routers" ]; then             ADD="del"             DNS=${old_domain_name_servers}             routers=${old_routers}             update_routes              ADD="add"             DNS=${new_domain_name_servers}             routers=`cat $DHCPLIB/routers.${interface}`             update_routes         fi          if [ "$old_ip_address" != "$new_ip_address" ]; then             ADD="-D"             INS="-D"             update_local_redirect ${old_ip_address}              ADD="-A"             INS="-I"             update_local_redirect ${new_ip_address}         fi         ;;     *)         warn "route-exit - unknown reason ${reason} used"         ;; esac 

— Добавляем static route для сайтов с биллингом провайдеров. Локалка провайдера мне не нужна, но её тоже можно добавить. Идентификация по DNS серверам.
— для режима RENEW добавил перенастройку (если вдруг у провайдера что-то изменится), но пока не тестировал.

# cat /etc/network/scripts/firewall #!/bin/bash  . /etc/network/scripts/functions  get_ip $IFACE log "$0 $IFACE $LOGICAL $ADDRFAM $METHOD $MODE $PHASE $VERBOSITY $IP"   case $MODE in     start)         INS="-I"         ADD="-A"         echo -n $IP > $DHCPLIB/ip_address.$IFACE         ;;      stop)         INS="-D"         ADD="-D"         echo -n > $DHCPLIB/ip_address.$IFACE         ;;     *)         INS="-C"         ADD="-C"         warn "Wrong MODE:$MODE"         ;; esac   case $IFACE in      --all)         case $PHASE in             pre-down|post-up)                 # skip proxy for local addresses                 for j in $IFLAN $IFWAN; do                     get_ip $j                     update_local_redirect $IP                 done                 ;;             post-up|pre-down)                 ;;         esac          ;;      lo)         ;;      *)         if [[ "$IFLAN" == *$IFACE* ]]; then                 # LAN                 case $PHASE in                     pre-up|post-down)                         cmd iptables $INS INPUT -p tcp -i $IFACE --dport 22 -j ACCEPT                         ;;                      post-up|pre-down)                         ;;                      *)                         warn "Wrong PHASE:$PHASE"                         ;;                 esac         fi          if [[ "$IFWAN" == *$IFACE* ]]; then                 # WAN                 case $PHASE in                     pre-up|post-down)                         # by default close all input connections                         cmd iptables $ADD INPUT -p tcp -i $IFACE --dport 1:10000 -j DROP                         cmd iptables $ADD INPUT -p udp -i $IFACE --dport 1:10000 -j DROP                          # open ports from list                         for PORT in $WAN_PORTS_OPEN; do                             cmd iptables $INS INPUT -p tcp -i $IFACE --dport $PORT -j ACCEPT                         done                         ;;                      post-up|pre-down)                         cmd iptables -t nat $ADD POSTROUTING -o $IFACE -j MASQUERADE                         ;;                      *)                         warn "Wrong PHASE:$PHASE"                         ;;                 esac         fi         ;; esac 

Правила firewall. Для общих таблиц пишем в момент pre-up и post-down, для NAT – в post-up и pre-down.

# cat /etc/network/scripts/autoroute #!/bin/sh # Script for cron to monitor WAN interfaces # and (in future) SQUID status  . /etc/network/scripts/functions  CURRENT_ROUTE_DEV=`ip route show | grep default | awk '{print $5}'`  unset ROUTE_GOOD PING_RESULTS=""  for i in $IFWAN; do     if [ -z $ROUTE_GOOD ]; then         PING_RESULT=`ping -c$PING_COUNT -q $HOST_TO_PING -I $i | grep 'packet loss' | awk '{print $6}'`          # If no route t host then set to 100% loss         if [ -z $PING_RESULT ]; then             warn "$0 No route to host $HOST_TO_PING on $i"             PING_RESULT='100%'         fi          if [ $PING_RESULT = '0%' ]; then             ROUTE_GOOD=$i             if [ -z $CURRENT_ROUTE_DEV ]; then                 log "$0 Adding default route to $i"                 cmd route add default gw `cat $DHCPLIB/routers.$i`             elif [ $CURRENT_ROUTE_DEV != $i ]; then                 log "$0 Change default route from $CURRENT_ROUTE_DEV to $i"                 cmd route del default                 cmd route add default gw `cat $DHCPLIB/routers.$i`             fi         else             log "$0 loss $PING_RESULT on $i"         fi     fi     PING_RESULTS="$PING_RESULTS $PING_RESULT" done  if [ -z $ROUTE_GOOD ]; then     warn "$0 lost all internet connections ($PING_RESULTS loss)" fi 

Тут всё просто: пингуем в порядке приоритета. Нашли лучший – переключаемся. Если что, пишем в лог.

Ну и напоследок

# cat /etc/cron.d/autoroute PATH="/usr/bin:/bin:/usr/sbin:/sbin"  */5 * * * * root /etc/network/scripts/autoroute 

# cat /etc/logrotate.conf  | tail  # system-specific logs may be configured here  /var/log/network-scripts.log {         weekly         missingok         rotate 7         compress } 

Симлинки

/etc/dhcp/dhclient-enter-hooks.d/route-enter -> ../../network/scripts/route-enter /etc/dhcp/dhclient-exit-hooks.d/route-exit -> ../../network/scripts/route-exit /etc/network/if-pre-up.d/firewall -> ../scripts/firewall /etc/network/if-down.d/firewall -> ../scripts/firewall /etc/network/if-up.d/firewall -> ../scripts/firewall /etc/network/if-post-down.d/firewall -> ../scripts/firewall  

ссылка на оригинал статьи http://habrahabr.ru/post/199478/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *