В недавнем прошлом в связи с переходом с ADSL на Ethernet представилась отличная возможность попробовать оборудование от Mikrotik. В связи с чем был куплен роутер RB750GL. Железка оказалась превосходной как внешне, так и с точки зрения функционала и надёжности.
В итоге у меня осталось оба канала и было решено настроить резервирование с автоматическим переключением. Стандартные средства переключения шлюзов не покрывают всё многообразие сбоев, поэтому нужно писать свои скрипты.
Конфигурация сети
1. Канал Ehernet от NLink втыкается в первый порт роутера, получает IP по DHCP и поднимает pptp соединение, названное nlink. Это будет основное соединение.
2. Канал ADSL от провайдера Домолинк проходит через DLink-2500 в режиме моста и втыкается во второй порт роутера, поверх поднимается pppoe соединение, названное domolink. Это будет резервное соединение.
3. Порты 3-5 роутера используются для подключения устройств локальное сети.
Настройки роутера
Настройка подключений и маскарадинга для компьютеров локальное сети является тривиальной и детально описана в официальной вики.
При настройке ppp подключений нужно отключить добавление маршрутов по умолчанию, а затем создать статические маршруты разными метриками и подходящими комментариями:
/ip route
add comment=MainGW disabled=no distance=1 dst-address=0.0.0.0/0 gateway=nlink scope=30 target-scope=10
add comment=RsrvGW disabled=no distance=2 dst-address=0.0.0.0/0 gateway=domolink scope=30 target-scope=10
Теперь чтобы переключать каналы достаточно изменять параметр distance. Трафик пойдёт через канал с меньшим значением этого параметра.
Скрипты
Установка глобальных параметров при запуске роутера
Скрипт называется set_global_parameters
#Main interface name
:global MainIf nlink
#Reserve interface name
:global RsrvIf domolink
#Main interface ip address
:global MainIfAddress ""
#Reserve interface ip address
:global RsrvIfAddress ""
Определение IP-адресов интерфейсов
Определение IP-адреса основного интерфейса
Скрипт называется define_main_if_ip
:global MainIf
:global MainIfAddress ""
:set MainIfAddress [/ip address get [find interface=$MainIf] address]
Данный скрипт определяет IP-адрес основного интерфейса для доступа в интернет. Если этот интерфейс отсутствует, то скрипт будет завершаться с ошибкой, а в переменной MainIfAddress будет пустая строка.
Определение IP-адреса резервного интерфейса
Скрипт называется define_reserved_if_ip
:global RsrvIf
:global RsrvIfAddress ""
:set RsrvIfAddress [/ip address get [find interface=$RsrvIf] address]
Определение этих адресов вынесено в отдельные скрипты, т.к. эти значения я использую ещё в ряде скриптов на роутере (например, обновление записей в DynDNS), а пользовательские функции тут создавать нельзя. Следует заметить, что напрямую команды определения адресов нельзя использовать в других скриптах, т.к. в случае проблем с интерфейсом они генерируют ошибку и приводят к завершению скрипта.
Переключение каналов
Скрипт называется connection_check
:global MainIf
:global RsrvIf
:global MainIfAddress
:global RsrvIfAddress
:local PingCount 3
#www.ru
:local PingTarget1 194.87.0.50
#ya.ru
:local PingTarget2 87.250.250.203
#google dns
:local PingTarget3 8.8.8.8
#Check main internet connection
:local MainIfInetOk false;
if ($MainIfAddress="") do={delay 5}
if ($MainIfAddress!="") do={
:local PingResult1 [/ping $PingTarget1 count=$PingCount interface=$MainIf]
:local PingResult2 [/ping $PingTarget2 count=$PingCount interface=$MainIf]
:local PingResult3 [/ping $PingTarget3 count=$PingCount interface=$MainIf]
:set MainIfInetOk (($PingResult1 + $PingResult2 + $PingResult3) >= (2 * $PingCount))
}
#Check reserved internet connection
:local RsrvIfInetOk false;
if ($RsrvIfAddress="") do={delay 5}
if ($RsrvIfAddress!="") do={
:local PingResult1 [/ping $PingTarget1 count=$PingCount interface=$RsrvIf]
:local PingResult2 [/ping $PingTarget2 count=$PingCount interface=$RsrvIf]
:local PingResult3 [/ping $PingTarget3 count=$PingCount interface=$RsrvIf]
:set RsrvIfInetOk (($PingResult1 + $PingResult2 + $PingResult3) >= (2 * $PingCount))
}
:put "MainIfInetOk=$MainIfInetOk"
:put "RsrvIfInetOk=$RsrvIfInetOk"
if (!$MainIfInetOk) do={
/log error "Main internet connection error"
}
if (!$RsrvIfInetOk) do={
/log error "Reserve internet connection error"
}
:local MainGWDistance [/ip route get [find comment="MainGW"] distance]
:local RsrvGWDistance [/ip route get [find comment="RsrvGW"] distance]
:put "MainGWDistance=$MainGWDistance"
:put "RsrvGWDistance=$RsrvGWDistance"
#SetUp gateways
if ($MainIfInetOk && ($MainGWDistance >= $RsrvGWDistance)) do={
/ip route set [find comment="MainGW"] distance=1
/ip route set [find comment="RsrvGW"] distance=2
/log info "Switch to main internet connection"
}
if (!$MainIfInetOk && $RsrvIfInetOk && ($MainGWDistance <= $RsrvGWDistance)) do={
/ip route set [find comment="MainGW"] distance=2
/ip route set [find comment="RsrvGW"] distance=1
/log warning "Switch to reserve internet connection"
}
Обратите внимание на пинг через конкретный интерфейс, а так же на критерий признания канала неисправным. Я пингую три разных узла и считаю, что интернет на данном интерфейсе не работает, если приходит меньше 2/3 ответов.
Планировщик
1. Скрипт set_global_parameters запускается один раз при запуске роутера.
2. Скрипты определения IP-адресов запускаются каждые 27 секунд. Такое значение выбрано чтобы минимизировать количество одновременных запусков с основным скриптом.
3. Скрипт connection_check запускается каждую минуту.
Выводы
Полученное решение при минимуме затрат существенно повысило надёжность моей домашней сети и успешно справляется с самыми изощрёнными сбоями местных провайдеров, при этом оно в достаточной степени защищено от сбоев внешних узлов сети.
Автор: magnitudo