Приветствую, Хабр! В связи с плохим качеством линии меня попросили настроить автоматическое переключение на резервный канал. Для этой цели предоставили роутер 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"
# 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={
:local EstablishedGateway [/ip route get [/ip route find comment="$Iface"] gateway ]
:log debug "EstablishedGateway $EstablishedGateway"
:if ( $CurrentGateway = $EstablishedGateway ) 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
Перегружаем…
Я думал, что возникнут проблемы с NAT при переключении маршрутов, но пока
полет нормальный. Если возникнут пишите…
Вот и все. Надеюсь, что эта статья окажется для кого-то полезной.
PS: Напоследок RouterOS подбросил еще одну задачку…
Как видите, несмотря на то, что маршрут указан верно — пинг не проходит.
Чтобы это исправить, добавил еще одну проверку (выше в коде скрипта она уже добавлена).