From 365af02f3799bfef21233ff0e36c45c048a15505 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 23 Jan 2015 18:33:49 -0700 Subject: [PATCH] Packet capture support for VirtualBox. --- gns3server/handlers/virtualbox_handler.py | 44 +++++++++++++ gns3server/modules/base_manager.py | 8 +-- gns3server/modules/nios/nio.py | 65 +++++++++++++++++++ gns3server/modules/nios/nio_tap.py | 5 +- gns3server/modules/nios/nio_udp.py | 7 +- gns3server/modules/project.py | 16 ++++- .../modules/virtualbox/virtualbox_vm.py | 7 -- gns3server/schemas/virtualbox.py | 15 +++++ 8 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 gns3server/modules/nios/nio.py 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",