0

Маршрутизация на основе политик. Часть третья

26.07.2023

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

Настройка сети

На компьютере установлена ОС Ubuntu Desktop, настройка сети выполнена с использованием Netplan:

# nano /etc/netplan/01-network-manager-all.yamlКопировать
network:
  version: 2
  renderer: networkd
  ethernets:
    enp0s3:
      addresses: [192.168.50.2/24]
      routes:
        - to: 0.0.0.0/0 # основной маршрут по умолчанию для таблицы main
          from: 192.168.50.2
          via: 192.168.50.1
          metric: 100
        - to: 0.0.0.0/0 # маршрут по умолчанию для таблицы primary (или 100)
          via: 192.168.50.1
          table: 100
      routing-policy:
        - from: 192.168.50.2
          table: 100
          priority: 31000
      nameservers:
        addresses: [8.8.8.8, 8.8.4.4]
    enp0s8:
      addresses: [192.168.150.2/24]
      routes:
        - to: 0.0.0.0/0 # резервный маршрут по умолчанию для таблицы main
          from: 192.168.150.2
          via: 192.168.150.1
          metric: 200
        - to: 0.0.0.0/0 # маршрут по умолчанию для таблицы secondary (или 200)
          via: 192.168.150.1
          table: 200
      routing-policy:
        - from: 192.168.150.2
          table: 200
          priority: 32000
      nameservers:
        addresses: [8.8.8.8, 8.8.4.4]Копировать

Служба NetworkManager отключена, вместо нее за сеть отвечает служба systemd-networkd:

# systemctl stop NetworkManager.service
# systemctl disable NetworkManager.service
# systemctl start systemd-networkd.service
# systemctl enable systemd-networkd.serviceКопировать

После загрузки системы смотрим маршруты таблицы main:

$ ip route show
default via 192.168.50.1 dev enp0s3 proto static src 192.168.50.2 metric 100
default via 192.168.150.1 dev enp0s8 proto static src 192.168.150.2 metric 200
192.168.50.0/24 dev enp0s3 proto kernel scope link src 192.168.50.2
192.168.150.0/24 dev enp0s8 proto kernel scope link src 192.168.150.2Копировать

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

Таблицы маршрутизации

И есть два правила, которые предписывают просматривать таблицы primary и secondary, если пакеты отправляются с ip-адресов 192.168.50.2 и 192.168.150.2 соответственно.

$ ip rule show
0:      from all lookup local
31000:  from 192.168.50.2 lookup primary
32000:  from 192.168.150.2 lookup secondary
32766:  from all lookup main
32767:  from all lookup defaultКопировать

Таблицы маршрутизации primary и secondary определены в файле /etc/iproute2/rt_tables:

# nano /etc/iproute2/rt_tablesКопировать
# предопределенные таблицы
255     local
254     main
253     default
0       unspec
# добавляем новые таблицы
100     primary
200     secondaryКопировать

Каждая из таблиц маршрутизации содержит маршрут по умолчанию:

$ ip route show table primary
default via 192.168.50.1 dev enp0s3 proto staticКопировать
$ ip route show table secondary
default via 192.168.150.1 dev enp0s8 proto staticКопировать

Доступ из интернета

Допустим, у нас на компьютере установлен ssh-сервер и нам нужна возможность подключения извне. Разумеется, здесь не обойтись без помощи интернет-провайдеров, потому что наш компьютер не имеет белого ip-адреса. На маршрутизаторах gateway1 и gateway2 нужно настроить проброс портов. Настройки, которые мы выполнили на компьютере, обеспечивают маршрутизацию ответов через интерфейс, на котором был получен запрос. Так что тут не будет такой ситуации, что входящее соединение на одном интерфейсе, а исходящее — на другом.

Проброс порта на маршрутизаторе gateway1:

# iptables -t nat -A PREROUTING -i enp0s3 -p tcp --dport 22 -j DNAT --to-destination 192.168.50.2Копировать

Проброс порта на маршрутизаторе gateway2:

# iptables -t nat -A PREROUTING -i enp0s3 -p tcp --dport 22 -j DNAT --to-destination 192.168.150.2Копировать

Проверяем подключение по ssh через маршрутизатор gateway1:

$ ssh evgeniy@111.111.111.111
The authenticity of host '111.111.111.111 (111.111.111.111)' can't be established.
ECDSA key fingerprint is SHA256:SNK5y+y11LWgJFlW8nKYVqhy3oDpgQtOXRcRaymhWAs.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '111.111.111.111' (ECDSA) to the list of known hosts.
evgeniy@111.111.111.111's password: пароль
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 5.3.0-51-generic x86_64)Копировать

Проверяем подключение по ssh через маршрутизатор gateway2:

$ ssh evgeniy@222.222.222.222
The authenticity of host '222.222.222.222 (222.222.222.222)' can't be established.
ECDSA key fingerprint is SHA256:SNK5y+y11LWgJFlW8nKYVqhy3oDpgQtOXRcRaymhWAs.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '222.222.222.222' (ECDSA) to the list of known hosts.
evgeniy@222.222.222.222's password: пароль
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 5.3.0-51-generic x86_64)Копировать

Переключение каналов

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

# mkdir /root/swicth-channel/Копировать
# nano /root/swicth-channel/swicth-channel.shКопировать
#!/bin/sh

# Доступность этого хоста означает доступность основного канала
PING_HOST='8.8.8.8'
# Файл-флаг создается в момент переключении на резервный канал
LOCK_FILE='/root/switch-channel/switch-channel.lock'
# Будем записывать в журнал события переключения на другой канал
LOG_FILE='/root/switch-channel/switch-channel.log'

