Инструменты пользователя

Инструменты сайта


mikrotik:rb3011uias:routeros:system:scripts:failover

Скрипт для переключения Интернет-каналов на MiktoTik

:!: Настоящий скрипт является сильно переработанной версией скрипта Дмитрия. Огромная нечеловеческая благодарность ему за оригинал! 8-)

Скрипт разрабатывался для работы на MikroTik RB3011UiAS с RouterOS 6.46.3.

Функции скрипта

  1. Проверять основной Интернет-канал
  2. Проверять резервный Интернет-канал
  3. Автоматически переключать каналы
  4. Отключать/включать гостевую Wi-Fi сеть
  5. Отправлять уведомления по SMS
  6. Уведомлять сотрудников по Email
  7. Принимать удаленные SMS команды

Комментарии к коду

Инициализация функций, используемых в скрипте. Эти функции вводятся скриптом «variable-initialization»!

:global sendEvent;
:global pingSession;
:global ispLogging;

Эти переменные также вводятся скриптом «variables-initialization».

:global eventEmail;
:global pingCount;
:global stableConnectFrom;
:global setFailCounter;
:global ether1Name;
:global ether2Name;

Объявление локальных переменных.

  • failoverScriptName должен совпадать с названием этого скрипта;
    • К сожалению, не нашел как этот процесс автоматизировать…
  • startModeEvent - это метка для логов по умолчанию;
  • failoverRunTime - максимальное время работы этого скрипта в секундах, вычисляется эмпирически;
:local failoverScriptName "failover";
:local startModeEvent "failover-scheduler";
:local failoverRunTime 30;

Объявление глобальных переменных, которые используются только этим скриптом, а также инициализация переменной pingFailCount, если она имеет не правильный тип.

:global pastCheckStatus;
:global failoverRun;
:global pingFailCount;
:if ( [ :typeof $pingFailCount ] = "nil" || [ :typeof $pingFailCount ] = "nothing" ) do={ :set pingFailCount $setFailCounter; }

Объявление прочих локальных переменных.

:local ether1Route;
:local ether2Route;
:local pingCountTotal ($pingCount * 2);
:local stableConnect $stableConnectFrom;
:local gateway1ip;
:local gateway2ip;
:local pingStatus;

Проверка двойного запуска

Необходимо для того, чтобы исключить работу нескольких копий этого скрипта одновременно. Получиться это может при запуске по расписанию и через команду SMS.

Проверяется значение переменной failoverRun, если оно равно true, значит в настоящий момент выполняется другая копия скрипта → ждем в течение failoverRunTime и пробуем еще раз. Если за это время значение изменилось на false, меняем его на true и продолжаем выполнять скрипт, если нет, то отключаем шедулер и сообщаем о внештатной ситуации и завершаем выполнение скрипта. Если значение и так было false, меняем его на true и продолжаем выполнять скрипт без ожидания.

:if ( $failoverRun != true ) do={ :set failoverRun true; } else={
  :local alreadyRun "Script is already running, after $failoverRunTime seconds we will try again, do not worry.";
  :local scrComplete "Execution of the previous script is completed, continue execution of the current script.";
  :log warning ("$alreadyRun");
  $sendEvent msg=$alreadyRun;
  :delay $failoverRunTime;
  :if ( $failoverRun = true ) do={
    :local didtComplete "Script did not complete in $failoverRunTime seconds, execution of the current script and schedule stopped!";
    /system scheduler set [find on-event="$failoverScriptName"] disable=yes comment="$didtComplete";
    :log error ("$didtComplete");
    $sendEvent msg=$didtComplete;
    :set failoverRun false;
    :error "$didtComplete";
    } else={
      :log info ("$scrComplete");
      :set failoverRun true; }
}

SMS команды

Эти команды меняют логику работы дальнейшей части скрипта посредством подмены значений переменных, тем самым заставляя его думать как нам нужно.

Резервный канал

SMS команда для переключения на резервный канал.

Значение переменной stableConnect устанавливается заведомо большим, чтобы при любом исходе тестирования основной канал казался скрипту неработоспособным и он переключил нас на резервный. А для того, чтобы скрипт изменил маршруты сразу, сбрасывается значение переменной pastCheckStatus в false. Ну и изменяется метка для логов startModeEvent, чтобы было понятно. Последним отключается расписание.

:if ( $channel = "backup") do={
  :set stableConnect 60;
  :set pastCheckStatus false;
  :set startModeEvent "failover-man-back";
  :local ChannelSw "Channel switched to the $ether2Name manually via SMS, schedule stopped!";
  /system scheduler set [find on-event="$failoverScriptName"] disable=yes comment="$ChannelSw";
  :log warning ("$ChannelSw");
  $sendEvent msg=$ChannelSw;
}

Основной канал

SMS команда для переключения на основной канал.

Значение переменной stableConnect устанавливается заведомо очень маленьким, чтобы при любом исходе тестирования основной канал казался скрипту работоспособным и он переключил нас на него. Также изменяется метка для логов startModeEvent, чтобы было понятно. Последним выключается расписание.

