Pull to refresh

Mikrotik Router OS, скрипт для динамического деления скорости (Версия 2)

Reading time 13 min
Views 21K

Mikrotik Router OS, скрипт для динамического деления скорости (Версия 2)



Это вторая версия, первая лежит тут: тут.

На днях встала следующая проблема: делить скорость поровну между всеми пользователями, причем так, чтобы скорость не выделялась на клиентов, которые на данный момент не пользуются интернетом, а отдавалась всем остальным, еще чтобы при большом количестве клиентов и узком канале получить некоторую «буферность» канала.

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

В новой версии стало все значительно проще в плане расположения пользователей, теперь не имеет значения, как подключился пользователь, главное, что у него есть ip адрес и этого нам достаточно.

Итак, поехали…

Пример на трех пользователей.

Адреса пользователей 192.168.5.100-192.168.5.102

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

/ip firewall address-list add list="users" address=192.168.5.100
/ip firewall address-list add list="users" address=192.168.5.101
/ip firewall address-list add list="users" address=192.168.5.102


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

#Settings
######################################################
:local start ("100");
:local stop ("102");
:local net ("192.168.5.");
######################################################

######################################################
:global count ($start);
:for count from=$start to=$stop step=1 do={
/ip firewall address-list add list="users" address=( $net . $count);};
######################################################

######################################################
# (C) Inlarion icq 429-587 mikrotik.axiom-pro.ru Copyright!
######################################################


Вторым шагом является добавление записей в /queue и /ip firewall mangle, опять же чтобы освободить администратора от рутинной работы с добавлением записей вручную, что чревато ошибками т.к. человеческий фактор не дремлет, был набросан небольшой скрипт. Данный скрипт необходимо запускать каждый раз, когда вы добавляете или удаляете записи в /ip firewall address-list list=«users», его основная задача добавлять новые записи и удалять ненужные старые, очевидно, что у нормального системного администратора не должно быть мусора, который забыли удалить. После окончания работы скрипта в системный лог будет записана информация о количестве добавленных и удаленных записей.

#Settings
######################################################
:local DownloadParent ("Download");
:local UploadParent ("Upload");
######################################################

#Internal Var
######################################################
:local i;
:local z;
:local userX;
:local enum (" ");
:local mark;
:local qrd;
:local qru;
:local mrd;
:local mru;
:local qrdadd;
:local qruadd;
:local mrdadd;
:local mruadd;
:set qrd (0);
:set qru (0);
:set mrd (0);
:set mru (0);
:set qrdadd (0);
:set qruadd (0);
:set mrdadd (0);
:set mruadd (0);
######################################################

######################################################
:log warning ("Rules Manager Started!");

:if ([/queue type find name="dshaper_down"] = "") do={ /queue type add name="dshaper_down" kind="pcq" pcq-classifier=dst-address pcq-rate=0 pcq-limit=50 pcq-total-limit=2000;};

:if ([/queue type find name="dshaper_up"] = "") do={ /queue type add name="dshaper_up" kind="pcq" pcq-classifier=src-address pcq-rate=0 pcq-limit=50 pcq-total-limit=2000;};

:if ([/queue tree find name=$DownloadParent] = "") do={ /queue tree add name=$DownloadParent parent="global-out" queue="dshaper_down" priority=8;};
:if ([/queue tree find name=$UploadParent] = "") do={ /queue tree add name=$UploadParent parent="global-out" queue="dshaper_up" priority=8;};

