<?php

class ConfigRouterOSFirewallMangle extends NetworkConfigItem
{
  function ProcessNode($Node): void
  {
    global $InetInterface, $ItemsFirewall;

    foreach ($Node['Items'] as $Item)
    {
      if (count($Item['Items']) == 0)
      {
        // Hosts
        $ParentSubnetId = GetSubgroupByRange($Node['Address']->AddressToString().'/'.$Node['Address']->Prefix);
        $Address = $Item['Address']->AddressToString();
        if ($Item['Address']->Prefix != 32) $Address .= '/'.$Item['Address']->Prefix;

        $PacketMark = GetMarkByComment($Item['Name'].'-out');
        $ItemsFirewall[] = array('chain' => 'inet-'.$ParentSubnetId.'-out', 'src-address' => $Address, 'out-interface' =>  $InetInterface, 'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'passthrough' => 'no', 'comment' => $Item['Name'].'-out');
        $PacketMark = GetMarkByComment($Item['Name'].'-in');
        $ItemsFirewall[] = array('chain' => 'inet-'.$ParentSubnetId.'-in', 'dst-address' => $Address, 'in-interface' => $InetInterface, 'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'passthrough' => 'no', 'comment' => $Item['Name'].'-in');
      } else
      {
        // Subnets
        $ParentSubnetId = GetSubgroupByRange($Node['Address']->AddressToString().'/'.$Node['Address']->Prefix);
        $SubnetId = GetSubgroupByRange($Item['Address']->AddressToString().'/'.$Item['Address']->Prefix);
        $PacketMark = GetMarkByComment($Item['Name'].'-out');

        $Address = $Item['Address']->AddressToString();
        if ($Item['Address']->Prefix != 32) $Address .= '/'.$Item['Address']->Prefix;

        $ItemsFirewall[] = array('chain' => 'inet-'.$ParentSubnetId.'-out', 'src-address' => $Address, 'out-interface' => $InetInterface, 'action' => 'jump', 'jump-target' => 'inet-'.$SubnetId.'-out', 'comment' => $Item['Name'].'-out');
        $ItemsFirewall[] = array('chain' => 'inet-'.$ParentSubnetId.'-in', 'dst-address' => $Address, 'in-interface' => $InetInterface, 'action' => 'jump', 'jump-target' => 'inet-'.$SubnetId.'-in', 'comment' => $Item['Name'].'-in');

        $this->ProcessNode($Item);
      }
    }
    if ($Node['ForceMark'] == true)
    {
      // Mark member subnets
      $ParentSubnetId = GetSubgroupByRange($Node['Address']->AddressToString().'/'.$Node['Address']->Prefix);
      $PacketMark = GetMarkByComment($Node['Name'].'-out');
      $ItemsFirewall[] = array('chain' => 'inet-'.$ParentSubnetId.'-out', 'src-address' => '', 'out-interface' =>  $InetInterface, 'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'passthrough' => 'no', 'comment' => $Node['Name'].'-all-out');
      $PacketMark = GetMarkByComment($Node['Name'].'-in');
      $ItemsFirewall[] = array('chain' => 'inet-'.$ParentSubnetId.'-in', 'dst-address' => '', 'in-interface' => $InetInterface, 'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'passthrough' => 'no', 'comment' => $Node['Name'].'-all-in');
    }
  }

  function Run(): void
  {
    $this->RunIPv4();
    $this->RunIPv6();
  }

  function RunIPv4(): void
  {
    global $ItemsFirewall;

    $PathFirewall = array('ip', 'firewall', 'mangle');

    $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;

    $InetInterface = $this->System->Config['MainRouter']['InetInterface'];

    // Generate address tree
    $AddressTree = array('Address' => new NetworkAddressIPv4(), 'Name' => 'main', 'Items' => array(), 'ForceMark' => false);

    // Divide rules by subnet number
    $DbResult = $this->System->Database->query('SELECT `Id`, `Name`, `AddressRange`, `Mask` FROM `NetworkSubnet` WHERE `Member` IS NULL');
    while ($Subnet = $DbResult->fetch_assoc())
    {
      $NewAddress = new NetworkAddressIPv4();
      $NewAddress->AddressFromString($Subnet['AddressRange']);
      $NewAddress->Prefix = $Subnet['Mask'];
      InsertToAddressTreeIPv4($AddressTree, $NewAddress, 'subnet-'.RouterOSIdent($Subnet['Name']));
    }

    // Process users
    $DbResult = $this->System->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())
    {
      $Member['Name'] = RouterOSIdent($Member['Name'].'-'.$Member['Id'] );
      echo('Uživatel '.$Member['Name'].': ');

      $DbResult2 = $this->System->Database->select('NetworkDevice', '*', '`Used` = 1 AND `Member` = '.$Member['Id']);
      while ($Device = $DbResult2->fetch_assoc())
      {
        $DbResult3 = $this->Database->select('NetworkInterface', '*', '`Device` = '.$Device['Id'].' AND `LocalIP` != ""');
        while ($Interface = $DbResult3->fetch_assoc())
        {
          $Name = $Device['Name'];
          if ($Interface['Name'] != '') $Name .= '-'.$Interface['Name'];
          $Name = RouterOSIdent($Name);
          echo($Name.', ');
          $NewAddress = new NetworkAddressIPv4();
          $NewAddress->AddressFromString($Interface['LocalIP']);
          $NewAddress->Prefix = IPV4_BIT_WIDTH;
          InsertToAddressTreeIPv4($AddressTree, $NewAddress, $Name);
        }
      }

      $DbResult2 = $this->Database->select('NetworkSubnet', '*', '(`Member`='.$Member['Id'].') AND (AddressRange != "")');
      while ($Subnet = $DbResult2->fetch_assoc())
      {
        $Subnet['Name'] = RouterOSIdent('subnet-'.$Subnet['Name']);
        echo($Subnet['Name'].', ');
        $NewAddress = new NetworkAddressIPv4();
        $NewAddress->AddressFromString($Subnet['AddressRange']);
        $NewAddress->Prefix = $Subnet['Mask'];
        if ($Subnet['Member'] != 0) $ForceMark = true;
        else $ForceMark = false;
        echo($ForceMark.', ');
        InsertToAddressTreeIPv4($AddressTree, $NewAddress, $Subnet['Name'], false, $ForceMark);
      }
      echo("\n");
    }

    ShowSubnetNode($AddressTree);

    // Generate firewall rules
    $ItemsFirewall = array();

    // Root of tree and main limit
    $ItemsFirewall[] = array('chain' => 'forward', 'out-interface' => $InetInterface, 'dst-address' => '!77.92.221.0/24', 'action' => 'jump', 'jump-target' => 'inet-1-out', 'comment' => 'main-out');
    $ItemsFirewall[] = array('chain' => 'forward', 'in-interface' => $InetInterface, 'src-address' => '!77.92.221.0/24', 'action' => 'jump', 'jump-target' => 'inet-1-in', 'comment' => 'main-in');

    $this->ProcessNode($AddressTree);

    // Limit direct input/output traffic to gateway
    $PacketMark = GetMarkByComment('rt-gateway-3-out');
    $ItemsFirewall[] = array('chain' => 'output', 'out-interface' => $InetInterface, 'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'comment' => 'local-out',);
    $PacketMark = GetMarkByComment('rt-gateway-3-in');
    $ItemsFirewall[] = array('chain' => 'input', 'in-interface' => $InetInterface, 'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'comment' => 'local-in',);

    // Limited free internet
    $PacketMark = GetMarkByComment('free-out');
    $ItemsFirewall[] = array('chain' => 'inet-1-out', 'out-interface' => $InetInterface,
        'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'comment' => 'free-out', 'passthrough' => 'yes');
    $PacketMark = GetMarkByComment('free-in');
    $ItemsFirewall[] = array('chain' => 'inet-1-in', 'in-interface' => $InetInterface,
        'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'comment' => 'free-in', 'passthrough' => 'no');
    // Unregistred clients add to address list
    $ItemsFirewall[] = array('chain' => 'inet-1-out', 'out-interface' => $InetInterface, 'src-address' => '10.145.0.0/16',
        'action' => 'add-src-to-address-list', 'address-list' => 'unregistred', 'address-list-timeout' => '1d',
        'comment' => 'unregistred-clients');

    $Routerboard->ListUpdate($PathFirewall, array('chain', 'dst-address', 'in-interface', 'action', 'new-packet-mark', 'passthrough', 'comment', 'out-interface', 'src-address', 'jump-target'), $ItemsFirewall, array(), true);
  }

  function RunIPv6(): void
  {
    global $ItemsFirewall;

    $PathFirewall = array('ipv6', 'firewall', 'mangle');

    $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;

    $InetInterface = $this->System->Config['MainRouter']['InetInterface'];

    // Generate address tree
    $AddressTree = array('Address' => new NetworkAddressIPv4(), 'Name' => 'main', 'Items' => array(), 'ForceMark' => false);

    // Divide rules by subnet number
    $DbResult = $this->System->Database->query('SELECT `Id`, `Name`, `AddressRangeIPv6`, `MaskIPv6` FROM `NetworkSubnet` '.
      'WHERE (`Member` IS NULL) AND (`AddressRangeIPv6` != "")');
    while ($Subnet = $DbResult->fetch_assoc())
    {
      $NewAddress = new NetworkAddressIPv6();
      $NewAddress->AddressFromString($Subnet['AddressRangeIPv6']);
      $NewAddress->Prefix = $Subnet['MaskIPv6'];
      InsertToAddressTreeIPv6($AddressTree, $NewAddress, 'subnet-'.RouterOSIdent($Subnet['Name']));
    }

    // Process users
    $DbResult = $this->System->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())
    {
      $Member['Name'] = RouterOSIdent($Member['Name'].'-'.$Member['Id'] );
      echo('Uživatel '.$Member['Name'].': ');

      $DbResult2 = $this->System->Database->select('NetworkDevice', '*', '`Used` = 1 AND `Member` = '.$Member['Id']);
      while ($Device = $DbResult2->fetch_assoc())
      {
        $DbResult3 = $this->Database->select('NetworkInterface', '*', '`Device` = '.$Device['Id'].' AND `IPv6` != ""');
        while ($Interface = $DbResult3->fetch_assoc())
        {
          $Name = $Device['Name'];
          if ($Interface['Name'] != '') $Name .= '-'.$Interface['Name'];
          $Name = RouterOSIdent($Name);
          echo($Name.', ');
          $NewAddress = new NetworkAddressIPv6();
          $NewAddress->AddressFromString($Interface['IPv6']);
          $NewAddress->Prefix = IPV6_BIT_WIDTH;
          InsertToAddressTreeIPv6($AddressTree, $NewAddress, $Name);
        }
      }

      $DbResult2 = $this->Database->select('NetworkSubnet', '*', '(`Member`='.$Member['Id'].') AND (AddressRangeIPv6 != "")');
      while ($Subnet = $DbResult2->fetch_assoc())
      {
        $Subnet['Name'] = RouterOSIdent('subnet-'.$Subnet['Name']);
        echo($Subnet['Name'].', ');
        $NewAddress = new NetworkAddressIPv6();
        $NewAddress->AddressFromString($Subnet['AddressRangeIPv6']);
        $NewAddress->Prefix = $Subnet['MaskIPv6'];
        if ($Subnet['Member'] != 0) $ForceMark = true;
        else $ForceMark = false;
        echo($ForceMark.', ');
        InsertToAddressTreeIPv6($AddressTree, $NewAddress, $Subnet['Name'], false, $ForceMark);
      }
      echo("\n");
    }

    ShowSubnetNode($AddressTree);

    // Generate firewall rules
    $ItemsFirewall = array();

    // Root of tree and main limit
    $ItemsFirewall[] = array('chain' => 'forward', 'out-interface' => $InetInterface, 'dst-address' => '!2a00:e580:244::/48',
      'action' => 'jump', 'jump-target' => 'inet-1-out', 'comment' => 'main-out');
    $ItemsFirewall[] = array('chain' => 'forward', 'in-interface' => $InetInterface, 'src-address' => '!2a00:e580:244::/48',
      'action' => 'jump', 'jump-target' => 'inet-1-in', 'comment' => 'main-in');

    $this->ProcessNode($AddressTree);

    // Limit direct input/output traffic to gateway
    $PacketMark = GetMarkByComment('rt-gateway-3-out');
    $ItemsFirewall[] = array('chain' => 'output', 'out-interface' => $InetInterface, 'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'comment' => 'local-out',);
    $PacketMark = GetMarkByComment('rt-gateway-3-in');
    $ItemsFirewall[] = array('chain' => 'input', 'in-interface' => $InetInterface, 'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'comment' => 'local-in',);

    // Limited free internet
    $PacketMark = GetMarkByComment('free-out');
    $ItemsFirewall[] = array('chain' => 'inet-1-out', 'out-interface' => $InetInterface,
        'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'comment' => 'free-out', 'passthrough' => 'yes');
    $PacketMark = GetMarkByComment('free-in');
    $ItemsFirewall[] = array('chain' => 'inet-1-in', 'in-interface' => $InetInterface,
        'action' => 'mark-packet', 'new-packet-mark' => $PacketMark, 'comment' => 'free-in', 'passthrough' => 'no');
    // Unregistred clients add to address list
    $ItemsFirewall[] = array('chain' => 'inet-1-out', 'out-interface' => $InetInterface, 'src-address' => '2a00:e580:244::/48',
        'action' => 'add-src-to-address-list', 'address-list' => 'unregistred', 'address-list-timeout' => '1d',
        'comment' => 'unregistred-clients');
    
    $Routerboard->ListUpdate($PathFirewall, array('chain', 'dst-address', 'in-interface', 'action', 'new-packet-mark',
      'passthrough', 'comment', 'out-interface', 'src-address', 'jump-target'), $ItemsFirewall, array(), true);
  }
}
