[Из песочницы] Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по DHCP)
Приветствую, Хабр! В связи с плохим качеством линии меня попросили настроить автоматическое переключение на резервный канал. Для этой цели предоставили роутер MikroTik RB 951Ui.
Думал, что проблем не возникнет… Всего-то настроить проверку канала и маршруты. Но, к сожалению, оба провайдера выдают IP динамически. Прочитав несколько статей, включая зарубежные сайты, но не нашел решения проблемы, которое мне подошло бы. Пришлось знакомится с RouterOS…В этой ОС можно создавать маршрут двумя способами:
вручную (через графический интерфейс или через терминал); автоматически DHCP-клиентом. При создании маршрута вручную не получится обновлять шлюз динамически. Для этого придется писать несколько достаточно больших скриптов и добавлять множество проверок. При их написании заметил, что в RouterОS проблематично заставить работать сложные скрипты. Очень тяжело отследить логику работы, хотя использовались логи и переменные для проверки. Скрипты были написаны, но работали нестабильно, несмотря на оптимизацию кода и добавления проверок. Когда количество проверок начало расти в геометрической прогрессии и многократная оптимизация логической схемы ненамного улучшила ситуацию — я решил отказаться от этого варианта и попробовать использовать в скрипте опцию автоматического создания маршрута DHCP- клиентом. /ip dhcp-client set [/ip dhcp-client find interface=$Iface] add-default-route=yes [no] Для нужного интерфейса в настройках DHCP-клиента устанавливается опция автоматического создания маршрута по умолчанию.Итак, скрипт будет работать по такому алгоритму:
Объяснение алгоритма Для каждого интерфейса будет запущена копия скрипта. Каждая копия будет автономно создавать маршрут по умолчанию. Приоритет маршрута будет зависеть от distance. То есть, когда «упадет» соединение на маршруте с Distance 10 произойдет переключение на 11. Благодаря этому переключение получается бесшовным.
Для начала скрипт пингует выбранный хост в интернете по маршруту по умолчанию. Если пинг меньше чем ($PingCount-$Margin) (Margin — задается погрешность для контроля точности), тогда пингуем по тестовому маршруту для проверки «живое» ли соединение. В случае негативного результата проверяем маршрут и наличие проблем с настройками:
перегружаем интерфейс каждые $TimeToWait раз (снижаем нагрузку на процессор); ждем загрузки интерфейса; проверяем есть ли настройки DHCP — клиента для данного интерфейса, в противном случае создаем; проверяем статус DHCP клиента (иногда RouterOS может «подсунуть свинью»); ждем получение DHCP lease; добавляем к значению $CurrentGateway нужный интерфейс; проверяем есть ли тестовый маршрут; проверяем правильный ли в тестовом маршруте шлюз. Скорость реакции на состояние соединения можно индивидуально подстраивать с помощью следующих переменных:
PingCount — количество посылаемых icmp запросов (также можно добавить еще одну переменную для определения количества посылаемых запросов по тестовому маршруту и на шлюз провайдера, то есть уменьшится количество запросов и соответственно увеличится скорость работы скрипта); Margin — коэффициент нужен для задания погрешности. Например, при $Margin=1 цикл проверки маршрутов запускается только тогда, когда пропадет больше одного пакета, что немаловажно в моей ситуации; TimeToWait при ожидании соединения интерфейс перегружается каждый $TimeToWait раз (это нужно для того, чтобы снять нагрузку на процессор) Подготовительная настройка Описывать стандартные настройки роутера я не буду по двум причинам: во-первых, эта тема не раз поднималась в интернете, в том числе на Хабре, во-вторых, сети отличаются своей конфигурацией. Так как работа скрипта затрагивает только маршруты по умолчанию и настройки DHCP клиента, думаю у вас не возникнет трудностей при адаптации скрипта под вашу сеть.Для работы скрипта не нужно создавать маршруты по умолчанию — он создаст их автоматически. Единственное, нужно подобрать подходящий distance для тестовых маршрутов (можно оба с $Distance = 1) и $DistanceDefault 10 и 11 для маршрутов по умолчанию (по одному для каждого провайдера). Также не нужно создавать dhcp клиентов.
При настройке роутера я использовал SSH и Winbox (специализированная программа для настройки устройств управляемых RouterOS, работает даже в *nix с помощью Wine).
Приступим.
В Interfaces меняем названия двух интерфейсов, чтобы совпадали со значением переменной $Iface в скрипте (у меня isp1, isp2):
Меняем DNS адреса на google-кие:
Создаем скрипт: System → Scripts → Add и вставляем код указанный ниже:
Код скрипта : delay 10s : local Iface «isp1» : local StatusIface : local CurrentGateway : local pingInet : local pingLink : local pingGateway : local IPToPingInet »213.180.193.3» : local IPToPing »8.8.4.4» : local PingCount 5 : local Margin 1 : local Distance 1 : local DistanceDefault 10 : local RunTime 0 : local TimeToWait 20
#Первый цикл while (true) do={ # пингуем общий интернет : set pingInet [/ping $IPToPingInet count=$PingCount interface=$Iface] : log debug »$pingInet $Iface $IPToPingInet» : if ($pingInet < ($PingCount-$Margin)) do={ :log error "No internet connection on $Iface." /ip dhcp-client set [/ip dhcp-client find interface=$Iface] add-default-route=no # Второй цикл :while ($pingInet < ($PingCount-$Margin)) do={ # пингуем интернет через тест :set pingLink [/ping $IPToPing count=$PingCount interface=$Iface] :log debug "$pingLink $Iface $IPToPing" :if ($pingLink < ($PingCount-$Margin)) do={ # Первая перезагрузка /interface ethernet disable $Iface; /interface ethernet enable $Iface :while ($pingLink < ($PingCount-$Margin)) do={ :log debug "$pingLink $Iface $IPToPing" :set RunTime ($RunTime + 1) :log debug $RunTime # Time to wait :if ( $RunTime = $TimeToWait ) do={ # Reboot interface :log info "reboot and release $Iface" /interface ethernet disable $Iface; /interface ethernet enable $Iface :set RunTime 0 } # Ждем загрузки интерфейса :if ([/interface ethernet get $Iface disabled] = false) do={ :log debug "Interface $Iface enabled" # Проверяем линк /interface ethernet monitor $Iface once do={ :set StatusIface $status } :if ($StatusIface = "link-ok") do={ :log debug "$Iface link-ok." # Проверяем dhcp :if ([/ip dhcp-client find interface=$Iface] != "") do={ :log debug "test1" # Проверяем или нет ошибки DHCP :if ( [/ip dhcp-client get [/ip dhcp-client find interface=$Iface] invalid ] != true) do={ :log debug "test2" # Ждем получения DHCP lease :set CurrentGateway [/ip dhcp-client get [/ip dhcp-client find interface=$Iface] gateway ] :log debug "Waiting DHCP lease" :if ($CurrentGateway != nil) do={ :set CurrentGateway [:put ("$CurrentGateway" . "%$Iface")] :log debug "CurrentGateway $CurrentGateway" :local ExistingGateway [/ip route get [/ip route find comment="$Iface"] gateway ] :log debug "ExistingGateway $ExistingGateway" # Looking for route test :log debug "Cheking test route for $Iface..." :local a [ /ip route find comment="$Iface" ] :if (($a) = "") do={ :log info ("Adding test route for $Iface...") /ip route add dst-address=$IPToPing gateway=$CurrentGateway comment="$Iface" distance=$Distance } else={ :if ( $CurrentGateway = $ExistingGateway ) do={ :log debug "No route changes needed for $Iface." } else={ :log info "Updating test route for $Iface..." /ip route set [/ip route find comment="$Iface" ] dst-address=$IPToPing gateway=$CurrentGateway comment="$Iface" distance=$Distance } } :set pingGateway [/ping [/ip dhcp-client get [/ip dhcp-client find interface=$Iface] gateway ] count=$PingCount interface=$Iface] :log debug "$pingGateway $Iface $IPToPing" :if ($pingGateway < ($PingCount-$Margin)) do={ :log error "route error on $Iface" :log debug [/ip dhcp-client get [/ip dhcp-client find interface=$Iface] gateway ] /ip dhcp-client release [/ip dhcp-client find interface=$Iface] } } else={ :log error "DHCP no lease." :delay 1s } } else={ :log error "DHCP failure on $Iface." :log info "reboot and release $Iface" /interface ethernet disable $Iface; /interface ethernet enable $Iface :delay 1s } } else={ :log info "adding DHCP client for $Iface" /ip dhcp-client add interface=$Iface disabled=no add-default-route=yes default-route-distance=$DistanceDefault use-peer-dns=no use-peer-ntp=no } } else={ :log debug "No-link on $Iface." :delay 1s } } else={ :log error "Interface $Iface disabled." } :set pingLink [/ping $IPToPing count=$PingCount interface=$Iface] } } else={ :log info "add default route= yes for $Iface" /ip dhcp-client set [/ip dhcp-client find interface=$Iface] add-default-route=yes } :set pingInet [/ping $IPToPingInet count=$PingCount interface=$Iface] } } else={ :log debug "Internet on $Iface connected." } } Повторяем предыдущий шаг для второго интерфейса, только заменяем значение переменной $Iface на «isp2», также меняем $DistanceDefault на вышеуказанные значения (у меня для isp1 — 10, а для isp2 — 11 ).
Теперь нужно настроить планировщик для автоматического запуска скриптов при загрузке роутера.System → Scheduler→
Также это можно сделать с помощью ssh или же из консоли, если возникают проблемы с кириллицей в дате:
/system scheduler add name=CheckTestRoute1 start-time=startup on-event=CheckTestRoute1 Перегружаем…Вот и все. Надеюсь, что эта статья окажется для кого-то полезной.
PS: Напоследок RouterOS подбросил еще одну задачку…
Как видите, несмотря на то, что маршрут указан верно — пинг не проходит.
Чтобы это исправить, добавил еще одну проверку (выше в коде скрипта она уже добавлена).