Mikrotik: авто включение/отключение резервного канала

Задача: имеется маршрутизатор Mikrotik. В нему подведен основной канал интернета с получением IP-адреса по DHCP и резервный 4G-канал. Необходимо обеспечить автоматическое переключение на резерный канал при пропадании основного.

Изучение задачи

В интернете присутствует множество способов решения этой задачи, в т. ч. и весьма интересный метод Advanced Routing Failover without Scripting. Однако, мы остановимся на этом варианте. Понадобится немного переделать скрипт, потому что в новых прошивках Mikrotik наблюдается не корректная работа команды ping с указанием конкретного интерфейса.

Общий принцип работы будет следующий:

  1. Выбираем 2 IP-адреса публичных DNS-сервисов. С помощью пингов на них можно определить качество работы сети. В нашем случаем это будут, 8.8.4.4 и 217.66.106.6.
  2. При получении IP-адреса через DHCP записываем два статических маршрута до выбранных публичных DNS.
  3. Для резервного канала устанавливаем флаг "Add default route" с distance 2
  4. Прописываем в планировщик скрипт, который каждую минуту проверяет качество работы канала и, при необходимости, меняет distance основного канала с 1 на 3, т. о. пуская траффик на резервный канал с distance 2.

Скрипт DHCP-Client

Статические маршруты к нашим DNS-серверам можно было бы прописать руками, однако, ситуацию осложняет использование DHCP. Используем удобный механизм запуска скрипта при получении/обновлении IP-адреса:

:local dns1 "8.8.4.4";
:local dns2 "217.66.106.6";

:local routingmarkWAN "WAN Route";
:local routingmarkDNS1 "DNS Check Route1";
:local routingmarkDNS2 "DNS Check Route2";

:local countWAN [/ip route print count-only where comment=$routingmarkWAN];
:local countDNS1 [/ip route print count-only where comment=$routingmarkDNS1];
:local countDNS2 [/ip route print count-only where comment=$routingmarkDNS2];

:if ($bound=1) do={
	:if ($countWAN = 0) do={
	    /ip route add gateway=$"gateway-address" comment=$routingmarkWAN dst-address="0.0.0.0/0" distance=1; 
	}
	:if ($countDNS1 = 0) do={
	    /ip route add gateway=$"gateway-address" comment=$routingmarkDNS1 dst-address=$dns1 distance=1; 
	} 
	:if ($countDNS2 = 0) do={
	    /ip route add gateway=$"gateway-address" comment=$routingmarkDNS2 dst-address=$dns2 distance=1; 
	}                 

	:local WANRoute [/ip route find where comment=$routingmarkWAN];
	:local DNS1Route [/ip route find where comment=$routingmarkDNS1];
	:local DNS2Route [/ip route find where comment=$routingmarkDNS2];

	:if ([/ip route get $WANRoute gateway] != $"gateway-address") do={
	    
	    /ip route set $WANRoute gateway=$"gateway-address";
	    /ip route set $DNS1Route gateway=$"gateway-address";
	    /ip route set $DNS2Route gateway=$"gateway-address";

	} else={
	    :log info "gateway not changed";
	}
}

Этот скрипт создает статические маршруты, и, при изменении адреса шлюза, изменяет их. Команда для консоли:

/ip dhcp-client
add add-default-route=no dhcp-options=hostname,clientid disabled=no interface=ether1 script=":local dns1 \"8.8.4.4\";\r\
    \n:local dns2 \"217.66.106.6\";\r\
    \n\r\
    \n:local routingmarkWAN \"WAN Route\";\r\
    \n:local routingmarkDNS1 \"DNS Check Route1\";\r\
    \n:local routingmarkDNS2 \"DNS Check Route2\";\r\
    \n\r\
    \n:local countWAN [/ip route print count-only where comment=\$routingmarkWAN];\r\
    \n:local countDNS1 [/ip route print count-only where comment=\$routingmarkDNS1];\r\
    \n:local countDNS2 [/ip route print count-only where comment=\$routingmarkDNS2];\r\
    \n\r\
    \n:log info \"Step1\";\r\
    \n\r\
    \n:if (\$bound=1) do={\r\
    \n\t:if (\$countWAN = 0) do={\r\
    \n\t    /ip route add gateway=\$\"gateway-address\" comment=\$routingmarkWAN dst-address=\"0.0.0.0/0\" distance=1; \r\
    \n\t}\r\
    \n\t:if (\$countDNS1 = 0) do={\r\
    \n\t    /ip route add gateway=\$\"gateway-address\" comment=\$routingmarkDNS1 dst-address=\$dns1 distance=1; \r\
    \n\t} \r\
    \n\t:if (\$countDNS2 = 0) do={\r\
    \n\t    /ip route add gateway=\$\"gateway-address\" comment=\$routingmarkDNS2 dst-address=\$dns2 distance=1; \r\
    \n\t}                 \r\
    \n\r\
    \n\t:local WANRoute [/ip route find where comment=\$routingmarkWAN];\r\
    \n\t:local DNS1Route [/ip route find where comment=\$routingmarkDNS1];\r\
    \n\t:local DNS2Route [/ip route find where comment=\$routingmarkDNS2];\r\
    \n\r\
    \n\t:if ([/ip route get \$WANRoute gateway] != \$\"gateway-address\") do={\r\
    \n\t    \r\
    \n\t    /ip route set \$WANRoute gateway=\$\"gateway-address\";\r\
    \n\t    /ip route set \$DNS1Route gateway=\$\"gateway-address\";\r\
    \n\t    /ip route set \$DNS2Route gateway=\$\"gateway-address\";\r\
    \n\r\
    \n\t} else={\r\
    \n\t    :log info \"gateway not changed\";\r\
    \n\t}\r\
    \n}" use-peer-dns=no use-peer-ntp=no

