From 1fb4ab7e339eae805042b05899d99148317a77b5 Mon Sep 17 00:00:00 2001
From: grossmj <grossmj@gns3.net>
Date: Thu, 17 Jul 2014 15:28:02 -0600
Subject: [PATCH] Minimal VirtualBox integration.

---
 gns3server/handlers/jsonrpc_websocket.py      |   7 +-
 gns3server/modules/virtualbox/__init__.py     | 312 +++++++-
 gns3server/modules/virtualbox/nios/nio.py     |  65 ++
 gns3server/modules/virtualbox/nios/nio_udp.py |   5 +-
 gns3server/modules/virtualbox/schemas.py      | 418 +++++++++++
 gns3server/modules/virtualbox/vboxapi_py3     |   1 +
 .../modules/virtualbox/vboxwrapper_client.py  |  68 +-
 .../modules/virtualbox/virtualbox_vm.py       | 708 ++++++++++++++++--
 gns3server/server.py                          |   8 +-
 9 files changed, 1495 insertions(+), 97 deletions(-)
 create mode 100644 gns3server/modules/virtualbox/nios/nio.py
 create mode 100644 gns3server/modules/virtualbox/schemas.py
 create mode 160000 gns3server/modules/virtualbox/vboxapi_py3

diff --git a/gns3server/handlers/jsonrpc_websocket.py b/gns3server/handlers/jsonrpc_websocket.py
index 677ebe2d..5b18496c 100644
--- a/gns3server/handlers/jsonrpc_websocket.py
+++ b/gns3server/handlers/jsonrpc_websocket.py
@@ -152,6 +152,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler):
 
         if method not in self.destinations:
             if request_id:
+                log.warn("JSON-RPC method not found: {}".format(method))
                 return self.write_message(JSONRPCMethodNotFound(request_id)())
             else:
                 # This is a notification, silently ignore this error...
@@ -176,11 +177,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler):
         Invoked when the WebSocket is closed.
         """
 
-        try:
-            log.info("Websocket client {} disconnected".format(self.session_id))
-        except RuntimeError:
-            # to ignore logging exception: RuntimeError: reentrant call inside <_io.BufferedWriter name='<stderr>'>
-            pass
+        log.info("Websocket client {} disconnected".format(self.session_id))
         self.clients.remove(self)
 
         # Reset the modules if there are no clients anymore
diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py
index ee944615..c8d7d7ec 100644
--- a/gns3server/modules/virtualbox/__init__.py
+++ b/gns3server/modules/virtualbox/__init__.py
@@ -19,8 +19,8 @@
 VirtualBox server module.
 """
 
+import sys
 import os
-import base64
 import socket
 import shutil
 
@@ -28,18 +28,28 @@ from gns3server.modules import IModule
 from gns3server.config import Config
 from .virtualbox_vm import VirtualBoxVM
 from .virtualbox_error import VirtualBoxError
+from .vboxwrapper_client import VboxWrapperClient
 from .nios.nio_udp import NIO_UDP
 from ..attic import find_unused_port
 
-#from .schemas import VBOX_CREATE_SCHEMA
-#from .schemas import VBOX_DELETE_SCHEMA
-#from .schemas import VBOX_UPDATE_SCHEMA
-#from .schemas import VBOX_START_SCHEMA
-#from .schemas import VBOX_STOP_SCHEMA
-#from .schemas import VBOX_RELOAD_SCHEMA
-#from .schemas import VBOX_ALLOCATE_UDP_PORT_SCHEMA
-#from .schemas import VBOX_ADD_NIO_SCHEMA
-#from .schemas import VBOX_DELETE_NIO_SCHEMA
+if sys.platform.startswith("win"):
+    # automatically generate the Typelib wrapper
+    import win32com
+    win32com.client.gencache.is_readonly = False
+    win32com.client.gencache.GetGeneratePath()
+
+from .schemas import VBOX_CREATE_SCHEMA
+from .schemas import VBOX_DELETE_SCHEMA
+from .schemas import VBOX_UPDATE_SCHEMA
+from .schemas import VBOX_START_SCHEMA
+from .schemas import VBOX_STOP_SCHEMA
+from .schemas import VBOX_SUSPEND_SCHEMA
+from .schemas import VBOX_RELOAD_SCHEMA
+from .schemas import VBOX_ALLOCATE_UDP_PORT_SCHEMA
+from .schemas import VBOX_ADD_NIO_SCHEMA
+from .schemas import VBOX_DELETE_NIO_SCHEMA
+from .schemas import VBOX_START_CAPTURE_SCHEMA
+from .schemas import VBOX_STOP_CAPTURE_SCHEMA
 
 import logging
 log = logging.getLogger(__name__)
@@ -56,6 +66,26 @@ class VirtualBox(IModule):
 
     def __init__(self, name, *args, **kwargs):
 
+        # get the vboxwrapper location
+        config = Config.instance()
+        vbox_config = config.get_section_config(name.upper())
+        self._vboxwrapper_path = vbox_config.get("vboxwrapper_path")
+        if not self._vboxwrapper_path or not os.path.isfile(self._vboxwrapper_path):
+            paths = [os.getcwd()] + os.environ["PATH"].split(":")
+            # look for iouyap in the current working directory and $PATH
+            for path in paths:
+                try:
+                    if "vboxwrapper" in os.listdir(path) and os.access(os.path.join(path, "vboxwrapper"), os.X_OK):
+                        self._vboxwrapper_path = os.path.join(path, "vboxwrapper")
+                        break
+                except OSError:
+                    continue
+
+        if not self._vboxwrapper_path:
+            log.warning("vboxwrapper couldn't be found!")
+        elif not os.access(self._vboxwrapper_path, os.X_OK):
+            log.warning("vboxwrapper is not executable")
+
         # a new process start when calling IModule
         IModule.__init__(self, name, *args, **kwargs)
         self._vbox_instances = {}
@@ -71,6 +101,43 @@ class VirtualBox(IModule):
         self._projects_dir = kwargs["projects_dir"]
         self._tempdir = kwargs["temp_dir"]
         self._working_dir = self._projects_dir