:foreach i in=[/ip firewall address-list find list="users"] do={ :set userX [/ip firewall address-list get $i address];

:if ([/queue tree find name=($userX . "_down")] = "") do={ /queue tree add name=($userX . "_down") parent=$DownloadParent queue="dshaper_down" packet-mark=($userX . "_down") priority=8; :set qrdadd ($qrdadd+1); };
:if ([/queue tree find name=($userX . "_up")] = "") do={ /queue tree add name=($userX . "_up") parent=$UploadParent queue="dshaper_up" packet-mark=($userX . "_up") priority=8; :set qruadd ($qruadd+1);};

:set enum (" ");
:set enum ([/ip firewall mangle find comment=($userX . "_up")]);
:if ($enum = "") do={ /ip firewall mangle add chain=forward src-address=$userX dst-address=0.0.0.0/0 action=mark-packet new-packet-mark=($userX . "_up") comment=($userX . "_up") disabled=no passthrough=yes; :set mruadd ($mruadd+1);
};

:set enum (" ");
:set enum ([/ip firewall mangle find comment=($userX . "_down")]);
:if ($enum = "") do={ /ip firewall mangle add chain=forward src-address=0.0.0.0/0 dst-address=$userX action=mark-packet new-packet-mark=($userX . "_down") comment=($userX . "_down") disabled=no passthrough=yes; :set mrdadd ($mrdadd+1);
};

};

:foreach z in=[/queue tree find parent=$DownloadParent] do={
:set mark [/queue tree get $z name];
:if ($mark !="") do={
:set mark ([:tostr $mark]);
:set mark ([:pick $mark 0 ([:len $mark]-5)]);
:if ([/ip firewall address-list find address=$mark] = "") do={/queue tree remove [/queue tree find name=($mark . "_down")]; :set qrd ($qrd+1); };};};

:foreach z in=[/queue tree find parent=$UploadParent] do={
:set mark [/queue tree get $z name];
:if ($mark !="") do={
:set mark ([:tostr $mark]);
:set mark ([:pick $mark 0 ([:len $mark]-3)]);
:if ([/ip firewall address-list find address=$mark] = "") do={/queue tree remove [/queue tree find name=($mark . "_up")]; :set qru ($qru+1); };};};

:foreach z in=[/ip firewall mangle find src-address="0.0.0.0/0" action="mark-packet" chain="forward"] do={
:set mark [/ ip firewall mangle get $z comment];
:if ($mark !="") do={
:set mark ([:tostr $mark]);
:set mark ([:pick $mark 0 ([:len $mark]-5)]);
:if ([/ip firewall address-list find address=$mark] = "") do={
:if ([/ip firewall mangle find comment=($mark . "_down")] != "") do={/ip firewall mangle remove [/ip firewall mangle find comment=($mark . "_down")]; :set mrd ($mrd+1); }}}}

:foreach z in=[/ip firewall mangle find dst-address="0.0.0.0/0" action="mark-packet" chain="forward"] do={
:set mark [/ ip firewall mangle get $z comment];
:if ($mark !="") do={
:set mark ([:tostr $mark]);
:set mark ([:pick $mark 0 ([:len $mark]-3)]);
:if ([/ip firewall address-list find address=$mark] = "") do={
:if ([/ip firewall mangle find comment=($mark . "_up")] != "") do={/ip firewall mangle remove [/ ip firewall mangle find comment=($mark . "_up")]; :set mru ($mru+1); }}}}
######################################################

######################################################
:log info ("------------------------------------------");
:log warning ("Rules Manager:");
:log info ("Queue Tree Download Records Added: " . $qrdadd);
:log info ("Queue Tree Upload Records Added: " . $qruadd);
:log info ("Mangle Download Records Added: " . $mrdadd);
:log info ("Mangle Upload Records Added: " . $mruadd);
:log info ("Queue Tree Download Records Deleted: " . $qrd);
:log info ("Queue Tree Upload Records Deleted: " . $qru);
:log info ("Mangle Download Records Deleted: " . $mrd);
:log info ("Mangle Upload Records Deleted: " . $mru);
:log info ("------------------------------------------");
######################################################

######################################################
# (C) Inlarion icq 429-587 mikrotik.axiom-pro.ru Copyright!
######################################################


Теперь скрипт вычислительной и исполнительной части схемы


Принцип работы скрипта.


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

