Pull to refresh

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

Reading time 6 min
Views 52K


Приветствую, Хабр! В связи с плохим качеством линии меня попросили настроить автоматическое переключение на резервный канал. Для этой цели предоставили роутер 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 подбросил еще одну задачку…



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

Чтобы это исправить, добавил еще одну проверку (выше в коде скрипта она уже добавлена).
Tags:
Hubs:
+11
Comments 22
Comments Comments 22

Articles