+        self._vboxmanager = None
+        self._vboxwrapper = None
+
+    def _start_vbox_service(self):
+        """
+        Starts the VirtualBox backend.
+        vboxapi on Windows or vboxwrapper on other platforms.
+        """
+
+        if sys.platform.startswith("win"):
+            import win32com.client
+            if win32com.client.gencache.is_readonly is True:
+                # dynamically generate the cache
+                # http://www.py2exe.org/index.cgi/IncludingTypelibs
+                # http://www.py2exe.org/index.cgi/UsingEnsureDispatch
+                win32com.client.gencache.is_readonly = False
+                #win32com.client.gencache.Rebuild()
+                win32com.client.gencache.GetGeneratePath()
+            try:
+                from .vboxapi_py3 import VirtualBoxManager
+                self._vboxmanager = VirtualBoxManager(None, None)
+            except Exception as e:
+                raise VirtualBoxError("Could not initialize the VirtualBox Manager: {}".format(e))
+
+            log.info("VirtualBox Manager has successful started: version is {} r{}".format(self._vboxmanager.vbox.version,
+                                                                                           self._vboxmanager.vbox.revision))
+        else:
+
+            if not self._vboxwrapper_path:
+                raise VirtualBoxError("No vboxwrapper path has been configured")
+
+            if not os.path.isfile(self._vboxwrapper_path):
+                raise VirtualBoxError("vboxwrapper path doesn't exist {}".format(self._vboxwrapper_path))
+
+            self._vboxwrapper = VboxWrapperClient(self._vboxwrapper_path, self._tempdir, "127.0.0.1")
+            #self._vboxwrapper.connect()
+            self._vboxwrapper.start()
 
     def stop(self, signum=None):
         """
@@ -84,6 +151,9 @@ class VirtualBox(IModule):
             vbox_instance = self._vbox_instances[vbox_id]
             vbox_instance.delete()
 
+        if self._vboxwrapper and self._vboxwrapper.started:
+            self._vboxwrapper.stop()
+
         IModule.stop(self, signum)  # this will stop the I/O loop
 
     def get_vbox_instance(self, vbox_id):
@@ -120,6 +190,9 @@ class VirtualBox(IModule):
         self._vbox_instances.clear()
         self._allocated_udp_ports.clear()
 
+        if self._vboxwrapper and self._vboxwrapper.connected():
+            self._vboxwrapper.send("vboxwrapper reset")
+
         log.info("VirtualBox module has been reset")
 
     @IModule.route("virtualbox.settings")
@@ -129,6 +202,7 @@ class VirtualBox(IModule):
 
         Optional request parameters:
         - working_dir (path to a working directory)
+        - vboxwrapper_path (path to vboxwrapper)
         - project_name
         - console_start_port_range
         - console_end_port_range
@@ -165,6 +239,9 @@ class VirtualBox(IModule):
                 vbox_instance = self._vbox_instances[vbox_id]
                 vbox_instance.working_dir = os.path.join(self._working_dir, "vbox", "vm-{}".format(vbox_instance.id))
 
+        if "vboxwrapper_path" in request:
+            self._vboxwrapper_path = request["vboxwrapper_path"]
+
         if "console_start_port_range" in request and "console_end_port_range" in request:
             self._console_start_port_range = request["console_start_port_range"]
             self._console_end_port_range = request["console_end_port_range"]
@@ -195,16 +272,23 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_CREATE_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_CREATE_SCHEMA):
+            return
 
         name = request["name"]
+        vmname = request["vmname"]
         console = request.get("console")
         vbox_id = request.get("vbox_id")
 
         try:
 
-            vbox_instance = VirtualBoxVM(name,
+            if not self._vboxwrapper and not self._vboxmanager:
+                self._start_vbox_service()
+
+            vbox_instance = VirtualBoxVM(self._vboxwrapper,
+                                         self._vboxmanager,
+                                         name,
+                                         vmname,
                                          self._working_dir,
                                          self._host,
                                          vbox_id,
@@ -239,8 +323,8 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_DELETE_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_DELETE_SCHEMA):
+            return
 
         # get the instance
         vbox_instance = self.get_vbox_instance(request["id"])
@@ -274,8 +358,8 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_UPDATE_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_UPDATE_SCHEMA):
+            return
 
         # get the instance
         vbox_instance = self.get_vbox_instance(request["id"])
@@ -310,8 +394,8 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_START_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_START_SCHEMA):
+            return
 
         # get the instance
         vbox_instance = self.get_vbox_instance(request["id"])
@@ -340,8 +424,8 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_STOP_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_STOP_SCHEMA):
+            return
 
         # get the instance
         vbox_instance = self.get_vbox_instance(request["id"])
@@ -370,18 +454,76 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_RELOAD_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_RELOAD_SCHEMA):
+            return
 
         # get the instance
-        vbox_instance = self.get_vpcs_instance(request["id"])
+        vbox_instance = self.get_vbox_instance(request["id"])
         if not vbox_instance:
             return
 
         try:
-            if vbox_instance.is_running():
-                vbox_instance.stop()
-            vbox_instance.start()
+            vbox_instance.reload()
+        except VirtualBoxError as e:
+            self.send_custom_error(str(e))
+            return
+        self.send_response(True)
+
+    @IModule.route("virtualbox.stop")
+    def vbox_stop(self, request):
+        """
+        Stops a VirtualBox VM instance.
+
+        Mandatory request parameters:
+        - id (VirtualBox VM instance identifier)
+
+        Response parameters:
+        - True on success
+
+        :param request: JSON request
+        """
+
+        # validate the request
+        if not self.validate_request(request, VBOX_STOP_SCHEMA):
+            return
+
+        # get the instance
+        vbox_instance = self.get_vbox_instance(request["id"])
+        if not vbox_instance:
+            return
+
+        try:
+            vbox_instance.stop()
+        except VirtualBoxError as e:
+            self.send_custom_error(str(e))
+            return
+        self.send_response(True)
+
+    @IModule.route("virtualbox.suspend")
+    def vbox_suspend(self, request):
+        """
+        Suspends a VirtualBox VM instance.
+
+        Mandatory request parameters:
+        - id (VirtualBox VM instance identifier)
+
+        Response parameters:
+        - True on success
+
+        :param request: JSON request
+        """
+
+        # validate the request
+        if not self.validate_request(request, VBOX_SUSPEND_SCHEMA):
+            return
+
+        # get the instance
+        vbox_instance = self.get_vbox_instance(request["id"])
+        if not vbox_instance:
+            return
+
+        try:
+            vbox_instance.suspend()
         except VirtualBoxError as e:
             self.send_custom_error(str(e))
             return
@@ -404,8 +546,8 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_ALLOCATE_UDP_PORT_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_ALLOCATE_UDP_PORT_SCHEMA):
+            return
 
         # get the instance
         vbox_instance = self.get_vbox_instance(request["id"])
@@ -454,8 +596,8 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_ADD_NIO_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_ADD_NIO_SCHEMA):
+            return
 
         # get the instance
         vbox_instance = self.get_vbox_instance(request["id"])
@@ -506,8 +648,8 @@ class VirtualBox(IModule):
         """
 
         # validate the request
-        #if not self.validate_request(request, VBOX_DELETE_NIO_SCHEMA):
-        #    return
+        if not self.validate_request(request, VBOX_DELETE_NIO_SCHEMA):
+            return
 
         # get the instance
         vbox_instance = self.get_vbox_instance(request["id"])
@@ -525,6 +667,110 @@ class VirtualBox(IModule):
 
         self.send_response(True)
 
