diff --git a/gns3server/handlers/virtualbox_handler.py b/gns3server/handlers/virtualbox_handler.py
index 6049ec3e..fef071bb 100644
--- a/gns3server/handlers/virtualbox_handler.py
+++ b/gns3server/handlers/virtualbox_handler.py
@@ -15,10 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import os
from ..web.route import Route
from ..schemas.virtualbox import VBOX_CREATE_SCHEMA
from ..schemas.virtualbox import VBOX_UPDATE_SCHEMA
from ..schemas.virtualbox import VBOX_NIO_SCHEMA
+from ..schemas.virtualbox import VBOX_CAPTURE_SCHEMA
from ..schemas.virtualbox import VBOX_OBJECT_SCHEMA
from ..modules.virtualbox import VirtualBox
@@ -262,3 +264,45 @@ class VirtualBoxHandler:
vm = vbox_manager.get_vm(request.match_info["uuid"])
vm.port_remove_nio_binding(int(request.match_info["port_id"]))
response.set_status(204)
+
+ @Route.post(
+ r"/virtualbox/{uuid}/capture/{port_id:\d+}/start",
+ parameters={
+ "uuid": "Instance UUID",
+ "port_id": "ID of the port to start a packet capture"
+ },
+ status_codes={
+ 200: "Capture started",
+ 400: "Invalid instance UUID",
+ 404: "Instance doesn't exist"
+ },
+ description="Start a packet capture on a VirtualBox VM instance",
+ input=VBOX_CAPTURE_SCHEMA)
+ def start_capture(request, response):
+
+ vbox_manager = VirtualBox.instance()
+ vm = vbox_manager.get_vm(request.match_info["uuid"])
+ port_id = int(request.match_info["port_id"])
+ pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["filename"])
+ vm.start_capture(port_id, pcap_file_path)
+ response.json({"port_id": port_id,
+ "pcap_file_path": pcap_file_path})
+
+ @Route.post(
+ r"/virtualbox/{uuid}/capture/{port_id:\d+}/stop",
+ parameters={
+ "uuid": "Instance UUID",
+ "port_id": "ID of the port to stop a packet capture"
+ },
+ status_codes={
+ 204: "Capture stopped",
+ 400: "Invalid instance UUID",
+ 404: "Instance doesn't exist"
+ },
+ description="Stop a packet capture on a VirtualBox VM instance")
+ def start_capture(request, response):
+
+ vbox_manager = VirtualBox.instance()
+ vm = vbox_manager.get_vm(request.match_info["uuid"])
+ vm.stop_capture(int(request.match_info["port_id"]))
+ response.set_status(204)
diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py
index 112f47d2..eafd5765 100644
--- a/gns3server/modules/base_manager.py
+++ b/gns3server/modules/base_manager.py
@@ -30,8 +30,8 @@ from uuid import UUID, uuid4
from ..config import Config
from .project_manager import ProjectManager
-from .nios.nio_udp import NIO_UDP
-from .nios.nio_tap import NIO_TAP
+from .nios.nio_udp import NIOUDP
+from .nios.nio_tap import NIOTAP
class BaseManager:
@@ -237,11 +237,11 @@ class BaseManager:
sock.connect((rhost, rport))
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
- nio = NIO_UDP(lport, rhost, rport)
+ nio = NIOUDP(lport, rhost, rport)
elif nio_settings["type"] == "nio_tap":
tap_device = nio_settings["tap_device"]
if not self._has_privileged_access(executable):
raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device))
- nio = NIO_TAP(tap_device)
+ nio = NIOTAP(tap_device)
assert nio is not None
return nio
diff --git a/gns3server/modules/nios/nio.py b/gns3server/modules/nios/nio.py
new file mode 100644
index 00000000..eee5f1d5
--- /dev/null
+++ b/gns3server/modules/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 .
+
+"""
+Base interface for NIOs.
+"""
+
+
+class NIO(object):
+ """
+ Network Input/Output.
+ """
+
+ 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/nios/nio_tap.py b/gns3server/modules/nios/nio_tap.py
index 43f440ad..9f51ce13 100644
--- a/gns3server/modules/nios/nio_tap.py
+++ b/gns3server/modules/nios/nio_tap.py
@@ -19,8 +19,10 @@
Interface for TAP NIOs (UNIX based OSes only).
"""
+from .nio import NIO
-class NIO_TAP(object):
+
+class NIOTAP(NIO):
"""
TAP NIO.
@@ -30,6 +32,7 @@ class NIO_TAP(object):
def __init__(self, tap_device):
+ super().__init__()
self._tap_device = tap_device
@property
diff --git a/gns3server/modules/nios/nio_udp.py b/gns3server/modules/nios/nio_udp.py
index a9765f03..a87875fe 100644
--- a/gns3server/modules/nios/nio_udp.py
+++ b/gns3server/modules/nios/nio_udp.py
@@ -19,8 +19,10 @@
Interface for UDP NIOs.
"""
+from .nio import NIO
-class NIO_UDP(object):
+
+class NIOUDP(NIO):
"""
UDP NIO.
@@ -30,10 +32,9 @@ class NIO_UDP(object):
:param rport: remote port number
"""
- _instance_count = 0
-
def __init__(self, lport, rhost, rport):
+ super().__init__()
self._lport = lport
self._rhost = rhost
self._rport = rport
diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py
index 14de142b..ca29aa93 100644
--- a/gns3server/modules/project.py
+++ b/gns3server/modules/project.py
@@ -122,7 +122,21 @@ class Project:
try:
os.makedirs(workdir, exist_ok=True)
except OSError as e:
- raise aiohttp.web.HTTPInternalServerError(text="Could not create VM working directory: {}".format(e))
+ raise aiohttp.web.HTTPInternalServerError(text="Could not create the VM working directory: {}".format(e))
+ return workdir
+
+ def capture_working_directory(self):
+ """
+ Return a working directory where to store packet capture files.
+
+ :returns: path to the directory
+ """
+
+ workdir = os.path.join(self._path, "captures")
+ try:
+ os.makedirs(workdir, exist_ok=True)
+ except OSError as e:
+ raise aiohttp.web.HTTPInternalServerError(text="Could not create the capture working directory: {}".format(e))
return workdir
def mark_vm_for_destruction(self, vm):
diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py
index aa08a54a..8822fc3e 100644
--- a/gns3server/modules/virtualbox/virtualbox_vm.py
+++ b/gns3server/modules/virtualbox/virtualbox_vm.py
@@ -23,7 +23,6 @@ import sys
import shlex
import re
import os
-import subprocess
import tempfile
import json
import socket
@@ -833,13 +832,7 @@ class VirtualBoxVM(BaseVM):
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), exist_ok=True)
- except OSError as e:
- raise VirtualBoxError("Could not create captures directory {}".format(e))
-
nio.startPacketCapture(output_file)
-
log.info("VirtualBox VM '{name}' [{uuid}]: starting packet capture on adapter {adapter_id}".format(name=self.name,
uuid=self.uuid,
adapter_id=adapter_id))
diff --git a/gns3server/schemas/virtualbox.py b/gns3server/schemas/virtualbox.py
index 5248240d..091682e4 100644
--- a/gns3server/schemas/virtualbox.py
+++ b/gns3server/schemas/virtualbox.py
@@ -154,6 +154,21 @@ VBOX_NIO_SCHEMA = {
"required": ["type"]
}
+VBOX_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": {
+ "capture_filename": {
+ "description": "Capture file name",
+ "type": "string",
+ "minLength": 1,
+ },
+ },
+ "additionalProperties": False,
+ "required": ["capture_filename"]
+}
+
VBOX_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "VirtualBox VM instance",