<?php

namespace App\Helpers\Tunnel;
use App\Helpers\Tunnel\Generic;
use App\Helpers\Utility\SanityChecker;
use Symfony\Component\Process\Process;

class Vxlan extends Generic {
 
 private $id, $dstport, $tunnel_ips;

   function __construct( $_name, $_id, $_local, $_remote, $_dstport, $_tunnel_ips ) {
      $this->name = $_name;
      $this->id = $_id;
      $this->local = $_local;
      $this->remote = $_remote;
      $this->dstport = $_dstport;
      $this->tunnel_ips = $_tunnel_ips;
   }

   function establish() {
      $outcome = false;
  
      // Test whether each input passes a validity and sanity check...
      $valid_addresses = SanityChecker::ip_address($this->local) && SanityChecker::ip_address($this->remote);
      $valid_port = SanityChecker::port($this->dstport);
      $valid_id = filter_var($this->id, FILTER_SANITIZE_NUMBER_INT) && $this->id > 0 ? true : false;
      $valid_name = SanityChecker::name($this->name);
      
      // Assume the tunnels are valid as a matter of convenience, then test every single one. Treat this as an atomic failure.
      $valid_tunnel_ips = true;
  
      foreach($this->tunnel_ips as $tunnel_ip) {
          if(!SanityChecker::mask($tunnel_ip['ip'], $tunnel_ip['mask']) || !SanityChecker::ip_address($tunnel_ip['ip'])) {
              $valid_tunnel_ips = false;
              // Bail out prematurely - If this is an atomic failure, only one needs to fail.
              break;
          }
      }

      if($valid_tunnel_ips) {
        // Only attempt tunnel creation if the values are sane and safe.
        if($valid_addresses && $valid_port && $valid_id && $valid_name) {
            $undo = false;

            // Prepare the process we'll run for creating a tunnel...
        $add_tunnel_process = new Process([
            'ip', 'link', 'add', $this->name, 'type', 'vxlan', 'id', $this->id, 'local', $this->local, 'remote', $this->remote, 'dstport', $this->dstport
        ]);

        $add_tunnel_process->run();

            // Only proceed in the event we successfully add a tunnel
        if($add_tunnel_process->isSuccessful()) {
                // Flick the tunnel to up
                $add_link_process = new Process(
                    ['ip', 'link', 'set', $this->name, 'up']
                );
                
                $add_link_process->run();

                // Only proceed if we can flick it to up...
                if($add_link_process->isSuccessful()) {
                    // Add the tunnel device addresses
                    foreach($this->tunnel_ips as $tunnel_ip) {
                        $add_address_process = new Process (
                            ['ip', 'addr', 'add', $tunnel_ip['ip'] . '/' . $tunnel_ip['mask'], 'dev', $this->name]
                        );

                        $add_address_process->run();

                        // If we fail to add a tunnel device, bail out. We're going to undo our previous steps. Failures should be atomic.
                    if(!$add_address_process->isSuccessful()) {
                        $undo = true;
                        break;
                    }
                    }
                }
                else {
                    $undo = true;
                }
            }
            else {
                $undo = true;
            }

            if($undo) {
                $delete_link_process = new Process([
                    'ip', 'link', 'delete', $this->name
                ]);

                $delete_link_process->run();
            } else {
                $outcome = true;
            }
        }
      }
  
      return $outcome;
   }

   function remove() {
      $outcome = false;

      if(SanityChecker::name($this->name)) {
         $delete_link = new Process([
            'ip', 'link', 'delete', $this->name
         ]);

         $delete_link->run();

         $outcome = $delete_link->isSuccessful();
      }

      return $outcome;
   }

}

?>