+    @IModule.route("virtualbox.start_capture")
+    def vbox_start_capture(self, request):
+        """
+        Starts a packet capture.
+
+        Mandatory request parameters:
+        - id (vm identifier)
+        - port (port number)
+        - port_id (port identifier)
+        - capture_file_name
+
+        Response parameters:
+        - port_id (port identifier)
+        - capture_file_path (path to the capture file)
+
+        :param request: JSON request
+        """
+
+        # validate the request
+        if not self.validate_request(request, VBOX_START_CAPTURE_SCHEMA):
+            return
+
+        # get the instance
+        vbox_instance = self.get_vbox_instance(request["id"])
+        if not vbox_instance:
+            return
+
+        port = request["port"]
+        capture_file_name = request["capture_file_name"]
+
+        try:
+            capture_file_path = os.path.join(self._working_dir, "captures", capture_file_name)
+            vbox_instance.start_capture(port, capture_file_path)
+        except VirtualBoxError as e:
+            self.send_custom_error(str(e))
+            return
+
+        response = {"port_id": request["port_id"],
+                    "capture_file_path": capture_file_path}
+        self.send_response(response)
+
+    @IModule.route("virtualbox.stop_capture")
+    def vbox_stop_capture(self, request):
+        """
+        Stops a packet capture.
+
+        Mandatory request parameters:
+        - id (vm identifier)
+        - port (port number)
+        - port_id (port identifier)
+
+        Response parameters:
+        - port_id (port identifier)
+
+        :param request: JSON request
+        """
+
+        # validate the request
+        if not self.validate_request(request, VBOX_STOP_CAPTURE_SCHEMA):
+            return
+
+        # get the instance
+        vbox_instance = self.get_vbox_instance(request["id"])
+        if not vbox_instance:
+            return
+
+        port = request["port"]
+        try:
+            vbox_instance.stop_capture(port)
+        except VirtualBoxError as e:
+            self.send_custom_error(str(e))
+            return
+
+        response = {"port_id": request["port_id"]}
+        self.send_response(response)
+
+    @IModule.route("virtualbox.vm_list")
+    def vm_list(self, request):
+        """
+        Gets VirtualBox VM list.
+
+        Response parameters:
+        - Server address/host
+        - List of VM names
+        """
+
+        if not self._vboxwrapper and not self._vboxmanager:
+            self._start_vbox_service()
+
+        if self._vboxwrapper:
+            vms = self._vboxwrapper.get_vm_list()
+        elif self._vboxmanager:
+            vms = []
+            machines = self._vboxmanager.getArray(self._vboxmanager.vbox, "machines")
+            for machine in range(len(machines)):
+                vms.append(machines[machine].name)
+        else:
+            self.send_custom_error("Vboxmanager hasn't been initialized!")
+            return
+
+        response = {"server": self._host,
+                    "vms": vms}
+        self.send_response(response)
+
     @IModule.route("virtualbox.echo")
     def echo(self, request):
         """
diff --git a/gns3server/modules/virtualbox/nios/nio.py b/gns3server/modules/virtualbox/nios/nio.py
new file mode 100644
index 00000000..c85569bd
--- /dev/null
+++ b/gns3server/modules/virtualbox/nios/nio.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Base interface for NIOs.
+"""
+
+
+class NIO(object):
+    """
+    IOU NIO.
+    """
+
+    def __init__(self):
+
+        self._capturing = False
+        self._pcap_output_file = ""
+
+    def startPacketCapture(self, pcap_output_file):
+        """
+
+        :param pcap_output_file: PCAP destination file for the capture
+        """
+
+        self._capturing = True
+        self._pcap_output_file = pcap_output_file
+
+    def stopPacketCapture(self):
+
+        self._capturing = False
+        self._pcap_output_file = ""
+
+    @property
+    def capturing(self):
+        """
+        Returns either a capture is configured on this NIO.
+
+        :returns: boolean
+        """
+
+        return self._capturing
+
+    @property
+    def pcap_output_file(self):
+        """
+        Returns the path to the PCAP output file.
+
+        :returns: path to the PCAP output file
+        """
+
+        return self._pcap_output_file
diff --git a/gns3server/modules/virtualbox/nios/nio_udp.py b/gns3server/modules/virtualbox/nios/nio_udp.py
index 3142d70e..41ffbc4f 100644
--- a/gns3server/modules/virtualbox/nios/nio_udp.py
+++ b/gns3server/modules/virtualbox/nios/nio_udp.py
@@ -19,8 +19,10 @@
 Interface for UDP NIOs.
 """
 
+from .nio import NIO
 
-class NIO_UDP(object):
+
+class NIO_UDP(NIO):
     """
     IOU UDP NIO.
 
@@ -33,6 +35,7 @@ class NIO_UDP(object):
 
     def __init__(self, lport, rhost, rport):
 
+        NIO.__init__(self)
         self._lport = lport
         self._rhost = rhost
         self._rport = rport
