From 8e236a7045ba6f8d329265e5b3efa5115db5adb8 Mon Sep 17 00:00:00 2001
From: Jeremy <grossmj@gns3.net>
Date: Tue, 21 Jul 2015 22:58:28 -0600
Subject: [PATCH] Prevent using different hypervisors that leverage hardware
 virtualization. - Implemented for Qemu when a VMware or VirtualBox VM with
 hardware virtualization is already running. - Implemented for VirtualBox only
 when a Qemu VM with KVM is already running.

---
 gns3server/handlers/api/qemu_handler.py       |  7 ++++++
 gns3server/handlers/api/virtualbox_handler.py |  6 +++++
 gns3server/modules/base_manager.py            |  4 ++--
 gns3server/modules/base_vm.py                 | 11 +++++++++
 gns3server/modules/project_manager.py         | 24 +++++++++++++++++++
 gns3server/modules/qemu/qemu_vm.py            | 13 +++++++---
 .../modules/virtualbox/virtualbox_vm.py       | 17 +++++++++++++
 gns3server/modules/vmware/vmware_vm.py        |  5 +++-
 8 files changed, 81 insertions(+), 6 deletions(-)

diff --git a/gns3server/handlers/api/qemu_handler.py b/gns3server/handlers/api/qemu_handler.py
index 3be59314..9a8cff10 100644
--- a/gns3server/handlers/api/qemu_handler.py
+++ b/gns3server/handlers/api/qemu_handler.py
@@ -15,8 +15,10 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import sys
 from aiohttp.web import HTTPConflict
 from ...web.route import Route
+from ...modules.project_manager import ProjectManager
 from ...schemas.nio import NIO_SCHEMA
 from ...schemas.qemu import QEMU_CREATE_SCHEMA
 from ...schemas.qemu import QEMU_UPDATE_SCHEMA
@@ -146,6 +148,11 @@ class QEMUHandler:
 
         qemu_manager = Qemu.instance()
         vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+        if sys.platform.startswith("linux") and qemu_manager.config.get_section_config("Qemu").getboolean("enable_kvm", True) \
+                and "-no-kvm" not in vm.options:
+            pm = ProjectManager.instance()
+            if pm.check_hardware_virtualization(vm) is False:
+                raise HTTPConflict(text="Cannot start VM with KVM enabled because hardware virtualization is already used by another software like VMware or VirtualBox")
         yield from vm.start()
         response.set_status(204)
 
diff --git a/gns3server/handlers/api/virtualbox_handler.py b/gns3server/handlers/api/virtualbox_handler.py
index 5036bef7..a66e472d 100644
--- a/gns3server/handlers/api/virtualbox_handler.py
+++ b/gns3server/handlers/api/virtualbox_handler.py
@@ -16,6 +16,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
+import sys
+
 from aiohttp.web import HTTPConflict
 from ...web.route import Route
 from ...schemas.nio import NIO_SCHEMA
@@ -190,6 +192,10 @@ class VirtualBoxHandler:
 
         vbox_manager = VirtualBox.instance()
         vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+        if sys.platform.startswith("linux") and (yield from vm.check_hw_virtualization()):
+            pm = ProjectManager.instance()
+            if pm.check_hardware_virtualization(vm) is False:
+                raise HTTPConflict(text="Cannot start VM because KVM is being used by a Qemu VM")
         yield from vm.start()
         response.set_status(204)
 
diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py
index ebf6d0d3..37478b43 100644
--- a/gns3server/modules/base_manager.py
+++ b/gns3server/modules/base_manager.py
@@ -372,7 +372,7 @@ class BaseManager:
         elif nio_settings["type"] == "nio_tap":
             tap_device = nio_settings["tap_device"]
             if not is_interface_up(tap_device):
-                raise aiohttp.web.HTTPConflict(text="TAP interface {} is down".format(tap_device))
+                raise aiohttp.web.HTTPConflict(text="TAP interface {} does not exist or is down".format(tap_device))
             # FIXME: check for permissions on tap device
             # if not self._has_privileged_access(executable):
             #    raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device))
@@ -380,7 +380,7 @@ class BaseManager:
         elif nio_settings["type"] == "nio_generic_ethernet":
             ethernet_device = nio_settings["ethernet_device"]
             if not is_interface_up(ethernet_device):
-                raise aiohttp.web.HTTPConflict(text="Ethernet interface {} is down".format(ethernet_device))
+                raise aiohttp.web.HTTPConflict(text="Ethernet interface {} does not exist or is down".format(ethernet_device))
             nio = NIOGenericEthernet(ethernet_device)
         elif nio_settings["type"] == "nio_nat":
             nio = NIONAT()
diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py
index 19c78d5e..197bb84b 100644
--- a/gns3server/modules/base_vm.py
+++ b/gns3server/modules/base_vm.py
@@ -49,6 +49,7 @@ class BaseVM:
         self._console = console
         self._console_type = console_type
         self._temporary_directory = None
+        self._hw_virtualization = False
         self._vm_status = "stopped"
 
         if self._console is not None:
@@ -262,3 +263,13 @@ class BaseVM:
                                                                                         name=self.name,
                                                                                         id=self.id,
                                                                                         console_type=console_type))
+
+    @property
+    def hw_virtualization(self):
+        """
+        Returns either the VM is using hardware virtualization or not.
+
+        :return: boolean
+        """
+
+        return self._hw_virtualization
diff --git a/gns3server/modules/project_manager.py b/gns3server/modules/project_manager.py
index 4ea21612..6d30bc99 100644
--- a/gns3server/modules/project_manager.py
+++ b/gns3server/modules/project_manager.py
@@ -95,3 +95,27 @@ class ProjectManager:
         if project_id not in self._projects:
             raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id))
         del self._projects[project_id]
