From 0ee31361c0fe98029cd8b69220578a1d53c2809d Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 13 Sep 2015 09:40:09 -0600 Subject: [PATCH] Support for packet capture on VMware VM links. --- gns3server/handlers/api/vmware_handler.py | 49 ++++++++++++ gns3server/modules/vmware/vmware_vm.py | 90 +++++++++++++++++++++++ gns3server/schemas/vmware.py | 15 ++++ 3 files changed, 154 insertions(+) diff --git a/gns3server/handlers/api/vmware_handler.py b/gns3server/handlers/api/vmware_handler.py index 9370d795..69654457 100644 --- a/gns3server/handlers/api/vmware_handler.py +++ b/gns3server/handlers/api/vmware_handler.py @@ -15,11 +15,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os + from aiohttp.web import HTTPConflict from ...web.route import Route from ...schemas.vmware import VMWARE_CREATE_SCHEMA from ...schemas.vmware import VMWARE_UPDATE_SCHEMA from ...schemas.vmware import VMWARE_OBJECT_SCHEMA +from ...schemas.vmware import VMWARE_CAPTURE_SCHEMA from ...schemas.nio import NIO_SCHEMA from ...modules.vmware import VMware from ...modules.project_manager import ProjectManager @@ -297,6 +300,52 @@ class VMwareHandler: yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"])) response.set_status(204) + @Route.post( + r"/projects/{project_id}/vmware/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the instance", + "adapter_number": "Adapter to start a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 200: "Capture started", + 400: "Invalid request", + 404: "Instance doesn't exist", + }, + description="Start a packet capture on a VMware VM instance", + input=VMWARE_CAPTURE_SCHEMA) + def start_capture(request, response): + + vmware_manager = VMware.instance() + vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + adapter_number = int(request.match_info["adapter_number"]) + pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"]) + yield from vm.start_capture(adapter_number, pcap_file_path) + response.json({"pcap_file_path": pcap_file_path}) + + @Route.post( + r"/projects/{project_id}/vmware/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the instance", + "adapter_number": "Adapter to stop a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 204: "Capture stopped", + 400: "Invalid request", + 404: "Instance doesn't exist", + }, + description="Stop a packet capture on a VMware VM instance") + def stop_capture(request, response): + + vmware_manager = VMware.instance() + vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + adapter_number = int(request.match_info["adapter_number"]) + yield from vm.stop_capture(adapter_number) + response.set_status(204) + @classmethod @Route.post( r"/projects/{project_id}/vmware/interfaces/vmnet", diff --git a/gns3server/modules/vmware/vmware_vm.py b/gns3server/modules/vmware/vmware_vm.py index e6e0bf9c..be83cc29 100644 --- a/gns3server/modules/vmware/vmware_vm.py +++ b/gns3server/modules/vmware/vmware_vm.py @@ -352,6 +352,34 @@ class VMwareVM(BaseVM): raise VMwareError("vnet {} not in VMX file".format(vnet)) yield from self._ubridge_hypervisor.send("bridge delete {name}".format(name=vnet)) + @asyncio.coroutine + def _start_ubridge_capture(self, adapter_number, output_file): + """ + Start a packet capture in uBridge. + + :param adapter_number: adapter number + :param output_file: PCAP destination file for the capture + """ + + vnet = "ethernet{}.vnet".format(adapter_number) + if vnet not in self._vmx_pairs: + raise VMwareError("vnet {} not in VMX file".format(vnet)) + yield from self._ubridge_hypervisor.send('bridge start_capture {name} "{output_file}"'.format(name=vnet, + output_file=output_file)) + + @asyncio.coroutine + def _stop_ubridge_capture(self, adapter_number): + """ + Stop a packet capture in uBridge. + + :param adapter_number: adapter number + """ + + vnet = "ethernet{}.vnet".format(adapter_number) + if vnet not in self._vmx_pairs: + raise VMwareError("vnet {} not in VMX file".format(vnet)) + yield from self._ubridge_hypervisor.send("bridge stop_capture {name}".format(name=vnet)) + @property def ubridge_path(self): """ @@ -927,3 +955,65 @@ class VMwareVM(BaseVM): else: self._serial_pipe.close() self._serial_pipe = None + + @asyncio.coroutine + def start_capture(self, adapter_number, output_file): + """ + Starts a packet capture. + + :param adapter_number: adapter number + :param output_file: PCAP destination file for the capture + """ + + try: + adapter = self._ethernet_adapters[adapter_number] + except KeyError: + raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name, + adapter_number=adapter_number)) + + nio = adapter.get_nio(0) + + if isinstance(nio, NIOVMNET): + raise VMwareError("Sorry, packet capture is not supported without uBridge enabled") + + if not nio: + raise VMwareError("Adapter {} is not connected".format(adapter_number)) + + if nio.capturing: + raise VMwareError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number)) + + nio.startPacketCapture(output_file) + + if self._started: + yield from self._start_ubridge_capture(adapter_number, output_file) + + log.info("VMware VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name, + id=self.id, + adapter_number=adapter_number)) + + def stop_capture(self, adapter_number): + """ + Stops a packet capture. + + :param adapter_number: adapter number + """ + + try: + adapter = self._ethernet_adapters[adapter_number] + except KeyError: + raise VMwareError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name, + adapter_number=adapter_number)) + + nio = adapter.get_nio(0) + + if not nio: + raise VMwareError("Adapter {} is not connected".format(adapter_number)) + + nio.stopPacketCapture() + + if self._started: + yield from self._stop_ubridge_capture(adapter_number) + + log.info("VMware VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name, + id=self.id, + adapter_number=adapter_number)) diff --git a/gns3server/schemas/vmware.py b/gns3server/schemas/vmware.py index 29343e37..24f6321c 100644 --- a/gns3server/schemas/vmware.py +++ b/gns3server/schemas/vmware.py @@ -140,6 +140,21 @@ VMWARE_UPDATE_SCHEMA = { "additionalProperties": False, } +VMWARE_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on a VMware VM instance port", + "type": "object", + "properties": { + "capture_file_name": { + "description": "Capture file name", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["capture_file_name"] +} + VMWARE_OBJECT_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "VMware VM instance",