diff --git a/gns3server/modules/virtualbox/schemas.py b/gns3server/modules/virtualbox/schemas.py
new file mode 100644
index 00000000..b86839a0
--- /dev/null
+++ b/gns3server/modules/virtualbox/schemas.py
@@ -0,0 +1,418 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2014 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+VBOX_CREATE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to create a new VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "name": {
+            "description": "VirtualBox VM instance name",
+            "type": "string",
+            "minLength": 1,
+        },
+        "vmname": {
+            "description": "VirtualBox VM name (in VirtualBox itself)",
+            "type": "string",
+            "minLength": 1,
+        },
+        "vbox_id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+        "console": {
+            "description": "console TCP port",
+            "minimum": 1,
+            "maximum": 65535,
+            "type": "integer"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["name", "vmname"],
+}
+
+VBOX_DELETE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to delete a VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id"]
+}
+
+VBOX_UPDATE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to update a VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+        "name": {
+            "description": "VirtualBox VM instance name",
+            "type": "string",
+            "minLength": 1,
+        },
+        "vmname": {
+            "description": "VirtualBox VM name (in VirtualBox itself)",
+            "type": "string",
+            "minLength": 1,
+        },
+        "adapters": {
+            "description": "number of adapters",
+            "type": "integer",
+            "minimum": 0,
+            "maximum": 36,  # maximum given by the ICH9 chipset in VirtualBox
+        },
+        "adapter_type": {
+            "description": "VirtualBox adapter type",
+            "type": "string",
+            "minLength": 1,
+        },
+        "console": {
+            "description": "console TCP port",
+            "minimum": 1,
+            "maximum": 65535,
+            "type": "integer"
+        },
+        "headless": {
+            "description": "headless mode",
+            "type": "boolean"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id"]
+}
+
+VBOX_START_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to start a VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id"]
+}
+
+VBOX_STOP_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to stop a VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id"]
+}
+
+VBOX_SUSPEND_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to suspend a VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id"]
+}
+
+VBOX_RELOAD_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to reload a VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id"]
+}
+
+VBOX_ALLOCATE_UDP_PORT_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to allocate an UDP port for a VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+        "port_id": {
+            "description": "Unique port identifier for the VirtualBox VM instance",
+            "type": "integer"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id", "port_id"]
+}
+
+VBOX_ADD_NIO_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to add a NIO for a VirtualBox VM instance",
+    "type": "object",
+
+    "definitions": {
+        "UDP": {
+            "description": "UDP Network Input/Output",
+            "properties": {
+                "type": {
+                    "enum": ["nio_udp"]
+                },
+                "lport": {
+                    "description": "Local port",
+                    "type": "integer",
+                    "minimum": 1,
+                    "maximum": 65535
+                },
+                "rhost": {
+                    "description": "Remote host",
+                    "type": "string",
+                    "minLength": 1
+                },
+                "rport": {
+                    "description": "Remote port",
+                    "type": "integer",
+                    "minimum": 1,
+                    "maximum": 65535
+                }
+            },
+            "required": ["type", "lport", "rhost", "rport"],
+            "additionalProperties": False
+        },
+        "Ethernet": {
+            "description": "Generic Ethernet Network Input/Output",
+            "properties": {
+                "type": {
+                    "enum": ["nio_generic_ethernet"]
+                },
+                "ethernet_device": {
+                    "description": "Ethernet device name e.g. eth0",
+                    "type": "string",
+                    "minLength": 1
+                },
+            },
+            "required": ["type", "ethernet_device"],
+            "additionalProperties": False
+        },
+        "LinuxEthernet": {
+            "description": "Linux Ethernet Network Input/Output",
+            "properties": {
+                "type": {
+                    "enum": ["nio_linux_ethernet"]
+                },
+                "ethernet_device": {
+                    "description": "Ethernet device name e.g. eth0",
+                    "type": "string",
+                    "minLength": 1
+                },
+            },
+            "required": ["type", "ethernet_device"],
+            "additionalProperties": False
+        },
+        "TAP": {
+            "description": "TAP Network Input/Output",
+            "properties": {
+                "type": {
+                    "enum": ["nio_tap"]
+                },
+                "tap_device": {
+                    "description": "TAP device name e.g. tap0",
+                    "type": "string",
+                    "minLength": 1
+                },
+            },
+            "required": ["type", "tap_device"],
+            "additionalProperties": False
+        },
+        "UNIX": {
+            "description": "UNIX Network Input/Output",
+            "properties": {
+                "type": {
+                    "enum": ["nio_unix"]
+                },
+                "local_file": {
+                    "description": "path to the UNIX socket file (local)",
+                    "type": "string",
+                    "minLength": 1
+                },
+                "remote_file": {
+                    "description": "path to the UNIX socket file (remote)",
+                    "type": "string",
+                    "minLength": 1
+                },
+            },
+            "required": ["type", "local_file", "remote_file"],
+            "additionalProperties": False
+        },
+        "VDE": {
+            "description": "VDE Network Input/Output",
+            "properties": {
+                "type": {
+                    "enum": ["nio_vde"]
+                },
+                "control_file": {
+                    "description": "path to the VDE control file",
+                    "type": "string",
+                    "minLength": 1
+                },
+                "local_file": {
+                    "description": "path to the VDE control file",
+                    "type": "string",
+                    "minLength": 1
+                },
+            },
+            "required": ["type", "control_file", "local_file"],
+            "additionalProperties": False
+        },
+        "NULL": {
+            "description": "NULL Network Input/Output",
+            "properties": {
+                "type": {
+                    "enum": ["nio_null"]
+                },
+            },
+            "required": ["type"],
+            "additionalProperties": False
+        },
+    },
+
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+        "port_id": {
+            "description": "Unique port identifier for the VirtualBox VM instance",
+            "type": "integer"
+        },
+        "port": {
+            "description": "Port number",
+            "type": "integer",
+            "minimum": 0,
+            "maximum": 36  # maximum given by the ICH9 chipset in VirtualBox
+        },
+        "nio": {
+            "type": "object",
+            "description": "Network Input/Output",
+            "oneOf": [
+                {"$ref": "#/definitions/UDP"},
+                {"$ref": "#/definitions/Ethernet"},
+                {"$ref": "#/definitions/LinuxEthernet"},
+                {"$ref": "#/definitions/TAP"},
+                {"$ref": "#/definitions/UNIX"},
+                {"$ref": "#/definitions/VDE"},
+                {"$ref": "#/definitions/NULL"},
+            ]
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id", "port_id", "port", "nio"]
+}
+
+
+VBOX_DELETE_NIO_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to delete a NIO for a VirtualBox VM instance",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+        "port": {
+            "description": "Port number",
+            "type": "integer",
+            "minimum": 0,
+            "maximum": 36  # maximum given by the ICH9 chipset in VirtualBox
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id", "port"]
+}
+
+VBOX_START_CAPTURE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to start a packet capture on a VirtualBox VM instance port",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+        "port": {
+            "description": "Port number",
+            "type": "integer",
+            "minimum": 0,
+            "maximum": 36  # maximum given by the ICH9 chipset in VirtualBox
+        },
+        "port_id": {
+            "description": "Unique port identifier for the VirtualBox VM instance",
+            "type": "integer"
+        },
+        "capture_file_name": {
+            "description": "Capture file name",
+            "type": "string",
+            "minLength": 1,
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id", "port", "port_id", "capture_file_name"]
+}
+
+VBOX_STOP_CAPTURE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Request validation to stop a packet capture on a VirtualBox VM instance port",
+    "type": "object",
+    "properties": {
+        "id": {
+            "description": "VirtualBox VM instance ID",
+            "type": "integer"
+        },
+        "port": {
+            "description": "Port number",
+            "type": "integer",
+            "minimum": 0,
+            "maximum": 36  # maximum given by the ICH9 chipset in VirtualBox
+        },
+        "port_id": {
+            "description": "Unique port identifier for the VirtualBox VM instance",
+            "type": "integer"
+        },
+    },
+    "additionalProperties": False,
+    "required": ["id", "port", "port_id"]
+}
+
diff --git a/gns3server/modules/virtualbox/vboxapi_py3 b/gns3server/modules/virtualbox/vboxapi_py3
new file mode 160000
index 00000000..ed33ed34
--- /dev/null
+++ b/gns3server/modules/virtualbox/vboxapi_py3
@@ -0,0 +1 @@
+Subproject commit ed33ed344d5687302979972f518a4cee17517d28
diff --git a/gns3server/modules/virtualbox/vboxwrapper_client.py b/gns3server/modules/virtualbox/vboxwrapper_client.py
index d1d5d0ed..43a1743d 100644
--- a/gns3server/modules/virtualbox/vboxwrapper_client.py
+++ b/gns3server/modules/virtualbox/vboxwrapper_client.py
@@ -26,6 +26,7 @@ import tempfile
 import socket
 import re
 
+from ..attic import wait_socket_is_ready
 from .virtualbox_error import VirtualBoxError
 
 import logging
@@ -51,6 +52,7 @@ class VboxWrapperClient(object):
         self._path = path
         self._command = []
         self._process = None
+        self._working_dir = working_dir
         self._stdout_file = ""
         self._started = False
         self._host = host
@@ -144,21 +146,47 @@ class VboxWrapperClient(object):
                                                  stderr=subprocess.STDOUT,
                                                  cwd=self._working_dir)
             log.info("VirtualBox wrapper started PID={}".format(self._process.pid))
+            self.wait_for_vboxwrapper(self._host, self._port)
+            self.connect()
             self._started = True
         except OSError as e:
             log.error("could not start VirtualBox wrapper: {}".format(e))
             raise VirtualBoxError("could not start VirtualBox wrapper: {}".format(e))
 
+    def wait_for_vboxwrapper(self, host, port):
+        """
+        Waits for vboxwrapper to be started (accepting a socket connection)
+
+        :param host: host/address to connect to the vboxwrapper
+        :param port: port to connect to the vboxwrapper
+        """
+
+        begin = time.time()
+        # wait for the socket for a maximum of 10 seconds.
+        connection_success, last_exception = wait_socket_is_ready(host, port, wait=10.0)
+
+        if not connection_success:
+            raise VirtualBoxError("Couldn't connect to vboxwrapper on {}:{} :{}".format(host, port,
+                                                                                 last_exception))
+        else:
+            log.info("vboxwrapper server ready after {:.4f} seconds".format(time.time() - begin))
+
     def stop(self):
         """
         Stops the VirtualBox wrapper process.
         """
 
+        if self.connected():
+            try:
+                self.send("vboxwrapper stop")
+            except VirtualBoxError:
+                pass
+            if self._socket:
+                self._socket.shutdown(socket.SHUT_RDWR)
+                self._socket.close()
+                self._socket = None
+
         if self.is_running():
-            self.send("hypervisor stop")
-            self._socket.shutdown(socket.SHUT_RDWR)
-            self._socket.close()
-            self._socket = None
             log.info("stopping VirtualBox wrapper PID={}".format(self._process.pid))
             try:
                 # give some time for the VirtualBox wrapper to properly stop.
