Переглянути джерело

added automatic configuration of shapers for gateway interfaces

Roman Dmitriev 2 місяців тому
батько
коміт
25b7891520

+ 2 - 1
docs/databases/mysql/en/create_db.sql

@@ -123,7 +123,8 @@ CREATE TABLE `device_l3_interfaces` (
   `device_id` int(11) DEFAULT NULL,
   `snmpin` int(11) DEFAULT NULL,
   `interface_type` int(11) NOT NULL DEFAULT 0,
-  `name` varchar(100) DEFAULT NULL
+  `name` varchar(100) DEFAULT NULL,
+  `bandwidth` int(11) DEFAULT NULL
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
 CREATE TABLE `device_models` (

+ 6 - 6
docs/databases/postgres/en/create_db.sql

@@ -157,14 +157,14 @@ COMMENT ON TABLE device_filter_instances IS 'Filter instances assigned to device
 
 -- Device L3 interfaces
 CREATE TABLE device_l3_interfaces (
-id SERIAL PRIMARY KEY,
-device_id INTEGER,
-snmpin INTEGER,
-interface_type SMALLINT NOT NULL DEFAULT 0,
-name VARCHAR(100)
+    id SERIAL PRIMARY KEY,
+    device_id INTEGER,
+    snmpin INTEGER,
+    interface_type SMALLINT NOT NULL DEFAULT 0,
+    name VARCHAR(100),
+    bandwidth INTEGER
 );
 COMMENT ON TABLE device_l3_interfaces IS 'Layer 3 interfaces on devices';
-COMMENT ON COLUMN device_l3_interfaces.interface_type IS 'Interface type: 0=unknown, 1=LAN, 2=WAN, 3=DMZ';
 
 -- Device models
 CREATE TABLE device_models (

+ 7 - 7
docs/databases/postgres/ru/create_db.sql

@@ -155,16 +155,16 @@ device_id INTEGER
 );
 COMMENT ON TABLE device_filter_instances IS 'Экземпляры фильтров, назначенные устройствам';
 
--- L3 интерфейсы устройств
+-- Device L3 interfaces
 CREATE TABLE device_l3_interfaces (
-id SERIAL PRIMARY KEY,
-device_id INTEGER,
-snmpin INTEGER,
-interface_type SMALLINT NOT NULL DEFAULT 0,
-name VARCHAR(100)
+    id SERIAL PRIMARY KEY,
+    device_id INTEGER,
+    snmpin INTEGER,
+    interface_type SMALLINT NOT NULL DEFAULT 0,
+    name VARCHAR(100),
+    bandwidth INTEGER
 );
 COMMENT ON TABLE device_l3_interfaces IS 'Интерфейсы 3 уровня на устройствах';
-COMMENT ON COLUMN device_l3_interfaces.interface_type IS 'Тип интерфейса: 0=неизвестно, 1=LAN, 2=WAN, 3=DMZ';
 
 -- Модели устройств
 CREATE TABLE device_models (

+ 7 - 4
html/admin/devices/edit_l3int.php

@@ -30,7 +30,8 @@ if (getPOST("s_save") !== null) {
     $selected_ids = getPOST("s_id", null, []);      // отмеченные чекбоксы
     $all_ids      = getPOST("n_id", null, []);      // все ID
     $types        = getPOST("s_type", null, []);
-    
+    $bandwidth    = getPOST("s_band",null,[]);
+
     if (!empty($selected_ids) && is_array($selected_ids)) {
         $selected_ids = array_map('intval', $selected_ids);
         $selected_set = array_flip($selected_ids);
@@ -38,11 +39,10 @@ if (getPOST("s_save") !== null) {
         foreach ($all_ids as $i => $id) {
             $id = (int)$id;
             if ($id <= 0 || !isset($selected_set[$id])) continue;
-            
             $new = [
-                'interface_type' => (int)($types[$i] ?? 0)
+                'interface_type' => (int)($types[$i] ?? 0),
+                'bandwidth' => bitrate_to_kbps($bandwidth[$i] ?? '0k')
             ];
-            
             update_record($db_link, "device_l3_interfaces", "id = ?", $new, [$id]);
         }
     }
@@ -62,6 +62,7 @@ if (getPOST("s_create") !== null) {
                 'name'           => preg_replace('/"/', '', trim($parts[0])),
                 'snmpin'         => trim($parts[1]),
                 'interface_type' => (int)trim($parts[2]),
+                'bandwidth'      => (int)trim($parts[3] ?? 0),
                 'device_id'      => $id
             ];
             
@@ -120,6 +121,7 @@ print_editdevice_submenu($page_url, $id, $device['device_type'], $user_info['log
     <td width=30><b>id</b></td>
     <td><b><?php echo WEB_cell_name; ?></b></td>
     <td><b><?php echo WEB_cell_type; ?></b></td>
+    <td><b><?php echo WEB_l3_interface_bandwidth; ?></b></td>
     <td>
         <!-- Кнопки управления справа -->
         <div style="text-align: right; white-space: nowrap;">
@@ -141,6 +143,7 @@ foreach ($t_l3_interface as $row) {
     print "<td class=\"data\">";
     print_qa_l3int_select('s_type[]', $row['interface_type']);
     print "</td>\n";
+    print "<td class=\"data\"><input type='text' style='text-align: center;' name=\"s_band[]\" value='".kbps_to_bitrate($row['bandwidth'])."'></td>\n";
     print "<td class=\"data\"></td>\n";
     print "</tr>\n";
 }

+ 2 - 0
html/admin/devices/editdevice.php

@@ -108,6 +108,8 @@ if (getPOST("editdevice") !== null && isset($id)) {
     $new['connected_user_only']   = (int)getPOST("f_connected_user_only", null, 0);
     $new['dhcp']                  = (int)getPOST("f_dhcp", null, 0);
     $new['user_acl']              = (int)getPOST("f_user_acl", null, 0);
+    // если включены шейперы - автоматически включаем фильтры юзеров
+//    if ($new['queue_enabled']) { $new['user_acl'] = 1; }
 
     // Расположение
     $new['building_id']           = (int)getPOST("f_building_id", null, 0);

+ 113 - 19
html/inc/common.php

@@ -377,40 +377,122 @@ function get_user_ip()
     return $auth_ip;
 }
 
+/**
+ * Преобразует строку вида "10G", "500M", "2k" в килобиты/с (целое число)
+ * 
+ * @param string $bitrate_str Строка с битрейтом (например: "10G", "500M", "2k")
+ * @return int Количество килобит/с (целое число)
+ */
+function bitrate_to_kbps($bitrate_str) {
+    if (!is_string($bitrate_str)) {
+        return 0;
+    }
+    
+    // Регулярное выражение: число (возможно с десятичной точкой) + необязательная единица
+    if (!preg_match('/^(\d+(?:\.\d+)?)\s*([kMG])?$/i', $bitrate_str, $matches)) {
+        return 0;
+    }
+    
+    $value = (float)$matches[1];
+    $unit = strtoupper($matches[2] ?? 'k');
+    
+    switch ($unit) {
+        case 'G':
+            return (int)($value * 1000000);
+        case 'M':
+            return (int)($value * 1000);
+        case 'K':
+        default:
+            return (int)$value;
+    }
+}
+
+/**
+ * Преобразует килобиты/с в человекочитаемую строку (только целые значения)
+ * 
+ * @param int $kbps Количество килобит/с
+ * @return string Человекочитаемая строка (например: "10G", "500M", "2k")
+ */
+function kbps_to_bitrate($kbps) {
+    if (!is_numeric($kbps) || $kbps <= 0) {
+        return "0k";
+    }
+    
+    $kbps = (int)$kbps;
+    
+    if ($kbps >= 1000000) {
+        return ($kbps / 1000000) . "G";
+    } elseif ($kbps >= 1000) {
+        return ($kbps / 1000) . "M";
+    } else {
+        return $kbps . "k";
+    }
+}
+
+
+/**
+ * Форматирует объём данных (в байтах) в человекочитаемый вид
+ * 
+ * Функция преобразует числовое значение в байтах в строку с единицами измерения,
+ * используя либо двоичную систему (KiB, MiB, GiB...) либо десятичную (kB, MB, GB...),
+ * в зависимости от настройки системы.
+ * 
+ * @param mixed $traff Объём данных в байтах (должен быть положительным числом)
+ * @return string Отформатированная строка (например: "1.5 GiB", "2.3 MB", "0 B")
+ * 
+ * @example
+ * fbytes(1024)        // "1 KiB" (если KB=1024) или "1.024 kB" (если KB=1000)
+ * fbytes(1048576)     // "1 MiB" или "1.049 MB"
+ * fbytes(0)           // "0 B"
+ */
 function fbytes($traff)
 {
+    // Определяем единицы измерения для двоичной системы (IEC 60027-2)
+    // KiB = kibibyte (1024 bytes), MiB = mebibyte (1024^2 bytes), etc.
     $units_IEC = array(
-        "",
-        "Ki",
-        "Mi",
-        "Gi",
-        "Ti"
+        "",      // bytes
+        "Ki",    // kibibytes
+        "Mi",    // mebibytes  
+        "Gi",    // gibibytes
+        "Ti"     // tebibytes
     );
 
+    // Определяем единицы измерения для десятичной системы (SI/metric)
+    // kB = kilobyte (1000 bytes), MB = megabyte (1000^2 bytes), etc.
     $units_metric = array(
-        "",
-        "k",
-        "M",
-        "G",
-        "T"
+        "",      // bytes
+        "k",     // kilobytes
+        "M",     // megabytes
+        "G",     // gigabytes
+        "T"      // terabytes
     );
 
-    if (!empty($traff) and $traff > 0) {
+    // Проверяем, что входное значение корректно (не пустое и положительное)
+    if (!empty($traff) && $traff > 0) {
+        // Определяем базу для расчётов: 1024 (двоичная) или 1000 (десятичная)
+        // get_const('KB') должен возвращать true для двоичной системы (1024)
+        // и false/null для десятичной системы (1000)
         $KB = get_const('KB');
         if ($KB) {
-            $KB = 1024;
+            $KB = 1024;  // Двоичная система (IEC)
         } else {
-            $KB = 1000;
+            $KB = 1000;  // Десятичная система (SI/metric)
         }
-        //IEC
+        // Выбираем соответствующую систему единиц измерения
         if ($KB == 1024) {
-            $index = min(((int) log($traff, $KB)), count($units_IEC) - 1);
+            // Двоичная система (IEC)
+            // Определяем индекс единицы измерения: log_KB(traff)
+            // Ограничиваем индекс максимальным доступным значением
+            $index = min((int)log($traff, $KB), count($units_IEC) - 1);
+            // Форматируем результат: значение + пробел + единица + "B"
             $result = round($traff / pow($KB, $index), 3) . ' ' . $units_IEC[$index] . 'B';
         } else {
-            $index = min(((int) log($traff, $KB)), count($units_metric) - 1);
+            // Десятичная система (SI/metric)
+            $index = min((int)log($traff, $KB), count($units_metric) - 1);
             $result = round($traff / pow($KB, $index), 3) . ' ' . $units_metric[$index] . 'B';
         }
     } else {
+        // Для нулевого или некорректного значения возвращаем "0 B"
         $result = '0 B';
     }
     return $result;
@@ -993,6 +1075,16 @@ function print_add_gw_instances($db, $device_id, $gs_name)
     echo "</select>\n";
 }
 
+function ninety_percent_rounded($number) {
+    if (!is_numeric($number)) {
+        return 0;
+    }
+    
+    $ninety_percent = $number * 0.9;
+    // Округляем до ближайшего десятка
+    return round($ninety_percent / 10) * 10;
+}
+
 function print_add_dev_interface($db, $device_id, $int_list, $int_name)
 {
     echo "&nbsp;<select id=\"" . htmlspecialchars($int_name) . "\" name=\"" . htmlspecialchars($int_name) . "\">\n";
@@ -1021,14 +1113,16 @@ function print_add_dev_interface($db, $device_id, $int_list, $int_name)
         
         // Отображаемое значение (видит пользователь) — экранируем
         $display_str = htmlspecialchars($interface['name']) . '&nbsp;|' . 
-                      htmlspecialchars($interface['ip']) . '|' . 
+                      htmlspecialchars($interface['ip']) . '&nbsp;|' . 
+                      htmlspecialchars(kbps_to_bitrate($interface['speed'])) . '|' .
                       htmlspecialchars($display_value);
         
         // Значение (отправляется на сервер) — НЕ экранируем
         $value = $interface['name'] . ';' . 
                 $interface['index'] . ';' . 
-                $interface['interface_type'];
-        
+                $interface['interface_type'] . ';' .
+                ninety_percent_rounded($interface['speed']);
+
         print_select_item($display_str, $value, 0);
     }
     echo "</select>\n";

+ 1 - 1
html/inc/consts.php

@@ -8,7 +8,7 @@ define("ifDescr",".1.3.6.1.2.1.2.2.1.2");
 //ipaddr
 define("ipAdEntIfIndex",".1.3.6.1.2.1.4.20.1.2");
 
-//pports
+//ports
 define("PORT_STATUS_OID",".1.3.6.1.2.1.2.2.1.8");
 define("PORT_ADMIN_STATUS_OID",".1.3.6.1.2.1.2.2.1.7");
 define("PORT_SPEED_OID",".1.3.6.1.2.1.2.2.1.5");

+ 1 - 0
html/inc/languages/english.php

@@ -360,6 +360,7 @@ define("WEB_nagios_template","Nagios Template");
 /* edit_l3int */
 define("WEB_list_l3_interfaces","List of L3 interfaces");
 define("WEB_l3_interface_add","Add interface");
+define("WEB_l3_interface_bandwidth","Bandwidth");
 define("WEB_list_gateway_subnets","List of subnets that work through the gateway");
 define("WEB_list_l3_networks","List of networks");
 

+ 1 - 0
html/inc/languages/russian.php

@@ -359,6 +359,7 @@ define("WEB_nagios_template","Шаблон Нагиос");
 /* edit_l3int */
 define("WEB_list_l3_interfaces","Список L3 интерфейсов");
 define("WEB_l3_interface_add","Добавить интерфейс");
+define("WEB_l3_interface_bandwidth","Полоса");
 define("WEB_list_gateway_subnets","Список подсетей, работающих через шлюз");
 define("WEB_list_l3_networks","Список терминируемых сетей");
 

+ 47 - 20
html/inc/snmp.php

@@ -103,46 +103,73 @@ function get_mac_table($ip, $snmp, $oid, $index_map)
     return $fdb_table;
 }
 
-//get ip interfaces
+// Get IP interfaces with speed
 function getIpAdEntIfIndex($db, $ip, $snmp)
 {
     $result = [];
     if (!isset($ip)) {
         return $result;
     }
-    //oid+ip = index
+
+    // Получаем данные по IP → interface index
     $ip_table = walk_snmp($ip, $snmp, ipAdEntIfIndex);
-    //oid+index=name
+    // Получаем имена интерфейсов
     $int_table = walk_snmp($ip, $snmp, ifDescr);
-    if (isset($ip_table) and gettype($ip_table) == 'array' and count($ip_table) > 0) {
+    // Получаем скорости портов
+    $speed_table = walk_snmp($ip, $snmp, PORT_SPEED_OID);
+
+    if (is_array($ip_table) && !empty($ip_table)) {
         foreach ($ip_table as $key => $value) {
-            if (empty($value)) {
-                continue;
-            }
-            if (empty($key)) {
+            if (empty($key) || empty($value)) {
                 continue;
             }
+
             $key = trim($key);
+            // Извлекаем index интерфейса из значения ipAdEntIfIndex
             $interface_index = intval(trim(str_replace('INTEGER:', '', $value)));
-            if (empty($value)) {
+            if ($interface_index <= 0) {
                 continue;
             }
-            $interface_name = $int_table[ifDescr . '.' . $interface_index];
-            $interface_name = trim(str_replace('STRING:', '', $interface_name));
-            $interface_ip = trim(str_replace(ipAdEntIfIndex . '.', '', $key));
-            if (empty($interface_name)) {
+
+            // Получаем имя интерфейса
+            $int_key = ifDescr . '.' . $interface_index;
+            if (!isset($int_table[$int_key])) {
                 continue;
             }
-            $result[$interface_index]['ip'] = $interface_ip;
-            $result[$interface_index]['index'] = $interface_index;
-            $result[$interface_index]['name'] = $interface_name;
-            //type: 0 - local, 1 - WAN
-            $result[$interface_index]['interface_type'] = 1;
-            if (is_our_network($db, $interface_ip)) {
-                $result[$interface_index]['interface_type'] = 0;
+            $interface_name = trim(str_replace('STRING:', '', $int_table[$int_key]));
+            if (empty($interface_name)) {
+                continue;
             }
+
+            // Получаем IP-адрес из OID ключа
+            $interface_ip = trim(str_replace(ipAdEntIfIndex . '.', '', $key));
+
+            // Определяем тип интерфейса
+            $interface_type = is_our_network($db, $interface_ip) ? 0 : 1;
+
+            // Получаем скорость (если есть)
+            $speed = null;
+            $speed_key = PORT_SPEED_OID . '.' . $interface_index;
+            if (isset($speed_table[$speed_key])) {
+                $speed_raw = $speed_table[$speed_key];
+                if (empty($speed_raw)) {
+                    $speed_raw = 'INT:0';
+                }
+                $speed = parse_snmp_value($speed_raw);
+                if (!empty($speed)) { $speed = intdiv($speed, 1000); }
+            }
+
+            // Формируем запись
+            $result[$interface_index] = [
+                'ip'              => $interface_ip,
+                'index'           => $interface_index,
+                'name'            => $interface_name,
+                'interface_type'  => $interface_type,
+                'speed'           => $speed ?? 10000000,
+            ];
         }
     }
+
     return $result;
 }
 

+ 39 - 0
scripts/eyelib/net_utils.pm

@@ -33,6 +33,8 @@ GetDhcpRange
 GetIpRange
 GetIP
 GetSubNet
+bitrate_to_kbps
+kbps_to_bitrate
 is_ip
 is_ipip_valid
 is_ip_valid
@@ -150,6 +152,43 @@ return \%dhcp;
 
 #--------------------------------------------------------------------------------------------------------
 
+# Преобразует строку вида "10G", "500M", "2k" в килобиты/с (целое число)
+sub bitrate_to_kbps {
+    my ($bitrate_str) = @_;
+    
+    # Проверка корректности формата
+    return 0 unless defined $bitrate_str && $bitrate_str =~ /^(\d+(?:\.\d+)?)\s*([kMG])?$/i;
+    
+    my ($value, $unit) = ($1, uc($2 // 'k'));
+    
+    # Преобразуем в килобиты и округляем до целого
+    if ($unit eq 'G') {
+        return int($value * 1_000_000);
+    } elsif ($unit eq 'M') {
+        return int($value * 1_000);
+    } else {  # 'k' или без единицы
+        return int($value);
+    }
+}
+
+#--------------------------------------------------------------------------------------------------------
+
+# Преобразует килобиты/с в человекочитаемую строку (только целые значения)
+sub kbps_to_bitrate {
+    my ($kbps) = @_;
+    
+    return "0k" unless defined $kbps && $kbps > 0;
+    
+    if ($kbps >= 1_000_000) {
+        return int($kbps / 1_000_000) . "G";
+    } elsif ($kbps >= 1_000) {
+        return int($kbps / 1_000) . "M";
+    } else {
+        return int($kbps) . "k";
+    }
+}
+#--------------------------------------------------------------------------------------------------------
+
 sub GetIpRange {
 my $gate_ip = trim($_[0]);
 my $mask = 30;

+ 141 - 73
scripts/sync_mikrotik.pl

@@ -192,9 +192,15 @@ my %hotspot_exceptions;
 my @lan_int=();
 my @wan_int=();
 
+# настройки испольуземых l3-интерфейсов
+my %l3_interfaces;
+
 my @l3_int = get_records_sql($dbh,'SELECT * FROM device_l3_interfaces WHERE device_id=?',$gate->{'id'});
 foreach my $l3 (@l3_int) {
 $l3->{'name'}=~s/\"//g;
+$l3_interfaces{$l3->{'name'}}{type} = $l3->{'interface_type'};
+$l3_interfaces{$l3->{'name'}}{bandwidth} = 0;
+if ($l3->{'bandwidth'}) { $l3_interfaces{$l3->{'name'}}{bandwidth} = $l3->{'bandwidth'}; }
 if ($l3->{'interface_type'} eq '0') { push(@lan_int,$l3->{'name'}); }
 if ($l3->{'interface_type'} eq '1') { push(@wan_int,$l3->{'name'}); }
 }
@@ -506,8 +512,8 @@ if (@ret_hotspot and scalar(@ret_hotspot)) {
             $actual_hotspot_bindings{$data{'mac-address'}} = $data{description} if (exists $data{description});
             }
     }
-    log_debug("Actual bindings:".Dumper(\%actual_hotspot_bindings));
-    log_debug("Configuration exceptions:".Dumper(\%hotspot_exceptions));
+    log_debug("Hotspot actual bindings:".Dumper(\%actual_hotspot_bindings));
+    log_debug("Hotspot configuration exceptions:".Dumper(\%hotspot_exceptions));
     #update binding
     foreach my $actual_mac (keys %actual_hotspot_bindings) {
         if (!exists $hotspot_exceptions{$actual_mac}) {
@@ -526,13 +532,24 @@ if (@ret_hotspot and scalar(@ret_hotspot)) {
 
 }#end dhcp config
 
+my %users;
+my %lists;
+
+my $group_sql = "SELECT DISTINCT filter_group_id FROM user_auth WHERE deleted = 0 ORDER BY filter_group_id;";
+my @grouplist_ref = get_records_sql($dbh,$group_sql);
+foreach my $row (@grouplist_ref) {
+    $lists{'group_'.$row->{filter_group_id}}=1;
+}
+#full ip list
+$lists{'group_all'}=1;
+
 #access lists config
 if ($gate->{user_acl}) {
 
 db_log_verbose($dbh,$gate_ident."Sync user state at router $router_name [".$router_ip."] started.");
 
 #get userid list
-my $user_auth_sql="SELECT user_auth.ip, user_auth.filter_group_id, user_auth.queue_id, user_auth.id
+my $user_auth_sql="SELECT user_auth.ip, user_auth.filter_group_id, user_auth.id
 FROM user_auth, user_list
 WHERE user_auth.user_id = user_list.id
 AND user_auth.deleted =0
@@ -544,41 +561,18 @@ AND user_auth.ou_id <> ?
 ORDER BY ip_int";
 
 my @authlist_ref = get_records_sql($dbh,$user_auth_sql,$default_hotspot_ou_id);
-my %users;
-my %lists;
-my %found_users;
 
 foreach my $row (@authlist_ref) {
 if ($connected_users_only) { next if (!$connected_users->match_string($row->{ip})); }
 #skip not office ip's
 next if (!$office_networks->match_string($row->{ip}));
-$found_users{$row->{'id'}}=$row->{ip};
 #filter group acl's
 $users{'group_'.$row->{filter_group_id}}->{$row->{ip}}=1;
 $users{'group_all'}->{$row->{ip}}=1;
-$lists{'group_'.$row->{filter_group_id}}=1;
-#queue acl's
-if ($row->{queue_id}) { $users{'queue_'.$row->{queue_id}}->{$row->{ip}}=1; }
 }
 
-log_debug($gate_ident."Users status:".Dumper(\%users));
+log_debug($gate_ident."Users status by ACL:".Dumper(\%users));
 
-#full list
-$lists{'group_all'}=1;
-
-#get queue list
-my @queuelist_ref = get_records_sql($dbh,"SELECT * FROM queue_list");
-
-my %queues;
-foreach my $row (@queuelist_ref) {
-$lists{'queue_'.$row->{id}}=1;
-next if ((!$row->{download}) and !($row->{upload}));
-$queues{'queue_'.$row->{id}}{id}=$row->{id};
-$queues{'queue_'.$row->{id}}{down}=$row->{download};
-$queues{'queue_'.$row->{id}}{up}=$row->{upload};
-}
-
-log_debug($gate_ident."Queues status:".Dumper(\%queues));
 
 my @filter_instances = get_records_sql($dbh,"SELECT * FROM filter_instances");
 
@@ -674,44 +668,6 @@ foreach my $row (@grouplist_ref) {
 
 log_debug($gate_ident."Group filters: ".Dumper(\%group_filters));
 
-my %cur_users;
-
-foreach my $group_name (keys %lists) {
-my @address_lists=netdev_cmd($gate,$t,'/ip firewall address-list print terse without-paging where list='.$group_name,1);
-
-log_debug($gate_ident."Get address lists:".Dumper(\@address_lists));
-
-foreach my $row (@address_lists) {
-    $row=trim($row);
-    next if (!$row);
-    my @address=split(' ',$row);
-    foreach my $row (@address) {
-        if ($row=~/address\=(.*)/i) { $cur_users{$group_name}{$1}=1; }
-        }
-    }
-}
-
-#new-ips
-foreach my $group_name (keys %users) {
-    foreach my $user_ip (keys %{$users{$group_name}}) {
-    if (!exists($cur_users{$group_name}{$user_ip})) {
-        db_log_verbose($dbh,$gate_ident."Add user with ip: $user_ip to access-list $group_name");
-        push(@cmd_list,"/ip firewall address-list add address=".$user_ip." list=".$group_name);
-        }
-    }
-}
-
-#old-ips
-foreach my $group_name (keys %cur_users) {
-    foreach my $user_ip (keys %{$cur_users{$group_name}}) {
-    if (!exists($users{$group_name}{$user_ip})) {
-        db_log_verbose($dbh,$gate_ident."Remove user with ip: $user_ip from access-list $group_name");
-        push(@cmd_list,":foreach i in [/ip firewall address-list find where address=".$user_ip." and list=".$group_name."] do={/ip firewall address-list remove \$i};");
-        }
-    }
-}
-
-timestamp;
 
 #sync firewall rules
 
@@ -736,8 +692,8 @@ foreach my $jump_list (@chain_list) {
     next if (!$jump_list);
     $jump_list=trim($jump_list);
     if ($jump_list=~/jump-target=(\S*)\s+/i) {
-	if ($1) { $cur_chain{$1}++; }
-	}
+        if ($1) { $cur_chain{$1}++; }
+        }
     }
 
 #old chains
@@ -885,15 +841,69 @@ if (!$chain_ok) {
         }
     }
 }
+}#end access lists config
 
 if ($shaper_enabled) {
 
+#get userid list
+my $user_auth_sql="SELECT ip, queue_id, id FROM user_auth WHERE user_auth.deleted =0 AND user_auth.ou_id <> ? ORDER BY ip_int";
+my @authlist_ref = get_records_sql($dbh,$user_auth_sql,$default_hotspot_ou_id);
+
+foreach my $row (@authlist_ref) {
+if ($connected_users_only) { next if (!$connected_users->match_string($row->{ip})); }
+#skip not office ip's
+next if (!$office_networks->match_string($row->{ip}));
+#queue acl's
+if ($row->{queue_id}) { $users{'queue_'.$row->{queue_id}}->{$row->{ip}}=1; }
+}
+
+#get queue list
+my @queuelist_ref = get_records_sql($dbh,"SELECT * FROM queue_list");
+
+my %queues;
+foreach my $row (@queuelist_ref) {
+$lists{'queue_'.$row->{id}}=1;
+next if ((!$row->{download}) and !($row->{upload}));
+$queues{'queue_'.$row->{id}}{id}=$row->{id};
+$queues{'queue_'.$row->{id}}{down}=$row->{download};
+$queues{'queue_'.$row->{id}}{up}=$row->{upload};
+}
+
+log_debug($gate_ident."Queues status:".Dumper(\%queues));
+
 #shapers
+my %get_queue_root=();
 my %get_queue_type=();
 my %get_queue_tree=();
 my %get_filter_mangle=();
 
-my @tmp=netdev_cmd($gate,$t,'/queue type print terse without-paging where name~"pcq_(down|up)load"',1);
+my @tmp=netdev_cmd($gate,$t,'/queue tree print terse without-paging where name~"(down|up)load_root"',1);
+
+log_debug($gate_ident."Get ROOT queues classes: ".Dumper(\@tmp));
+#5   name=upload_root_sfp-sfpplus1-wan parent=sfp-sfpplus1-wan packet-mark= limit-at=0 queue=pcq-upload-default priority=8 max-limit=10G burst-limit=0 burst-threshold=0 burst-time=0s bucket-size=0.1
+#0   name=download_root_vlan0201 parent=vlan0201 packet-mark= limit-at=0 queue=pcq-download-default priority=8 max-limit=2G burst-limit=0 burst-threshold=0 burst-time=0s bucket-size=0.1
+
+foreach my $row (@tmp) {
+next if (!$row);
+$row = trim($row);
+next if ($row!~/^(\d){1,3}/);
+$row=~s/^\d{1,3}\s+//;
+next if (!$row);
+if ($row=~/name=(down|up)load_root_(\S*)\s+/i) {
+    next if (!$2);
+    my $parent_device = $2;
+    $get_queue_root{$parent_device}{name} = $parent_device;
+    if ($row=~/\s+max-limit=(\S*)\s+/) {
+        $get_queue_root{$parent_device}{bandwidth} = bitrate_to_kbps($1);
+        } else { $get_queue_root{$parent_device}{bandwidth} = 0; }
+    }
+}
+
+#log_debug($gate_ident.'GET ROOT:'.Dumper(\%get_queue_root));
+
+@tmp=();
+
+@tmp=netdev_cmd($gate,$t,'/queue type print terse without-paging where name~"pcq_(down|up)load"',1);
 
 log_debug($gate_ident."Get queues: ".Dumper(\@tmp));
 
@@ -924,7 +934,7 @@ if ($row=~/name=pcq_(down|up)load_(\d){1,3}\s+/i) {
 
 @tmp=();
 @tmp=netdev_cmd($gate,$t,'/queue tree print terse without-paging where parent~"(download|upload)_root"',1);
-log_debug($gate_ident."Get root queues: ".Dumper(\@tmp));
+#log_debug($gate_ident."Get user queues: ".Dumper(\@tmp));
 
 #print Dumper(\@tmp);
 # 0 I name=queue_3_out parent=upload_root packet-mark=upload_3 limit-at=0 queue=*2A priority=8 max-limit=0 burst-limit=0 burst-threshold=0 burst-time=0s bucket-size=0.1
@@ -995,6 +1005,7 @@ if ($row=~/new-packet-mark=download_(\d){1,3}_(\S*)\s+/i) {
     }
 }
 
+log_debug($gate_ident."Queues root classes:".Dumper(\%get_queue_root));
 log_debug($gate_ident."Queues type status:".Dumper(\%get_queue_type));
 log_debug($gate_ident."Queues tree status:".Dumper(\%get_queue_tree));
 log_debug($gate_ident."Firewall mangle status:".Dumper(\%get_filter_mangle));
@@ -1003,11 +1014,29 @@ my %queue_type;
 my %queue_tree;
 my %filter_mangle;
 
+#analyze root class
+foreach my $l3_int (keys %l3_interfaces) {
+my $int_type = 'download';
+if ($l3_interfaces{$l3_int}->{type}) { $int_type = 'upload'; }
+#clear unknown root queue
+push(@cmd_list,'/queue tree remove [ find name!~"'.$int_type . '_root_' . $l3_int .'" and parent='.$l3_int.' ]');
+# add root queue if not exists
+if (!exists $get_queue_root{$l3_int}) {
+    push(@cmd_list,'/queue tree add max-limit=' . kbps_to_bitrate($l3_interfaces{$l3_int}->{bandwidth}) . ' name=' . $int_type . '_root_' . $l3_int .' parent=' . $l3_int . ' queue=pcq-' . $int_type . '-default');
+    next;
+    }
+# change bandwidth if differs
+if ($get_queue_root{$l3_int}{bandwidth} ne $l3_interfaces{$l3_int}->{bandwidth}) {
+    push(@cmd_list,'/queue tree set max-limit=' . kbps_to_bitrate($l3_interfaces{$l3_int}->{bandwidth}) . '[ find name='.$int_type . '_root_' . $l3_int . ' ]');
+    next;
+    }
+}
+
 #generate new config
 foreach my $queue_name (keys %queues) {
 my $q_id=$queues{$queue_name}{id};
-my $q_up=$queues{$queue_name}{up}+1;
-my $q_down=$queues{$queue_name}{down}+1;
+my $q_up=$queues{$queue_name}{up};
+my $q_down=$queues{$queue_name}{down};
 
 #queue_types
 $queue_type{$q_id}{up}="name=pcq_upload_".$q_id." kind=pcq pcq-rate=".$q_up."k pcq-limit=500KiB pcq-classifier=src-address pcq-total-limit=2000KiB pcq-burst-rate=0 pcq-burst-threshold=0 pcq-burst-time=10s pcq-src-address-mask=32 pcq-dst-address-mask=32 pcq-src-address6-mask=64 pcq-dst-address6-mask=64";
@@ -1089,12 +1118,51 @@ if (!$queue_ok) {
     push(@cmd_list,'/ip firewall mangle add '.$filter_mangle{$q_id}{$int}{down});
     }
 }
-#end shaper
+
+}
+
+}#end shaper
+
+# Analyze actual ACL
+
+my %cur_users;
+
+foreach my $group_name (keys %lists) {
+my @address_lists=netdev_cmd($gate,$t,'/ip firewall address-list print terse without-paging where list='.$group_name,1);
+
+log_debug($gate_ident."Get address lists:".Dumper(\@address_lists));
+
+foreach my $row (@address_lists) {
+    $row=trim($row);
+    next if (!$row);
+    my @address=split(' ',$row);
+    foreach my $row (@address) {
+        if ($row=~/address\=(.*)/i) { $cur_users{$group_name}{$1}=1; }
+        }
+    }
+}
+
+#new-ips
+foreach my $group_name (keys %users) {
+    foreach my $user_ip (keys %{$users{$group_name}}) {
+    if (!exists($cur_users{$group_name}{$user_ip})) {
+        db_log_verbose($dbh,$gate_ident."Add user with ip: $user_ip to access-list $group_name");
+        push(@cmd_list,"/ip firewall address-list add address=".$user_ip." list=".$group_name);
+        }
+    }
 }
 
+#old-ips
+foreach my $group_name (keys %cur_users) {
+    foreach my $user_ip (keys %{$cur_users{$group_name}}) {
+    if (!exists($users{$group_name}{$user_ip})) {
+        db_log_verbose($dbh,$gate_ident."Remove user with ip: $user_ip from access-list $group_name");
+        push(@cmd_list,":foreach i in [/ip firewall address-list find where address=".$user_ip." and list=".$group_name."] do={/ip firewall address-list remove \$i};");
+        }
+    }
 }
 
-}#end access lists config
+timestamp;
 
 if (scalar(@cmd_list)) {
     log_debug($gate_ident."Apply:");

+ 1 - 0
scripts/updates/3-0-4/device_l3_interfaces.msql

@@ -0,0 +1 @@
+ALTER TABLE `device_l3_interfaces` ADD COLUMN `bandwidth` INT DEFAULT NULL;

+ 1 - 0
scripts/updates/3-0-4/device_l3_interfaces.psql

@@ -0,0 +1 @@
+ALTER TABLE device_l3_interfaces ADD COLUMN bandwidth INTEGER;