Скрипт перелючения каналов

Скрипт последовательно выполняет такие шаги:

  1. Включаем основной и резервный каналы, если они отключены;
  2. Пингуем оба DNS-сервера и вычисляем процент качества $pingStatus, отражающий качество линии, где 100 - нет потерь, 0 - все пакеты потеряны.
  3. Если пакеты теряются только при пинге к одному серверу, игнорируем результат (так мы исключаем ситуацию, когда один из двух серверов не работает)
  4. Если процент качества ниже допустимого, установленного в переменной $stableConnectFrom, меняем distance основного канала на 3
  5. Если интернет на основном канале пришел в норму, меняем distance назад и отключаем ppp-out.
  6. При переключении отсылаем e-mail с отчетом о происходящем
# Set local variables
:local firstInterface "ether1";
:local secondInterface "ppp-out";
:local pingTo1 "8.8.4.4";
:local pingTo2 "217.66.106.6";
:local pingCount 5;
:local stableConnectFrom 30;
:local prefix ">>> ";

# Local variables
:local firstInterfaceName $firstInterface;
:local secondInterfaceName $secondInterface;

# Function to cleaning ARP table
:local clearArp do={
    :local dumplist [/ip arp find]
    :foreach i in=$dumplist do={
        /ip arp remove $i
    }
    :log info ($prefix . "ARP cleaned");
}

# Check FIRST interface
/interface ethernet {
    :if ( [get $firstInterface disable] = true) do={
        set $firstInterface disable=no;

    }
}

# Check SECOND interface
/interface ppp-client {
    :if ( [get $secondInterface disable] = true) do={
        set $secondInterface disable=no;
        :delay 5s;
    }
}

:local reconnectPPPoE do={
    /interface ppp-client set $secondInterfaceName disable=yes;
    :delay 5s;
    /interface ppp-client set $secondInterfaceName disable=no;
}


/ip route {
    # Set objects to variables
    :set firstInterface [find dst-address="0.0.0.0/0" comment="WAN Route"];


    # Get ping successfully packets. In percent
    :local pingStatus1 [/ping $pingTo1 count=$pingCount];

    :local pingStatus2 [/ping $pingTo2 count=$pingCount];

    :local pingStatus (($pingStatus1 + $pingStatus2) * 100 / ($pingCount * 2) );

    :if ($pingStatus < 100) do={
      :log info ("pingStatus1: " . $pingStatus1 . " pingStatus2: " . $pingStatus2);
    }

    :if (($pingStatus1=0 and $pingStatus2=$pingCount) or ($pingStatus1=$pingCount and $pingStatus2=0)) do={
        :set pingStatus 100;
    }

    # Check Internet
    :if ( $pingStatus <= $stableConnectFrom) do={

        # Change distance
        :log info ($prefix . "FIRST NO INTERNET!!!");
        :if ( [get $firstInterface distance] != 3 ) do={
            set $firstInterface distance=3;
            :log info ($prefix . "Distance for " . $firstInterfaceName . " changed to 3");
            $clearArp;
            /tool e-mail send to="user@gmail.com" from="Mikrotik <mikrotik@company.com>" \
            subject="Switch to backup channel: $[/system clock get date], $[/system clock get time]" \
            body="Switch to backup channel: $[/system clock get date]\nTime: $[/system clock get time]\nPing status:\n$pingTo1 : ($pingStatus1 / $pingCount)\n$pingTo2 : ($pingStatus2 / $pingCount)";
           :beep;
        }

    } else={
        # Change distance
        :if ( [get $firstInterface distance] != 1 and $pingStatus=100) do={
        :log info ($prefix . "FIRST INTERNET CONNECTED");
            set $firstInterface distance=1;
            :log info ($prefix . "Distance for " . $firstInterfaceName . " changed to 1");
            $clearArp;
            /tool e-mail send to="user@gmail.com" from="Mikrotik <mikrotik@company.com>" \
            subject="Switch to main channel: $[/system clock get date], $[/system clock get time]" \
            body="Switch to main channel: $[/system clock get date]\nTime: $[/system clock get time]\nPing status:\n$pingTo1 : ($pingStatus1 / $pingCount)\n$pingTo2 : ($pingStatus2 / $pingCount)";
           :beep;
           :delay 1s;            
           :beep;
           $reconnectPPPoE;
        }
    }
}

 


Комментарии