<?php

namespace App\Helpers\Tunnel;

use Symfony\Component\Process\Process;
use App\Helpers\Utility\SanityChecker;
use Illuminate\Support\Facades\Log;

class Gre extends Generic {

 private $tunnel_ips = [];

 function __construct( $_name, $_local, $_remote, $_tunnel_ips ) {
    $this->name = $_name;
    $this->local = $_local;
    $this->remote = $_remote;
    $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_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;
        }
    }

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

        // Prepare the process we'll run for creating a tunnel...
        $add_tunnel_process = new Process(['ip', 'tunnel', 'add', $this->name, 'mode' ,'gre' ,'remote', $this->remote, 'local', $this->local, 'ttl', '255']);

        $add_tunnel_process->run();

        Log::info($add_tunnel_process->getCommandLine());
        Log::error($add_tunnel_process->getErrorOutput());
        
        // 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();

            Log::info($add_link_process->getCommandLine());
            Log::error($add_link_process->getErrorOutput());

            // 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();

                    Log::info($add_address_process->getCommandLine());
                    Log::error($add_address_process->getErrorOutput());

                    // 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_tunnel_process = new Process([
                'ip', 'tunnel', 'delete', $this->name
            ]);

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

    return $outcome;
 }

 function remove() {
    $outcome = false;

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

        $delete_tunnel_process = new Process([
            'ip', 'tunnel', 'delete', $this->name
        ]);

        $delete_link_process->run();
        $delete_tunnel_process->run();

        if($delete_link_process->isSuccessful() || $delete_tunnel_process->isSuccessful()) {
            $outcome = true;
        }
    }

    return $outcome;
 }

}

?>