@@ -210,10 +238,10 @@ class VboxWrapperClient(object):
         """
 
         command = [self._path]
-        #if self._host != "0.0.0.0" and self._host != "::":
-        #    command.extend(["-H", "{}:{}".format(self._host, self._port)])
-        #else:
-        #    command.extend(["-H", str(self._port)])
+        if self._host != "0.0.0.0" and self._host != "::":
+            command.extend(["-l", self._host, "-p", str(self._port)])
+        else:
+            command.extend(["-p", str(self._port)])
         return command
 
     def connect(self):
@@ -235,6 +263,17 @@ class VboxWrapperClient(object):
         except OSError as e:
             raise VirtualBoxError("Could not connect to the VirtualBox wrapper: {}".format(e))
 
+    def connected(self):
+        """
+        Returns either the client is connected to vboxwrapper or not.
+
+        :return: boolean
+        """
+
+        if self._socket:
+            return True
+        return False
+
     def reset(self):
         """
         Resets the VirtualBox wrapper (used to get an empty configuration).
@@ -276,6 +315,15 @@ class VboxWrapperClient(object):
         assert self._socket
         return self._socket
 
+    def get_vm_list(self):
+        """
+        Returns the list of all VirtualBox VMs.
+
+        :returns: list of VM names
+        """
+
+        return self.send('vbox vm_list')
+
     def send(self, command):
         """
         Sends commands to the VirtualBox wrapper.
@@ -306,6 +354,7 @@ class VboxWrapperClient(object):
             log.debug("sending {}".format(command))
             self.socket.sendall(command.encode('utf-8'))
         except OSError as e:
+            self._socket = None
             raise VirtualBoxError("Lost communication with {host}:{port} :{error}"
                                 .format(host=self._host, port=self._port, error=e))
 
@@ -317,14 +366,17 @@ class VboxWrapperClient(object):
                 chunk = self.socket.recv(1024)
                 buf += chunk.decode("utf-8")
             except OSError as e:
+                self._socket = None
                 raise VirtualBoxError("Communication timed out with {host}:{port} :{error}"
                                       .format(host=self._host, port=self._port, error=e))
 
+
             # If the buffer doesn't end in '\n' then we can't be done
             try:
                 if buf[-1] != '\n':
                     continue
             except IndexError:
+                self._socket = None
                 raise VirtualBoxError("Could not communicate with {host}:{port}"
                                       .format(host=self._host, port=self._port))
 
diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py
index aedc5d8a..76a6532a 100644
--- a/gns3server/modules/virtualbox/virtualbox_vm.py
+++ b/gns3server/modules/virtualbox/virtualbox_vm.py
@@ -19,24 +19,28 @@
 VirtualBox VM instance.
 """
 
+import sys
 import os
 import shutil
+import tempfile
+import re
+import time
 
-from pkg_resources import parse_version
 from .virtualbox_error import VirtualBoxError
 from .adapters.ethernet_adapter import EthernetAdapter
-from .nios.nio_udp import NIO_UDP
 from ..attic import find_unused_port
 
 import logging
 log = logging.getLogger(__name__)
 
-
 class VirtualBoxVM(object):
     """
     VirtualBox VM implementation.
 
+    :param vboxwrapper client: VboxWrapperClient instance
+    :param vboxmanager: VirtualBox manager from the VirtualBox API
     :param name: name of this VirtualBox VM
+    :param vmname: name of this VirtualBox VM in VirtualBox itself
     :param working_dir: path to a working directory
     :param host: host/address to bind for console and UDP connections
     :param vbox_id: VirtalBox VM instance ID
@@ -49,8 +53,10 @@ class VirtualBoxVM(object):
     _allocated_console_ports = []
 
     def __init__(self,
+                 vboxwrapper,
+                 vboxmanager,
                  name,
-                 path,
+                 vmname,
                  working_dir,
                  host="127.0.0.1",
                  vbox_id=None,
@@ -75,19 +81,28 @@ class VirtualBoxVM(object):
             self._instances.append(self._id)
 
         self._name = name
-        self._console = console
         self._working_dir = None
         self._host = host
         self._command = []
-        self._vboxwrapper_process = None
-        self._vboxwrapper_stdout_file = ""
+        self._vboxwrapper = vboxwrapper
+
         self._host = "127.0.0.1"
         self._started = False
         self._console_start_port_range = console_start_port_range
         self._console_end_port_range = console_end_port_range
 
+        # VirtualBox API variables
+        self._machine = None
+        self._session = None
+        self._vboxmanager = vboxmanager
+        self._maximum_adapters = 0
+
         # VirtualBox settings
-        self._ethernet_adapter = EthernetAdapter()  # one adapter with 1 Ethernet interface
+        self._console = console
+        self._ethernet_adapters = []
+        self._headless = False
+        self._vmname = vmname
+        self._adapter_type = "Automatic"
 
         working_dir_path = os.path.join(working_dir, "vbox", "vm-{}".format(self._id))
 
@@ -111,6 +126,23 @@ class VirtualBoxVM(object):
             raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(console))
         self._allocated_console_ports.append(self._console)
 
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox create vbox "{}"'.format(self._name))
+            self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
+            self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console))
+            self._vboxwrapper.send('vbox setattr "{}" console_support True'.format(self._name))
+            self._vboxwrapper.send('vbox setattr "{}" console_telnet_server True'.format(self._name))
+        else:
+            try:
+                self._machine = self._vboxmanager.vbox.findMachine(self._vmname)
+            except Exception as e:
+                raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+            # The maximum support network cards depends on the Chipset (PIIX3 or ICH9)
+            self._maximum_adapters = self._vboxmanager.vbox.systemProperties.getMaxNetworkAdapters(self._machine.chipsetType)
+
+        self.adapters = 2
+
         log.info("VirtualBox VM {name} [id={id}] has been created".format(name=self._name,
                                                                           id=self._id))
 
@@ -122,7 +154,11 @@ class VirtualBoxVM(object):
         """
 
         vbox_defaults = {"name": self._name,
-                         "console": self._console}
+                         "vmname": self._vmname,
+                         "adapters": len(self._ethernet_adapters),
+                         "adapter_type": "Automatic",
+                         "console": self._console,
+                         "headless": self._headless}
 
         return vbox_defaults
 
@@ -166,6 +202,9 @@ class VirtualBoxVM(object):
         log.info("VirtualBox VM {name} [id={id}]: renamed to {new_name}".format(name=self._name,
                                                                                 id=self._id,
                                                                                 new_name=new_name))
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox rename "{}" "{}"'.format(self._name, new_name))
         self._name = new_name
 
     @property
@@ -222,6 +261,10 @@ class VirtualBoxVM(object):
         self._allocated_console_ports.remove(self._console)
         self._console = console
         self._allocated_console_ports.append(self._console)
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console))
+
         log.info("VirtualBox VM {name} [id={id}]: console port set to {port}".format(name=self._name,
                                                                                      id=self._id,
                                                                                      port=console))
@@ -238,6 +281,9 @@ class VirtualBoxVM(object):
         if self.console and self.console in self._allocated_console_ports:
             self._allocated_console_ports.remove(self.console)
 
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox delete "{}"'.format(self._name))
+
         log.info("VirtualBox VM {name} [id={id}] has been deleted".format(name=self._name,
                                                                           id=self._id))
 
