Цели и задачи
Работающий интернет в частный дом в Московской области. Проводных аналогов нет.
Что имеем на руках
Роутер Mikrotik hap ac3 LTE. Но можно любой микротик + LTE модем - я настраивал сначала все именно так, а потом переносил на микротик LTE. Всю сложную логику будем реализовывать на микротике.
Прошивка RouterOS 7.20.7 long term.
Мобильный тариф с безлимитным интернетом. Тут развилка:
-
Либо у нас тариф для роутера - тогда перед нами все дороги открыты. Можем использовать роутер микротик LTE - просто вставляем симку в него и настраиваем. Модем использовать микротик без LTE - тогда покупаем любой LTE модем с USB и втыкаем его в микротик. Главное чтобы модем не был прошит под IMEI смартфона, т.к. в смартфоне данная симкарта может не заработать (зависит от оператора)
-
Либо у нас тариф для смартфона. Тогда симку нельзя вставлять в микротик LTE или другой LTE роутер, потому что оператор это просечет (по IMEI LTE модуля роутера и может в добавок по TTL) и заблокирует интернет на симке. Тут нужен как раз LTE модем прошитый под телефонный IMEI - такие сейчас продаются готовые на любом маркетплейсе. Смотрите его IMEI в настройках роутера в GUI и проверяете на любом сайте проверке IMEI - должен показать модель телефона к которому он относится. Тут у нас получится вариант микротик (любой) + прошитый LTE модем USB.
Нулевой вариант
Вставляем симкарту в LTE роутер и интернет работает. Но не стабильно, медленно, многие сайты периодически не открываются. Если у вас все стабильно и все всегда открывается, нет проблем с сайтами 4pda, xiaomi, не говоря уже у google play, дальше можете не читать - статья для тех у кого есть проблемы при настройках по умолчанию, как было у меня.
Базовые (простейшие) настройки
Закрепить в настройках LTE роутера выбор сети - только LTE, остальные варианты отключаем.

Закрепить конкретную частоту в настроке LTE роутера. Для этого смотрим за какую частоту он зацепился, выбираем только ее, остальные выключаем.


Смотрим качество сигнала, меряем скорость интернета.

