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]
:debug log "$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]
:debug log "$pingLink $Iface $IPToPing"
:if ($pingLink < ($PingCount-$Margin)) do={
# Перша перезавантаження 
/interface ethernet disable $Iface; /interface ethernet enable $Iface
:while ($pingLink < ($PingCount-$Margin)) do={
:debug log "$pingLink $Iface $IPToPing"
:set RunTime ($RunTime + 1)
:debug log $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={
:debug log "Interface $Iface enabled"
# Перевіряємо лінк 
/interface ethernet monitor $Iface once do={
:set StatusIface $status
}
:if ($StatusIface = "link-ok") do={
:debug log "$Iface link-ok." 
# Перевіряємо dhcp
:if ([/ip, dhcp-client find interface=$Iface] != "") do={
:debug log "test1"
# Перевіряємо чи немає помилки DHCP
:if ( [/ip, dhcp-client get [/ip, dhcp-client find interface=$Iface] invalid ] != true) do={
:debug log "test2"
# Чекаємо отримання DHCP lease
:set CurrentGateway [/ip, dhcp-client get [/ip, dhcp-client find interface=$Iface] gateway ]
:debug log "Waiting DHCP lease"
:if ($CurrentGateway != nil) do={
:set CurrentGateway [:put ("$CurrentGateway" . "%$Iface")]
:debug log "CurrentGateway $CurrentGateway"
:local ExistingGateway [/ip route get [/ip route find comment="$Iface"] gateway ] 
:debug log "ExistingGateway $ExistingGateway"
# Looking for route test
:debug log "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={
:debug log "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]
:debug log "$pingGateway $Iface $IPToPing"
:if ($pingGateway < ($PingCount-$Margin)) do={
:log error "route error on $Iface"
:debug log [/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={
:debug log "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={
:debug log "Internet on $Iface connected."
}
}



Повторюємо попередній крок для другого інтерфейсу, тільки замінюємо значення змінної $Iface на «isp2», також міняємо $DistanceDefault на вищевказані значення (у мене для isp1 — 10, а для isp2 — 11 ).



Тепер потрібно налаштувати планувальник для автоматичного запуску скриптів при завантаженні роутера.
System → Scheduler->



Також це можна зробити за допомогою ssh або ж з консолі, якщо виникають проблеми з кирилицею в даті:

/system scheduler add name=CheckTestRoute1 start-time=on startup-event=CheckTestRoute1

Перевантажуємо…

От і все. Сподіваюся, що ця стаття виявиться для когось корисною.

PS: Наостанок RouterOS підкинув ще одну задачку…



Як бачите, незважаючи на те, що маршрут вказано вірно — пінг не проходить.

Щоб це виправити, додав ще одну перевірку (вище в коді скрипта вона вже додано).

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.