@@ -253,6 +299,9 @@ class VirtualBoxVM(object):
         if self.console:
             self._allocated_console_ports.remove(self.console)
 
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox delete "{}"'.format(self._name))
+
         try:
             shutil.rmtree(self._working_dir)
         except OSError as e:
@@ -264,55 +313,618 @@ class VirtualBoxVM(object):
         log.info("VirtualBox VM {name} [id={id}] has been deleted (including associated files)".format(name=self._name,
                                                                                                        id=self._id))
 
+    @property
+    def headless(self):
+        """
+        Returns either the VM will start in headless mode
+
+        :returns: boolean
+        """
+
+        return self._headless
+
+    @headless.setter
+    def headless(self, headless):
+        """
+        Sets either the VM will start in headless mode
+
+        :param headless: boolean
+        """
+
+        if headless:
+            if self._vboxwrapper:
+                self._vboxwrapper.send('vbox setattr "{}" headless_mode True'.format(self._name))
+            log.info("VirtualBox VM {name} [id={id}] has enabled the headless mode".format(name=self._name, id=self._id))
+        else:
+            if self._vboxwrapper:
+                self._vboxwrapper.send('vbox setattr "{}" headless_mode False'.format(self._name))
+            log.info("VirtualBox VM {name} [id={id}] has disabled the headless mode".format(name=self._name, id=self._id))
+        self._headless = headless
+
+    @property
+    def vmname(self):
+        """
+        Returns the VM name associated with this VirtualBox VM.
+
+        :returns: VirtualBox VM name
+        """
+
+        return self._vmname
+
+    @vmname.setter
+    def vmname(self, vmname):
+        """
+        Sets the VM name associated with this VirtualBox VM.
+
+        :param vmname: VirtualBox VM name
+        """
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
+        else:
+            try:
+                self._machine = self._vboxmanager.vbox.findMachine(vmname)
+            except Exception as e:
+                raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+            # The maximum support network cards depends on the Chipset (PIIX3 or ICH9)
+            self._maximum_adapters = self._vboxmanager.vbox.systemProperties.getMaxNetworkAdapters(self._machine.chipsetType)
+
+        log.info("VirtualBox VM {name} [id={id}] has set the VM name to {vmname}".format(name=self._name, id=self._id, vmname=vmname))
+        self._vmname = vmname
+
+    @property
+    def adapters(self):
+        """
+        Returns the number of Ethernet adapters for this VirtualBox VM instance.
+
+        :returns: number of adapters
+        """
+
+        return len(self._ethernet_adapters)
+
+    @adapters.setter
+    def adapters(self, adapters):
+        """
+        Sets the number of Ethernet adapters for this VirtualBox VM instance.
+
+        :param adapters: number of adapters
+        """
+
+        self._ethernet_adapters.clear()
+        for _ in range(0, adapters):
+            self._ethernet_adapters.append(EthernetAdapter())
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox setattr "{}" nics {}'.format(self._name, len(self._ethernet_adapters)))
+
+        log.info("VirtualBox VM {name} [id={id}]: number of Ethernet adapters changed to {adapters}".format(name=self._name,
+                                                                                                            id=self._id,
+                                                                                                            adapters=len(self._ethernet_adapters)))
+
+    @property
+    def adapter_type(self):
+        """
+        Returns the adapter type for this VirtualBox VM instance.
+
+        :returns: adapter type (string)
+        """
+
+        return self._adapter_type
+
+    @adapter_type.setter
+    def adapter_type(self, adapter_type):
+        """
+        Sets the adapter type for this VirtualBox VM instance.
+
+        :param adapter_type: adapter type (string)
+        """
+
+        self._adapter_type = adapter_type
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox setattr "{}" netcard "{}"'.format(self._name, adapter_type))
+
+        log.info("VirtualBox VM {name} [id={id}]: adapter type changed to {adapter_type}".format(name=self._name,
+                                                                                                 id=self._id,
+                                                                                                 adapter_type=adapter_type))
+
     def start(self):
         """
         Starts this VirtualBox VM.
         """
 
-        pass
+        if self._vboxwrapper:
+            status = int(self._vboxwrapper.send('vbox status "{}"'.format(self._name))[0])
+            if status == 6:  # paused
+                self.resume()
+                return
+            self._vboxwrapper.send('vbox start "{}"'.format(self._name))
+        else:
+
+            if self._machine.state == self._vboxmanager.constants.MachineState_Paused:
+                self.resume()
+                return
+
+            self._get_session()
+            self._set_network_options()
+            self._set_console_options()
+
+            progress = self._launch_vm_process()
+            log.info("VM is starting with {}% completed".format(progress.percent))
+            if progress.percent != 100:
+                # This will happen if you attempt to start VirtualBox with unloaded "vboxdrv" module.
+                # or have too little RAM or damaged vHDD, or connected to non-existent network.
+                # We must unlock machine, otherwise it locks the VirtualBox Manager GUI. (on Linux hosts)
+                self._unlock_machine()
+                raise VirtualBoxError("Unable to start the VM (failed at {}%)".format(progress.percent))
+
+            try:
+                self._machine.setGuestPropertyValue("NameInGNS3", self._name)
+            except Exception:
+                pass
 
     def stop(self):
         """
         Stops this VirtualBox VM.
         """
 
-        pass
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox stop "{}"'.format(self._name))
+        else:
+            try:
+                progress = self._session.console.powerDown()
+                # wait for VM to actually go down
+                progress.waitForCompletion(-1)
+                log.info("VM is stopping with {}% completed".format(self.vmname, progress.percent))
 
-#     def port_add_nio_binding(self, port_id, nio):
-#         """
-#         Adds a port NIO binding.
-# 
-#         :param port_id: port ID
-#         :param nio: NIO instance to add to the slot/port
-#         """
-# 
-#         if not self._ethernet_adapter.port_exists(port_id):
-#             raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
-#                                                                                        port_id=port_id))
-# 
-#         self._ethernet_adapter.add_nio(port_id, nio)
-#         log.info("VPCS {name} [id={id}]: {nio} added to port {port_id}".format(name=self._name,
-#                                                                                id=self._id,
-#                                                                                nio=nio,
-#                                                                                port_id=port_id))
+                self._lock_machine()
+                for adapter_id in range(0, len(self._ethernet_adapters)):
+                    self._disable_adapter(adapter_id, disable=True)
+                self._session.machine.saveSettings()
+                self._unlock_machine()
+            except Exception as e:
+                # Do not crash "vboxwrapper", if stopping VM fails.
+                # But return True anyway, so VM state in GNS3 can become "stopped"
+                # This can happen, if user manually kills VBox VM.
+                log.warn("could not stop VM for {}: {}".format(self._vmname, e))
+                return
 