:if ( $channel = "primary") do={
  :set stableConnect -60;
  :set startModeEvent "failover-man-prim";
  :local ChannelSw "Channel switched to the $ether1Name manually via SMS, schedule stopped!";
  /system scheduler set [find on-event="$failoverScriptName"] disable=yes comment="$ChannelSw";
  :log warning ("$ChannelSw");
  $sendEvent msg=$ChannelSw;
}

Автоматический выбор

Команда возвращает работу скрипта в нормальный режим и сразу же завершает работу.

:if ( $channel = "auto") do={
  :local ChannelSw "Automatic channel selection enabled via SMS, schedule running.";
  /system scheduler set [find on-event="$failoverScriptName"] disable=no comment="";
  :log warning ("$ChannelSw");
  $sendEvent msg=$ChannelSw;
  :set failoverRun false; 
  :error "The script completed its task and stopped!";
}

Проверка состояния каналов

Команда просто проверяет оба канала, отчитывается в ответной SMS и завершает работу.

:if ( $channel = "test") do={
  :local ether1Status [ $pingSession pingFrom=$ether1Name myPingCount=8; ];
  $ispLogging outInt=$ether1Name receivedPing=$ether1Status totalPing=16 startMode="failover-sms" newFileAt=month;
  :local ether2Status [ $pingSession pingFrom=$ether2Name myPingCount=8; ];
  $ispLogging outInt=$ether2Name receivedPing=$ether2Status totalPing=16 startMode="failover-sms" newFileAt=month;
  :local channelStatus "Received ping's:\r\n$ether1Name - $ether1Status/16;\r\n$ether2Name - $ether2Status/16";
  $sendEvent msg=$channelStatus;
  :set failoverRun false; 
  :error "The script completed its task and stopped!";
}

Получение IP шлюзов

Ищется по дефолтным маршрутам и названиям интерфейсов, если хоть одного из них нет - выключаем шедулер, пишем в лог ошибку и отправляем алерт.

/ip route { :do {
  :set gateway1ip [get [find dst-address="0.0.0.0/0" gateway-status~"$ether1Name"] gateway];
  :set gateway2ip [get [find dst-address="0.0.0.0/0" gateway-status~"$ether2Name"] gateway];
  } on-error={
    :local noRoute "No default route found for channel $ether1Name or $ether2Name, schedule stopped!";
    /system scheduler set [find on-event="$failoverScriptName"] disable=yes comment="$noRoute";
    :log error ("$noRoute");
    $sendEvent msg=$noRoute;
    :set failoverRun false;
    :error "$noRoute"; }
}

Выборочная очистка ARP. Не используется, осталась из оригинального скрипта. Так и не понял, зачем она нужна была, т.к. кроме проблем ничего не дает. В случае чего можно быстро включить, раскомментировав соответствующие строки тут.

:local clearArp do={
  :global ether1Name;
  :global ether2Name;
    /ip arp remove [find interface="$ether1Name" || interface="$ether2Name"];
    :log warning ("ARP for $ether1Name and $ether2Name removed!");
}

Функция для CAPsMAN

Функция для переключения Wi-Fi сетей. Для выключения гостевой сети ждет переменную hotSpotOff с параметром yes, для включения нужно вызвать без переменных. Для использования нужна соответствующая настройка.

:local swWireless do={
  /caps-man provisioning {
    :if ( $hotSpotOff = "yes" ) do={
      set [find name-prefix~"Wi.Ghs"] disable=yes;
      set [find name-prefix~"Wi.Gln"] disable=no;
      :log warning ("CAPsMAN settings has changed: mobile Wi-Fi disabled!");
      } else={
        set [find name-prefix~"Wi.Ghs"] disable=no;
        set [find name-prefix~"Wi.Gln"] disable=yes;
        :log info ("CAPsMAN settings has changed: mobile Wi-Fi enabled!"); }
      }
  /caps-man radio provision [find remote-cap-identity~"cs-ap"];
}

Проверка интерфейсов

Проверяем, чтобы оба интерфейса были включены. Если это не так, ключаем.

/interface ethernet {
  :if ( [get $ether1Name disable] = true ) do={
    set $ether1Name disable=no;
    :delay 2s;
  }
  :if ( [get $ether2Name disable] = true ) do={
    set $ether2Name disable=no;
    :delay 2s;
  }
}

Проверка резервного канала

Вызываем функцию для проверки резервного канала.

:set pingStatus [ $pingSession pingFrom=$ether2Name; ];