+
+    def check_hardware_virtualization(self, source_vm):
+        """
+        Checks if hardware virtualization can be used.
+
+        :returns: boolean
+        """
+
+        from .qemu import QemuVM
+        from .virtualbox import VirtualBoxVM
+        from .vmware import VMwareVM
+        for project in self._projects.values():
+            for vm in project.vms:
+                if vm == source_vm:
+                    continue
+                if vm.hw_virtualization:
+                    if isinstance(source_vm, QemuVM) and not isinstance(vm, QemuVM):
+                        # A Qemu VM won't start if any other virtualization software uses hardware virtualization
+                        return False
+                    elif isinstance(source_vm, VirtualBoxVM) and not isinstance(vm, VirtualBoxVM) and not isinstance(vm, VMwareVM):
+                        # A VirtualBox VM won't start if KVM is being used
+                        return False
+                    # VMware doesn't seem to be bothered by any other virtualization software.
+        return True
diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py
index 979a28ce..54e54297 100644
--- a/gns3server/modules/qemu/qemu_vm.py
+++ b/gns3server/modules/qemu/qemu_vm.py
@@ -73,7 +73,6 @@ class QemuVM(BaseVM):
         self._stdout_file = ""
 
         # QEMU VM settings
-
         if qemu_path:
             try:
                 self.qemu_path = qemu_path
@@ -483,8 +482,11 @@ class QemuVM(BaseVM):
                                                                                         id=self._id,
                                                                                         options=options))
 
-        if not sys.platform.startswith("linux") and "-no-kvm" in options:
-            options = options.replace("-no-kvm")
+        if not sys.platform.startswith("linux"):
+            if "-no-kvm" in options:
+                options = options.replace("-no-kvm")
+            if "-enable-kvm" in options:
+                options = options.replace("-enable-kvm")
         self._options = options.strip()
 
     @property
@@ -696,6 +698,9 @@ class QemuVM(BaseVM):
             if self._cpu_throttling:
                 self._set_cpu_throttling()
 
+            if "-enable-kvm" in command_string:
+                self._hw_virtualization = True
+
     def _termination_callback(self, returncode):
         """
         Called when the process has stopped.
@@ -706,6 +711,7 @@ class QemuVM(BaseVM):
         if self.started:
             log.info("QEMU process has stopped, return code: %d", returncode)
             self.status = "stopped"
+            self._hw_virtualization = False
             self._process = None
             if returncode != 0:
                 self.project.emit("log.error", {"message": "QEMU process has stopped, return code: {}\n{}".format(returncode, self.read_stdout())})
@@ -717,6 +723,7 @@ class QemuVM(BaseVM):
         """
 
         # stop the QEMU process
+        self._hw_virtualization = False
         if self.is_running():
             log.info('Stopping QEMU VM "{}" PID={}'.format(self._name, self._process.pid))
             try:
diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py
index 7c48ccdf..8fd750d7 100644
--- a/gns3server/modules/virtualbox/virtualbox_vm.py
+++ b/gns3server/modules/virtualbox/virtualbox_vm.py
@@ -169,6 +169,19 @@ class VirtualBoxVM(BaseVM):
         if "memory" in vm_info:
             self._ram = int(vm_info["memory"])
 
+    @asyncio.coroutine
+    def check_hw_virtualization(self):
+        """
+        Returns either hardware virtualization is activated or not.
+
+        :returns: boolean
+        """
+
+        vm_info = yield from self._get_vm_info()
+        if "hwvirtex" in vm_info and vm_info["hwvirtex"] == "on":
+            return True
+        return False
+
     @asyncio.coroutine
     def start(self):
         """
@@ -203,12 +216,16 @@ class VirtualBoxVM(BaseVM):
         if self._enable_remote_console and self._console is not None:
             self._start_remote_console()
 
+        if (yield from self.check_hw_virtualization()):
+            self._hw_virtualization = True
+
     @asyncio.coroutine
     def stop(self):
         """
         Stops this VirtualBox VM.
         """
 
+        self._hw_virtualization = False
         self._stop_remote_console()
         vm_state = yield from self._get_vm_state()
         if vm_state == "running" or vm_state == "paused" or vm_state == "stuck":
diff --git a/gns3server/modules/vmware/vmware_vm.py b/gns3server/modules/vmware/vmware_vm.py
index be1f352f..99705ac1 100644
--- a/gns3server/modules/vmware/vmware_vm.py
+++ b/gns3server/modules/vmware/vmware_vm.py
@@ -28,7 +28,6 @@ import tempfile
 
 from pkg_resources import parse_version
 from gns3server.ubridge.hypervisor import Hypervisor
-from gns3server.ubridge.ubridge_error import UbridgeError
 from gns3server.utils.telnet_server import TelnetServer
 from gns3server.utils.interfaces import get_windows_interfaces
 from collections import OrderedDict
@@ -387,6 +386,9 @@ class VMwareVM(BaseVM):
             yield from asyncio.sleep(1)  # give some time to VMware to create the pipe file.
             self._start_remote_console()
 
+        if self._get_vmx_setting("vhv.enable", "TRUE"):
+            self._hw_virtualization = True
+
         self._started = True
         log.info("VMware VM '{name}' [{id}] started".format(name=self.name, id=self.id))
 
@@ -396,6 +398,7 @@ class VMwareVM(BaseVM):
         Stops this VMware VM.
         """
 
+        self._hw_virtualization = False
         self._stop_remote_console()
         if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running():
             yield from self._ubridge_hypervisor.stop()