-#     def port_remove_nio_binding(self, port_id):
-#         """
-#         Removes a port NIO binding.
-# 
-#         :param port_id: port ID
-# 
-#         :returns: NIO instance
-#         """
-# 
-#         if not self._ethernet_adapter.port_exists(port_id):
-#             raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
-#                                                                                        port_id=port_id))
-# 
-#         nio = self._ethernet_adapter.get_nio(port_id)
-#         self._ethernet_adapter.remove_nio(port_id)
-#         log.info("VPCS {name} [id={id}]: {nio} removed from port {port_id}".format(name=self._name,
-#                                                                                    id=self._id,
-#                                                                                    nio=nio,
-#                                                                                    port_id=port_id))
-#        return nio
+    def suspend(self):
+        """
+        Suspends this VirtualBox VM.
+        """
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox suspend "{}"'.format(self._name))
+        else:
+            try:
+                self._session.console.pause()
+            except Exception as e:
+                raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+    def reload(self):
+        """
+        Reloads this VirtualBox VM.
+        """
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox reset "{}"'.format(self._name))
+        else:
+            try:
+                progress = self._session.console.reset()
+                progress.waitForCompletion(-1)
+            except Exception as e:
+                raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+    def resume(self):
+        """
+        Resumes this VirtualBox VM.
+        """
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox resume "{}"'.format(self._name))
+        else:
+            try:
+                self._session.console.resume()
+            except Exception as e:
+                raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+    def port_add_nio_binding(self, adapter_id, nio):
+        """
+        Adds a port NIO binding.
+
+        :param adapter_id: adapter ID
+        :param nio: NIO instance to add to the slot/port
+        """
+
+        try:
+            adapter = self._ethernet_adapters[adapter_id]
+        except IndexError:
+            raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
+                                                                                                      adapter_id=adapter_id))
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox create_udp "{}" {} {} {} {}'.format(self._name,
+                                                                            adapter_id,
+                                                                            nio.lport,
+                                                                            nio.rhost,
+                                                                            nio.rport))
+        else:
+            self._create_udp(adapter_id, nio.lport, nio.rhost, nio.rport)
+
+        adapter.add_nio(0, nio)
+        log.info("VirtualBox VM {name} [id={id}]: {nio} added to adapter {adapter_id}".format(name=self._name,
+                                                                                              id=self._id,
+                                                                                              nio=nio,
+                                                                                              adapter_id=adapter_id))
+
+    def port_remove_nio_binding(self, adapter_id):
+        """
+        Removes a port NIO binding.
+
+        :param adapter_id: adapter ID
+
+        :returns: NIO instance
+        """
+
+        try:
+            adapter = self._ethernet_adapters[adapter_id]
+        except IndexError:
+            raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
+                                                                                                      adapter_id=adapter_id))
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox delete_udp "{}" {}'.format(self._name,
+                                                                    adapter_id))
+        else:
+            self._delete_udp(adapter_id)
+
+        nio = adapter.get_nio(0)
+        adapter.remove_nio(0)
+        log.info("VirtualBox VM {name} [id={id}]: {nio} removed from adapter {adapter_id}".format(name=self._name,
+                                                                                                  id=self._id,
+                                                                                                  nio=nio,
+                                                                                                  adapter_id=adapter_id))
+        return nio
+
+    def start_capture(self, adapter_id, output_file):
+        """
+        Starts a packet capture.
+
+        :param adapter_id: adapter ID
+        :param output_file: PCAP destination file for the capture
+        """
+
+        try:
+            adapter = self._ethernet_adapters[adapter_id]
+        except IndexError:
+            raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
+                                                                                                      adapter_id=adapter_id))
+
+        nio = adapter.get_nio(0)
+        if nio.capturing:
+            raise VirtualBoxError("Packet capture is already activated on adapter {adapter_id}".format(adapter_id=adapter_id))
+
+        try:
+            os.makedirs(os.path.dirname(output_file))
+        except FileExistsError:
+            pass
+        except OSError as e:
+            raise VirtualBoxError("Could not create captures directory {}".format(e))
+
+        nio.startPacketCapture(output_file)
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox create_capture "{}" {} "{}"'.format(self._name,
+                                                                            adapter_id,
+                                                                            output_file))
+
+        log.info("VirtualBox VM {name} [id={id}]: starting packet capture on adapter {adapter_id}".format(name=self._name,
+                                                                                                          id=self._id,
+                                                                                                          adapter_id=adapter_id))
+
+    def stop_capture(self, adapter_id):
+        """
+        Stops a packet capture.
+
+        :param adapter_id: adapter ID
+        """
+
+        try:
+            adapter = self._ethernet_adapters[adapter_id]
+        except IndexError:
+            raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
+                                                                                                      adapter_id=adapter_id))
+
+        nio = adapter.get_nio(0)
+        nio.stopPacketCapture()
+
+        if self._vboxwrapper:
+            self._vboxwrapper.send('vbox delete_capture "{}" {}'.format(self._name,
+                                                                        adapter_id))
+
+        log.info("VirtualBox VM {name} [id={id}]: stopping packet capture on adapter {adapter_id}".format(name=self._name,
+                                                                                                          id=self._id,
+                                                                                                          adapter_id=adapter_id))
+
+    def _get_session(self):
+
+        log.debug("getting session for {}".format(self._vmname))
+        try:
+            self._session = self._vboxmanager.mgr.getSessionObject(self._vboxmanager.vbox)
+        except Exception as e:
+            # fails on heavily loaded hosts...
+            raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+    def _set_network_options(self):
+
+        log.debug("setting network options for {}".format(self._vmname))
+
+        self._lock_machine()
+
+        first_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
+        try:
+            first_adapter = self._session.machine.getNetworkAdapter(0)
+            first_adapter_type = first_adapter.adapterType
+        except Exception as e:
+            pass
+            #raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+        for adapter_id in range(0, len(self._ethernet_adapters)):
+            try:
+                # VirtualBox starts counting from 0
+                adapter = self._session.machine.getNetworkAdapter(adapter_id)
+                adapter_type = adapter.adapterType
+
+                if self._adapter_type == "PCnet-PCI II (Am79C970A)":
+                    adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C970A
+                if self._adapter_type == "PCNet-FAST III (Am79C973)":
+                    adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C973
+                if self._adapter_type == "Intel PRO/1000 MT Desktop (82540EM)":
+                    adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
+                if self._adapter_type == "Intel PRO/1000 T Server (82543GC)":
+                    adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82543GC
+                if self._adapter_type == "Intel PRO/1000 MT Server (82545EM)":
+                    adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82545EM
+                if self._adapter_type == "Paravirtualized Network (virtio-net)":
+                    adapter_type = self._vboxmanager.constants.NetworkAdapterType_Virtio
+                if self._adapter_type == "Automatic":  # "Auto-guess, based on first NIC"
+                    adapter_type = first_adapter_type
+
+                adapter.adapterType = adapter_type
+
+            except Exception as e:
+                raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+            nio = self._ethernet_adapters[adapter_id].get_nio(0)
+            if nio:
+                log.debug("setting UDP params on adapter {}".format(adapter_id))
+                try:
+                    adapter.enabled = True
+                    adapter.cableConnected = True
+                    adapter.traceEnabled = False
+                    # Temporary hack around VBox-UDP patch limitation: inability to use DNS
+                    if nio.rhost == 'localhost':
+                        rhost = '127.0.0.1'
+                    else:
+                        rhost = nio.rhost
+                    adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Generic
+                    adapter.genericDriver = "UDPTunnel"
+                    adapter.setProperty("sport", str(nio.lport))
+                    adapter.setProperty("dest", rhost)
+                    adapter.setProperty("dport", str(nio.rport))
+                except Exception as e:
+                    # usually due to COM Error: "The object is not ready"
+                    raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+                if nio.capturing:
+                    self._enable_capture(adapter, nio.pcap_output_file)
+
+            else:
+                # shutting down unused adapters...
+                try:
+                    adapter.enabled = True
+                    adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
+                    adapter.cableConnected = False
+                except Exception as e:
+                    raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+        #for adapter_id in range(len(self._ethernet_adapters), self._maximum_adapters):
+        #    log.debug("disabling remaining adapter {}".format(adapter_id))
+        #    self._disable_adapter(adapter_id)
+
+        try:
+            self._session.machine.saveSettings()
+        except Exception as e:
+            raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+        self._unlock_machine()
+
+    def _disable_adapter(self, adapter_id, disable=True):
+
+        log.debug("disabling network adapter for {}".format(self._vmname))
+        # this command is retried several times, because it fails more often...
+        retries = 6
+        last_exception = None
+        for retry in range(retries):
+            if retry == (retries - 1):
+                raise VirtualBoxError("Could not disable network adapter after 4 retries: {}".format(last_exception))
+            try:
+                adapter = self._session.machine.getNetworkAdapter(adapter_id)
+                adapter.traceEnabled = False
+                adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
+                if disable:
+                    adapter.enabled = False
+                break
+            except Exception as e:
+                # usually due to COM Error: "The object is not ready"
+                log.warn("cannot disable network adapter for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
+                last_exception = e
+                time.sleep(1)
+                continue
+
+    def _enable_capture(self, adapter, output_file):
+
+        log.debug("enabling capture for {}".format(self._vmname))
+        # this command is retried several times, because it fails more often...
+        retries = 4
+        last_exception = None
+        for retry in range(retries):
+            if retry == (retries - 1):
+                raise VirtualBoxError("Could not enable packet capture after 4 retries: {}".format(last_exception))
+            try:
+                adapter.traceEnabled = True
+                adapter.traceFile = output_file
+                break
+            except Exception as e:
+                log.warn("cannot enable packet capture for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
+                last_exception = e
+                time.sleep(0.75)
+                continue
+
+    def _create_udp(self, adapter_id, sport, daddr, dport):
+
+        if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
+                self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
+            # the machine is being executed
+            retries = 4
+            last_exception = None
+            for retry in range(retries):
+                if retry == (retries - 1):
+                    raise VirtualBoxError("Could not create an UDP tunnel after 4 retries :{}".format(last_exception))
+                try:
+                    adapter = self._session.machine.getNetworkAdapter(adapter_id)
+                    adapter.cableConnected = True
+                    adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
+                    self._session.machine.saveSettings()
+                    adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Generic
+                    adapter.genericDriver = "UDPTunnel"
+                    adapter.setProperty("sport", str(sport))
+                    adapter.setProperty("dest", daddr)
+                    adapter.setProperty("dport", str(dport))
+                    self._session.machine.saveSettings()
+                    break
+                except Exception as e:
+                    # usually due to COM Error: "The object is not ready"
+                    log.warn("cannot create UDP tunnel for {}: {}".format(self._vmname, e))
+                    last_exception = e
+                    time.sleep(0.75)
+                    continue
+
+    def _delete_udp(self, adapter_id):
+
+        if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
+                self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
+            # the machine is being executed
+            retries = 4
+            last_exception = None
+            for retry in range(retries):
+                if retry == (retries - 1):
+                    raise VirtualBoxError("Could not delete an UDP tunnel after 4 retries :{}".format(last_exception))
+                try:
+                    adapter = self._session.machine.getNetworkAdapter(adapter_id)
+                    adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
+                    adapter.cableConnected = False
+                    self._session.machine.saveSettings()
+                    break
+                except Exception as e:
+                    # usually due to COM Error: "The object is not ready"
+                    log.debug("cannot delete UDP tunnel for {}: {}".format(self._vmname, e))
+                    last_exception = e
+                    time.sleep(0.75)
+                    continue
+
+    def _set_console_options(self):
+
+        log.info("setting console options for {}".format(self.vmname))
+
+        self._lock_machine()
+
+        # pick a pipe name
+        p = re.compile('\s+', re.UNICODE)
+        pipe_name = p.sub("_", self._vmname)
+        if sys.platform.startswith('win'):
+            pipe_name = r"\\.\pipe\VBOX\{}".format(pipe_name)
+        else:
+            pipe_name = os.path.join(tempfile.gettempdir(), "pipe_{}".format(pipe_name))
+
+        try:
+            serial_port = self._session.machine.getSerialPort(0)
+            serial_port.enabled = True
+            serial_port.path = pipe_name
+            serial_port.hostMode = 1
+            serial_port.server = True
+            self._session.machine.saveSettings()
+        except Exception as e:
+            raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+        self._unlock_machine()
+
+    def _launch_vm_process(self):
+
+        log.debug("launching VM {}".format(self._vmname))
+        # this command is retried several times, because it fails more often...
+        retries = 4
+        last_exception = None
+        for retry in range(retries):
+            if retry == (retries - 1):
+                raise VirtualBoxError("Could not launch the VM after 4 retries: {}".format(last_exception))
+            try:
+                if self._headless:
+                    mode = "headless"
+                else:
+                    mode = "gui"
+                log.info("starting {} in {} mode".format(self._vmname, mode))
+                progress = self._machine.launchVMProcess(self._session, mode, "")
+                break
+            except Exception as e:
+                # This will usually happen if you try to start the same VM twice,
+                # but may happen on loaded hosts too...
+                log.warn("cannot launch VM {}, retrying {}: {}".format(self._vmname, retry + 1, e))
+                last_exception = e
+                time.sleep(0.6)
+                continue
+
+        try:
+            progress.waitForCompletion(-1)
+        except Exception as e:
+            raise VirtualBoxError("VirtualBox error: {}".format(e))
+
+        return progress
+
+    def _lock_machine(self):
+
+        log.debug("locking machine for {}".format(self._vmname))
+        # this command is retried several times, because it fails more often...
+        retries = 4
+        last_exception = None
+        for retry in range(retries):
+            if retry == (retries - 1):
+                raise VirtualBoxError("Could not lock the machine after 4 retries: {}".format(last_exception))
+            try:
+                self._machine.lockMachine(self._session, 1)
+                break
+            except Exception as e:
+                log.warn("cannot lock the machine for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
+                last_exception = e
+                time.sleep(1)
+                continue
+
+    def _unlock_machine(self):
+
+        log.debug("unlocking machine for {}".format(self._vmname))
+        # this command is retried several times, because it fails more often...
+        retries = 4
+        last_exception = None
+        for retry in range(retries):
+            if retry == (retries - 1):
+                raise VirtualBoxError("Could not unlock the machine after 4 retries: {}".format(last_exception))
+            try:
+                self._session.unlockMachine()
+                break
+            except Exception as e:
+                log.warn("cannot unlock the machine for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
+                time.sleep(1)
+                last_exception = e
+                continue
diff --git a/gns3server/server.py b/gns3server/server.py
index ded769b6..d4869e53 100644
--- a/gns3server/server.py
+++ b/gns3server/server.py
@@ -173,8 +173,12 @@ class Server(object):
         tornado.autoreload.add_reload_hook(self._reload_callback)
 
         def signal_handler(signum=None, frame=None):
-            log.warning("Server got signal {}, exiting...".format(signum))
-            self._cleanup(signum)
+            try:
+                log.warning("Server got signal {}, exiting...".format(signum))
+                self._cleanup(signum)
+            except RuntimeError:
+                # to ignore logging exception: RuntimeError: reentrant call inside <_io.BufferedWriter name='<stderr>'>
+                pass
 
         signals = [signal.SIGTERM, signal.SIGINT]
         if not sys.platform.startswith("win"):