Проверяем ответ функции, сравниваем с эталоном. Если плохо, то, или сразу сообщаем об этом (SMS и логи), или молчим, пока pingFailCount не станет равен setFailCounter. Сделано это для того, чтобы при неработающем резервном канале SMS-ки не сыпались при каждом запуске скрипта (а это часто), что… перебор. Если хорошо, то просто молчим. Если было плохо, но стало хорошо, сообщаем об этом, логируем и сбрасываем счетчик.

 :if ( $pingStatus < $stableConnectFrom ) do={
  :if ( $pingFailCount = $setFailCounter ) do={
    $ispLogging outInt=$ether2Name receivedPing=$pingStatus totalPing=$pingCountTotal startMode=$startModeEvent newFileAt=month;
    :local noInet "$ether2Name failure (received $pingStatus/$pingCountTotal)!";
    :log error ("$noInet");
    $sendEvent msg=$noInet;
    :set pingFailCount 0; } else={ :set pingFailCount ( $pingFailCount + 1 ); }
    } else={
      :if ( $pingFailCount != $setFailCounter ) do={
        $ispLogging outInt=$ether2Name receivedPing=$pingStatus totalPing=$pingCountTotal startMode=$startModeEvent newFileAt=month;
        :set pingFailCount $setFailCounter;
        :local worksFine "$ether2Name works fine (received $pingStatus/$pingCountTotal).";
        $sendEvent msg=$worksFine;
        :log info ("$worksFine"); }
}

Основная часть скрипта

Собственно, отсюда она только начинается. До этого были цветочки. 8-O

/ip route {
  :set ether1Route [find dst-address="0.0.0.0/0" gateway=$gateway1ip];
  :set ether2Route [find dst-address="0.0.0.0/0" gateway=$gateway2ip];

Тексты писем

Тут вводится текст для сотрудников, который сообщает им в каком состоянии находится Интернет.

Для удобства он разбит на 4 фрагмента - заголовок, текст при переходе на резервный канал, текст при переходе на основной канал и подпись.

Для переноса строк можно использовать \r\n. Кириллица тоже поддерживается, но вводить текст нужно непосредственно в редакторе скриптов WinBox, т.к. у MikroTik'а с этим есть определенные сложности.

  :local eventHeader "Header,";
  :local eventTextBackup "Text\r\n\r\nBackup";
  :local eventTextPrimary "Text\r\nPrimary";
  :local eventSign "Sign";

А тут формируется итоговое письмо и тема, которая будет состоять из пояснительного текста и временной метки. Опять же, текст темы можно писать на русском.

  :local dateTime ([/system clock get date]."@".[/system clock get time]);
  :local userSubjBackup "Subj Backup $dateTime";
  :local userSubjPrimary "Subj Primary $dateTime";
  :local eventPrimary "$eventHeader\r\n\r\n$eventTextPrimary\r\n\r\n$eventSign";
  :local eventBackup "$eventHeader\r\n\r\n$eventTextBackup\r\n\r\n$eventSign";
Пример письма

Подготовка маршрутов

Проверка и, при необходимости, корректировка обоих маршрутов. Для удобства, основной канал имеет дистанцию 2, а резервный 3. При переходе на резервный канал меняется только дистанция у маршрута этого канала - с 3 на 1.

  :if ( [get $ether1Route distance] != 2 ) do={
    set $ether1Route distance=2;
    :log warning ("Distance for " . $ether1Name . " corrected!");
  }

  :if ( [get $ether2Route distance] != 1 && [get $ether2Route distance] != 3 ) do={
    set $ether2Route distance=3;
    :log warning ("Distance for " . $ether2Name . " corrected!");
  }

Проверка основного канала

Вызываем функцию для проверки основного канала.

:local pingStatus [ $pingSession pingFrom=$ether1Name; ];

Вот наконец и самая суть скрипта. Если pingStatus меньше stableConnect проверяем, первый ли это раз. Если первый, то просто предупреждаем администратора и выставляем метку pastCheckStatus в значение false. Если не первый, т.е. pastCheckStatus уже равно false, то переключаем канал на резервный, пишем в лог, уведомляем сотрудников и отключаем гостевую Wi-Fi сеть.

Если pingStatus не меньше stableConnect, значит основной канал работает нормально и можно переключиться обратно на него. В этом случае переключаем канал, меняя дистанцию резервного канала на 3, пишем в лог и всех уведомляем. Все!

  :if ( $pingStatus < $stableConnect ) do={
    :if ( [get $ether2Route distance] != 1 ) do={
      $ispLogging outInt=$ether1Name receivedPing=$pingStatus totalPing=$pingCountTotal startMode=$startModeEvent newFileAt=month;
      :local noPriInet "$ether1Name failure (received $pingStatus/$pingCountTotal), $ether2Name enabled!";
      :if ( $pastCheckStatus = false ) do={
        set $ether2Route distance=1;
        :log warning ("Distance for " . $ether2Name . " changed!");
#        $clearArp;
        $swWireless hotSpotOff=yes;
        :log error ("$noPriInet");
        $sendEvent msg=$noPriInet;
        $sendEvent mode=mail to=$eventEmail subj=$userSubjBackup msg=$eventBackup;
        } else={
          :local badPriInet "$ether1Name is bad (received $pingStatus/$pingCountTotal)!";
          :log warning ("$badPriInet");
          $sendEvent msg=$badPriInet;
          :set pastCheckStatus false; }
      }
      } else={
        :if ( [get $ether2Route distance] != 3 ) do={
          $ispLogging outInt=$ether1Name receivedPing=$pingStatus totalPing=$pingCountTotal startMode=$startModeEvent newFileAt=month;
          :local PriInetGood "$ether1Name connected (received $pingStatus/$pingCountTotal), $ether2Name disabled.";
          :log warning ("$PriInetGood");
          set $ether2Route distance=3;
          :log warning ("Distance for " . $ether2Name . " changed!");
#          $clearArp;
          $swWireless;
          $sendEvent msg=$PriInetGood;
          $sendEvent mode=mail to=$eventEmail subj=$userSubjPrimary msg=$eventPrimary;
        }
        :set pastCheckStatus true;
      }
}

