Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по DHCP)

в 19:31, , рубрики: failover, mikrotik, routeros, администрирование, Серверное администрирование, Сетевые технологии, системное администрирование, скрипт

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-клиента устанавливается опция автоматического создания маршрута по умолчанию.

Итак, скрипт будет работать по такому алгоритму:

Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по 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):

Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по DHCP)

Меняем DNS адреса на google-кие:

Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по DHCP)

Создаем скрипт: System → Scripts → Add и вставляем код указанный ниже:

Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по DHCP)

Код скрипта

: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 ).

Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по DHCP)

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

Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по DHCP)

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

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

Перегружаем…

Вот и все. Надеюсь, что эта статья окажется для кого-то полезной.

PS: Напоследок RouterOS подбросил еще одну задачку…

Mikrotik автоматическое переключение на резервный канал для динамического ip адреса (выдаваемого по DHCP)

Как видите, несмотря на то, что маршрут указан верно — пинг не проходит.

Чтобы это исправить, добавил еще одну проверку (выше в коде скрипта она уже добавлена).

Автор: Thund3rHabr

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js