# Сетевой интерфейс, который смотрит в сеть первого интернет провайдера
IFACE_ONE='enp0s3'
# Сетевой интерфейс, который смотрит в сеть второго интернет провайдера
IFACE_TWO='enp0s8'
# IP-адрес первого сетевого интерфейса (первый интернет провайдер)
IP_IF_ONE='192.168.50.2'
# IP-адрес второго сетевого интерфейса (второй интернет провайдер)
IP_IF_TWO='192.168.150.2'
# IP-адрес шлюза первого интернет провайдера
IP_GW_ONE='192.168.50.1'
# IP-адрес шлюза второго интернет провайдера
IP_GW_TWO='192.168.150.1'

# Возможна такая ситуация, что компьютер был перезагружен в момент, когда работал
# резервный канал интернет. А за время перезагрузки восстановился основной канал
# интернет. В этом случае файл-флаг продолжит существовать и не позволит скрипту
# в дальнейшем переключаться на резервный канал.
if [ -f ${LOCK_FILE} ]; then
    # Получаем вывод команды ip route show, берем только первую строку, ищем в этой
    # строке подстроку ${IFACE_ONE}. Если такая подстрока найдена, значит работает
    # основной канал интернет — и нужно удалить файл-флаг.
    ip route show | head -1 | grep ${IFACE_ONE}
    if [ $? -eq 0 ]; then
        rm -f ${LOCK_FILE}
    fi
fi

# Пингуем проверочный хост через основной канал
ping -I ${IP_IF_ONE} -c1 -n ${PING_HOST} > /dev/null

# Если проверочный хост не доступен
if [ $? -ne 0 ]; then
    # Если нет файла-флага, значит мы сейчас на основном канале
    if [ ! -f ${LOCK_FILE} ]; then
        # Переключаемся на резервный канал (метрика основного маршрута 300, метрика резервного маршрута 200)
        ip route del default via ${IP_GW_ONE} dev ${IFACE_ONE} src ${IP_IF_ONE} proto static metric 100
        ip route add default via ${IP_GW_ONE} dev ${IFACE_ONE} src ${IP_IF_ONE} proto static metric 300
        # Создаём файл-флаг, что мы на резервном канале
        touch ${LOCK_FILE}
        # Делаем запись в файл журнала
        echo `date +'%Y/%m/%d %H:%M:%S'` Switching to secondary channel >> ${LOG_FILE}
    fi
else # Если проверочный хост доступен
    # Если есть файл-флаг, значит мы сейчас на резервном канале
    if [ -f ${LOCK_FILE} ]; then
        # Переключаемся на основной канал (метрика основного маршрута 100, метрика резервного маршрута 200)
        ip route del default via ${IP_GW_ONE} dev ${IFACE_ONE} src ${IP_IF_ONE} proto static metric 300
        ip route add default via ${IP_GW_ONE} dev ${IFACE_ONE} src ${IP_IF_ONE} proto static metric 100
        # Удаляем файл-флаг, мы опять на основном канале
        rm -f ${LOCK_FILE}
        # Делаем запись в файл журнала
        echo `date +'%Y/%m/%d %H:%M:%S'` Switching to primary channel >> ${LOG_FILE}
    fi
fi
# chmod +x /root/swicth-channel/swicth-channel.shКопировать

И будем запускать этот скрипт каждую минуту:

# nano /etc/crontabКопировать
# проверка каналов выхода в интернет и переключение канала при необходимости
* *    * * *   root    /root/switch-channel/switch-channel.shКопировать

Чтобы проверить работу скрипта, выключим интерфейс enp0s8 на маршрутизаторе gateway1:

# ip link set dev enp0s8 downКопировать

Подождем минуту и проверим маршруты на нашем компьютере:

$ ip route show
default via 192.168.150.1 dev enp0s8 proto static src 192.168.150.2 metric 200
default via 192.168.50.1 dev enp0s3 proto static src 192.168.50.2 metric 300
192.168.50.0/24 dev enp0s3 proto kernel scope link src 192.168.50.2
192.168.150.0/24 dev enp0s8 proto kernel scope link src 192.168.150.2Копировать

Включим интерфейс enp0s8 на маршрутизаторе gateway1:

# ip link set dev enp0s8 upКопировать

Подождем минуту и еще раз проверим маршруты на компьютере:

$ ip route show
default via 192.168.50.1 dev enp0s3 proto static src 192.168.50.2 metric 100 
default via 192.168.150.1 dev enp0s8 proto static src 192.168.150.2 metric 200 
192.168.50.0/24 dev enp0s3 proto kernel scope link src 192.168.50.2 
192.168.150.0/24 dev enp0s8 proto kernel scope link src 192.168.150.2

Часть четвертая

Свежие комментарии

Подписка

Лучшие статьи


Fatal error: Uncaught Error: Call to a member function have_posts() on null in /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/blog.php:380 Stack trace: #0 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/widgets/latest-posts/widget.php(257): fox56_blog_grid(NULL, Array) #1 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/widgets/latest-posts/register.php(33): include('/home/host18670...') #2 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/class-wp-widget.php(394): Wi_Widget_Latest_Posts->widget(Array, Array) #3 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/widgets.php(837): WP_Widget->display_callback(Array, Array) #4 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(417): dynamic_sidebar('sidebar') #5 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(136): fox56_single_sidebar() #6 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(7): fox56_single_inner() #7 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/single.php(23): fox56_single() #8 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/template-loader.php(106): include('/home/host18670...') #9 /home/host1867038/the-devops.ru/htdocs/www/wp-blog-header.php(19): require_once('/home/host18670...') #10 /home/host1867038/the-devops.ru/htdocs/www/index.php(17): require('/home/host18670...') #11 {main} thrown in /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/blog.php on line 380