И напоследок меняем значение переменной failoverRun на false, уведомляя другие копии этого скрипта о том, что текущий скрипт завершился и можно занимать его место. LOL

:set failoverRun false;

Код для импорта

failover.rsc
/system script
add comment="\D2\E5\F1\F2\E8\F0\EE\E2\E0\ED\E8\E5 \E8\ED\F2\E5\F0\ED\E5\F2 \EA\
    \E0\ED\E0\EB\EE\E2 \E8 \EF\E5\F0\E5\EA\EB\FE\F7\E5\ED\E8\E5 \ED\E0 \F0\E5\
    \E7\E5\F0\E2\ED\FB\E9" dont-require-permissions=no name=script1 owner=\
    petya policy=ftp,read,write,policy,test,sensitive source="# Written by Nik\
    olay Soloshin (nikolay@soloshin.su) for RouterOS v6.46.3 on RB3011UiAS (ar\
    m) @ 2020.03\r\
    \n# Based on script \"Mikrotik Advanced Failover\" by Dmitriy (https://web\
    lance.com.ua)\r\
    \n\r\
    \n# Functions used in the script. They are entered by the script \"variabl\
    e-initialization\" when the device starts!\r\
    \n:global sendEvent;\r\
    \n:global pingSession;\r\
    \n:global ispLogging;\r\
    \n\r\
    \n# These variables are set by the script \"variables-initialization\" whe\
    n the device starts!\r\
    \n:global eventEmail;\r\
    \n:global pingCount;\r\
    \n:global stableConnectFrom;\r\
    \n:global setFailCounter;\r\
    \n:global ether1Name;\r\
    \n:global ether2Name;\r\
    \n\r\
    \n# Local variables\r\
    \n:local failoverScriptName \"failover\";\r\
    \n:local startModeEvent \"failover-scheduler\";\r\
    \n:local failoverRunTime 30;\r\
    \n\r\
    \n# Global variables set and used only by this script.\r\
    \n:global pastCheckStatus;\r\
    \n:global failoverRun;\r\
    \n:global pingFailCount;\r\
    \n:if ( [ :typeof \$pingFailCount ] = \"nil\" || [ :typeof \$pingFailCount\
    \_] = \"nothing\" ) do={ :set pingFailCount \$setFailCounter; }\r\
    \n\r\
    \n# Local variables used in the script.\r\
    \n:local ether1Route;\r\
    \n:local ether2Route;\r\
    \n:local pingCountTotal (\$pingCount * 2);\r\
    \n:local stableConnect \$stableConnectFrom;\r\
    \n:local gateway1ip;\r\
    \n:local gateway2ip;\r\
    \n:local pingStatus;\r\
    \n\r\
    \n# Prohibition of execution of the second copy of the script and waiting \
    for the completion of the first (the delay is calculated empirically for t\
    he current version of the script)\r\
    \n:if ( \$failoverRun != true ) do={ :set failoverRun true; } else={\r\
    \n  :local alreadyRun \"Script is already running, after \$failoverRunTime\
    \_seconds we will try again, do not worry.\";\r\
    \n  :local scrComplete \"Execution of the previous script is completed, co\
    ntinue execution of the current script.\";\r\
    \n  :log warning (\"\$alreadyRun\");\r\
    \n  \$sendEvent msg=\$alreadyRun;\r\
    \n  :delay \$failoverRunTime;\r\
    \n  :if ( \$failoverRun = true ) do={\r\
    \n    :local didtComplete \"Script did not complete in \$failoverRunTime s\
    econds, execution of the current script and schedule stopped!\";\r\
    \n    /system scheduler set [find on-event=\"\$failoverScriptName\"] disab\
    le=yes comment=\"\$didtComplete\";\r\
    \n    :log error (\"\$didtComplete\");\r\
    \n    \$sendEvent msg=\$didtComplete;\r\
    \n    :set failoverRun false;\r\
    \n    :error \"\$didtComplete\";\r\
    \n    } else={\r\
    \n      :log info (\"\$scrComplete\");\r\
    \n      :set failoverRun true; }\r\
    \n}\r\
    \n\r\
    \n# SMS command to force switch to the backup channel and disable the sche\
    duler\r\
    \n:if ( \$channel = \"backup\") do={\r\
    \n  :set stableConnect 60;\r\
    \n  :set pastCheckStatus false;\r\
    \n  :set startModeEvent \"failover-man-back\";\r\
    \n  :local ChannelSw \"Channel switched to the \$ether2Name manually via S\
    MS, schedule stopped!\";\r\
    \n  /system scheduler set [find on-event=\"\$failoverScriptName\"] disable\
    =yes comment=\"\$ChannelSw\";\r\
    \n  :log warning (\"\$ChannelSw\");\r\
    \n  \$sendEvent msg=\$ChannelSw;\r\
    \n}\r\
    \n\r\
    \n# SMS command to force switch to the main channel and disable the schedu\
    ler\r\
    \n:if ( \$channel = \"primary\") do={\r\
    \n  :set stableConnect -60;\r\
    \n  :set startModeEvent \"failover-man-prim\";\r\
    \n  :local ChannelSw \"Channel switched to the \$ether1Name manually via S\
    MS, schedule stopped!\";\r\
    \n  /system scheduler set [find on-event=\"\$failoverScriptName\"] disable\
    =yes comment=\"\$ChannelSw\";\r\
    \n  :log warning (\"\$ChannelSw\");\r\
    \n  \$sendEvent msg=\$ChannelSw;\r\
    \n}\r\
    \n\r\
    \n# The command to enable automatic channel selection\r\
    \n:if ( \$channel = \"auto\") do={\r\
    \n  :local ChannelSw \"Automatic channel selection enabled via SMS, schedu\
    le running.\";\r\
    \n  /system scheduler set [find on-event=\"\$failoverScriptName\"] disable\
    =no comment=\"\";\r\
    \n  :log warning (\"\$ChannelSw\");\r\
    \n  \$sendEvent msg=\$ChannelSw;\r\
    \n  :set failoverRun false; \r\
    \n  :error \"The script completed its task and stopped!\";\r\
    \n}\r\
    \n\r\
    \n# SMS command to check both channels and send a report\r\
    \n:if ( \$channel = \"test\") do={\r\
    \n  :local ether1Status [ \$pingSession pingFrom=\$ether1Name myPingCount=\
    8; ];\r\
    \n  \$ispLogging outInt=\$ether1Name receivedPing=\$ether1Status totalPing\
    =16 startMode=\"failover-sms\" newFileAt=month;\r\
    \n  :local ether2Status [ \$pingSession pingFrom=\$ether2Name myPingCount=\
    8; ];\r\
    \n  \$ispLogging outInt=\$ether2Name receivedPing=\$ether2Status totalPing\
    =16 startMode=\"failover-sms\" newFileAt=month;\r\
    \n  :local channelStatus \"Received ping's:\\r\\n\$ether1Name - \$ether1St\
    atus/16;\\r\\n\$ether2Name - \$ether2Status/16\";\r\
    \n  \$sendEvent msg=\$channelStatus;\r\
    \n  :set failoverRun false; \r\
    \n  :error \"The script completed its task and stopped!\";\r\
    \n}\r\
    \n\r\
    \n# Checking routes, if one of them is not - turn off the sheduler, error \
    and send an alert\r\
    \n/ip route { :do {\r\
    \n  :set gateway1ip [get [find dst-address=\"0.0.0.0/0\" gateway-status~\"\
    \$ether1Name\"] gateway];\r\
    \n  :set gateway2ip [get [find dst-address=\"0.0.0.0/0\" gateway-status~\"\
    \$ether2Name\"] gateway];\r\
    \n  } on-error={\r\
    \n    :local noRoute \"No default route found for channel \$ether1Name or \
    \$ether2Name, schedule stopped!\";\r\
    \n    /system scheduler set [find on-event=\"\$failoverScriptName\"] disab\
    le=yes comment=\"\$noRoute\";\r\
    \n    :log error (\"\$noRoute\");\r\
    \n    \$sendEvent msg=\$noRoute;\r\
    \n    :set failoverRun false;\r\
    \n    :error \"\$noRoute\"; }\r\
    \n}\r\
    \n\r\
    \n# Selective ARP cleaning function, not used\r\
    \n:local clearArp do={\r\
    \n  :global ether1Name;\r\
    \n  :global ether2Name;\r\
    \n    /ip arp remove [find interface=\"\$ether1Name\" || interface=\"\$eth\
    er2Name\"];\r\
    \n    :log warning (\"ARP for \$ether1Name and \$ether2Name removed!\");\r\
    \n}\r\
    \n\r\
    \n# Function of switching guest network availability through CAPsMAN\r\
    \n:local swWireless do={\r\
    \n  /caps-man provisioning {\r\
    \n    :if ( \$hotSpotOff = \"yes\" ) do={\r\
    \n      set [find name-prefix~\"Wi.Ghs\"] disable=yes;\r\
    \n      set [find name-prefix~\"Wi.Gln\"] disable=no;\r\
    \n      :log warning (\"CAPsMAN settings has changed: mobile Wi-Fi disable\
    d!\");\r\
    \n      } else={\r\
    \n        set [find name-prefix~\"Wi.Ghs\"] disable=no;\r\
    \n        set [find name-prefix~\"Wi.Gln\"] disable=yes;\r\
    \n        :log info (\"CAPsMAN settings has changed: mobile Wi-Fi enabled!\
    \"); }\r\
    \n      }\r\
    \n  /caps-man radio provision [find remote-cap-identity~\"cs-ap\"];\r\
    \n}\r\
    \n\r\
    \n# Check interfaces, if any is turned off - turn on\r\
    \n/interface ethernet {\r\
    \n  :if ( [get \$ether1Name disable] = true ) do={\r\
    \n    set \$ether1Name disable=no;\r\
    \n    :delay 2s;\r\
    \n  }\r\
    \n  :if ( [get \$ether2Name disable] = true ) do={\r\
    \n    set \$ether2Name disable=no;\r\
    \n    :delay 2s;\r\
    \n  }\r\
    \n}\r\
    \n\r\
    \n# Call ping session function to check the backup channel\r\
    \n:set pingStatus [ \$pingSession pingFrom=\$ether2Name; ];\r\
    \n\r\
    \n# Checking the response of the function and suppressing frequent alerts \
    about the failure of the backup channel\r\
    \n :if ( \$pingStatus < \$stableConnectFrom ) do={\r\
    \n  :if ( \$pingFailCount = \$setFailCounter ) do={\r\
    \n    \$ispLogging outInt=\$ether2Name receivedPing=\$pingStatus totalPing\
    =\$pingCountTotal startMode=\$startModeEvent newFileAt=month;\r\
    \n    :local noInet \"\$ether2Name failure (received \$pingStatus/\$pingCo\
    untTotal)!\";\r\
    \n    :log error (\"\$noInet\");\r\
    \n    \$sendEvent msg=\$noInet;\r\
    \n    :set pingFailCount 0; } else={ :set pingFailCount ( \$pingFailCount \
    + 1 ); }\r\
    \n    } else={\r\
    \n      :if ( \$pingFailCount != \$setFailCounter ) do={\r\
    \n        \$ispLogging outInt=\$ether2Name receivedPing=\$pingStatus total\
    Ping=\$pingCountTotal startMode=\$startModeEvent newFileAt=month;\r\
    \n        :set pingFailCount \$setFailCounter;\r\
    \n        :local worksFine \"\$ether2Name works fine (received \$pingStatu\
    s/\$pingCountTotal).\";\r\
    \n        \$sendEvent msg=\$worksFine;\r\
    \n        :log info (\"\$worksFine\"); }\r\
    \n}\r\
    \n\r\
    \n# Script kernel - checks the availability of the main channel, switches \
    to the backup and sends notifications to the administrator and employees\r\
    \n/ip route {\r\
    \n  :set ether1Route [find dst-address=\"0.0.0.0/0\" gateway=\$gateway1ip]\
    ;\r\
    \n  :set ether2Route [find dst-address=\"0.0.0.0/0\" gateway=\$gateway2ip]\
    ;\r\
    \n\r\
    \n# Text strings used in the email to employees on the mailing list.\r\
    \n  :local eventHeader \"\D3\E2\E0\E6\E0\E5\EC\FB\E5 \EA\EE\EB\EB\E5\E3\E8\
    ,\";\r\
    \n  :local eventTextBackup \"\E2 \F1\E2\FF\E7\E8 \F1 \EF\E5\F0\E5\E1\EE\E5\
    \EC \E2 \F0\E0\E1\EE\F2\E5 \EE\F1\ED\EE\E2\ED\EE\E3\EE \E8\ED\F2\E5\F0\ED\
    \E5\F2-\EA\E0\ED\E0\EB\E0, \EE\F4\E8\F1 \EA\EE\EC\EF\E0\ED\E8\E8 \E1\FB\EB\
    \_\E0\E2\F2\EE\EC\E0\F2\E8\F7\E5\F1\EA\E8 \EF\E5\F0\E5\EA\EB\FE\F7\E5\ED \
    \ED\E0 \F0\E5\E7\E5\F0\E2\ED\FB\E9, \E0 Wi-Fi-\F1\E5\F2\FC \EE\F2\EA\EB\FE\
    \F7\E5\ED\E0!\\r\\n\\r\\n\CF\F0\EE\F8\F3 \EF\EE \E2\EE\E7\EC\EE\E6\ED\EE\
    \F1\F2\E8 \E2\EE\E7\E4\E5\F0\E6\E0\F2\FC\F1\FF \EE\F2 \EF\EE\F1\E5\F9\E5\
    \ED\E8\FF \C8\ED\F2\E5\F0\ED\E5\F2-\F0\E5\F1\F3\F0\F1\EE\E2 \E2 \FD\F2\EE\
    \F2, \EF\F0\FF\EC\EE \F1\EA\E0\E6\E5\EC, \ED\E5 \EF\F0\EE\F1\F2\EE\E9 \E4\
    \EB\FF \E2\F1\E5\F5 \ED\E0\F1 \EF\E5\F0\E8\EE\E4, \E8 \E7\E0\ED\FF\F2\FC\
    \F1\FF \F7\E5\EC-\F2\EE \E4\F0\F3\E3\E8\EC - \EE\E3\EB\FF\ED\E8\F2\E5\F1\
    \FC \E2\EE\EA\F0\F3\E3, \E2\EE\E7\EC\EE\E6\ED\EE, \F1\F2\EE\E8\F2 \EF\F0\
    \E8\E1\F0\E0\F2\FC\F1\FF \ED\E0 \F1\F2\EE\EB\E5 \E8 \F0\E0\E7\EE\E1\F0\E0\
    \F2\FC \E7\E0\EB\E5\E6\E0\E2\F8\E8\E5\F1\FF \E4\EE\EA\F3\EC\E5\ED\F2\FB\? \
    \CD\F3 \E8\EB\E8, \ED\EE \FD\F2\EE, \EA\EE\ED\E5\F7\ED\EE \E6\E5, \ED\E0 \
    \F1\E0\EC\FB\E9 \EA\F0\E0\E9\ED\E8\E9 \F1\EB\F3\F7\E0\E9, \E2\FB\E9\F2\E8 \
    \ED\E0 \F3\EB\E8\F6\F3 \E8 \EF\EE\EF\E8\F2\FC \F7\E0\E9\EA\F3\? \CD\E5\F2\
    \?.. \CD\F3 \EB\E0\E4\ED\EE. :)\\r\\n\\r\\n\C2\ED\E8\EC\E0\ED\E8\E5! \CE \
    \EF\E5\F0\E5\F5\EE\E4\E5 \ED\E0 \EE\F1\ED\EE\E2\ED\EE\E9 \EA\E0\ED\E0\EB \
    \E1\F3\E4\E5\F2 \F1\EE\EE\E1\F9\E5\ED\EE \EE\F2\E4\E5\EB\FC\ED\EE.\";\r\
    \n  :local eventTextPrimary \"\EE\F4\E8\F1 \EF\E5\F0\E5\EA\EB\FE\F7\E5\ED \
    \ED\E0 \EE\F1\ED\EE\E2\ED\EE\E9 \EA\E0\ED\E0\EB, \E0 Wi-Fi-\F1\E5\F2\FC \
    \F1\ED\EE\E2\E0 \E4\EE\F1\F2\F3\EF\ED\E0! \CD\E0\EA\EE\ED\E5\F6-\F2\EE!\\r\
    \\n\\r\\n\CC\EE\E6\E5\F2\E5 \EF\F0\EE\E4\EE\EB\E6\E8\F2\FC \F1\EC\EE\F2\F0\
    \E5\F2\FC \FD\F2\E8\F5 \E2\E0\F8\E8\F5 \EA\EE\F2\E8\EA\EE\E2 \E2 \ED\E5 \
    \EC\E5\ED\E5\E5 \FD\F2\E8\F5 \E2\E0\F8\E8\F5 \E8\ED\F2\E5\F0\ED\E5\F2\E0\
    \F5! \C5\F1\EB\E8, \EA\EE\ED\E5\F7\ED\EE, \E2\F1\E5 \F1\E4\E5\EB\E0\EB\E8 \
    \E8 \E8\EC\E5\E5\F2\E5 \EC\EE\F0\E0\EB\FC\ED\EE\E5 \EF\F0\E0\E2\EE. ;) \CA\
    \_\F1\EB\EE\E2\F3, \F3\F7\E5\ED\FB\E5 \E4\EE\EA\E0\E7\E0\EB\E8, \F7\F2\EE \
    \FD\F2\EE \EF\EE\EB\E5\E7\ED\EE. \CD\EE \F3\E2\EB\E5\EA\E0\F2\FC\F1\FF \E2\
    \F1\E5 \E6\E5 \ED\E5 \F1\F2\EE\E8\F2, \E1\EE\EB\FC\F8\EE\E9 \E1\F0\E0\F2 \
    \E1\E4\E8\F2, \F7\F2\EE \E5\EC\F3 \E1\F3\E4\E5\F2.\";\r\
    \n  :local eventSign \"\D1 \F3\E2\E0\E6\E5\ED\E8\E5\EC,\\r\\n\D0\FD\E9 \D1\
    \FD\EC\FE\FD\EB \D2\EE\EC\EB\E8\ED\F1\EE\ED.\";\r\
    \n\r\
    \n  :local dateTime ([/system clock get date].\"@\".[/system clock get tim\
    e]);\r\
    \n  :local userSubjBackup \"\D0\E5\E7\E5\F0\E2\ED\FB\E9 \EA\E0\ED\E0\EB \
    \E0\EA\F2\E8\E2\E8\F0\EE\E2\E0\ED \$dateTime\";\r\
    \n  :local userSubjPrimary \"\CE\F1\ED\EE\E2\ED\EE\E9 \EA\E0\ED\E0\EB \E0\
    \EA\F2\E8\E2\E8\F0\EE\E2\E0\ED \$dateTime\";\r\
    \n  :local eventPrimary \"\$eventHeader\\r\\n\\r\\n\$eventTextPrimary\\r\\\
    n\\r\\n\$eventSign\";\r\
    \n  :local eventBackup \"\$eventHeader\\r\\n\\r\\n\$eventTextBackup\\r\\n\
    \\r\\n\$eventSign\";\r\
    \n\r\
    \n  :if ( [get \$ether1Route distance] != 2 ) do={\r\
    \n    set \$ether1Route distance=2;\r\
    \n    :log warning (\"Distance for \" . \$ether1Name . \" corrected!\");\r\
    \n  }\r\
    \n\r\
    \n  :if ( [get \$ether2Route distance] != 1 && [get \$ether2Route distance\
    ] != 3 ) do={\r\
    \n    set \$ether2Route distance=3;\r\
    \n    :log warning (\"Distance for \" . \$ether2Name . \" corrected!\");\r\
    \n  }\r\
    \n\r\
    \n:local pingStatus [ \$pingSession pingFrom=\$ether1Name; ];\r\
    \n\r\
    \n  :if ( \$pingStatus < \$stableConnect ) do={\r\
    \n    :if ( [get \$ether2Route distance] != 1 ) do={\r\
    \n      \$ispLogging outInt=\$ether1Name receivedPing=\$pingStatus totalPi\
    ng=\$pingCountTotal startMode=\$startModeEvent newFileAt=month;\r\
    \n      :local noPriInet \"\$ether1Name failure (received \$pingStatus/\$p\
    ingCountTotal), \$ether2Name enabled!\";\r\
    \n      :if ( \$pastCheckStatus = false ) do={\r\
    \n        set \$ether2Route distance=1;\r\
    \n        :log warning (\"Distance for \" . \$ether2Name . \" changed!\");\
    \r\
    \n#        \$clearArp;\r\
    \n        \$swWireless hotSpotOff=yes;\r\
    \n        :log error (\"\$noPriInet\");\r\
    \n        \$sendEvent msg=\$noPriInet;\r\
    \n        \$sendEvent mode=mail to=\$eventEmail subj=\$userSubjBackup msg=\
    \$eventBackup;\r\
    \n        } else={\r\
    \n          :local badPriInet \"\$ether1Name is bad (received \$pingStatus\
    /\$pingCountTotal)!\";\r\
    \n          :log warning (\"\$badPriInet\");\r\
    \n          \$sendEvent msg=\$badPriInet;\r\
    \n          :set pastCheckStatus false; }\r\
    \n      }\r\
    \n      } else={\r\
    \n        :if ( [get \$ether2Route distance] != 3 ) do={\r\
    \n          \$ispLogging outInt=\$ether1Name receivedPing=\$pingStatus tot\
    alPing=\$pingCountTotal startMode=\$startModeEvent newFileAt=month;\r\
    \n          :local PriInetGood \"\$ether1Name connected (received \$pingSt\
    atus/\$pingCountTotal), \$ether2Name disabled.\";\r\
    \n          :log warning (\"\$PriInetGood\");\r\
    \n          set \$ether2Route distance=3;\r\
    \n          :log warning (\"Distance for \" . \$ether2Name . \" changed!\"\
    );\r\
    \n#          \$clearArp;\r\
    \n          \$swWireless;\r\
    \n          \$sendEvent msg=\$PriInetGood;\r\
    \n          \$sendEvent mode=mail to=\$eventEmail subj=\$userSubjPrimary m\
    sg=\$eventPrimary;\r\
    \n        }\r\
    \n        :set pastCheckStatus true;\r\
    \n      }\r\
    \n}\r\
    \n\r\
    \n:set failoverRun false;\r\
    \n\r\
    \n# With love from Vladivostok."