Сила сигнала чем больше тем лучше (ближе к нулю лучше, всегда показывает с минусом)
Качество сигнала (SINR) чем больше тем лучше (должно быть 10 и выше, если сигнал хороший).
Далее в настройках оставляем все частоты, кроме той которую замерили, и повторяем все по кругу - смотрим за какую частоту теперь зацепился, проверяем ее качество и скорость. Так пока не переберем все доступные частоты (т.е. у нас остануться включенными часть частот на которые уже не будет подключаться интернет).
В принципе для ускорения предварительно можно посмотреть доступные частоты в точке установки LTE модема через телефон, приложение NetMonster.
Ставите в телефон ту симкарту которая будет в модеме, телефон в ту точку где будет стоять LTE модем, и смотрите доступные частоты и силу и качество сигнала в них. Но учтите, что модем в телефоне гораздо мощнее чем в LTE модеме, кроме того в телефоне есть агрегация частот, и мгновенное переключение между ними (подключение новых). Так что сила и качество сигнала которую покажет телефон будет намного лучше чем в модеме потом. Но нам главное понять 1-2 самых лучших частоты в данной точке и потом проверить их в LTE модеме.
Управление качеством
Если после выполнения базовых настроек у нас все равно что-то отваливается, не открываются сайты периодически, то читаем дальше.
У решил сделать комплексную систему мониторинга, оповещения и управления качеством.
На самом деле вариантов управления у нас не так много:
-
Уменьшать / балансировать MSS (размер пакета) при плохом качестве интернета
-
Перезапускать LTE модуль для получения нового ip и выхода из блокировок DPI / залипания сессии и т.п.
Для этого нам нужно 1) мониторить качество интернета - настраиваем 2 правила mangle (счетчики качества) на микротик. Заходим в GUI (192.168.88.1), терминал:
/ip firewall mangle
add action=passthrough chain=forward comment=DATA_TRAFFIC connection-state=established protocol=tcp
add action=passthrough chain=forward comment=TCP_ERRORS connection-state=invalid protocol=tcp
Проверить можно в IP --> Firewall --> Mangle
Качество = 100% - %ошибок, где %ошибок = TCP-ошибки / TCP-траффик за период
Можно также смотреть ошибки TCP_RST, но я их не пока не успел настроить.
Если ошибок > порога, будем снижать MSS для стабилизации канала. Если ошибок < порога в течении определенного времени, будем медленно повышать MSS обратно.
2) проверять доступность ключевых сервисов (сайтов). В моем случае это базовые Android и Microsoft (те которые проверяются смартфоном и виндой и если с ними проблемы появляется восклицательный знак на сети Wifi) и важные для меня xiaomi и 4pda. Базовые я рекомендую оставить, а дополнительные у вас могут быть свои.
Если не доступна часть (или хотя бы один) сервисов - пробуем резко снизить mss, и проверить этот сервис еще раз. Если не получается даже с очень низким MSS, то имеем либо физическую проблему на канале, либо зависание сессии LTE, либо что чаще всего для тяжелых https сервисов типа 4pda - DPI блокировку оператора.
Во всех этих случаях наш вариант - перезагрузка LTE модема. Я делал мягкую перезагрузку LTE интерфейса в микротик (disable/enable), но можно попробовать и более быстрый вариант (режим полета вкл/выкл) - через AT команду. Цель этого - перерегистрироваться в сети оператора и получить новый ip, который пройдет по новым чистым путям до наших сервисов и они станут доступны.
Автоматизация - мониторинг качества интернета
-
Создаем счетчики mangle для расчета качества (см. выше скрипт)
-
Создаем правило mangle для управление параметром MSS
-
Создаем скрипт для анализа счетчиков, расчета качества и динамическим управлением MSS. Дополнительно этот скрипт будет логировать метрики если %ошибок > 0 или если меняем MSS (в любую сторону) (скрипт будет ниже)
-
Ставим на график запуска - каждые 5 сек (чтобы быстрее реагировать на падение качества)
Правило mangle для управления параметром MSS (обратите внимание на out-interface - там должно быть имя вашего LTE интерфейса в микротик)
/ip firewall mangle
add action=change-mss chain=forward comment=MSS_ADAPTIVE connection-state=new new-mss=1100 out-interface=lte1 protocol=tcp tcp-flags=syn tcp-mss=501-65535
Скрипт "script_changeMSS_on_Network_Quality"
# Запускать каждые 5 сек!
##########################
:local runPeriodSec 5
:local dataComment "DATA_TRAFFIC"
:local errorComment "TCP_ERRORS"
:local mssComment "MSS_ADAPTIVE"
####################################
### ВЫСТАВИТЬ ВЕРСИЮ СКРИПТА!!! ####
####################################
:global gMSSVersion "1.82"
####################################
####################################
:global gLastDataPacketsMangle
:global gLastErrPacketsMangle
:global gNoErrCycles
:global gMSSnotGoDownCycles
:global gSumData
:global gMSSchangedWhileCheckingServices
:global gIsCheckingServices
:local mssMax 1360
:local mssMin 800
:local mssGood (($mssMax + $mssMin) / 2)
:local mssStepUpSlow 10
:local mssStepUpFast 50
:local mssStepUp $mssStepUpSlow
:local mssStepDownFast 300
:local mssStepDownMiddle 150
:local mssStepDownSlow 100
:local mssStepDown $mssStepDownMiddle
:local rateThreshold 3
:local noErrCyclesForMssUp (50 / $runPeriodSec); # Сколько циклов тишины ждем (50 сек)
:local minCyclesBetweenMssDown (30 / $runPeriodSec);
:local dataPacketsIgnore (3 * $runPeriodSec)
:local errPacketsIgnore 2
:local currData [/ip firewall mangle get [find comment=$dataComment] packets]
:local currErr [/ip firewall mangle get [find comment=$errorComment] packets]
:if ([:len $gLastDataPacketsMangle] = 0) do={ :set gLastDataPacketsMangle $currData; :set gLastErrPacketsMangle $currErr; :set gNoErrCycles 0; :set gSumData 0; :set gMSSnotGoDownCycles 0; }
:if ([:len $gIsCheckingServices] = 0) do={ :set gIsCheckingServices false; }
:local dataPackets ($currData - $gLastDataPacketsMangle)
:local errPackets ($currErr - $gLastErrPacketsMangle)
:set gLastDataPacketsMangle $currData
:set gLastErrPacketsMangle $currErr
:local mssID [/ip firewall mangle find comment=$mssComment]
# ЕСЛИ Правило Mangle задано (есть чем управлять) и есть данные с предыдушего запуска
:if ([:len $mssID] > 0 && $dataPackets > 0) do={
:local currentMSS [/ip firewall mangle get $mssID new-mss]
:if ([:len $currentMSS] = 0 || [:typeof [:tonum $currentMSS]] = "nil") do={
:set currentMSS $mssGood
}
:local nextMSS $currentMSS
:if ($currentMSS < $mssGood) do={ :set mssStepUp $mssStepUpFast } else={ :set mssStepUp $mssStepUpSlow }
:local rawRate 0
:if ($dataPackets > 0) do={
:set rawRate (($errPackets * 10000) / $dataPackets)
}
:local intRate ($rawRate / 100)
:local fracRate ($rawRate % 100)
:local padding ""
:if ($fracRate < 10) do={ :set padding "0" }
:if ($errPackets > 0) do={
:set gNoErrCycles 0;
:set gSumData $dataPackets;
} else={
:set gNoErrCycles ($gNoErrCycles + 1);
:set gSumData ($gSumData + $dataPackets);
}
# Увеличиваем счетчик здесь, чтобы не зависить от объема трафика ниже. Если будет сброс ниже, то этот счетчик все равно обнулится
:set gMSSnotGoDownCycles ($gMSSnotGoDownCycles + 1)
# Формируем строку NoErrCycles для лога (потому что потом gNoErrCycles может изменится, а мы хотим в логе видеть то что было)
:local noErrCyclesLogString $gNoErrCycles
:if ($dataPackets >= $dataPacketsIgnore) do={
# ЛОГИКА СБРОСА (Снижение) - превышен порог ошибок как в % так и в шт., есть куда опускаться, не опускались в ближайших предыдущих циклах
:if ($intRate >= $rateThreshold && $errPackets > $errPacketsIgnore && $currentMSS > $mssMin && $gMSSnotGoDownCycles >= $minCyclesBetweenMssDown) do={
:if ($intRate > 30) do={
:set mssStepDown $mssStepDownFast
} else={
:if ($intRate > 10) do={
:set mssStepDown $mssStepDownMiddle
} else={
:set mssStepDown $mssStepDownSlow
}
}
:set nextMSS ($currentMSS - $mssStepDown)
:if ($nextMSS < $mssMin) do={ :set nextMSS $mssMin }
:set gMSSnotGoDownCycles 0;
} else={
# ЛОГИКА ПОДЪЕМА - есть куда подниматься, нет ошибок в течении нескольких последних циклов, не выполняются проверки сервисов
:if ($currentMSS < $mssMax && $gNoErrCycles >= $noErrCyclesForMssUp && $gIsCheckingServices = false) do={
:set nextMSS ($currentMSS + $mssStepUp)
:if ($nextMSS > $mssMax) do={ :set nextMSS $mssMax }
:set gNoErrCycles 0;
}
}
}
# Формируем строку MSS для лога И МЕНЯЕМ MSS!
:local mssLogString $currentMSS
:if ($nextMSS != $currentMSS) do={
/ip firewall mangle set $mssID new-mss=$nextMSS
:set mssLogString "$currentMSS -> $nextMSS"
:if ($gIsCheckingServices = true) do={ :set gMSSchangedWhileCheckingServices true; }
}
# ЛОГИРОВАНИЕ С РАЗДЕЛЕНИЕМ УРОВНЕЙ (только если есть ошибочные пакеты или изменили MSS)
:if ($errPackets > 0 || $nextMSS != $currentMSS) do={
:local logMsg "MSS_ADAPT: Traffic: $gSumData, Errors: $errPackets, Rate: $intRate.$padding$fracRate%, MSS: $mssLogString, NoErrCycles: $noErrCyclesLogString"
:if ($nextMSS < $currentMSS || ($intRate >= $rateThreshold && $errPackets > $errPacketsIgnor)) do={
# Если стало хуже (снижаем MSS) - пишем Warning
:log warning $logMsg
} else={
# Если стало лучше (повышаем) или сохраняем стабильный MSS - пишем Info
:log info $logMsg
}
# Если напечатали трафик, сбрасываем чтобы в следующий раз считать заново
:set gSumData 0
}
}
Ставим на график запуска:
/system scheduler
add interval=5s name=job_changeMSS_on_Network_Quality on-event=script_changeMSS_on_Network_Quality policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-time=startup
Через несколько секунд/минут в логе должны пойти примерно такие записи:
2026-01-23 09:03:59 script,info MSS_ADAPT: Traffic: 151, Errors: 0, Rate: 0.00%, MSS: 910 -> 915, NoErrCycles: 4
2026-01-23 09:04:39 script,info MSS_ADAPT: Traffic: 255, Errors: 0, Rate: 0.00%, MSS: 915 -> 920, NoErrCycles: 4
2026-01-23 09:05:19 script,info MSS_ADAPT: Traffic: 429, Errors: 0, Rate: 0.00%, MSS: 920 -> 925, NoErrCycles: 4
2026-01-23 09:05:59 script,info MSS_ADAPT: Traffic: 432, Errors: 0, Rate: 0.00%, MSS: 925 -> 930, NoErrCycles: 4
2026-01-23 09:06:19 script,info MSS_ADAPT: Traffic: 63, Errors: 1, Rate: 1.58%, MSS: 800, NoErrCycles: 0
2026-01-23 09:07:09 script,warning MSS_ADAPT: Traffic: 18, Errors: 4, Rate: 22.22%, MSS: 800, NoErrCycles: 0
2026-01-23 09:07:59 script,info MSS_ADAPT: Traffic: 82, Errors: 0, Rate: 0.00%, MSS: 800 -> 805, NoErrCycles: 5
2026-01-23 09:08:09 script,info MSS_ADAPT: Traffic: 90, Errors: 1, Rate: 1.11%, MSS: 805, NoErrCycles: 0
Автоматизация - мониторинг доступности сервисов и жесткие действия
-
Создаем бота в телеге который будет нам слать ошибки
-
Создаем скрипт мониторинга доступности сервисов, тестирования на экстремально низком MSS и перезагрузки LTE-модема. А также будем слать уведомления в Telegram при ошибках. В лог конечно тоже все запишем.
-
Ставим на график раз в 3 минуты
Скрипт "script_Check_Limited_Internet"
:global gIsCheckingServices true;
:global gMSSchangedWhileCheckingServices;
:if ([:len $gMSSchangedWhileCheckingServices] = 0) do={ :set gMSSchangedWhileCheckingServices false; }
:local mssComment "MSS_ADAPTIVE"
:local isDPIbock false
:local androidCheckHttps "ER";
:local windowsCheckHttp "ER";
:local miCheckHttps "ER";
:local forPDACheckHttps "ER";
####################################
### ВЫСТАВИТЬ ВЕРСИЮ СКРИПТА!!! ####
####################################
:global gServicesVersion "4.64"
####################################
##########################################################
# ЗАМЕНИТЬ МНОГОТОЧИЯ НА ID СВОЕГО БОТА и ЧАТА В ТЕЛЕГЕ ##
##########################################################
:local sBotId "........"
:local sChatId "......."
##########################################################
:local sDiagnoseMessages ""
:local logGlobalPfx "CHECK_INET:"
# Считывем текущее значение MSS
:local mssID [/ip firewall mangle find comment=$mssComment]
:local initialMSS ""
:if ([:len $mssID] > 0) do={
:set initialMSS [/ip firewall mangle get $mssID new-mss]
}
# Объявляем функцию сброса MSS
:local dropMSS do={
:local newMSSonError 500
:global gNoErrCycles 0;
:global gSumData 0;
:if ([:len $3] > 0) do={
:global gPrevMSS [/ip firewall mangle get $3 new-mss]
:if ($gPrevMSS > $newMSSonError) do={
/ip firewall mangle set $3 new-mss=$newMSSonError
:log warning "$1 $2 failure detected. Try dropping MSS: $gPrevMSS -> $newMSSonError, NoErrCycles: 0"
:delay 2s
}
}
}
# Объявляем функцию восстановления MSS
:local restoreMSS do={
:if ([:len $3] > 0) do={
:local curMSS [/ip firewall mangle get $3 new-mss]
:global gPrevMSS;
:if ($curMSS != $gPrevMSS) do={
/ip firewall mangle set $3 new-mss=$gPrevMSS
:log warning "$1 $2 DPI block detected, restoring MSS: $gPrevMSS"
:delay 2s
}
}
}
# 1. ЗАЩИТА: Не трогаем модем первые 5 минут после старта роутера
:local lteStatus [/interface lte monitor lte1 once as-value];
:local lteUp ($lteStatus->"session-uptime");
:if ([:len $lteUp] = 0) do={ :set lteUp ($lteStatus->"uptime") };
# Если аптайм пустой (модем инициализируется) или меньше 5 минут
:if ([:len $lteUp] = 0 or $lteUp < 00:05:00) do={
:log warning "$logGlobalPfx LTE Guard: Modem is warming up (current: $lteUp). Skipping check.";
:error "Warmup mode";
}
# 1.1 Проверка Android Style HTTPS (max 9 sec)
:do {
:set $gMSSchangedWhileCheckingServices false
/tool fetch url="https://connectivitycheck.gstatic.com/generate_204" check-certificate=no keep-result=no idle-timeout=3s;
:set androidCheckHttps "ok";
} on-error={
:log warning "$logGlobalPfx Android Check HTTPS: FAILED (https://connectivitycheck.gstatic.com/generate_204)"
# МГНОВЕННЫЙ СБРОС MSS ДЛЯ БЫСТРОГО ВОССТАНОВЛЕНИЯ
[$dropMSS $logGlobalPfx "Android" $mssID];
:do {
/tool fetch url="https://www.google.com/generate_204" check-certificate=no keep-result=no idle-timeout=3s;
:set androidCheckHttps "ok";
} on-error={
:log warning "$logGlobalPfx Android Check HTTPS: FAILED (https://www.google.com/generate_204)"
:do {
/tool fetch url="https://play.google.com/generate_204" check-certificate=no keep-result=no idle-timeout=3s;
:set androidCheckHttps "ok";
} on-error={
:log warning "$logGlobalPfx Android Check HTTPS: FAILED (https://play.google.com/generate_204)"
# Если после сброса все проверки отвалились, то восстанавливаем MSS (имеем дело с DPI блокировкой или полным отсутствием интернета или попали на опускание MSS другим скриптом)
[$restoreMSS $logGlobalPfx "Android" $mssID];
#если при этом попали на опускание mss другим скриптом, то не доверяем результату - ставим skipped
if ($gMSSchangedWhileCheckingServices = true) do={
:set androidCheckHttps "skipped"
:log info "$logGlobalPfx Android Check HTTPS: skipped due to MSS change while checking"
} else={
:set isDPIbock true;
}
};
};
};
# 1.2 Проверка Microsoft Style (max 15 sec)
# если все проверки Android провалились, не тратим время на MS - ставим skipped, нужно быстрее перезагружать модем!
:if ($androidCheckHttps = "ER" ) do={
:set windowsCheckHttp "skipped"
:log info "$logGlobalPfx Microsoft Check HTTP: skipped due to Android HTTPS fail"
} else={
:set $gMSSchangedWhileCheckingServices false
:local isMSSdroped false
:for i from=1 to=3 do={
:if ($windowsCheckHttp != "ok") do={
:do {
/tool fetch url="http://www.msftconnecttest.com/connecttest.txt" keep-result=no idle-timeout=3s;
:set windowsCheckHttp "ok";
} on-error={
:log warning "$logGlobalPfx Microsoft Check HTTP: FAILED, i=$i"
# МГНОВЕННЫЙ СБРОС MSS ДЛЯ БЫСТРОГО ВОССТАНОВЛЕНИЯ
if ($isMSSdroped = false) do={
[$dropMSS $logGlobalPfx "Microsoft" $mssID];
:set isMSSdroped true;
}
}
}
}
# Если после сброса все проверки отвалились, то восстанавливаем MSS (имеем дело с DPI блокировкой или полным отсутствием интернета или попали на опускание MSS другим скриптом)
if ($windowsCheckHttp != "ok") do={
[$restoreMSS $logGlobalPfx "Microsoft" $mssID];
#если при этом попали на опускание mss другим скриптом, то не доверяем результату - ставим skipped
if ($gMSSchangedWhileCheckingServices = true) do={
:set windowsCheckHttp "skipped"
:log info "$logGlobalPfx Microsoft Check HTTP: skipped due to MSS change while checking"
} else={
:set isDPIbock true;
}
}
}
# 1.3 Проверка Xiaomi (max 21 sec)
# если все проверки Android провалились или проверка Microsoft провалилась, не тратим время на Xiaomi - ставим skipped
:if ($windowsCheckHttp = "skipped" or $windowsCheckHttp = "ER") do={
:set miCheckHttps "skipped"
:log info "$logGlobalPfx Xiaomi Check HTTPS: skipped due to Android/Microsoft HTTPS/HTTP fail or DPI block"
} else={
:set $gMSSchangedWhileCheckingServices false
:local isMSSdroped false
:for i from=1 to=2 do={
:if ($miCheckHttps != "ok") do={
:do {
/tool fetch url="https://region.hlth.io.mi.com/abroad_download?redir=11817" check-certificate=no keep-result=no idle-timeout=5s http-max-redirect-count=5;
:set miCheckHttps "ok";
} on-error={
:log warning "$logGlobalPfx Xiaomi Check HTTPS: FAILED, i=$i"
# МГНОВЕННЫЙ СБРОС MSS ДЛЯ БЫСТРОГО ВОССТАНОВЛЕНИЯ
if ($isMSSdroped = false) do={
[$dropMSS $logGlobalPfx "Xiaomi" $mssID];
:set isMSSdroped true;
}
}
}
}
# Если после сброса все проверки отвалились, то восстанавливаем MSS (имеем дело с DPI блокировкой или полным отсутствием интернета или попали на опускание MSS другим скриптом)
if ($miCheckHttps != "ok") do={
[$restoreMSS $logGlobalPfx "Xiaomi" $mssID];
#если при этом попали на опускание mss другим скриптом, то не доверяем результату - ставим skipped
if ($gMSSchangedWhileCheckingServices = true) do={
:set miCheckHttps "skipped"
:log info "$logGlobalPfx Xiaomi Check HTTPS: skipped due to MSS change while checking"
} else={
:set isDPIbock true;
}
}
};
# 1.4 Проверка forPDA (max 21 sec)
# если все проверки Android провалились или проверка Microsoft провалилась не тратим время на forPDA - ставим skipped
:if ($windowsCheckHttp = "skipped" or $windowsCheckHttp = "ER" or $isDPIbock) do={
:set forPDACheckHttps "skipped"
:log info "$logGlobalPfx forPDA Check HTTPS: skipped due to Android/Microsoft HTTPS/HTTP fail or DPI block"
} else={
:set $gMSSchangedWhileCheckingServices false
:local isMSSdroped false
:for i from=1 to=2 do={
:if ($forPDACheckHttps != "ok") do={
:do {
/tool fetch url="https://4pda.to/forum/index.php?showtopic=1065946&st=2240#entry141234147" check-certificate=no keep-result=no idle-timeout=5s http-max-redirect-count=5;
:set forPDACheckHttps "ok";
} on-error={
:log warning "$logGlobalPfx forPDA Check HTTPS: FAILED, i=$i"
# МГНОВЕННЫЙ СБРОС MSS ДЛЯ БЫСТРОГО ВОССТАНОВЛЕНИЯ
if ($isMSSdroped = false) do={
[$dropMSS $logGlobalPfx "4pda" $mssID];
:set isMSSdroped true;
}
}
}
}
# Если после сброса все проверки отвалились, то восстанавливаем MSS (имеем дело с DPI блокировкой или полным отсутствием интернета или попали на опускание MSS другим скриптом)
if ($forPDACheckHttps != "ok") do={
[$restoreMSS $logGlobalPfx "4pda" $mssID];
#если при этом попали на опускание mss другим скриптом, то не доверяем результату - ставим skipped
if ($gMSSchangedWhileCheckingServices = true) do={
:set forPDACheckHttps "skipped"
:log info "$logGlobalPfx 4pda Check HTTPS: skipped due to MSS change while checking"
} else={
:set isDPIbock true;
}
}
};
:local finalMSS ""
:local MSSmsg ""
:if ([:len $mssID] > 0) do={
:set finalMSS [/ip firewall mangle get $mssID new-mss]
:if ($finalMSS != $initialMSS) do={
:set MSSmsg "$initialMSS -> $finalMSS"
} else={
:set MSSmsg $initialMSS
}
}
# Получаем внешний IP: Сначала Yandex API, если нет - Ipify
:global gLastPublicIP
:local currentPublicIP "unknown"
:do {
:do {
# Попытка через Yandex API
:local result [/tool fetch url="https://yandex.ru/internet/api/v0/ip" mode=https output=user as-value duration=3s]
# Проверяем наличие данных перед обработкой
:if ([:len ($result->"data")] > 0) do={
:local rawData ($result->"data")
:set currentPublicIP [:pick $rawData ([:find $rawData """] + 1) [:find $rawData """ ([:find $rawData """] + 1)]]
} else={ :error "fail" }
} on-error={
# Если Яндекс не ответил или данные пустые, пробуем Ipify
:local result [/tool fetch url="https://api.ipify.org" mode=https output=user as-value duration=3s]
:if ([:len ($result->"data")] > 0) do={
:set currentPublicIP ($result->"data")
} else={ :error "fail" }
}
} on-error={
# Сюда попадем, если оба запроса не вернули данные или упали по таймауту
:set currentPublicIP "timeout"
}
# Очистка от возможных невидимых символов (переносы строк)
:if ($currentPublicIP != "timeout") do={
:if ([:find $currentPublicIP "n"] > 0) do={ :set currentPublicIP [:pick $currentPublicIP 0 [:find $currentPublicIP "n"]] }
:if ([:find $currentPublicIP "r"] > 0) do={ :set currentPublicIP [:pick $currentPublicIP 0 [:find $currentPublicIP "r"]] }
}
# Логика уведомления о смене IP
:local ipMsg ""
:if ([:len $currentPublicIP] > 0 && [:len $gLastPublicIP] > 0 && $currentPublicIP != $gLastPublicIP && $currentPublicIP != "timeout") do={
:set ipMsg "$gLastPublicIP -> $currentPublicIP"
} else={
:set ipMsg $currentPublicIP
}
:if ([:len $currentPublicIP] > 0 && $currentPublicIP != "timeout") do={
:set gLastPublicIP $currentPublicIP
}
:local sStatus ("As: " . $androidCheckHttps . ", MS: " . $windowsCheckHttp . ", XiaoMIs: " . $miCheckHttps . ", 4pda: " . $forPDACheckHttps . ", MSS: " . $MSSmsg . ", IP: " . $ipMsg)
:if ($isDPIbock = true) do={ :set sStatus ($sStatus . ", DPI BLOCK!!!") }
:set gIsCheckingServices false;
:set gMSSchangedWhileCheckingServices false;
# 2. Логика перезапуска и формирование сообщения для телеграмм: если не прошел AndroidHttpS или WindowsHttp
:if ($androidCheckHttps = "ER" or $windowsCheckHttp = "ER" or $isDPIbock = true) do={
:log error "$logGlobalPfx INTERNET LIMITED!!! ($sStatus). Restarting LTE...";
:set sStatus ("FAILED! " . $sStatus . ". Restarting LTE...")
:if ([:len $sDiagnoseMessages] > 0) do={:set sStatus ($sStatus . "rn" . $sDiagnoseMessages)}
/interface lte disable lte1;
:delay 10s;
/interface lte enable lte1;
# Пауза 30 секунд, чтобы модем успел подключиться, прежде чем слать уведомление
:delay 30s;
} else={
:if ($androidCheckHttps = "ok" and $windowsCheckHttp = "ok" and $miCheckHttps = "ok" and $forPDACheckHttps = "ok") do={
:log info "$logGlobalPfx FULL ACCESS (All OK: $sStatus)";
:set sStatus ("success. " . $sStatus)
} else= {
:log warning "$logGlobalPfx CORE SERVICES ACCESS (Warning: $sStatus)";
:set sStatus ("warning. " . $sStatus)
}
:if ([:len $sDiagnoseMessages] > 0) do={:set sStatus ($sStatus . "rn" . $sDiagnoseMessages)}
}
# 3. Отправка сообщения в телеграмм
:if ($androidCheckHttps = "ER" or $windowsCheckHttp = "ER" or $miCheckHttps = "ER" or $forPDACheckHttps = "ER") do={
:do {
/tool fetch http-method=post
url="https://api.telegram.org/bot$sBotId/sendMessage"
http-data="chat_id=$sChatId&text=$sStatus"
keep-result=no
} on-error= {
:log error "$logGlobalPfx Error sending statis message in Telegram , No Internet!"
}
}
ВАЖНО!
-
Впишите id бота и чата телеграм в переменные в начале скрипта
-
Проверьте имя своего LTE интерфейса в микротике. У меня lte1, если у вас другое, поменяйте в скрипте
-
Данная версия скрипта делаем мягкую перезагрузку модема при недоступности базовых сервисов (Android или Microsoft) или наличии DPI блокировки. Вы можете изменить это условие - добавить другой критичный вам сервис или отключить перезагрузку при DPI блокировке (она снимается перезагрузкой LTE модема, если он получает новый ip адрес). Ищите к в коде раздел "#2. Логика перезапуска ". Также можете поиграться с вкл/выкл режим полета через AT команды вместо мягкой перезагрузки, это должно работать быстрее чем перезагрузка и меньше вредить абонентам подключенным к роутеру.
-
Для проверки наличия DPI блокировки я обваливаю MSS в пол (500) и делаю повторную проверку. Если она проходит, оставляю MSS=500, если нет возвращаю MSS обратно и ставлю флаг DPI блокировка. Возможно для вас даже краткосрочное падение MSS до 500 будет катастрофично (для каких то других сервисов), тогда скорректируйте уровень пола под себя. Это настраивается в функции dropMSS.
Ставим скрипт на график
/system scheduler
add interval=3m name=job_Check_Limited_Internet on-event=script_Check_Limited_Internet policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-time=startup
Через несколько минут в логе пойдут сообщения, например такие:
2026-01-25 19:35:09 script,warning CHECK_INET: Xiaomi Check HTTPS: FAILED, i=1
2026-01-25 19:35:10 script,warning CHECK_INET: Xiaomi failure detected. Try dropping MSS: 1000 -> 500, NoErrCycles: 0
2026-01-25 19:35:43 script,warning CHECK_INET: Xiaomi Check HTTPS: FAILED, i=2
2026-01-25 19:35:43 script,warning CHECK_INET: Xiaomi DPI block detected, restoring MSS: 1000
2026-01-25 19:35:45 script,info CHECK_INET: forPDA Check HTTPS: skipped due to Android/Microsoft HTTPS/HTTP fail or DPI block
2026-01-25 19:35:46 script,error CHECK_INET: INTERNET LIMITED!!! (As: ok, MS: ok, XiaoMIs: ER, 4pda: skipped, MSS: 1000, IP: 31.173.86.163, DPI BLOCK!!!). Restarting LTE...
2026-01-25 19:38:55 script,info CHECK_INET: FULL ACCESS (All OK: As: ok, MS: ok, XiaoMIs: ok, 4pda: ok, MSS: 1050, IP: 31.173.86.163 -> 31.173.81.232)
2026-01-25 19:40:55 script,info CHECK_INET: FULL ACCESS (All OK: As: ok, MS: ok, XiaoMIs: ok, 4pda: ok, MSS: 1110, IP: 31.173.81.232)
В случае ошибок - будут сыпаться сообщения в телегу!
Расчет и накопление статистики для анализа
Самом сложным в всем этом управлении качеством был поиск стратегии балансировки MSS чтобы уменьшить кол-во DPI блокировок, недоступности сервисов и перезагрузки LTE модема.
Насколько быстро опускать, как поднимать MSS, насколько быстро и жестко реагировать про появлении ошибок TCP или ошибок в сервисах.
Я хотел добиться поведения приближенного к android смартфону. Ясно что он управляет не только MSS, и в первую очередь у него есть другие инструменты которые не применимы здесь. Но уподобиться идеалу хотелось. Меня всегда возмущало что при появлении сообщения DPI block в моем скрипте тот же сайт в ту же секунду открывался через мобильный интернет того же самого оператора на смартфоне.
Я думаю я не достиг идеала в поиске стратегии, но для того чтобы хотя бы попытаться мне нужна была статистика для сравнения разных версий стратегий (разных версий скриптов).
Я решил запилить третий скрипт, который читает логи, рассчитывает метрики качества и отправляет их в телегу и (самое вкусное!) в excel-таблицу для последующего анализа!
Для записи в excel-таблицу мы будем использовать google -форму с сохранением результатов в таблицу. Я хотел по православному писать в яндекс-форму, с сохранением в таблицу, но когда сделал столкнулся с тем что из скрипта в нее отправить не получается - ругается 302 предлагает заполнить капчу)). Пришлось на делать на гуглоформе.
-
Создаем бота в телеге, куда будем получать отчеты качества
-
Создаем google-форму и включаем сохранение результатов в таблицу
-
Создаем скрипт расчета статистики качества и ее отправки по 2 каналам
-
Ставим на график запуска 1 раз в час
Создаем гуглоформу с такими полями:
|
Кого мониторим |
Дата |
Час |
Доступность сервисов, % |
Доступность 4pda, % |
Доступность Android, % |
Доступность Microsoft, % |
Доступность xiaomi, % |
RTO_AVG 4pda, мин |
RTO_AVG ANDROID, мин |
RTO_AVG MICROSOFT, мин |
RTO_AVG XIAOMI, мин |
RTO_MIN 4PDA, мин |
RTO_MIN ANDROID, мин |
RTO_MIN MICROSOFT, мин |
RTO_MIN XIAOMI, мин |
RTO_MAX 4PDA, мин |
RTO_MAX ANDROID, мин |
RTO_MAX MICROSOFT, мин |
RTO_MAX XIAOMI, мин |
DPI_blocks, шт |
DPI_blocks, % |
MSS_AVG |
MSS_MIN |
MSS_MAX |
MSS_STABILITY, % |
MSS_CHANGES |
Версия скриптов подстройки |
Включаем сохранение результатов в таблицу: Ответы --> Установить связь с Таблицами
Выгружаем id формы и id ее полей для скрипта: Три точки справа вверху --> Форма с предварительным заполнением. Заполняем поля формы краткими названиями полей по-английски (иначе потом не разберетесь где какое поле) и нажимаем Получить ссылку. Копируем ее в блокнот и в ней будут все id.
Скрипт "script_Analyze_InternetStability_Logs"
:local periodInHours 1
:local sendTelegram true
##########################################################
# ЗАМЕНИТЬ МНОГОТОЧИЯ НА ID СВОЕГО БОТА и ЧАТА В ТЕЛЕГЕ ##
# Если у вас несколько устройств - для разных укажите ##
# разный sMonitoringSource ##
##########################################################
:local sBotId "........"
:local sChatId "......."
:local sMonitoringSource "me"
:local googleFormId "........"
##########################################################
:global gMSSVersion; :if ([:len $gMSSVersion] = 0) do={ :set gMSSVersion 0; }
:global gServicesVersion; :if ([:len $gServicesVersion] = 0) do={ :set gServicesVersion 0; }
:local period [:totime ($periodInHours . "h")]
:local hMinMss 2000; :local hMaxMss 0; :local hSumMss 0; :local hCountMss 0; :local hStabChanges 0
:local totalDPI 0; :local stabThreshold (60 * 2 * $periodInHours)
:local svcNames {"ANDROID";"MICROSOFT";"XIAOMI";"4PDA"}
:local stats [:toarray ""]
# --- УНИВЕРСАЛЬНЫЙ БЛОК ДАТЫ ---
:local cDate [/system clock get date]
:local cTime [/system clock get time]
:local fDate ""
:local gDate ""
:if ([:pick $cDate 4] = "-" || [:pick $cDate 7] = "-") do={
# Формат ISO (YYYY-MM-DD)
:set fDate ([:pick $cDate 8 10] . "." . [:pick $cDate 5 7] . "." . [:pick $cDate 0 4])
:set gDate $cDate
}
:local startTime ($cTime - $period)
:if ($startTime < 0s) do { :set startTime (00:00:00) }
:local timeRange ($fDate . " " . [:pick $startTime 0 5] . " - " . [:pick $cTime 0 5])
:foreach s in=$svcNames do={
:set ($stats->$s) {"err"=0; "ok"=0; "recSum"=0; "recCnt"=0; "maxRec"=0; "minRec"=999; "lastFail"=""}
}
:local logEntries [/log find where ((([:timestamp] + ([/system clock get gmt-offset] . "s")) - [:totime (time)]) <= $period)]
:foreach i in=$logEntries do={
:local item [/log get $i]
:local msg ($item->"message")
:local lTime ($item->"time")
:if ($msg ~ "MSS_ADAPT" || $msg ~ "MSS_STAT") do={
:local p [:find $msg "MSS: "]
:if ([:len $p] > 0) do={
:local pStart ($p + 5)
:local arrow [:find $msg "->" $p]
:if ([:len $arrow] > 0) do={ :set pStart ($arrow + 3); :set hStabChanges ($hStabChanges + 1) }
:local pEnd [:find $msg "," $pStart]
:if ([:len $pEnd] = 0) do={ :set pEnd [:find $msg " " $pStart] }
:if ([:len $pEnd] = 0) do={ :set pEnd [:len $msg] }
:local mVal [:tonum [:pick $msg $pStart $pEnd]]
:if ($mVal > 0) do={
:set hSumMss ($hSumMss + $mVal); :set hCountMss ($hCountMss + 1)
:if ($mVal < $hMinMss) do={ :set hMinMss $mVal }; :if ($mVal > $hMaxMss) do={ :set hMaxMss $mVal }
}
}
}
:if ($msg ~ "DPI BLOCK!!!" || $msg ~ "BLOCKED") do={ :set totalDPI ($totalDPI + 1) }
:if ($msg ~ "As:" || $msg ~ "MS:" || $msg ~ "XiaoMIs:" || $msg ~ "4pda:") do={
:foreach sName in=$svcNames do={
:local found false; :local sErr false; :local sOk false
:if ($sName = "ANDROID" && $msg ~ "As:") do={ :set found true; if ($msg ~ "As: ok") do={:set sOk true} else={:if ($msg ~ "As: ER") do={:set sErr true} } }
:if ($sName = "MICROSOFT" && $msg ~ "MS:") do={ :set found true; if ($msg ~ "MS: ok") do={:set sOk true} else={:if ($msg ~ "MS: ER") do={:set sErr true} } }
:if ($sName = "XIAOMI" && $msg ~ "XiaoMIs:") do={ :set found true; if ($msg ~ "XiaoMIs: ok") do={:set sOk true} else={:if ($msg ~ "XiaoMIs: ER") do={:set sErr true} } }
:if ($sName = "4PDA" && $msg ~ "4pda:") do={ :set found true; if ($msg ~ "4pda: ok") do={:set sOk true} else={:if ($msg ~ "4pda: ER") do={:set sErr true} } }
:if ($found) do={
:if ($sErr) do={
:set ($stats->$sName->"err") (($stats->$sName->"err") + 1)
:if ([:len ($stats->$sName->"lastFail")] = 0) do={ :set ($stats->$sName->"lastFail") $lTime }
}
:if ($sOk) do={
:set ($stats->$sName->"ok") (($stats->$sName->"ok") + 1)
:local fTime ($stats->$sName->"lastFail")
:if ([:len $fTime] > 0) do={
:local diff ($lTime - $fTime)
:local dur (([:tonum [:pick $diff 0 2]] * 60) + [:tonum [:pick $diff 3 5]])
:set ($stats->$sName->"recSum") (($stats->$sName->"recSum") + $dur)
:set ($stats->$sName->"recCnt") (($stats->$sName->"recCnt") + 1)
:if ($dur > ($stats->$sName->"maxRec")) do={ :set ($stats->$sName->"maxRec") $dur }
:if ($dur < ($stats->$sName->"minRec")) do={ :set ($stats->$sName->"minRec") $dur }
:set ($stats->$sName->"lastFail") ""
}
}
}
}
}
}
:local avgMss 0; :if ($hCountMss > 0) do={ :set avgMss ($hSumMss / $hCountMss) }
:local stabPct (100 - (($hStabChanges * 100) / $stabThreshold)); :if ($stabPct < 0) do={ :set stabPct 0 }
:local dpiPct (($totalDPI * 100) / (30 * $periodInHours) );
:local weightSum 0
:foreach sn,sd in=$stats do={
:local eC ($sd->"err"); :local oC ($sd->"ok"); :local av 100
:if (($eC + $oC) > 0) do={ :set av (($oC * 100) / ($eC + $oC)) }
:local w 1; :if ($sn = "ANDROID" || $sn = "MICROSOFT") do={ :set w 2 }
:set weightSum ($weightSum + ($av * $w))
}
:local integralAvail ($weightSum / 6)
:local out "rn=== ОТЧЕТ КАЧЕСТВА ===rn"
:set out ($out . "$timeRange ($periodInHours ч.)rn")
:set out ($out . "1. доступность сервисов $integralAvail%rn")
:foreach sName,sData in=$stats do={
:local eC ($sData->"err"); :local oC ($sData->"ok"); :local av 100
:local total ($eC + $oC)
:if ($total > 0) do={ :set av (($oC * 100) / $total) }
:local rSum ($sData->"recSum"); :local rCnt ($sData->"recCnt"); :local rAvg 0
:if ($rCnt > 0) do={ :set rAvg ($rSum / $rCnt) }
:local rMin ($sData->"minRec"); :if ($rMin = 999) do={ :set rMin 0 }
:local rMax ($sData->"maxRec")
:local sRTO "";
:if ($rAvg > 0) do={
:set sRTO ", RTO ~$rAvg мин "
:if ($rMin != $rMax) do={ :set sRTO "$sRTO ($rMin..$rMax мин)" }
}
:set out ($out . " - $sName: $av%$sRTOrn")
}
:set out ($out . "2. блокировки DPI: $totalDPI шт. ($dpiPct%)rn")
:set out ($out . "3. MSS: ~$avgMss ($hMinMss..$hMaxMss), стабильность: $stabPct% ($hStabChanges изм.)rn")
:log info $out
# Отправка в Telegram
:if ($sendTelegram) do={
:do {
/tool fetch http-method=post
url="https://api.telegram.org/bot$sBotId/sendMessage"
http-data="chat_id=$sChatId&text=$out"
keep-result=no
} on-error= {
:log error "Error sending statis message in Telegram , No Internet!"
}
}
# --- ОТПРАВКА В GOOGLE ФОРМУ ---
:local currentHour [:tonum [:pick $cTime 0 2]]
:local aAv 100; :local mAv 100; :local xAv 100; :local pAv 100
:local aSum (($stats->"ANDROID"->"ok") + ($stats->"ANDROID"->"err"))
:local mSum (($stats->"MICROSOFT"->"ok")+ ($stats->"MICROSOFT"->"err"))
:local xSum (($stats->"XIAOMI"->"ok") + ($stats->"XIAOMI"->"err"))
:local pSum (($stats->"4PDA"->"ok") + ($stats->"4PDA"->"err"))
:if ($aSum > 0) do={ :set aAv (($stats->"ANDROID"->"ok" * 100) / $aSum) }
:if ($mSum > 0) do={ :set mAv (($stats->"MICROSOFT"->"ok" * 100) / $mSum) }
:if ($xSum > 0) do={ :set xAv (($stats->"XIAOMI"->"ok" * 100) / $xSum) }
:if ($pSum > 0) do={ :set pAv (($stats->"4PDA"->"ok" * 100) / $pSum) }
:local aRa 0; :if (($stats->"ANDROID"->"recCnt") > 0) do={ :set aRa (($stats->"ANDROID"->"recSum") / $stats->"ANDROID"->"recCnt") }
:local mRa 0; :if (($stats->"MICROSOFT"->"recCnt") > 0) do={ :set mRa (($stats->"MICROSOFT"->"recSum") / $stats->"MICROSOFT"->"recCnt") }
:local xRa 0; :if (($stats->"XIAOMI"->"recCnt") > 0) do={ :set xRa (($stats->"XIAOMI"->"recSum") / $stats->"XIAOMI"->"recCnt") }
:local pRa 0; :if (($stats->"4PDA"->"recCnt") > 0) do={ :set pRa (($stats->"4PDA"->"recSum") / $stats->"4PDA"->"recCnt") }
:local aMi ($stats->"ANDROID"->"minRec"); :if ($aMi = 999) do={:set aMi 0}; :local aMa ($stats->"ANDROID"->"maxRec")
:local mMi ($stats->"MICROSOFT"->"minRec"); :if ($mMi = 999) do={:set mMi 0}; :local mMa ($stats->"MICROSOFT"->"maxRec")
:local xMi ($stats->"XIAOMI"->"minRec"); :if ($xMi = 999) do={:set xMi 0}; :local xMa ($stats->"XIAOMI"->"maxRec")
:local pMi ($stats->"4PDA"->"minRec"); :if ($pMi = 999) do={:set pMi 0}; :local pMa ($stats->"4PDA"->"maxRec")
:local urlData ("entry.263257593=" . $sMonitoringSource .
"&entry.922317815=MSS v$gMSSVersion, Services v$gServicesVersion" .
"&entry.1972153670=" . $gDate .
"&entry.1309071647=" . $currentHour .
"&entry.1232031999=" . $integralAvail .
"&entry.1037735613=" . $pAv .
"&entry.310815387=" . $aAv .
"&entry.235660758=" . $mAv .
"&entry.1138524409=" . $xAv .
"&entry.1825690509=" . $pRa .
"&entry.472580822=" . $aRa .
"&entry.1981616555=" . $mRa .
"&entry.1297338224=" . $xRa .
"&entry.1749190856=" . $pMi .
"&entry.939473659=" . $aMi .
"&entry.355961275=" . $mMi .
"&entry.1374920035=" . $xMi .
"&entry.2101070622=" . $pMa .
"&entry.1109708284=" . $aMa .
"&entry.1109226438=" . $mMa .
"&entry.1999941440=" . $xMa .
"&entry.1938952119=" . $totalDPI .
"&entry.1999467399=" . $dpiPct .
"&entry.1882390038=" . $avgMss .
"&entry.631664603=" . $hMinMss .
"&entry.1412379302=" . $hMaxMss .
"&entry.1772357571=" . $stabPct .
"&entry.1644530981=" . $hStabChanges)
:do {
/tool fetch http-method=post
url="https://docs.google.com/forms/d/e/$googleFormId/formResponse"
http-data=$urlData
check-certificate=no
keep-result=no
} on-error={ :log error "GoogleForm: Send failed." }
ВАЖНО!
-
Впишите id бота и чата телеграм в переменные в начале скрипта
-
Впишите id google формы в начале скрипта
-
Замените id полей гугл формы (entry.*) в скрипте на ваши. Как их узнать см. выше
Ставим скрипт на график:
/system scheduler
add interval=1h name=job_script_Analyze_InternetStability_Logs on-event=script_Analyze_InternetStability_Logs policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2026-01-24 start-time=17:00:00
Через час в телегу начнут приходить такие уведомления:

А в google таблице должны накапливаться такие записи:

Поиск идеала
Проверяем что все логи пишутся, уведомления приходят, а записи падают в гугл таблицу. Затем даем поработать несколько дней или лучше неделю - накапливаем статистику.
После этого можно изменить параметры скриптов, например скорость падения MSS при ошибках, порог %ошибки для падения, скорость роста MSS при отсутствии ошибок, а также изменить реакцию на недоступность сервисов. Не забудьте в скриптах поменять версию в глобальной переменной, чтобы в excel-файле со статистой потом можно было разобраться к какой версии относятся те или иные записи.
Даем системе еще поработать с новыми параметрами несколько дней или неделю.
Затем открываем файл в старом добром excel, строим сводную отчетность, графики, короче проводим анализ всеми известными способами. Цель - сравнить разные версии скриптов доступность базовых сервисов, %DPI блокировок, стабильность MSS (частые дерганья MSS не идут на пользу). Выбираем версию которая показала себя лучше. Еще подкручиваем параметры у нее и повторяем все по кругу. И так можно до бесконечности. Работать также хорошо как на смартфоне android все равно не будет. Но увеличить доступность сервисов с базового уровня можно очень существенно!
Кстати ради интереса можете установить постоянную MSS и посмотреть, что будет. Для этого параметры max и min MSS можно установить в одно и тоже значение (например 1100 при плохом сигнале), такое же значение прописать в функции dropMSS.Накопить статистику также за неделю и сравнить. Не забывайте при любых изменениях менять версию скрипта в глобальной переменной в начале скрипта.
Выводы
Данная работа была посвящена изучению возможностей роутеров микротик в деле управления качеством мобильного интернета с плохим качеством LTE сигнала, т.е. в удаленных уголках. И да, качество можно поднять антенной, но оно все равно не будет таким как по проводному интернету.
Дополнительно, по ходу дела мы научились пулять сообщения в телегу из микротика, парсить логи микротика, а также отправлять статистику работы в гугл таблицу для накопления и последующего удаленного анализа.
Автор: occ2
