Min = $Min; $this->Max = $Max; $this->PacketMark = $PacketMark; } function Print(): string { $Output = '(Min: '.$this->Min.' Max: '.$this->Max; if ($this->PacketMark != null) $Output .= ' PacketMark: '.$this->PacketMark; $Output .= ')'; return $Output; } } class SpeedLimitItem { public string $Name; public ?SpeedLimitItem $Parent; public SpeedLimit $LimitIn; public SpeedLimit $LimitOut; public bool $FixedSpeed; public SpeedLimitItems $SubItems; function __construct(string $Name, SpeedLimitItem $Parent = null) { $this->Name = $Name; $this->Parent = $Parent; if ($Parent != null) $Parent->SubItems->Add($this); $this->SubItems = new SpeedLimitItems(); $this->FixedSpeed = false; } function Print(int $Indent = 0): string { $Output = str_repeat(' ', $Indent * 2).$this->Name.' In:'.$this->LimitIn->Print().' Out:'.$this->LimitOut->Print()."\n"; $Output .= $this->SubItems->Print($Indent + 1); return $Output; } function CheckName($Name, &$UsedNames): void { if (in_array($Name, $UsedNames)) die("\n".'Duplicate name: '.$Name); else $UsedNames[] = $Name; } function GetCommands(&$UsedNames = null): array { if ($UsedNames == null) $UsedNames = array(); $this->CheckName($this->Name.'-out', $UsedNames); $Item = array('name' => $this->Name.'-out', 'limit-at' => $this->LimitOut->Min, 'max-limit' => $this->LimitOut->Max, 'parent' => $this->GetParentName('-out'), 'packet-mark' => $this->LimitOut->PacketMark); if ($this->LimitOut->PacketMark != null) $Item['packet-mark'] = $this->LimitOut->PacketMark; $Output[] = $Item; $this->CheckName($this->Name.'-in', $UsedNames); $Item = array('name' => $this->Name.'-in', 'limit-at' => $this->LimitIn->Min, 'max-limit' => $this->LimitIn->Max, 'parent' => $this->GetParentName('-in')); if ($this->LimitIn->PacketMark != null) $Item['packet-mark'] = $this->LimitIn->PacketMark; $Output[] = $Item; $Output = array_merge($Output, $this->SubItems->GetCommands($UsedNames)); return $Output; } function GetParentName(string $Suffix): string { if ($this->Parent != null) return $this->Parent->Name.$Suffix; return 'global'; } function UpdateMinSpeeds(): void { if (($this->LimitIn->Min == 0) or ($this->LimitOut->Min == 0)) { $SpeedMinOut = 0; $SpeedMinIn = 0; foreach ($this->SubItems->Items as $Index => $Item) { $this->SubItems->Items[$Index]->UpdateMinSpeeds(); $SpeedMinOut += $this->SubItems->Items[$Index]->LimitOut->Min; $SpeedMinIn += $this->SubItems->Items[$Index]->LimitIn->Min; } if ($SpeedMinOut > $this->LimitOut->Max) $SpeedMinOut = $this->LimitOut->Max; if ($SpeedMinIn > $this->LimitIn->Max) $SpeedMinIn = $this->LimitIn->Max; $this->LimitOut->Min = $SpeedMinOut; $this->LimitIn->Min = $SpeedMinIn; } } function AdjustMinSpeedsToMax(float $MultiplierIn, float $MultiplierOut): void { foreach ($this->SubItems->Items as $Index => $Item) { $this->SubItems->Items[$Index]->AdjustMinSpeedsToMax($MultiplierIn, $MultiplierOut); } if ($this->FixedSpeed == false) { $this->LimitOut->Min = round($this->LimitOut->Min * $MultiplierOut); if ($this->LimitOut->Min > $this->LimitOut->Max) { echo($this->Name.': '.$this->LimitOut->Min.' > '.$this->LimitOut->Max."\n"); $this->LimitOut->Min = $this->LimitOut->Max; } $this->LimitIn->Min = round($this->LimitIn->Min * $MultiplierIn); if ($this->LimitIn->Min > $this->LimitIn->Max) { echo($this->Name.': '.$this->LimitIn->Min.' > '.$this->LimitIn->Max."\n"); $this->LimitIn->Min = $this->LimitIn->Max; } } } } class SpeedLimitItems extends GenericList { function AddNew(string $Name, SpeedLimitItem $Parent = null): SpeedLimitItem { $Item = new SpeedLimitItem($Name, $Parent); $Item->LimitIn = new SpeedLimit(0, 0); $Item->LimitOut = new SpeedLimit(0, 0); $this->Items[] = $Item; return $Item; } function Print(int $Indent = 0): string { $Output = ''; foreach ($this->Items as $SubItem) { $Output .= $SubItem->Print($Indent); } return $Output; } function GetCommands(&$UsedNames): array { $Output = array(); foreach ($this->Items as $SubItem) { $Output = array_merge($Output, $SubItem->GetCommands($UsedNames)); } return $Output; } } class ConfigRouterOSQueue extends NetworkConfigItem { var $UsedNames; var $Devices; var $QueueItems; var $SpeedLimits; function Run(): void { $PathQueue = array('queue', 'tree'); $Routerboard = new Routerboard(); $Routerboard->UserName = $this->System->Config['MainRouter']['UserName']; $Routerboard->Timeout = $this->System->Config['MainRouter']['ConnectTimeout']; $Routerboard->HostName = $this->System->Config['MainRouter']['HostName']; $Routerboard->Debug = true; $this->UsedNames = array(); $Finance = &ModuleFinance::Cast($this->System->GetModule('Finance'))->Finance; $Finance->LoadMonthParameters(0); // Generate traffic shaping rules $InDivider = 1; $OutDivider = 1; $TotalMaxSpeedIn = round($Finance->RealMaxSpeed / $InDivider) * 1000; $TotalMaxSpeedOut = round($Finance->RealMaxSpeed / $OutDivider) * 1000; $UsersMaxSpeedIn = round($Finance->MaxSpeed / $InDivider) * 1000; $UsersMaxSpeedOut = round($Finance->MaxSpeed / $OutDivider) * 1000; $OutInterface = 'eth1'; $InInterface = 'ifb0'; $InetInterface = $this->System->Config['MainRouter']['InetInterface']; $DbResult = $this->Database->select('Service', '*', '(`ChangeAction` IS NULL) AND (`Id`='.TARIFF_FREE.')'); if ($DbResult->num_rows == 1) { $Service = $DbResult->fetch_array(); $FreeInetSpeed = $Service['InternetSpeedMax']; } else $FreeInetSpeed = 0; // Root of tree and main limit $Main = new SpeedLimitItem('main'); $Main->LimitIn = new SpeedLimit(0, $UsersMaxSpeedIn); $Main->LimitOut = new SpeedLimit(0, $UsersMaxSpeedOut); $this->LoadSpeedLimits($Main); // Free internet $Free = new SpeedLimitItem('free', $Main); $Free->LimitIn = new SpeedLimit($FreeInetSpeed, $FreeInetSpeed, GetMarkByComment('free-in')); $Free->LimitOut = new SpeedLimit($FreeInetSpeed, $FreeInetSpeed, GetMarkByComment('free-out')); $Free->FixedSpeed = true; // Process users $DbResult = $this->Database->query('SELECT `Member`.*, `Subject`.`Name` FROM `Member` '. 'LEFT JOIN `Subject` ON `Subject`.`Id` = `Member`.`Subject` WHERE `Member`.`Blocked`=0'); while ($Member = $DbResult->fetch_assoc()) { $ServiceIndex = 1; echo('Zákazník '.$Member['Name']."\n"); $DbResult4 = $this->Database->query('SELECT `Service`.*, `ServiceCustomerRel`.`Id` AS `RelId`, '. '`ServiceCustomerRel`.`SpeedLimit` AS `SpeedLimit` FROM `ServiceCustomerRel` '. 'JOIN `Service` ON `Service`.`Id` = `ServiceCustomerRel`.`Service` '. 'WHERE (`ServiceCustomerRel`.`Customer` = '.$Member['Id'].') AND (`ServiceCustomerRel`.`ChangeAction` IS NULL) '. 'AND (`Service`.`InternetSpeedMax` > 0) AND (`Service`.`InternetSpeedMin` > 0)'); while ($Service = $DbResult4->fetch_assoc()) { $MinSpeed = $Service['InternetSpeedMin']; $MaxSpeed = $Service['InternetSpeedMax']; if ($Service['InternetSpeedBonus'] > $MaxSpeed) $MaxSpeed = $Service['InternetSpeedBonus']; echo('Služba '.$Service['Name'].': '); $MemberName = RouterOSIdent($Member['Name'].'-'.$Member['Id'].'-'.$ServiceIndex); $MinReduction = 100; $SpeedIn = round($MinSpeed / $InDivider / $MinReduction); $SpeedOut = round($MinSpeed / $OutDivider / $MinReduction); $UserMaxSpeedIn = round($MaxSpeed / $InDivider); $UserMaxSpeedOut = round($MaxSpeed / $OutDivider); // Reduce max speed by speed limits $SpeedLimitItem = $Main; if ($Service['SpeedLimit'] != null) { $SpeedLimit = $this->SpeedLimits[$Service['SpeedLimit']]; $SpeedLimitItem = $SpeedLimit['SpeedLimitItem']; if ($UserMaxSpeedIn > $SpeedLimit['SpeedMaxIn']) $UserMaxSpeedIn = $SpeedLimit['SpeedMaxIn']; if ($UserMaxSpeedOut > $SpeedLimit['SpeedMaxOut']) $UserMaxSpeedOut = $SpeedLimit['SpeedMaxOut']; while ($SpeedLimit['Parent'] != null) { $SpeedLimit = $this->SpeedLimits[$SpeedLimit['Parent']]; if ($UserMaxSpeedIn > $SpeedLimit['SpeedMaxIn']) $UserMaxSpeedIn = $SpeedLimit['SpeedMaxIn']; if ($UserMaxSpeedOut > $SpeedLimit['SpeedMaxOut']) $UserMaxSpeedOut = $SpeedLimit['SpeedMaxOut']; } } $LimitMember = new SpeedLimitItem($MemberName, $SpeedLimitItem); $LimitMember->LimitIn = new SpeedLimit($SpeedIn, $UserMaxSpeedIn); $LimitMember->LimitOut = new SpeedLimit($SpeedOut, $UserMaxSpeedOut); $Filter = '(`Used` = 1) AND (`Service` = '.$Service['RelId'].')'; $DbResult2 = $this->Database->select('NetworkDevice', 'COUNT(*)', $Filter); $Row = $DbResult2->fetch_row(); $HostCount = $Row[0]; if ($HostCount > 0) { $HostSpeedIn = round($SpeedIn / $HostCount); $HostSpeedOut = round($SpeedOut / $HostCount); } else { $HostSpeedIn = $SpeedIn; $HostSpeedOut = $SpeedOut; } $DbResult2 = $this->Database->select('NetworkDevice', '*', $Filter); while ($Device = $DbResult2->fetch_assoc()) { $DbResult3 = $this->Database->select('NetworkInterface', '*', '`Device` = '.$Device['Id'].' AND `LocalIP` != ""'); while ($Interface = $DbResult3->fetch_assoc()) { $DeviceName = $Device['Name']; if ($Interface['Name'] != '') $DeviceName .= '-'.$Interface['Name']; $DeviceName = RouterOSIdent($DeviceName); echo($DeviceName.', '); $LimitDevice = new SpeedLimitItem($DeviceName, $LimitMember); $LimitDevice->LimitIn = new SpeedLimit($HostSpeedIn, $UserMaxSpeedIn, GetMarkByComment($DeviceName.'-in')); $LimitDevice->LimitOut = new SpeedLimit($HostSpeedOut, $UserMaxSpeedOut, GetMarkByComment($DeviceName.'-out')); } } $DbResult2 = $this->Database->select('NetworkSubnet', '*', '`Service`='.$Service['RelId']); while ($Subnet = $DbResult2->fetch_assoc()) { $SubnetName = RouterOSIdent('subnet-'.$Subnet['Name']); echo($SubnetName.', '); $LimitSubnet = new SpeedLimitItem($SubnetName, $LimitMember); $LimitSubnet->LimitIn = new SpeedLimit($HostSpeedIn, $UserMaxSpeedIn, GetMarkByComment($SubnetName.'-in')); $LimitSubnet->LimitOut = new SpeedLimit($HostSpeedOut, $UserMaxSpeedOut, GetMarkByComment($SubnetName.'-out')); } echo("\n"); $ServiceIndex++; } } $Main->UpdateMinSpeeds(); $Main->AdjustMinSpeedsToMax($Main->LimitIn->Max / $Main->LimitIn->Min, $Main->LimitOut->Max / $Main->LimitOut->Min); echo($Main->Print()); $ItemsQueue = $Main->GetCommands(); $Routerboard->ListUpdate($PathQueue, array('name', 'limit-at', 'max-limit', 'parent', 'packet-mark'), $ItemsQueue, array(), true); } function BuildSpeedLimit(&$SpeedLimit, $TopSpeedLimitItem): void { $SpeedLimitName = $SpeedLimit['Name'].'-grp'; $SpeedLimitName = RouterOSIdent($SpeedLimitName); echo($SpeedLimitName.', '); $SpeedLimitItem = new SpeedLimitItem($SpeedLimitName, $TopSpeedLimitItem); $SpeedLimitItem->LimitIn = new SpeedLimit(0, $SpeedLimit['SpeedMaxIn']); $SpeedLimitItem->LimitOut = new SpeedLimit(0, $SpeedLimit['SpeedMaxOut']); $SpeedLimit['SpeedLimitItem'] = $SpeedLimitItem; foreach ($SpeedLimit['Childs'] as $ChildId) { $this->BuildSpeedLimit($this->SpeedLimits[$ChildId], $SpeedLimitItem); } } function LoadSpeedLimits($SpeedLimitItem): void { echo('Limit groups: '); // Load all speed limits $this->SpeedLimits = array(); $DbResult = $this->Database->query('SELECT * FROM `NetworkSpeedLimit`'); while ($SpeedLimit = $DbResult->fetch_array()) { $SpeedLimit['Childs'] = array(); $this->SpeedLimits[$SpeedLimit['Id']] = $SpeedLimit; } // Calculate childs from parent foreach ($this->SpeedLimits as $Index => $SpeedLimit) { if ($SpeedLimit['Parent'] != null) $this->SpeedLimits[$SpeedLimit['Parent']]['Childs'][] = $Index; } // Build speed limits from top foreach ($this->SpeedLimits as $Index => $SpeedLimit) { if ($SpeedLimit['Parent'] == null) { $this->BuildSpeedLimit($this->SpeedLimits[$Index], $SpeedLimitItem); } } echo("\n"); } function UpdateMinSpeed($DeviceId): void { $MinSpeed = 0; foreach ($this->Devices[$DeviceId]['Childs'] as $DeviceChild) { $this->UpdateMinSpeed($DeviceChild); $MinSpeed += $this->Devices[$DeviceChild]['MinSpeed']; } $this->Devices[$DeviceId]['MinSpeed'] = $MinSpeed; if ($this->Devices[$DeviceId]['DeviceCount'] > 0) $this->Devices[$DeviceId]['MinSpeed'] += round($this->Devices[$DeviceId]['InternetSpeedMin'] / $this->Devices[$DeviceId]['DeviceCount']); } // Calculate maximum real speed available for each network device Start with main router and continue with adjecement nodes. function BuildTree($RootDeviceId, $BaseSpeed): void { // Load network devices $this->Devices = array(); $DbResult = $this->Database->query('SELECT `NetworkDevice`.`Name`,`NetworkDevice`.`Id`, '. '`Service`.`InternetSpeedMin`, `Service`.`InternetSpeedMax`, '. '(SELECT COUNT(*) FROM `NetworkDevice` AS `T` WHERE `T`.`Service` = `NetworkDevice`.`Service`) AS `DeviceCount` FROM `NetworkDevice` '. 'LEFT JOIN `ServiceCustomerRel` ON `ServiceCustomerRel`.`Id`=`NetworkDevice`.`Service` '. 'LEFT JOIN `Service` ON `Service`.`Id` = `ServiceCustomerRel`.`Service`'); while ($Device = $DbResult->fetch_assoc()) { $Device['Interfaces'] = array(); $Device['Calculated'] = false; $Device['MaxSpeed'] = 0; $Device['MinSpeed'] = 0; $Device['Childs'] = array(); $Device['Parent'] = 0; $Device['QueueName'] = ''; $this->Devices[$Device['Id']] = $Device; } // Load network interfaces and assign them to device $Interfaces = array(); $DbResult = $this->Database->query('SELECT `Device`,`Name`,`Id` FROM `NetworkInterface`'); while ($Interface = $DbResult->fetch_assoc()) { $Interface['Links'] = array(); $Interfaces[$Interface['Id']] = $Interface; $this->Devices[$Interface['Device']]['Interfaces'][] = $Interface['Id']; } // Load network links and assign them to interfaces $Links = array(); $DbResult = $this->Database->query('SELECT `NetworkLink`.`Id`,`NetworkLink`.`Interface1`,'. '`NetworkLink`.`Interface2`,`NetworkLinkType`.`MaxRealSpeed` FROM `NetworkLink` '. 'LEFT JOIN `NetworkLinkType` ON `NetworkLinkType`.`Id`=`NetworkLink`.`Type`'); while ($Link = $DbResult->fetch_assoc()) { $Links[$Link['Id']] = $Link; $Interfaces[$Link['Interface1']]['Links'][] = $Link['Id']; $Interfaces[$Link['Interface2']]['Links'][] = $Link['Id']; } // Calculate maximum speed for network devices $DevicesToCheck = array($RootDeviceId); $this->Devices[$RootDeviceId]['MaxSpeed'] = $BaseSpeed; $this->Devices[$RootDeviceId]['Calculated'] = true; while (count($DevicesToCheck) > 0) { $NewDevicesToCheck = array(); foreach ($DevicesToCheck as $DeviceId) { foreach ($this->Devices[$DeviceId]['Interfaces'] as $InterfaceId) { foreach ($Interfaces[$InterfaceId]['Links'] as $LinkId) { $Link = $Links[$LinkId]; $Interface2Id = $Link['Interface1']; if ($Interface2Id == $InterfaceId) $Interface2Id = $Links[$LinkId]['Interface2']; $Device2Id = $Interfaces[$Interface2Id]['Device']; if ($this->Devices[$Device2Id]['Calculated'] == false) { $this->Devices[$Device2Id]['Calculated'] = true; $NewMaxSpeed = $this->Devices[$DeviceId]['MaxSpeed']; if ($NewMaxSpeed > $Link['MaxRealSpeed']) $NewMaxSpeed = $Link['MaxRealSpeed']; $this->Devices[$Device2Id]['MaxSpeed'] = $NewMaxSpeed; // Set nodes tree relation $this->Devices[$Device2Id]['Parent'] = $DeviceId; $this->Devices[$DeviceId]['Childs'][] = $Device2Id; $NewDevicesToCheck[] = $Device2Id; } } } } $DevicesToCheck = $NewDevicesToCheck; } // Calculate maximum speed for network devices $this->UpdateMinSpeed($RootDeviceId); echo('Not linked network devices: '); foreach ($this->Devices as $Device) { if ($Device['MaxSpeed'] == 0) echo($Device['Name'].', '); } echo("\n"); } function BuildQueueItems($DeviceId, $SpeedLimitParent): void { $Device = $this->Devices[$DeviceId]; // Device $DeviceName = $Device['Name']; $DeviceName = RouterOSIdent($DeviceName); $this->Devices[$DeviceId]['QueueName'] = $DeviceName; echo($DeviceName.', '); $LimitDevice = new SpeedLimitItem($DeviceName, $SpeedLimitParent); $LimitDevice->LimitIn = new SpeedLimit($Device['MinSpeed'], $Device['MaxSpeed'], GetMarkByComment($DeviceName.'-in')); $LimitDevice->LimitOut = new SpeedLimit($Device['MinSpeed'], $Device['MaxSpeed'], GetMarkByComment($DeviceName.'-out')); // Interfaces $DbResult3 = $this->Database->select('NetworkInterface', '*', '`Device` = '.$DeviceId.' AND `LocalIP` != ""'); $IntCount = $DbResult3->num_rows; while ($Interface = $DbResult3->fetch_assoc()) { $InterfaceName = $Device['Name']; if ($Interface['Name'] != '') $InterfaceName .= '-'.$Interface['Name']; else $InterfaceName .= '-'; $InterfaceName = RouterOSIdent($InterfaceName); echo($InterfaceName.', '); $LimitInterface = new SpeedLimitItem($InterfaceName, $LimitDevice); $LimitInterface->LimitIn = new SpeedLimit(round($Device['MinSpeed'] / $IntCount), $Device['MaxSpeed'], GetMarkByComment($InterfaceName.'-in')); $LimitInterface->LimitOut = new SpeedLimit(round($Device['MinSpeed'] / $IntCount), $Device['MaxSpeed'], GetMarkByComment($InterfaceName.'-out')); } // Process childs foreach ($Device['Childs'] as $DeviceChild) { $this->BuildQueueItems($DeviceChild, $LimitDevice); } } function RunTopology(): void { $PathQueue = array('queue', 'tree'); $Routerboard = new Routerboard(); $Routerboard->UserName = $this->System->Config['MainRouter']['UserName']; $Routerboard->Timeout = $this->System->Config['MainRouter']['ConnectTimeout']; $Routerboard->HostName = $this->System->Config['MainRouter']['HostName']; $Routerboard->Debug = true; $this->UsedNames = array(); $Finance = &ModuleFinance::Cast($this->System->GetModule('Finance'))->Finance; $Finance->LoadMonthParameters(0); $InDivider = 1; $OutDivider = 1; $UsersMaxSpeedIn = round($Finance->MaxSpeed / $InDivider) * 1000; $UsersMaxSpeedOut = round($Finance->MaxSpeed / $OutDivider) * 1000; $DbResult = $this->Database->select('Service', '*', '(`ChangeAction` IS NULL) AND (`Id`='.TARIFF_FREE.')'); if ($DbResult->num_rows == 1) { $Service = $DbResult->fetch_array(); $FreeInetSpeed = $Service['InternetSpeedMax']; } else $FreeInetSpeed = 0; $this->ItemsQueue = array(); // Root of tree and main limit $Main = new SpeedLimitItem('main'); $Main->LimitIn = new SpeedLimit($UsersMaxSpeedIn, $UsersMaxSpeedIn); $Main->LimitOut = new SpeedLimit($UsersMaxSpeedOut, $UsersMaxSpeedOut); // Slow free internet $Free = new SpeedLimitItem('free', $Main); $Free->LimitIn = new SpeedLimit($FreeInetSpeed, $FreeInetSpeed, GetMarkByComment('free-in')); $Free->LimitOut = new SpeedLimit($FreeInetSpeed, $FreeInetSpeed, GetMarkByComment('free-out')); $this->BuildTree($this->System->Config['MainRouter']['DeviceId'], $UsersMaxSpeedIn); $this->BuildQueueItems($this->System->Config['MainRouter']['DeviceId'], $Main); echo($Main->Print()); die(); print_r($this->ItemsQueue); //$Routerboard->ListUpdate($PathQueue, array('name', 'limit-at', 'max-limit', // 'parent', 'packet-mark'), $this->ItemsQueue, array(), true); } }