Рассчитывает:
MaxRateDownload делит на количество активных пользователей на прием, выводит скорость на пользователя.
MaxRateUpload делит на количество активных пользователей на отдачу, выводит скорость на пользователя.

Далее устанавливает лимит всем пользователям в Queue Tree согласно рассчитанной выше схеме.

Далее рассчитывает значение в килобитах и выводит в лог статистику.

Переменные в скрипте в самом начале:


MaxRateDownload -Ширина канала на всех пользователей (прием) Бит/сек.
MaxRateUpload -Ширина канала на всех пользователей (отдача) Бит/сек.
MaxRateDownloadNight -Ширина канала на всех пользователей в ночное время (прием) Бит/сек.
MaxRateUploadNight -Ширина канала на всех пользователей в ночное время (отдача) Бит/сек.

ActiveThresholddown -Порог при превышении, которого пользователь будет считаться активным (прием) Бит/сек.
ActiveThresholdup — Порог при превышении, которого пользователь будет считаться активным (отдача) Бит/сек.

usenighttime -может принимать значения «yes» и «no», разрешает скрипту использовать другую ширину канала. В соответствии с ночным тарифом.
nighttimestart -сообщает скрипту начало действия ночного тарифа.
nighttimestop -сообщает скрипту конец действия ночного тарифа.

#Settings
######################################################
:local MaxRateDownload ("15000000");
:local MaxRateUpload ("15000000");
:local MaxRateDownloadNight ("20000000");
:local MaxRateUploadNight ("20000000");

:local ActiveThresholddown ("15000");
:local ActiveThresholdup ("15000");

:local usenighttime ("yes");
:local nighttimestart ("02:00");
:local nighttimestop ("08:00");
######################################################

#Internal Var
######################################################
:local z;
:local i;
:local ii;
:local userX;
:local timedelay (0);
:local startmin;
:local startsec;
:local stopmin;
:local stopsec;
:local scripttimedelay (0);
:local scriptstartmin;
:local scriptstartsec;
:local scriptstopmin;
:local scriptstopsec;
:local userscount ("0");
:local userstmp ("");
:local firstdowntmp ("");
:local firstuptmp ("");
:local twodowntmp ("");
:local twouptmp ("");
:local activedownuserstmp ("");
:local activeupuserstmp ("");
:local activedowncount ("0");
:local activeupcount ("0");
######################################################

######################################################
:set scriptstartmin ([: pick [/system clock get time] 3 5]);
:set scriptstartsec ([: pick [/system clock get time] 6 8]);

:if ($usenighttime = "yes") do={
:set nighttimestart ([: pick $nighttimestart 0 2] . [: pick $nighttimestart 3 5]);
:set nighttimestop ([: pick $nighttimestop 0 2] . [: pick $nighttimestop 3 5]);
:local currenthours ([: pick [/system clock get time] 0 2]);
:local currenttime ([: pick [/system clock get time] 0 2] . [: pick [/system clock get time] 3 5] );
:local acttime ("day");
:local starttime ("day");
:if ($currenthours < 10) do={ :set acttime ("night"); };
:if ( [: pick $nighttimestart 0 2] < 10) do={ :set starttime ("night"); };
:local night ("no");

:if ($starttime = "night") do={
:if ( $currenttime > $nighttimestart && $currenttime < $nighttimestop) do={ :set night ("yes"); };
};
:if ($starttime = "day") do={
:if ( $acttime = "day") do={
:if ( $currenttime >= $nighttimestart) do={ :set night ("yes"); };
};
:if ( $acttime = "night") do={
:if ( $currenttime < $nighttimestop ) do={ :set night ("yes"); };
};};

:if ($night = "yes") do={
:set MaxRateDownload ($MaxRateDownloadNight);
:set MaxRateUpload ($MaxRateUploadNight);
};
};