Запуск скрипта

Скрипт должен выполняться периодически или может запускаться через SMS на номер модема с синтаксисом :cmd 12345 script failover channel=variable, где 12345, это секрет, назначенный при настройке приема SMS, а variable - одна из поддерживаемых переменных.

Политики запуска

Это минимально необходимый набор для работы скрипта.

  • ftp,
  • read,
  • write,
  • policy,
  • test
  • sensitive.

Дисклеймер

  • Использование материалов данной базы знаний разрешено на условиях лицензии, указанной внизу каждой страницы! При использовании материалов активная гиперссылка на соответствующую страницу данной базы знаний обязательна!
  • Автор не несет и не может нести какую либо ответственность за последствия использования материалов, размещенных в данной базе знаний. Все материалы предоставляются по принципу «как есть». Используйте их исключительно на свой страх и риск.
  • Все высказывания, мысли или идеи автора, размещенные в материалах данной базе знаний, являются исключительно его личным субъективным мнением и могут не совпадать с мнением читателей!
  • При размещении ссылок в данной базе знаний на интернет-страницы третьих лиц автор не несет ответственности за их техническую функциональность (особенно отсутствие вирусов) и содержание! При обнаружении таких ссылок, можно и желательно сообщить о них в комментариях к соответствующей статье.

Обсуждение

Ваш комментарий:
C R H S I S X M O U​ U D N D S B
 
Последнее изменение: 2022/02/12 11:40 (внешнее изменение)