:set ActiveThresholddown ($ActiveThresholddown / 8);
:set ActiveThresholdup ($ActiveThresholdup / 8);

:foreach i in=[/ip firewall address-list find list="users"] do={ :set userX [/ip firewall address-list get $i address];
:set userscount ($userscount+1);
:set userstmp ($userstmp . $userX . ",");
};

:local users [:toarray $userstmp];

:set startmin ([: pick [/system clock get time] 3 5]);
:set startsec ([: pick [/system clock get time] 6 8]);

:global dcount ("1");
:for dcount from=1 to=$userscount step=1 do={
:set firstdowntmp ($firstdowntmp . [/ip firewall mangle get [/ip firewall mangle find comment=[:pick $users ($dcount-1)] . "_down"] bytes] . ",");
:set firstuptmp ($firstuptmp . [/ip firewall mangle get [/ip firewall mangle find comment=[:pick $users ($dcount-1)] . "_up"] bytes] . ",");
};

:set stopmin ([: pick [/system clock get time] 3 5]);
:set stopsec ([: pick [/system clock get time] 6 8]);

:global dcount ("1");
:for dcount from=1 to=$userscount step=1 do={
:set twodowntmp ($twodowntmp . [/ip firewall mangle get [/ip firewall mangle find comment=[:pick $users ($dcount-1)] . "_down"] bytes] . ",");
:set twouptmp ($twouptmp . [/ip firewall mangle get [/ip firewall mangle find comment=[:pick $users ($dcount-1)] . "_up"] bytes] . ",");
};

:if ( $stopmin > $startmin) do={
:set timedelay (($stopmin-$startmin) * 60);
};
:set timedelay (($timedelay+$stopsec)-$startsec);

:local firstdown [:toarray $firstdowntmp];
:local firstup [:toarray $firstuptmp];
:local twodown [:toarray $twodowntmp];
:local twoup [:toarray $twouptmp];

:global dcount ("1");
:for dcount from=1 to=$userscount step=1 do={

:if ( ($ActiveThresholddown * $timedelay) < ([:pick $twodown ($dcount-1)] - [:pick $firstdown ($dcount-1)]) ) do={
:set activedownuserstmp ($activedownuserstmp . [:pick $users ($dcount-1)] . ",");
:set activedowncount ($activedowncount+1);
};

:if ( ($ActiveThresholdup * $timedelay) < ([:pick $twoup ($dcount-1)] - [:pick $firstup ($dcount-1)]) ) do={
:set activeupuserstmp ($activeupuserstmp . [:pick $users ($dcount-1)] . ",");
:set activeupcount ($activeupcount+1);
};

};

:local activedownusers [:toarray $activedownuserstmp];
:local activeupusers [:toarray $activeupuserstmp];

:local maxlimitdown ("0");
:local maxlimitup ("0");

:if ( $activedowncount > 0 ) do={
:set maxlimitdown ($MaxRateDownload/$activedowncount);
:global dcount ("1");
:for dcount from=1 to=$activedowncount step=1 do={
:if ([/queue tree get [find name=[:pick $activedownusers ($dcount-1)] . "_down"] max-limit] != $maxlimitdown) do={
/queue tree set [/queue tree find name=[:pick $activedownusers ($dcount-1)] . "_down"] max-limit=$maxlimitdown; };
};
};

:if ( $activeupcount > 0 ) do={
:set maxlimitup ($MaxRateUpload/$activeupcount);
:global dcount ("1");
:for dcount from=1 to=$activeupcount step=1 do={
:if ([/queue tree get [find name=[:pick $activeupusers ($dcount-1)] . "_up"] max-limit] != $maxlimitup) do={
/queue tree set [/queue tree find name=[:pick $activeupusers ($dcount-1)] . "_up"] max-limit=$maxlimitup; };
};
};

:local kbsmaxdown ($MaxRateDownload/1000);
:local kbsmaxup ($MaxRateUpload /1000);

:if ( $maxlimitdown = 0 ) do={ :set maxlimitdown ($MaxRateDownload); };
:if ( $maxlimitup = 0 ) do={ :set maxlimitup ($MaxRateUpload); };
:local kbsmaxlimitdown ($maxlimitdown/1024);
:local kbsmaxlimitup ($maxlimitup/1024);

:set scriptstopmin ([: pick [/system clock get time] 3 5]);
:set scriptstopsec ([: pick [/system clock get time] 6 8]);

:if ( $scriptstopmin > $scriptstartmin) do={
:set scripttimedelay (($scriptstopmin-$scriptstartmin) * 60);
};
:set scripttimedelay (($scripttimedelay+$scriptstopsec)-$scriptstartsec);
######################################################

######################################################
:log info ("------------------------------------------");
:log warning ("Shaper:");
:log info ("MaxRate Download : " . $MaxRateDownload . " bps /" . $kbsmaxdown . " kbs / Upload : " . $MaxRateUpload . " bps /" . $kbsmaxup . " kbs");
:log info ("Active Users : Download : " . $activedowncount . " / Upload : " . $activeupcount );
:log info ("User Speed Download : " . $maxlimitdown . " bps /" . $kbsmaxlimitdown . " kbs / Upload : " . $maxlimitdown . " bps /" . $kbsmaxlimitup . " kbs" );
:log warning ("Performance Time: " . $scripttimedelay . " seconds.");
:log info ("------------------------------------------");
######################################################

######################################################
# (C) Inlarion icq 429-587 mikrotik.axiom-pro.ru Copyright!
######################################################


Что касается последних пожеланий


В данной версии можно заметить добавление новой строки в системном логе «Performance Time», данная строка позволяет правильно установить интервал выполнения скрипта в планировщике. «Performance Time» отражает время выполнения скрипта в секундах с точностью +-1сек. Данное время резко увеличивается с количеством клиентов добавленных в /ip firewall address-list list=«users», нагрузкой на ваш микротик и неправильно выставленным интервалом выполнения скрипта, так что данный параметр необходимо постоянно контролировать.

Интервал выполнения скрипта устанавливайте исходя из формулы: Performance Time + 10-15 сек. При первом запуске скрипта установите интервал равный 1-2 минутам, в идеальном случае если система будет максимально загружена в этот момент.

Параметры скрипта:

:local MaxRateDownload ("15000000");
:local MaxRateUpload ("15000000");
:local MaxRateDownloadNight ("20000000");
:local MaxRateUploadNight ("20000000");

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

:local ActiveThresholddown ("15000");
:local ActiveThresholdup ("15000");


Эти значения устанавливайте согласно своим потребностям, но исходя из расчета: кол-во пользователей умноженное на Thresholddown = не дожно превышать MaxRateDownload иначе ваши пользователи будут всегда не активными! Естественно users*ActiveThresholdup = не должно превышать MaxRateUpload. Всегда оставляйте некоторый запас.

В дополнение ко всему хочу отметить то, что вопрос нецелесообразности применения скрипта в версиях 5.x закрыт, т.к. после тестирования версии 5.0rc7 я действительно убедился, что /queue в микротик стали работать значительно лучше по сравнению с более ранними версиями, поэтому в версиях 5.х скрипт не нужен. Но вопрос перехода на пятую версию весьма сомнителен, ввиду тех багов, которые допускают разработчики. К примеру, в rc3 коряво работает утилита ping и не возвращает параметры, в rc7 не работает маршрутизация при использовании имен в таблице, хотя в ранних версиях все работает на ура. Но это всего лишь RC, хотя уже седьмая по счету и это очень сильно беспокоит.

Даже если вам не понадобится основная часть, но вы используете /queue tree, можно просто использовать скрипт для создания правил, очень удобная штука при добавлении/удалении пользователей.
Tags:
Hubs:
+5
Comments 5
Comments Comments 5

Articles