diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index 85152b74..5e9a0d50 100644 --- a/gns3server/compute/base_manager.py +++ b/gns3server/compute/base_manager.py @@ -428,6 +428,47 @@ class BaseManager: assert nio is not None return nio + async def stream_pcap_file(self, nio, project_id, request, response): + """ + Streams a PCAP file. + + :param nio: NIO object + :param project_id: Project identifier + :param request: request object + :param response: response object + """ + + if not nio.capturing: + raise aiohttp.web.HTTPConflict(text="Nothing to stream because there is no packet capture active") + + project = ProjectManager.instance().get_project(project_id) + path = os.path.normpath(os.path.join(project.capture_working_directory(), nio.pcap_output_file)) + # Raise an error if user try to escape + #if path[0] == ".": + # raise aiohttp.web.HTTPForbidden() + #path = os.path.join(project.path, path) + + response.content_type = "application/vnd.tcpdump.pcap" + response.set_status(200) + response.enable_chunked_encoding() + + try: + with open(path, "rb") as f: + await response.prepare(request) + while nio.capturing: + data = f.read(4096) + if not data: + await asyncio.sleep(0.1) + continue + try: + await response.write(data) + except ConnectionError: + break + except FileNotFoundError: + raise aiohttp.web.HTTPNotFound() + except PermissionError: + raise aiohttp.web.HTTPForbidden() + def get_abs_image_path(self, path): """ Get the absolute path of an image diff --git a/gns3server/compute/builtin/nodes/cloud.py b/gns3server/compute/builtin/nodes/cloud.py index 1db44c1a..071066b0 100644 --- a/gns3server/compute/builtin/nodes/cloud.py +++ b/gns3server/compute/builtin/nodes/cloud.py @@ -16,7 +16,6 @@ # along with this program. If not, see . import sys -import asyncio import subprocess from ...error import NodeError @@ -461,13 +460,13 @@ class Cloud(BaseNode): await self.start() return nio - async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"): + def get_nio(self, port_number): """ - Starts a packet capture. + Gets a port NIO binding. - :param port_number: allocated port number - :param output_file: PCAP destination file for the capture - :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB + :param port_number: port number + + :returns: NIO instance """ if not [port["port_number"] for port in self._ports_mapping if port_number == port["port_number"]]: @@ -479,6 +478,18 @@ class Cloud(BaseNode): nio = self._nios[port_number] + return nio + + async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"): + """ + Starts a packet capture. + + :param port_number: allocated port number + :param output_file: PCAP destination file for the capture + :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB + """ + + nio = self.get_nio(port_number) if nio.capturing: raise NodeError("Packet capture is already activated on port {port_number}".format(port_number=port_number)) nio.startPacketCapture(output_file) @@ -496,14 +507,7 @@ class Cloud(BaseNode): :param port_number: allocated port number """ - if not [port["port_number"] for port in self._ports_mapping if port_number == port["port_number"]]: - raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name, - port_number=port_number)) - - if port_number not in self._nios: - raise NodeError("Port {} is not connected".format(port_number)) - - nio = self._nios[port_number] + nio = self.get_nio(port_number) nio.stopPacketCapture() bridge_name = "{}-{}".format(self._id, port_number) await self._ubridge_send("bridge stop_capture {name}".format(name=bridge_name)) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 9a6c06d7..91986479 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -853,10 +853,10 @@ class DockerVM(BaseNode): async def adapter_update_nio_binding(self, adapter_number, nio): """ - Update a port NIO binding. + Update an adapter NIO binding. :param adapter_number: adapter number - :param nio: NIO instance to add to the adapter + :param nio: NIO instance to update the adapter """ if self.ubridge: @@ -895,6 +895,28 @@ class DockerVM(BaseNode): nio=adapter.host_ifc, adapter_number=adapter_number)) + def get_nio(self, adapter_number): + """ + Gets an adapter NIO binding. + + :param adapter_number: adapter number + + :returns: NIO instance + """ + + try: + adapter = self._ethernet_adapters[adapter_number] + except KeyError: + raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name, + adapter_number=adapter_number)) + + nio = adapter.get_nio(0) + + if not nio: + raise DockerError("Adapter {} is not connected".format(adapter_number)) + + return nio + @property def adapters(self): """ @@ -967,17 +989,7 @@ class DockerVM(BaseNode): :param output_file: PCAP destination file for the capture """ - try: - adapter = self._ethernet_adapters[adapter_number] - except KeyError: - raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name, - adapter_number=adapter_number)) - - nio = adapter.get_nio(0) - - if not nio: - raise DockerError("Adapter {} is not connected".format(adapter_number)) - + nio = self.get_nio(adapter_number) if nio.capturing: raise DockerError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number)) @@ -997,19 +1009,8 @@ class DockerVM(BaseNode): :param adapter_number: adapter number """ - try: - adapter = self._ethernet_adapters[adapter_number] - except KeyError: - raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name, - adapter_number=adapter_number)) - - nio = adapter.get_nio(0) - - if not nio: - raise DockerError("Adapter {} is not connected".format(adapter_number)) - + nio = self.get_nio(adapter_number) nio.stopPacketCapture() - if self.status == "started" and self.ubridge: await self._stop_ubridge_capture(adapter_number) diff --git a/gns3server/compute/dynamips/nodes/atm_switch.py b/gns3server/compute/dynamips/nodes/atm_switch.py index 7b1896eb..5d9ef849 100644 --- a/gns3server/compute/dynamips/nodes/atm_switch.py +++ b/gns3server/compute/dynamips/nodes/atm_switch.py @@ -223,6 +223,25 @@ class ATMSwitch(Device): del self._nios[port_number] return nio + def get_nio(self, port_number): + """ + Gets a port NIO binding. + + :param port_number: port number + + :returns: NIO instance + """ + + if port_number not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port_number)) + + nio = self._nios[port_number] + + if not nio: + raise DynamipsError("Port {} is not connected".format(port_number)) + + return nio + async def set_mappings(self, mappings): """ Applies VC mappings @@ -424,11 +443,7 @@ class ATMSwitch(Device): :param data_link_type: PCAP data link type (DLT_*), default is DLT_ATM_RFC1483 """ - if port_number not in self._nios: - raise DynamipsError("Port {} is not allocated".format(port_number)) - - nio = self._nios[port_number] - + nio = self.get_nio(port_number) data_link_type = data_link_type.lower() if data_link_type.startswith("dlt_"): data_link_type = data_link_type[4:] @@ -450,10 +465,7 @@ class ATMSwitch(Device): :param port_number: allocated port number """ - if port_number not in self._nios: - raise DynamipsError("Port {} is not allocated".format(port_number)) - - nio = self._nios[port_number] + nio = self.get_nio(port_number) await nio.unbind_filter("both") log.info('ATM switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name, id=self._id, diff --git a/gns3server/compute/dynamips/nodes/ethernet_hub.py b/gns3server/compute/dynamips/nodes/ethernet_hub.py index 250c245c..16e50b73 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_hub.py +++ b/gns3server/compute/dynamips/nodes/ethernet_hub.py @@ -19,8 +19,6 @@ Hub object that uses the Bridge interface to create a hub with ports. """ -import asyncio - from .bridge import Bridge from ..nios.nio_udp import NIOUDP from ..dynamips_error import DynamipsError @@ -177,6 +175,25 @@ class EthernetHub(Bridge): del self._mappings[port_number] return nio + def get_nio(self, port_number): + """ + Gets a port NIO binding. + + :param port_number: port number + + :returns: NIO instance + """ + + if port_number not in self._mappings: + raise DynamipsError("Port {} is not allocated".format(port_number)) + + nio = self._mappings[port_number] + + if not nio: + raise DynamipsError("Port {} is not connected".format(port_number)) + + return nio + async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"): """ Starts a packet capture. @@ -186,11 +203,7 @@ class EthernetHub(Bridge): :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB """ - if port_number not in self._mappings: - raise DynamipsError("Port {} is not allocated".format(port_number)) - - nio = self._mappings[port_number] - + nio = self.get_nio(port_number) data_link_type = data_link_type.lower() if data_link_type.startswith("dlt_"): data_link_type = data_link_type[4:] @@ -212,10 +225,7 @@ class EthernetHub(Bridge): :param port_number: allocated port number """ - if port_number not in self._mappings: - raise DynamipsError("Port {} is not allocated".format(port_number)) - - nio = self._mappings[port_number] + nio = self.get_nio(port_number) await nio.unbind_filter("both") log.info('Ethernet hub "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name, id=self._id, diff --git a/gns3server/compute/dynamips/nodes/ethernet_switch.py b/gns3server/compute/dynamips/nodes/ethernet_switch.py index 54f4f209..09a6ad21 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_switch.py +++ b/gns3server/compute/dynamips/nodes/ethernet_switch.py @@ -300,6 +300,25 @@ class EthernetSwitch(Device): return nio + def get_nio(self, port_number): + """ + Gets a port NIO binding. + + :param port_number: port number + + :returns: NIO instance + """ + + if port_number not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port_number)) + + nio = self._nios[port_number] + + if not nio: + raise DynamipsError("Port {} is not connected".format(port_number)) + + return nio + async def set_port_settings(self, port_number, settings): """ Applies port settings to a specific port. @@ -413,14 +432,7 @@ class EthernetSwitch(Device): :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB """ - if port_number not in self._nios: - raise DynamipsError("Port {} is not allocated".format(port_number)) - - nio = self._nios[port_number] - - if not nio: - raise DynamipsError("Port {} is not connected".format(port_number)) - + nio = self.get_nio(port_number) data_link_type = data_link_type.lower() if data_link_type.startswith("dlt_"): data_link_type = data_link_type[4:] @@ -442,14 +454,7 @@ class EthernetSwitch(Device): :param port_number: allocated port number """ - if port_number not in self._nios: - raise DynamipsError("Port {} is not allocated".format(port_number)) - - nio = self._nios[port_number] - - if not nio: - raise DynamipsError("Port {} is not connected".format(port_number)) - + nio = self.get_nio(port_number) await nio.unbind_filter("both") log.info('Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name, id=self._id, diff --git a/gns3server/compute/dynamips/nodes/frame_relay_switch.py b/gns3server/compute/dynamips/nodes/frame_relay_switch.py index 73d97432..4a8af42b 100644 --- a/gns3server/compute/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/compute/dynamips/nodes/frame_relay_switch.py @@ -209,6 +209,25 @@ class FrameRelaySwitch(Device): del self._nios[port_number] return nio + def get_nio(self, port_number): + """ + Gets a port NIO binding. + + :param port_number: port number + + :returns: NIO instance + """ + + if port_number not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port_number)) + + nio = self._nios[port_number] + + if not nio: + raise DynamipsError("Port {} is not connected".format(port_number)) + + return nio + async def set_mappings(self, mappings): """ Applies VC mappings @@ -309,10 +328,7 @@ class FrameRelaySwitch(Device): :param data_link_type: PCAP data link type (DLT_*), default is DLT_FRELAY """ - if port_number not in self._nios: - raise DynamipsError("Port {} is not allocated".format(port_number)) - - nio = self._nios[port_number] + nio = self.get_nio(port_number) data_link_type = data_link_type.lower() if data_link_type.startswith("dlt_"): @@ -335,10 +351,7 @@ class FrameRelaySwitch(Device): :param port_number: allocated port number """ - if port_number not in self._nios: - raise DynamipsError("Port {} is not allocated".format(port_number)) - - nio = self._nios[port_number] + nio = self.get_nio(port_number) await nio.unbind_filter("both") log.info('Frame relay switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name, id=self._id, diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index 3032dd47..19aa36fe 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -1264,7 +1264,7 @@ class Router(BaseNode): raise DynamipsError("Adapter is missing in slot {slot_number}".format(slot_number=slot_number)) if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter, + raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, port_number=port_number)) try: @@ -1299,6 +1299,7 @@ class Router(BaseNode): :param port_number: port number :param nio: NIO instance to add to the slot/port """ + await nio.update() async def slot_remove_nio_binding(self, slot_number, port_number): @@ -1321,7 +1322,7 @@ class Router(BaseNode): raise DynamipsError("Adapter is missing in slot {slot_number}".format(slot_number=slot_number)) if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter, + raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, port_number=port_number)) await self.slot_disable_nio(slot_number, port_number) @@ -1362,6 +1363,32 @@ class Router(BaseNode): slot_number=slot_number, port_number=port_number)) + def get_nio(self, slot_number, port_number): + """ + Gets an slot NIO binding. + + :param slot_number: slot number + :param port_number: port number + + :returns: NIO instance + """ + + try: + adapter = self._slots[slot_number] + except IndexError: + raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, + slot_number=slot_number)) + if not adapter.port_exists(port_number): + raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, + port_number=port_number)) + + nio = adapter.get_nio(port_number) + + if not nio: + raise DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number, + port_number=port_number)) + return nio + async def slot_disable_nio(self, slot_number, port_number): """ Disables a slot NIO binding. @@ -1402,7 +1429,7 @@ class Router(BaseNode): raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, slot_number=slot_number)) if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter, + raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, port_number=port_number)) data_link_type = data_link_type.lower() @@ -1442,7 +1469,7 @@ class Router(BaseNode): raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, slot_number=slot_number)) if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter, + raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, port_number=port_number)) nio = adapter.get_nio(port_number) diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index b270245a..7f22d055 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -840,7 +840,7 @@ class IOUVM(BaseNode): async def adapter_add_nio_binding(self, adapter_number, port_number, nio): """ - Adds a adapter NIO binding. + Adds an adapter NIO binding. :param adapter_number: adapter number :param port_number: port number @@ -854,7 +854,7 @@ class IOUVM(BaseNode): adapter_number=adapter_number)) if not adapter.port_exists(port_number): - raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter, + raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, port_number=port_number)) adapter.add_nio(port_number, nio) @@ -877,7 +877,7 @@ class IOUVM(BaseNode): async def adapter_update_nio_binding(self, adapter_number, port_number, nio): """ - Update a port NIO binding. + Updates an adapter NIO binding. :param adapter_number: adapter number :param port_number: port number @@ -913,6 +913,7 @@ class IOUVM(BaseNode): :param adapter_number: adapter number :param port_number: port number + :returns: NIO instance """ @@ -923,7 +924,7 @@ class IOUVM(BaseNode): adapter_number=adapter_number)) if not adapter.port_exists(port_number): - raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter, + raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, port_number=port_number)) nio = adapter.get_nio(port_number) @@ -944,6 +945,33 @@ class IOUVM(BaseNode): return nio + def get_nio(self, adapter_number, port_number): + """ + Gets an adapter NIO binding. + + :param adapter_number: adapter number + :param port_number: port number + + :returns: NIO instance + """ + + try: + adapter = self._adapters[adapter_number] + except IndexError: + raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name, + adapter_number=adapter_number)) + + if not adapter.port_exists(port_number): + raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, + port_number=port_number)) + + nio = adapter.get_nio(port_number) + + if not nio: + raise IOUError("NIO {port_number} does not exist on adapter {adapter}".format(adapter=adapter, + port_number=port_number)) + return nio + @property def l1_keepalives(self): """ @@ -1221,21 +1249,7 @@ class IOUVM(BaseNode): :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB """ - try: - adapter = self._adapters[adapter_number] - except IndexError: - raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name, - adapter_number=adapter_number)) - - if not adapter.port_exists(port_number): - raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter, - port_number=port_number)) - - nio = adapter.get_nio(port_number) - if not nio: - raise IOUError("NIO {port_number} does not exist in adapter {adapter}".format(adapter=adapter, - port_number=port_number)) - + nio = self.get_nio(adapter_number, port_number) if nio.capturing: raise IOUError("Packet capture is already activated on {adapter_number}/{port_number}".format(adapter_number=adapter_number, port_number=port_number)) @@ -1263,21 +1277,7 @@ class IOUVM(BaseNode): :param port_number: port number """ - try: - adapter = self._adapters[adapter_number] - except IndexError: - raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name, - adapter_number=adapter_number)) - - if not adapter.port_exists(port_number): - raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter, - port_number=port_number)) - - nio = adapter.get_nio(port_number) - if not nio: - raise IOUError("NIO {port_number} does not exist in adapter {adapter}".format(adapter=adapter, - port_number=port_number)) - + nio = self.get_nio(adapter_number, port_number) nio.stopPacketCapture() log.info('IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(name=self._name, id=self._id, diff --git a/gns3server/compute/project.py b/gns3server/compute/project.py index 1807ec03..35623986 100644 --- a/gns3server/compute/project.py +++ b/gns3server/compute/project.py @@ -239,12 +239,12 @@ class Project: def capture_working_directory(self): """ - Returns a working directory where to temporary store packet capture files. + Returns a working directory where to store packet capture files. :returns: path to the directory """ - workdir = os.path.join(self._path, "tmp", "captures") + workdir = os.path.join(self._path, "project-files", "captures") if not self._deleted: try: os.makedirs(workdir, exist_ok=True) diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 40d31819..081df9e0 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1209,7 +1209,7 @@ class QemuVM(BaseNode): async def adapter_add_nio_binding(self, adapter_number, nio): """ - Adds a port NIO binding. + Adds an adapter NIO binding. :param adapter_number: adapter number :param nio: NIO instance to add to the adapter @@ -1239,10 +1239,10 @@ class QemuVM(BaseNode): async def adapter_update_nio_binding(self, adapter_number, nio): """ - Update a port NIO binding. + Update an adapter NIO binding. :param adapter_number: adapter number - :param nio: NIO instance to add to the adapter + :param nio: NIO instance to update the adapter """ if self.is_running(): @@ -1260,7 +1260,7 @@ class QemuVM(BaseNode): async def adapter_remove_nio_binding(self, adapter_number): """ - Removes a port NIO binding. + Removes an adapter NIO binding. :param adapter_number: adapter number @@ -1288,12 +1288,13 @@ class QemuVM(BaseNode): adapter_number=adapter_number)) return nio - async def start_capture(self, adapter_number, output_file): + def get_nio(self, adapter_number): """ - Starts a packet capture. + Gets an adapter NIO binding. :param adapter_number: adapter number - :param output_file: PCAP destination file for the capture + + :returns: NIO instance """ try: @@ -1307,6 +1308,17 @@ class QemuVM(BaseNode): if not nio: raise QemuError("Adapter {} is not connected".format(adapter_number)) + return nio + + async 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 + """ + + nio = self.get_nio(adapter_number) if nio.capturing: raise QemuError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number)) @@ -1327,16 +1339,7 @@ class QemuVM(BaseNode): :param adapter_number: adapter number """ - try: - adapter = self._ethernet_adapters[adapter_number] - except IndexError: - raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, - adapter_number=adapter_number)) - nio = adapter.get_nio(0) - - if not nio: - raise QemuError("Adapter {} is not connected".format(adapter_number)) - + nio = self.get_nio(adapter_number) nio.stopPacketCapture() if self.ubridge: diff --git a/gns3server/compute/traceng/traceng_vm.py b/gns3server/compute/traceng/traceng_vm.py index c2755e36..22689063 100644 --- a/gns3server/compute/traceng/traceng_vm.py +++ b/gns3server/compute/traceng/traceng_vm.py @@ -322,8 +322,15 @@ class TraceNGVM(BaseNode): return nio async def port_update_nio_binding(self, port_number, nio): + """ + Updates a port NIO binding. + + :param port_number: port number + :param nio: NIO instance to update on the slot/port + """ + if not self._ethernet_adapter.port_exists(port_number): - raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, + raise TraceNGError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, port_number=port_number)) if self.is_running(): await self.update_ubridge_udp_connection("TraceNG-{}".format(self._id), self._local_udp_tunnel[1], nio) @@ -355,6 +362,23 @@ class TraceNGVM(BaseNode): port_number=port_number)) return nio + def get_nio(self, port_number): + """ + Gets a port NIO binding. + + :param port_number: port number + + :returns: NIO instance + """ + + if not self._ethernet_adapter.port_exists(port_number): + raise TraceNGError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, + port_number=port_number)) + nio = self._ethernet_adapter.get_nio(port_number) + if not nio: + raise TraceNGError("Port {} is not connected".format(port_number)) + return nio + async def start_capture(self, port_number, output_file): """ Starts a packet capture. @@ -363,15 +387,7 @@ class TraceNGVM(BaseNode): :param output_file: PCAP destination file for the capture """ - if not self._ethernet_adapter.port_exists(port_number): - raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, - port_number=port_number)) - - nio = self._ethernet_adapter.get_nio(0) - - if not nio: - raise TraceNGError("Port {} is not connected".format(port_number)) - + nio = self.get_nio(port_number) if nio.capturing: raise TraceNGError("Packet capture is already activated on port {port_number}".format(port_number=port_number)) @@ -392,15 +408,7 @@ class TraceNGVM(BaseNode): :param port_number: port number """ - if not self._ethernet_adapter.port_exists(port_number): - raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, - port_number=port_number)) - - nio = self._ethernet_adapter.get_nio(0) - - if not nio: - raise TraceNGError("Port {} is not connected".format(port_number)) - + nio = self.get_nio(port_number) nio.stopPacketCapture() if self.ubridge: diff --git a/gns3server/compute/virtualbox/virtualbox_vm.py b/gns3server/compute/virtualbox/virtualbox_vm.py index 36342711..652bb919 100644 --- a/gns3server/compute/virtualbox/virtualbox_vm.py +++ b/gns3server/compute/virtualbox/virtualbox_vm.py @@ -1014,10 +1014,10 @@ class VirtualBoxVM(BaseNode): async def adapter_update_nio_binding(self, adapter_number, nio): """ - Update a port NIO binding. + Update an adapter NIO binding. :param adapter_number: adapter number - :param nio: NIO instance to add to the adapter + :param nio: NIO instance to update on the adapter """ if self.is_running(): @@ -1030,10 +1030,8 @@ class VirtualBoxVM(BaseNode): else: await self._control_vm("setlinkstate{} on".format(adapter_number + 1)) except IndexError: - raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format( - name=self._name, - adapter_number=adapter_number - )) + raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(name=self._name, + adapter_number=adapter_number)) async def adapter_remove_nio_binding(self, adapter_number): """ @@ -1067,6 +1065,28 @@ class VirtualBoxVM(BaseNode): adapter_number=adapter_number)) return nio + def get_nio(self, adapter_number): + """ + Gets an adapter NIO binding. + + :param adapter_number: adapter number + + :returns: NIO instance + """ + + try: + adapter = self.ethernet_adapters[adapter_number] + except KeyError: + raise VirtualBoxError("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 VirtualBoxError("Adapter {} is not connected".format(adapter_number)) + + return nio + def is_running(self): """ :returns: True if the vm is not stopped @@ -1081,17 +1101,7 @@ class VirtualBoxVM(BaseNode): :param output_file: PCAP destination file for the capture """ - try: - adapter = self._ethernet_adapters[adapter_number] - except KeyError: - raise VirtualBoxError("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 VirtualBoxError("Adapter {} is not connected".format(adapter_number)) - + nio = self.get_nio(adapter_number) if nio.capturing: raise VirtualBoxError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number)) @@ -1112,17 +1122,7 @@ class VirtualBoxVM(BaseNode): :param adapter_number: adapter number """ - try: - adapter = self._ethernet_adapters[adapter_number] - except KeyError: - raise VirtualBoxError("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 VirtualBoxError("Adapter {} is not connected".format(adapter_number)) - + nio = self.get_nio(adapter_number) nio.stopPacketCapture() if self.ubridge: diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py index 41410cb3..2ca01096 100644 --- a/gns3server/compute/vmware/vmware_vm.py +++ b/gns3server/compute/vmware/vmware_vm.py @@ -750,20 +750,18 @@ class VMwareVM(BaseNode): async def adapter_update_nio_binding(self, adapter_number, nio): """ - Update a port NIO binding. + Updates an adapter NIO binding. :param adapter_number: adapter number - :param nio: NIO instance to add to the adapter + :param nio: NIO instance to update on the adapter """ if self._ubridge_hypervisor: try: await self._update_ubridge_connection(adapter_number, nio) except IndexError: - raise VMwareError('Adapter {adapter_number} does not exist on VMware VM "{name}"'.format( - name=self._name, - adapter_number=adapter_number - )) + raise VMwareError('Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(name=self._name, + adapter_number=adapter_number)) async def adapter_remove_nio_binding(self, adapter_number): """ @@ -794,6 +792,27 @@ class VMwareVM(BaseNode): return nio + def get_nio(self, adapter_number): + """ + Gets an adapter NIO binding. + + :param adapter_number: adapter number + + :returns: NIO instance + """ + + 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 not nio: + raise VMwareError("Adapter {} is not connected".format(adapter_number)) + + return nio + def _get_pipe_name(self): """ Returns the pipe name to create a serial connection. @@ -875,17 +894,7 @@ class VMwareVM(BaseNode): :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 not nio: - raise VMwareError("Adapter {} is not connected".format(adapter_number)) - + nio = self.get_nio(adapter_number) if nio.capturing: raise VMwareError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number)) @@ -905,17 +914,7 @@ class VMwareVM(BaseNode): :param adapter_number: adapter number """ - 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 not nio: - raise VMwareError("Adapter {} is not connected".format(adapter_number)) - + nio = self.get_nio(adapter_number) nio.stopPacketCapture() if self._started: diff --git a/gns3server/compute/vpcs/vpcs_vm.py b/gns3server/compute/vpcs/vpcs_vm.py index 003bec15..0b8cf3ee 100644 --- a/gns3server/compute/vpcs/vpcs_vm.py +++ b/gns3server/compute/vpcs/vpcs_vm.py @@ -367,7 +367,7 @@ class VPCSVM(BaseNode): """ if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, + raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, port_number=port_number)) if self.is_running(): @@ -382,8 +382,15 @@ class VPCSVM(BaseNode): return nio async def port_update_nio_binding(self, port_number, nio): + """ + Updates a port NIO binding. + + :param port_number: port number + :param nio: NIO instance to update on the slot/port + """ + if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, + raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, port_number=port_number)) if self.is_running(): await self.update_ubridge_udp_connection("VPCS-{}".format(self._id), self._local_udp_tunnel[1], nio) @@ -398,7 +405,7 @@ class VPCSVM(BaseNode): """ if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, + raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, port_number=port_number)) if self.is_running(): @@ -415,6 +422,23 @@ class VPCSVM(BaseNode): port_number=port_number)) return nio + def get_nio(self, port_number): + """ + Gets a port NIO binding. + + :param port_number: port number + + :returns: NIO instance + """ + + if not self._ethernet_adapter.port_exists(port_number): + raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, + port_number=port_number)) + nio = self._ethernet_adapter.get_nio(port_number) + if not nio: + raise VPCSError("Port {} is not connected".format(port_number)) + return nio + async def start_capture(self, port_number, output_file): """ Starts a packet capture. @@ -423,17 +447,9 @@ class VPCSVM(BaseNode): :param output_file: PCAP destination file for the capture """ - if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, - port_number=port_number)) - - nio = self._ethernet_adapter.get_nio(0) - - if not nio: - raise VPCSError("Port {} is not connected".format(port_number)) - + nio = self.get_nio(port_number) if nio.capturing: - raise VPCSError("Packet capture is already activated on port {port_number}".format(port_number=port_number)) + raise VPCSError("Packet capture is already active on port {port_number}".format(port_number=port_number)) nio.startPacketCapture(output_file) @@ -452,15 +468,7 @@ class VPCSVM(BaseNode): :param port_number: port number """ - if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, - port_number=port_number)) - - nio = self._ethernet_adapter.get_nio(0) - - if not nio: - raise VPCSError("Port {} is not connected".format(port_number)) - + nio = self.get_nio(port_number) nio.stopPacketCapture() if self.ubridge: diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 65b046e9..f6216fc4 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -330,31 +330,17 @@ class Compute: :returns: A file stream """ - # Due to Python 3.4 limitation we can't use with and asyncio - # https://www.python.org/dev/peps/pep-0492/ - # that why we wrap the answer - class StreamResponse: - - def __init__(self, response): - self._response = response - - def __enter__(self): - return self._response.content - - def __exit__(self): - self._response.close() - url = self._getUrl("/projects/{}/stream/{}".format(project.id, path)) response = await self._session().request("GET", url, auth=self._auth, timeout=timeout) if response.status == 404: - raise aiohttp.web.HTTPNotFound(text="{} not found on compute".format(path)) + raise aiohttp.web.HTTPNotFound(text="file '{}' not found on compute".format(path)) elif response.status == 403: - raise aiohttp.web.HTTPForbidden(text="forbidden to open {} on compute".format(path)) + raise aiohttp.web.HTTPForbidden(text="forbidden to open '{}' on compute".format(path)) elif response.status != 200: raise aiohttp.web.HTTPInternalServerError(text="Unexpected error {}: {}: while opening {} on compute".format(response.status, response.reason, path)) - return StreamResponse(response) + return response async def http_query(self, method, path, data=None, dont_connect=False, **kwargs): """ diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index 226b4a7b..37c0cb59 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -19,7 +19,6 @@ import os import re import uuid import html -import asyncio import aiohttp import logging @@ -118,6 +117,7 @@ class Link: self._nodes = [] self._project = project self._capturing = False + self._capture_node = None self._capture_file_name = None self._streaming_pcap = None self._created = False @@ -139,6 +139,34 @@ class Link: """ return self._nodes + @property + def project(self): + """ + Get the project this link belongs to. + + :return: Project instance. + """ + return self._project + + @property + def capture_node(self): + """ + Get the capturing node + + :return: Node instance. + """ + return self._capture_node + + @property + def compute(self): + """ + Get the capturing node + + :return: Node instance. + """ + assert self.capture_node + return self.capture_node["node"].compute + def get_active_filters(self): """ Return the active filters. @@ -289,44 +317,8 @@ class Link: self._capturing = True self._capture_file_name = capture_file_name - self._streaming_pcap = asyncio.ensure_future(self._start_streaming_pcap()) self._project.controller.notification.project_emit("link.updated", self.__json__()) - async def _start_streaming_pcap(self): - """ - Dump a pcap file on disk - """ - - if os.path.exists(self.capture_file_path): - try: - os.remove(self.capture_file_path) - except OSError as e: - raise aiohttp.web.HTTPConflict(text="Could not delete old capture file '{}': {}".format(self.capture_file_path, e)) - - try: - stream_content = await self.read_pcap_from_source() - except aiohttp.web.HTTPException as e: - error_msg = "Could not stream PCAP file: error {}: {}".format(e.status, e.text) - log.error(error_msg) - self._capturing = False - self._project.notification.project_emit("log.error", {"message": error_msg}) - self._project.controller.notification.project_emit("link.updated", self.__json__()) - - with stream_content as stream: - try: - with open(self.capture_file_path, "wb") as f: - while self._capturing: - # We read 1 bytes by 1 otherwise the remaining data is not read if the traffic stops - data = await stream.read(1) - if data: - f.write(data) - # Flush to disk otherwise the live is not really live - f.flush() - else: - break - except OSError as e: - raise aiohttp.web.HTTPConflict(text="Could not write capture file '{}': {}".format(self.capture_file_path, e)) - async def stop_capture(self): """ Stop capture on the link @@ -335,12 +327,26 @@ class Link: self._capturing = False self._project.controller.notification.project_emit("link.updated", self.__json__()) - async def _read_pcap_from_source(self): + def pcap_streaming_url(self): """ - Return a FileStream of the Pcap from the compute server + Get the PCAP streaming URL on compute + + :returns: URL """ - raise NotImplementedError + assert self.capture_node + compute = self.capture_node["node"].compute + node_type = self.capture_node["node"].node_type + node_id = self.capture_node["node"].id + adapter_number = self.capture_node["adapter_number"] + port_number = self.capture_node["port_number"] + url = "/projects/{project_id}/{node_type}/nodes/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap".format(project_id=self.project.id, + node_type=node_type, + node_id=node_id, + adapter_number=adapter_number, + port_number=port_number) + + return compute._getUrl(url) async def node_updated(self, node): """ diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index 4a4ed070..c5121b9b 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import asyncio + import aiohttp @@ -26,7 +26,6 @@ class UDPLink(Link): def __init__(self, project, link_id=None): super().__init__(project, link_id=link_id) - self._capture_node = None self._created = False self._link_data = [] @@ -164,10 +163,8 @@ class UDPLink(Link): if not capture_file_name: capture_file_name = self.default_capture_file_name() self._capture_node = self._choose_capture_side() - data = { - "capture_file_name": capture_file_name, - "data_link_type": data_link_type - } + data = {"capture_file_name": capture_file_name, + "data_link_type": data_link_type} await self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/start_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"]), data=data) await super().start_capture(data_link_type=data_link_type, capture_file_name=capture_file_name) @@ -210,14 +207,6 @@ class UDPLink(Link): raise aiohttp.web.HTTPConflict(text="Cannot capture because there is no running device on this link") - async def read_pcap_from_source(self): - """ - Return a FileStream of the Pcap from the compute node - """ - if self._capture_node: - compute = self._capture_node["node"].compute - return compute.stream_file(self._project, "tmp/captures/" + self._capture_file_name) - async def node_updated(self, node): """ Called when a node member of the link is updated diff --git a/gns3server/handlers/api/compute/atm_switch_handler.py b/gns3server/handlers/api/compute/atm_switch_handler.py index 5a9d01a3..6b53faa1 100644 --- a/gns3server/handlers/api/compute/atm_switch_handler.py +++ b/gns3server/handlers/api/compute/atm_switch_handler.py @@ -288,3 +288,25 @@ class ATMSwitchHandler: port_number = int(request.match_info["port_number"]) await node.stop_capture(port_number) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/atm_relay_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture (always 0)", + "port_number": "Port on the switch" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = node.get_nio(port_number) + await node.stream_pcap_file(nio, node.project.id, request, response) diff --git a/gns3server/handlers/api/compute/cloud_handler.py b/gns3server/handlers/api/compute/cloud_handler.py index 60fcc7d1..b735e866 100644 --- a/gns3server/handlers/api/compute/cloud_handler.py +++ b/gns3server/handlers/api/compute/cloud_handler.py @@ -222,21 +222,16 @@ class CloudHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a Cloud instance") + description="Update a NIO on a Cloud instance") async def update_nio(request, response): builtin_manager = Builtin.instance() node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - adapter_number = int(request.match_info["adapter_number"]) - - try: - nio = node.nios[adapter_number] - except KeyError: - raise HTTPConflict(text="NIO `{}` doesn't exist".format(adapter_number)) - - if "filters" in request.json and nio: + port_number = int(request.match_info["port_number"]) + nio = node.get_nio(port_number) + if "filters" in request.json: nio.filters = request.json["filters"] - await node.update_nio(int(request.match_info["port_number"]), nio) + await node.update_nio(port_number, nio) response.set_status(201) response.json(request.json) @@ -307,3 +302,25 @@ class CloudHandler: port_number = int(request.match_info["port_number"]) await node.stop_capture(port_number) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/cloud/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture (always 0)", + "port_number": "Port on the cloud" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + builtin_manager = Builtin.instance() + node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = node.get_nio(port_number) + await builtin_manager.stream_pcap_file(nio, node.project.id, request, response) diff --git a/gns3server/handlers/api/compute/docker_handler.py b/gns3server/handlers/api/compute/docker_handler.py index af595acd..9bd13848 100644 --- a/gns3server/handlers/api/compute/docker_handler.py +++ b/gns3server/handlers/api/compute/docker_handler.py @@ -238,8 +238,9 @@ class DockerHandler: nio_type = request.json["type"] if nio_type != "nio_udp": raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) + adapter_number = int(request.match_info["adapter_number"]) nio = docker_manager.create_nio(request.json) - await container.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio) + await container.adapter_add_nio_binding(adapter_number, nio) response.set_status(201) response.json(nio) @@ -249,7 +250,7 @@ class DockerHandler: "project_id": "Project UUID", "node_id": "Node UUID", "adapter_number": "Network adapter where the nio is located", - "port_number": "Port from where the nio should be updated" + "port_number": "Port from where the nio should be updated (always 0)" }, status_codes={ 201: "NIO updated", @@ -258,15 +259,16 @@ class DockerHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a Docker instance") + description="Update a NIO on a Docker instance") async def update_nio(request, response): docker_manager = Docker.instance() container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - nio = container.ethernet_adapters[int(request.match_info["adapter_number"])].get_nio(0) + adapter_number = int(request.match_info["adapter_number"]) + nio = container.get_nio(adapter_number) if "filters" in request.json and nio: nio.filters = request.json["filters"] - await container.adapter_update_nio_binding(int(request.match_info["port_number"]), nio) + await container.adapter_update_nio_binding(adapter_number, nio) response.set_status(201) response.json(request.json) @@ -276,7 +278,7 @@ class DockerHandler: "project_id": "Project UUID", "node_id": "Node UUID", "adapter_number": "Adapter where the nio should be added", - "port_number": "Port on the adapter" + "port_number": "Port on the adapter (always 0)" }, status_codes={ 204: "NIO deleted", @@ -287,7 +289,8 @@ class DockerHandler: async def delete_nio(request, response): docker_manager = Docker.instance() container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - await container.adapter_remove_nio_binding(int(request.match_info["adapter_number"])) + adapter_number = int(request.match_info["adapter_number"]) + await container.adapter_remove_nio_binding(adapter_number) response.set_status(204) @Route.put( @@ -333,7 +336,7 @@ class DockerHandler: "project_id": "Project UUID", "node_id": "Node UUID", "adapter_number": "Adapter to start a packet capture", - "port_number": "Port on the adapter" + "port_number": "Port on the adapter (always 0)" }, status_codes={ 200: "Capture started", @@ -349,7 +352,6 @@ class DockerHandler: container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) adapter_number = int(request.match_info["adapter_number"]) pcap_file_path = os.path.join(container.project.capture_working_directory(), request.json["capture_file_name"]) - await container.start_capture(adapter_number, pcap_file_path) response.json({"pcap_file_path": str(pcap_file_path)}) @@ -372,11 +374,32 @@ class DockerHandler: docker_manager = Docker.instance() container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - adapter_number = int(request.match_info["adapter_number"]) await container.stop_capture(adapter_number) response.set_status(204) + @Route.get( + r"/projects/{project_id}/docker/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + docker_manager = Docker.instance() + container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + adapter_number = int(request.match_info["adapter_number"]) + nio = container.get_nio(adapter_number) + await docker_manager.stream_pcap_file(nio, container.project.id, request, response) + @Route.get( r"/docker/images", status_codes={ diff --git a/gns3server/handlers/api/compute/dynamips_vm_handler.py b/gns3server/handlers/api/compute/dynamips_vm_handler.py index f1a021b1..86de57fd 100644 --- a/gns3server/handlers/api/compute/dynamips_vm_handler.py +++ b/gns3server/handlers/api/compute/dynamips_vm_handler.py @@ -285,15 +285,15 @@ class DynamipsVMHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a Dynamips instance") + description="Update a NIO on a Dynamips instance") async def update_nio(request, response): dynamips_manager = Dynamips.instance() vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) slot_number = int(request.match_info["adapter_number"]) port_number = int(request.match_info["port_number"]) - nio = vm.slots[slot_number].get_nio(port_number) - if "filters" in request.json and nio: + nio = vm.get_nio(slot_number, port_number) + if "filters" in request.json: nio.filters = request.json["filters"] await vm.slot_update_nio_binding(slot_number, port_number, nio) response.set_status(201) @@ -379,6 +379,29 @@ class DynamipsVMHandler: await vm.stop_capture(slot_number, port_number) response.set_status(204) + @Route.get( + r"/projects/{project_id}/dynamips/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + dynamips_manager = Dynamips.instance() + vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + slot_number = int(request.match_info["adapter_number"]) + port_number = int(request.match_info["port_number"]) + nio = vm.get_nio(slot_number, port_number) + await dynamips_manager.stream_pcap_file(nio, vm.project.id, request, response) + @Route.get( r"/projects/{project_id}/dynamips/nodes/{node_id}/idlepc_proposals", parameters={ @@ -485,10 +508,8 @@ class DynamipsVMHandler: description="Duplicate a dynamips instance") async def duplicate(request, response): - new_node = await Dynamips.instance().duplicate_node( - request.match_info["node_id"], - request.json["destination_node_id"] - ) + new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"], + request.json["destination_node_id"]) response.set_status(201) response.json(new_node) diff --git a/gns3server/handlers/api/compute/ethernet_hub_handler.py b/gns3server/handlers/api/compute/ethernet_hub_handler.py index 54d051b4..326f64d7 100644 --- a/gns3server/handlers/api/compute/ethernet_hub_handler.py +++ b/gns3server/handlers/api/compute/ethernet_hub_handler.py @@ -94,10 +94,8 @@ class EthernetHubHandler: description="Duplicate an ethernet hub instance") async def duplicate(request, response): - new_node = await Dynamips.instance().duplicate_node( - request.match_info["node_id"], - request.json["destination_node_id"] - ) + new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"], + request.json["destination_node_id"]) response.set_status(201) response.json(new_node) @@ -292,3 +290,25 @@ class EthernetHubHandler: port_number = int(request.match_info["port_number"]) await node.stop_capture(port_number) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/ethernet_hub/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture (always 0)", + "port_number": "Port on the hub" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = node.get_nio(port_number) + await node.stream_pcap_file(nio, node.project.id, request, response) diff --git a/gns3server/handlers/api/compute/ethernet_switch_handler.py b/gns3server/handlers/api/compute/ethernet_switch_handler.py index bd7fee9a..e47db0e4 100644 --- a/gns3server/handlers/api/compute/ethernet_switch_handler.py +++ b/gns3server/handlers/api/compute/ethernet_switch_handler.py @@ -105,10 +105,8 @@ class EthernetSwitchHandler: description="Duplicate an ethernet switch instance") async def duplicate(request, response): - new_node = await Dynamips.instance().duplicate_node( - request.match_info["node_id"], - request.json["destination_node_id"] - ) + new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"], + request.json["destination_node_id"]) response.set_status(201) response.json(new_node) @@ -319,3 +317,25 @@ class EthernetSwitchHandler: port_number = int(request.match_info["port_number"]) await node.stop_capture(port_number) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture (always 0)", + "port_number": "Port on the switch" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = node.get_nio(port_number) + await node.stream_pcap_file(nio, node.project.id, request, response) diff --git a/gns3server/handlers/api/compute/frame_relay_switch_handler.py b/gns3server/handlers/api/compute/frame_relay_switch_handler.py index 24a903ec..0ec4caee 100644 --- a/gns3server/handlers/api/compute/frame_relay_switch_handler.py +++ b/gns3server/handlers/api/compute/frame_relay_switch_handler.py @@ -288,3 +288,25 @@ class FrameRelaySwitchHandler: port_number = int(request.match_info["port_number"]) await node.stop_capture(port_number) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/frame_relay_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture (always 0)", + "port_number": "Port on the switch" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = node.get_nio(port_number) + await node.stream_pcap_file(nio, node.project.id, request, response) diff --git a/gns3server/handlers/api/compute/iou_handler.py b/gns3server/handlers/api/compute/iou_handler.py index b5583033..8241f03a 100644 --- a/gns3server/handlers/api/compute/iou_handler.py +++ b/gns3server/handlers/api/compute/iou_handler.py @@ -290,7 +290,7 @@ class IOUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Update a NIO from a IOU instance", + description="Update a NIO on an IOU instance", input=NIO_SCHEMA, output=NIO_SCHEMA) async def update_nio(request, response): @@ -299,13 +299,10 @@ class IOUHandler: vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) adapter_number = int(request.match_info["adapter_number"]) port_number = int(request.match_info["port_number"]) - nio = vm.adapters[adapter_number].get_nio(port_number) - if "filters" in request.json and nio: + nio = vm.get_nio(adapter_number, port_number) + if "filters" in request.json: nio.filters = request.json["filters"] - await vm.adapter_update_nio_binding( - adapter_number, - port_number, - nio) + await vm.adapter_update_nio_binding(adapter_number, port_number, nio) response.set_status(201) response.json(nio) @@ -375,12 +372,34 @@ class IOUHandler: iou_manager = IOU.instance() vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - adapter_number = int(request.match_info["adapter_number"]) port_number = int(request.match_info["port_number"]) await vm.stop_capture(adapter_number, port_number) response.set_status(204) + @Route.get( + r"/projects/{project_id}/iou/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + iou_manager = IOU.instance() + vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + adapter_number = int(request.match_info["adapter_number"]) + port_number = int(request.match_info["port_number"]) + nio = vm.get_nio(adapter_number, port_number) + await iou_manager.stream_pcap_file(nio, vm.project.id, request, response) + @Route.get( r"/iou/images", status_codes={ diff --git a/gns3server/handlers/api/compute/nat_handler.py b/gns3server/handlers/api/compute/nat_handler.py index 45e6f21f..ae13c87e 100644 --- a/gns3server/handlers/api/compute/nat_handler.py +++ b/gns3server/handlers/api/compute/nat_handler.py @@ -21,6 +21,7 @@ from gns3server.web.route import Route from gns3server.schemas.node import NODE_CAPTURE_SCHEMA from gns3server.schemas.nio import NIO_SCHEMA from gns3server.compute.builtin import Builtin +from aiohttp.web import HTTPConflict from gns3server.schemas.nat import ( NAT_CREATE_SCHEMA, @@ -213,15 +214,16 @@ class NatHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a NAT instance") + description="Update a NIO on a NAT instance") async def update_nio(request, response): builtin_manager = Builtin.instance() node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - nio = node.nios[int(request.match_info["adapter_number"])] - if "filters" in request.json and nio: + port_number = int(request.match_info["port_number"]) + nio = node.get_nio(port_number) + if "filters" in request.json: nio.filters = request.json["filters"] - await node.update_nio(int(request.match_info["port_number"]), nio) + await node.update_nio(port_number, nio) response.set_status(201) response.json(request.json) @@ -292,3 +294,25 @@ class NatHandler: port_number = int(request.match_info["port_number"]) await node.stop_capture(port_number) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/nat/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture (always 0)", + "port_number": "Port on the nat" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + builtin_manager = Builtin.instance() + node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = node.get_nio(port_number) + await builtin_manager.stream_pcap_file(nio, node.project.id, request, response) diff --git a/gns3server/handlers/api/compute/project_handler.py b/gns3server/handlers/api/compute/project_handler.py index 2834e4ec..fc207967 100644 --- a/gns3server/handlers/api/compute/project_handler.py +++ b/gns3server/handlers/api/compute/project_handler.py @@ -295,6 +295,7 @@ class ProjectHandler: response.set_status(200) response.enable_chunked_encoding() + # FIXME: file streaming is never stopped try: with open(path, "rb") as f: await response.prepare(request) @@ -303,7 +304,6 @@ class ProjectHandler: if not data: await asyncio.sleep(0.1) await response.write(data) - except FileNotFoundError: raise aiohttp.web.HTTPNotFound() except PermissionError: diff --git a/gns3server/handlers/api/compute/qemu_handler.py b/gns3server/handlers/api/compute/qemu_handler.py index 41aba44d..d505275c 100644 --- a/gns3server/handlers/api/compute/qemu_handler.py +++ b/gns3server/handlers/api/compute/qemu_handler.py @@ -334,17 +334,18 @@ class QEMUHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a Qemu instance") + description="Update a NIO on a Qemu instance") async def update_nio(request, response): qemu_manager = Qemu.instance() vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])] - if "filters" in request.json and nio: + adapter_number = int(request.match_info["adapter_number"]) + nio = vm.get_nio(adapter_number) + if "filters" in request.json: nio.filters = request.json["filters"] - if "suspend" in request.json and nio: + if "suspend" in request.json: nio.suspend = request.json["suspend"] - await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio) + await vm.adapter_update_nio_binding(adapter_number, nio) response.set_status(201) response.json(request.json) @@ -366,7 +367,8 @@ class QEMUHandler: qemu_manager = Qemu.instance() vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"])) + adapter_number = int(request.match_info["adapter_number"]) + await vm.adapter_remove_nio_binding(adapter_number) response.set_status(204) @Route.post( @@ -415,6 +417,28 @@ class QEMUHandler: await vm.stop_capture(adapter_number) response.set_status(204) + @Route.get( + r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + qemu_manager = Qemu.instance() + vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + adapter_number = int(request.match_info["adapter_number"]) + nio = vm.get_nio(adapter_number) + await qemu_manager.stream_pcap_file(nio, vm.project.id, request, response) + @Route.get( r"/qemu/binaries", status_codes={ diff --git a/gns3server/handlers/api/compute/traceng_handler.py b/gns3server/handlers/api/compute/traceng_handler.py index 360c4d87..f4061692 100644 --- a/gns3server/handlers/api/compute/traceng_handler.py +++ b/gns3server/handlers/api/compute/traceng_handler.py @@ -261,15 +261,16 @@ class TraceNGHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a TraceNG instance") + description="Update a NIO on a TraceNG instance") async def update_nio(request, response): traceng_manager = TraceNG.instance() vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - nio = vm.ethernet_adapter.get_nio(int(request.match_info["port_number"])) - if "filters" in request.json and nio: + port_number = int(request.match_info["port_number"]) + nio = vm.get_nio(port_number) + if "filters" in request.json: nio.filters = request.json["filters"] - await vm.port_update_nio_binding(int(request.match_info["port_number"]), nio) + await vm.port_update_nio_binding(port_number, nio) response.set_status(201) response.json(request.json) @@ -291,7 +292,8 @@ class TraceNGHandler: traceng_manager = TraceNG.instance() vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - await vm.port_remove_nio_binding(int(request.match_info["port_number"])) + port_number = int(request.match_info["port_number"]) + await vm.port_remove_nio_binding(port_number) response.set_status(204) @Route.post( @@ -339,3 +341,25 @@ class TraceNGHandler: port_number = int(request.match_info["port_number"]) await vm.stop_capture(port_number) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture", + "port_number": "Port on the adapter" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + traceng_manager = TraceNG.instance() + vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = vm.get_nio(port_number) + await traceng_manager.stream_pcap_file(nio, vm.project.id, request, response) diff --git a/gns3server/handlers/api/compute/virtualbox_handler.py b/gns3server/handlers/api/compute/virtualbox_handler.py index a89efe96..6e63c9f5 100644 --- a/gns3server/handlers/api/compute/virtualbox_handler.py +++ b/gns3server/handlers/api/compute/virtualbox_handler.py @@ -309,17 +309,18 @@ class VirtualBoxHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a Virtualbox instance") + description="Update a NIO on a Virtualbox instance") async def update_nio(request, response): virtualbox_manager = VirtualBox.instance() vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])] - if "filters" in request.json and nio: + adapter_number = int(request.match_info["adapter_number"]) + nio = vm.get_nio(adapter_number) + if "filters" in request.json: nio.filters = request.json["filters"] - if "suspend" in request.json and nio: + if "suspend" in request.json: nio.suspend = request.json["suspend"] - await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio) + await vm.adapter_update_nio_binding(adapter_number, nio) response.set_status(201) response.json(request.json) @@ -341,7 +342,8 @@ class VirtualBoxHandler: vbox_manager = VirtualBox.instance() vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"])) + adapter_number = int(request.match_info["adapter_number"]) + await vm.adapter_remove_nio_binding(adapter_number) response.set_status(204) @Route.post( @@ -386,9 +388,32 @@ class VirtualBoxHandler: vbox_manager = VirtualBox.instance() vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - await vm.stop_capture(int(request.match_info["adapter_number"])) + adapter_number = int(request.match_info["adapter_number"]) + await vm.stop_capture(adapter_number) response.set_status(204) + @Route.get( + r"/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + virtualbox_manager = VirtualBox.instance() + vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + adapter_number = int(request.match_info["adapter_number"]) + nio = vm.get_nio(adapter_number) + await virtualbox_manager.stream_pcap_file(nio, vm.project.id, request, response) + @Route.get( r"/virtualbox/vms", status_codes={ diff --git a/gns3server/handlers/api/compute/vmware_handler.py b/gns3server/handlers/api/compute/vmware_handler.py index fa5f0b90..9c596deb 100644 --- a/gns3server/handlers/api/compute/vmware_handler.py +++ b/gns3server/handlers/api/compute/vmware_handler.py @@ -276,17 +276,18 @@ class VMwareHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a Virtualbox instance") + description="Update a NIO on a VMware VM instance") async def update_nio(request, response): vmware_manager = VMware.instance() vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])] - if "filters" in request.json and nio: + adapter_number = int(request.match_info["adapter_number"]) + nio = vm.get_nio(adapter_number) + if "filters" in request.json: nio.filters = request.json["filters"] - await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio) - response.set_status(201) - response.json(request.json) + await vm.adapter_update_nio_binding(adapter_number, nio) + response.set_status(201) + response.json(request.json) @Route.delete( r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio", @@ -306,7 +307,8 @@ class VMwareHandler: vmware_manager = VMware.instance() vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"])) + adapter_number = int(request.match_info["adapter_number"]) + await vm.adapter_remove_nio_binding(adapter_number) response.set_status(204) @Route.post( @@ -355,6 +357,28 @@ class VMwareHandler: await vm.stop_capture(adapter_number) response.set_status(204) + @Route.get( + r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + vmware_manager = VMware.instance() + vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + adapter_number = int(request.match_info["adapter_number"]) + nio = vm.get_nio(adapter_number) + await vmware_manager.stream_pcap_file(nio, vm.project.id, request, response) + @Route.post( r"/projects/{project_id}/vmware/nodes/{node_id}/interfaces/vmnet", parameters={ diff --git a/gns3server/handlers/api/compute/vpcs_handler.py b/gns3server/handlers/api/compute/vpcs_handler.py index 0d3ba5f9..51a3ca7e 100644 --- a/gns3server/handlers/api/compute/vpcs_handler.py +++ b/gns3server/handlers/api/compute/vpcs_handler.py @@ -239,8 +239,9 @@ class VPCSHandler: nio_type = request.json["type"] if nio_type not in ("nio_udp", "nio_tap"): raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) + port_number = int(request.match_info["port_number"]) nio = vpcs_manager.create_nio(request.json) - await vm.port_add_nio_binding(int(request.match_info["port_number"]), nio) + await vm.port_add_nio_binding(port_number, nio) response.set_status(201) response.json(nio) @@ -259,15 +260,16 @@ class VPCSHandler: }, input=NIO_SCHEMA, output=NIO_SCHEMA, - description="Update a NIO from a VPCS instance") + description="Update a NIO on a VPCS instance") async def update_nio(request, response): vpcs_manager = VPCS.instance() vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - nio = vm.ethernet_adapter.get_nio(int(request.match_info["port_number"])) - if "filters" in request.json and nio: + port_number = int(request.match_info["port_number"]) + nio = vm.get_nio(port_number) + if "filters" in request.json: nio.filters = request.json["filters"] - await vm.port_update_nio_binding(int(request.match_info["port_number"]), nio) + await vm.port_update_nio_binding(port_number, nio) response.set_status(201) response.json(request.json) @@ -289,7 +291,8 @@ class VPCSHandler: vpcs_manager = VPCS.instance() vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) - await vm.port_remove_nio_binding(int(request.match_info["port_number"])) + port_number = int(request.match_info["port_number"]) + await vm.port_remove_nio_binding(port_number) response.set_status(204) @Route.post( @@ -337,3 +340,25 @@ class VPCSHandler: port_number = int(request.match_info["port_number"]) await vm.stop_capture(port_number) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap", + description="Stream the pcap capture file", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to steam a packet capture", + "port_number": "Port on the adapter" + }, + status_codes={ + 200: "File returned", + 403: "Permission denied", + 404: "The file doesn't exist" + }) + async def stream_pcap_file(request, response): + + vpcs_manager = VPCS.instance() + vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = vm.get_nio(port_number) + await vpcs_manager.stream_pcap_file(nio, vm.project.id, request, response) diff --git a/gns3server/handlers/api/controller/link_handler.py b/gns3server/handlers/api/controller/link_handler.py index b4176fe7..7854583a 100644 --- a/gns3server/handlers/api/controller/link_handler.py +++ b/gns3server/handlers/api/controller/link_handler.py @@ -15,9 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os -import asyncio import aiohttp +import multidict from gns3server.web.route import Route from gns3server.controller import Controller @@ -160,7 +159,8 @@ class LinkHandler: project = await Controller.instance().get_loaded_project(request.match_info["project_id"]) link = project.get_link(request.match_info["link_id"]) - await link.start_capture(data_link_type=request.json.get("data_link_type", "DLT_EN10MB"), capture_file_name=request.json.get("capture_file_name")) + await link.start_capture(data_link_type=request.json.get("data_link_type", "DLT_EN10MB"), + capture_file_name=request.json.get("capture_file_name")) response.set_status(201) response.json(link) @@ -206,7 +206,7 @@ class LinkHandler: "project_id": "Project UUID", "link_id": "Link UUID" }, - description="Stream the pcap capture file", + description="Stream the PCAP capture file from compute", status_codes={ 200: "File returned", 403: "Permission denied", @@ -216,25 +216,25 @@ class LinkHandler: project = await Controller.instance().get_loaded_project(request.match_info["project_id"]) link = project.get_link(request.match_info["link_id"]) + if not link.capturing: + raise aiohttp.web.HTTPConflict(text="This link has no active packet capture") - while link.capture_file_path is None: - raise aiohttp.web.HTTPNotFound(text="pcap file not found") + compute = link.compute + pcap_streaming_url = link.pcap_streaming_url() + headers = multidict.MultiDict(request.headers) + headers['Host'] = compute.host + headers['Router-Host'] = request.host + body = await request.read() - while not os.path.isfile(link.capture_file_path): - await asyncio.sleep(0.5) + connector = aiohttp.TCPConnector(limit=None, force_close=True) + async with aiohttp.ClientSession(connector=connector, headers=headers) as session: + async with session.request(request.method, pcap_streaming_url, timeout=None, data=body) as response: + proxied_response = aiohttp.web.Response(headers=response.headers, status=response.status) + if response.headers.get('Transfer-Encoding', '').lower() == 'chunked': + proxied_response.enable_chunked_encoding() - try: - with open(link.capture_file_path, "rb") as f: - - response.content_type = "application/vnd.tcpdump.pcap" - response.set_status(200) - response.enable_chunked_encoding() - await response.prepare(request) - - while True: - chunk = f.read(4096) - if not chunk: - await asyncio.sleep(0.1) - await response.write(chunk) - except OSError: - raise aiohttp.web.HTTPNotFound(text="pcap file {} not found or not accessible".format(link.capture_file_path)) + await proxied_response.prepare(request) + async for data in response.content.iter_any(): + if not data: + break + await proxied_response.write(data) diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 4f36932a..41dec3f8 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -248,9 +248,10 @@ class Route(object): """ To avoid strange effect we prevent concurrency between the same instance of the node + (excepting when streaming a PCAP file). """ - if "node_id" in request.match_info: + if "node_id" in request.match_info and not "pcap" in request.path: node_id = request.match_info.get("node_id") if "compute" in request.path: diff --git a/tests/controller/test_link.py b/tests/controller/test_link.py index ed870933..7fc4d5da 100644 --- a/tests/controller/test_link.py +++ b/tests/controller/test_link.py @@ -282,23 +282,6 @@ def test_json_serial_link(async_run, project, compute, link): async_run(link.add_node(node2, 1, 3)) assert link.__json__()["link_type"] == "serial" - -def test_start_streaming_pcap(link, async_run, tmpdir, project): - - async def fake_reader(): - output = AsyncioBytesIO() - await output.write(b"hello") - output.seek(0) - return output - - link._capture_file_name = "test.pcap" - link._capturing = True - link.read_pcap_from_source = fake_reader - async_run(link._start_streaming_pcap()) - with open(os.path.join(project.captures_directory, "test.pcap"), "rb") as f: - c = f.read() - assert c == b"hello" - def test_default_capture_file_name(project, compute, async_run): node1 = Node(project, compute, "Hello@", node_type="qemu") node1._ports = [EthernetPort("E0", 0, 0, 4)] diff --git a/tests/controller/test_udp_link.py b/tests/controller/test_udp_link.py index 0d1d3713..ab7b5b1f 100644 --- a/tests/controller/test_udp_link.py +++ b/tests/controller/test_udp_link.py @@ -266,27 +266,6 @@ def test_capture(async_run, project): compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/stop_capture".format(project.id, node_vpcs.id)) -def test_read_pcap_from_source(project, async_run): - compute1 = MagicMock() - - node_vpcs = Node(project, compute1, "V1", node_type="vpcs") - node_vpcs._status = "started" - node_vpcs._ports = [EthernetPort("E0", 0, 0, 4)] - node_iou = Node(project, compute1, "I1", node_type="iou") - node_iou._ports = [EthernetPort("E0", 0, 3, 1)] - - link = UDPLink(project) - link.create = AsyncioMagicMock() - async_run(link.add_node(node_vpcs, 0, 4)) - async_run(link.add_node(node_iou, 3, 1)) - - capture = async_run(link.start_capture()) - assert link._capture_node is not None - - async_run(link.read_pcap_from_source()) - link._capture_node["node"].compute.stream_file.assert_called_with(project, "tmp/captures/" + link._capture_file_name) - - def test_node_updated(project, async_run): """ If a node stop when capturing we stop the capture diff --git a/tests/handlers/api/compute/test_cloud.py b/tests/handlers/api/compute/test_cloud.py index 737b3c6e..0d96a80b 100644 --- a/tests/handlers/api/compute/test_cloud.py +++ b/tests/handlers/api/compute/test_cloud.py @@ -108,3 +108,29 @@ def test_cloud_update(http_compute, vm, tmpdir): example=True) assert response.status == 200 assert response.json["name"] == "test" + + +def test_cloud_start_capture(http_compute, vm): + + with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.start_capture") as start_capture: + params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"} + response = http_compute.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True) + assert response.status == 200 + assert start_capture.called + assert "test.pcap" in response.json["pcap_file_path"] + + +def test_cloud_stop_capture(http_compute, vm): + + with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.stop_capture") as stop_capture: + response = http_compute.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True) + assert response.status == 204 + assert stop_capture.called + + +def test_cloud_pcap(http_compute, vm, project): + + with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.get_nio"): + with asyncio_patch("gns3server.compute.builtin.Builtin.stream_pcap_file"): + response = http_compute.get("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True) + assert response.status == 200 diff --git a/tests/handlers/api/compute/test_docker.py b/tests/handlers/api/compute/test_docker.py index a5018703..3f9c3618 100644 --- a/tests/handlers/api/compute/test_docker.py +++ b/tests/handlers/api/compute/test_docker.py @@ -125,6 +125,12 @@ def test_docker_nio_create_udp(http_compute, vm): def test_docker_update_nio(http_compute, vm): + + response = http_compute.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"type": "nio_udp", + "lport": 4242, + "rport": 4343, + "rhost": "127.0.0.1"}) + assert response.status == 201 with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.adapter_update_nio_binding") as mock: response = http_compute.put("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), { @@ -166,12 +172,9 @@ def test_docker_start_capture(http_compute, vm, tmpdir, project): with patch("gns3server.compute.docker.docker_vm.DockerVM.is_running", return_value=True) as mock: with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start_capture") as start_capture: - params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"} response = http_compute.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True) - assert response.status == 200 - assert start_capture.called assert "test.pcap" in response.json["pcap_file_path"] @@ -180,11 +183,8 @@ def test_docker_stop_capture(http_compute, vm, tmpdir, project): with patch("gns3server.compute.docker.docker_vm.DockerVM.is_running", return_value=True) as mock: with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.stop_capture") as stop_capture: - response = http_compute.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True) - assert response.status == 204 - assert stop_capture.called diff --git a/tests/handlers/api/compute/test_iou.py b/tests/handlers/api/compute/test_iou.py index 74147a8e..5365f921 100644 --- a/tests/handlers/api/compute/test_iou.py +++ b/tests/handlers/api/compute/test_iou.py @@ -290,6 +290,14 @@ def test_iou_stop_capture(http_compute, vm, tmpdir, project): assert stop_capture.called +def test_iou_pcap(http_compute, vm, project): + + with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.get_nio"): + with asyncio_patch("gns3server.compute.iou.IOU.stream_pcap_file"): + response = http_compute.get("/projects/{project_id}/iou/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True) + assert response.status == 200 + + def test_images(http_compute, fake_iou_bin): response = http_compute.get("/iou/images", example=True) diff --git a/tests/handlers/api/compute/test_nat.py b/tests/handlers/api/compute/test_nat.py index 07504410..f0ff7776 100644 --- a/tests/handlers/api/compute/test_nat.py +++ b/tests/handlers/api/compute/test_nat.py @@ -112,3 +112,29 @@ def test_nat_update(http_compute, vm, tmpdir): example=True) assert response.status == 200 assert response.json["name"] == "test" + + +def test_nat_start_capture(http_compute, vm): + + with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.start_capture") as start_capture: + params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"} + response = http_compute.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True) + assert response.status == 200 + assert start_capture.called + assert "test.pcap" in response.json["pcap_file_path"] + + +def test_nat_stop_capture(http_compute, vm): + + with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.stop_capture") as stop_capture: + response = http_compute.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True) + assert response.status == 204 + assert stop_capture.called + + +def test_nat_pcap(http_compute, vm, project): + + with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.get_nio"): + with asyncio_patch("gns3server.compute.builtin.Builtin.stream_pcap_file"): + response = http_compute.get("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True) + assert response.status == 200 diff --git a/tests/handlers/api/compute/test_qemu.py b/tests/handlers/api/compute/test_qemu.py index a1a9ab88..7c63edcd 100644 --- a/tests/handlers/api/compute/test_qemu.py +++ b/tests/handlers/api/compute/test_qemu.py @@ -361,3 +361,31 @@ def test_qemu_duplicate(http_compute, vm): example=True) assert mock.called assert response.status == 201 + + +def test_qemu_start_capture(http_compute, vm): + + with patch("gns3server.compute.qemu.qemu_vm.QemuVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.start_capture") as start_capture: + params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"} + response = http_compute.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True) + assert response.status == 200 + assert start_capture.called + assert "test.pcap" in response.json["pcap_file_path"] + + +def test_qemu_stop_capture(http_compute, vm): + + with patch("gns3server.compute.qemu.qemu_vm.QemuVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.stop_capture") as stop_capture: + response = http_compute.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True) + assert response.status == 204 + assert stop_capture.called + + +def test_qemu_pcap(http_compute, vm, project): + + with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.get_nio"): + with asyncio_patch("gns3server.compute.qemu.Qemu.stream_pcap_file"): + response = http_compute.get("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True) + assert response.status == 200 diff --git a/tests/handlers/api/compute/test_traceng.py b/tests/handlers/api/compute/test_traceng.py index c248bfa1..f9ece5e5 100644 --- a/tests/handlers/api/compute/test_traceng.py +++ b/tests/handlers/api/compute/test_traceng.py @@ -60,6 +60,13 @@ def test_traceng_nio_create_udp(http_compute, vm): def test_traceng_nio_update_udp(http_compute, vm): + + with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.add_ubridge_udp_connection"): + response = http_compute.post("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"type": "nio_udp", + "lport": 4242, + "rport": 4343, + "rhost": "127.0.0.1"}) + assert response.status == 201 response = http_compute.put("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), { "type": "nio_udp", @@ -136,3 +143,31 @@ def test_traceng_update(http_compute, vm, tmpdir, free_console_port): assert response.status == 200 assert response.json["name"] == "test" assert response.json["ip_address"] == "192.168.1.1" + + +def test_traceng_start_capture(http_compute, vm): + + with patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.start_capture") as start_capture: + params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"} + response = http_compute.post("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True) + assert response.status == 200 + assert start_capture.called + assert "test.pcap" in response.json["pcap_file_path"] + + +def test_traceng_stop_capture(http_compute, vm): + + with patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.stop_capture") as stop_capture: + response = http_compute.post("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True) + assert response.status == 204 + assert stop_capture.called + + +def test_traceng_pcap(http_compute, vm, project): + + with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.get_nio"): + with asyncio_patch("gns3server.compute.traceng.TraceNG.stream_pcap_file"): + response = http_compute.get("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True) + assert response.status == 200 diff --git a/tests/handlers/api/compute/test_virtualbox.py b/tests/handlers/api/compute/test_virtualbox.py index 49fbb462..743141c7 100644 --- a/tests/handlers/api/compute/test_virtualbox.py +++ b/tests/handlers/api/compute/test_virtualbox.py @@ -113,6 +113,7 @@ def test_vbox_nio_create_udp(http_compute, vm): def test_virtualbox_nio_update_udp(http_compute, vm): + with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.ethernet_adapters'): with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_remove_nio_binding') as mock: response = http_compute.put("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/nio".format( @@ -150,3 +151,31 @@ def test_vbox_update(http_compute, vm, free_console_port): assert response.status == 200 assert response.json["name"] == "test" assert response.json["console"] == free_console_port + + +def test_virtualbox_start_capture(http_compute, vm): + + with patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.start_capture") as start_capture: + params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"} + response = http_compute.post("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True) + assert response.status == 200 + assert start_capture.called + assert "test.pcap" in response.json["pcap_file_path"] + + +def test_virtualbox_stop_capture(http_compute, vm): + + with patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.stop_capture") as stop_capture: + response = http_compute.post("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True) + assert response.status == 204 + assert stop_capture.called + + +def test_virtualbox_pcap(http_compute, vm, project): + + with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.get_nio"): + with asyncio_patch("gns3server.compute.virtualbox.VirtualBox.stream_pcap_file"): + response = http_compute.get("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True) + assert response.status == 200 diff --git a/tests/handlers/api/compute/test_vmware.py b/tests/handlers/api/compute/test_vmware.py index e1f3acaf..3cca7cd1 100644 --- a/tests/handlers/api/compute/test_vmware.py +++ b/tests/handlers/api/compute/test_vmware.py @@ -159,3 +159,30 @@ def test_vmware_update(http_compute, vm, free_console_port): assert response.status == 200 assert response.json["name"] == "test" assert response.json["console"] == free_console_port + +def test_vmware_start_capture(http_compute, vm): + + with patch("gns3server.compute.vmware.vmware_vm.VMwareVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.start_capture") as start_capture: + params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"} + response = http_compute.post("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True) + assert response.status == 200 + assert start_capture.called + assert "test.pcap" in response.json["pcap_file_path"] + + +def test_vmware_stop_capture(http_compute, vm): + + with patch("gns3server.compute.vmware.vmware_vm.VMwareVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.stop_capture") as stop_capture: + response = http_compute.post("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True) + assert response.status == 204 + assert stop_capture.called + + +def test_vmware_pcap(http_compute, vm, project): + + with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.get_nio"): + with asyncio_patch("gns3server.compute.vmware.VMware.stream_pcap_file"): + response = http_compute.get("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True) + assert response.status == 200 diff --git a/tests/handlers/api/compute/test_vpcs.py b/tests/handlers/api/compute/test_vpcs.py index e72d4021..9cd4ce13 100644 --- a/tests/handlers/api/compute/test_vpcs.py +++ b/tests/handlers/api/compute/test_vpcs.py @@ -17,8 +17,6 @@ import pytest import uuid -import sys -import os from tests.utils import asyncio_patch from unittest.mock import patch @@ -77,6 +75,13 @@ def test_vpcs_nio_create_udp(http_compute, vm): def test_vpcs_nio_update_udp(http_compute, vm): + + with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.add_ubridge_udp_connection"): + response = http_compute.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"type": "nio_udp", + "lport": 4242, + "rport": 4343, + "rhost": "127.0.0.1"}) + assert response.status == 201 response = http_compute.put("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), { "type": "nio_udp", @@ -153,3 +158,31 @@ def test_vpcs_update(http_compute, vm, tmpdir, free_console_port): assert response.status == 200 assert response.json["name"] == "test" assert response.json["console"] == free_console_port + + +def test_vpcs_start_capture(http_compute, vm): + + with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.start_capture") as start_capture: + params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"} + response = http_compute.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True) + assert response.status == 200 + assert start_capture.called + assert "test.pcap" in response.json["pcap_file_path"] + + +def test_vpcs_stop_capture(http_compute, vm): + + with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.is_running", return_value=True): + with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.stop_capture") as stop_capture: + response = http_compute.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True) + assert response.status == 204 + assert stop_capture.called + + +def test_vpcs_pcap(http_compute, vm, project): + + with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.get_nio"): + with asyncio_patch("gns3server.compute.vpcs.VPCS.stream_pcap_file"): + response = http_compute.get("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True) + assert response.status == 200 diff --git a/tests/handlers/api/controller/test_link.py b/tests/handlers/api/controller/test_link.py index 135c51fb..172f3952 100644 --- a/tests/handlers/api/controller/test_link.py +++ b/tests/handlers/api/controller/test_link.py @@ -340,22 +340,23 @@ def test_stop_capture(http_controller, tmpdir, project, compute, async_run): assert response.status == 201 -def test_pcap(http_controller, tmpdir, project, compute, async_run): - - async def go(): - async with aiohttp.request("GET", http_controller.get_url("/projects/{}/links/{}/pcap".format(project.id, link.id))) as response: - response.body = await response.content.read(5) - return response - - link = Link(project) - link._capture_file_name = "test" - link._capturing = True - with open(link.capture_file_path, "w+") as f: - f.write("hello") - project._links = {link.id: link} - response = async_run(asyncio.ensure_future(go())) - assert response.status == 200 - assert b'hello' == response.body +# def test_pcap(http_controller, tmpdir, project, compute, async_run): +# +# async def go(): +# async with aiohttp.request("GET", http_controller.get_url("/projects/{}/links/{}/pcap".format(project.id, link.id))) as response: +# response.body = await response.content.read(5) +# return response +# +# with asyncio_patch("gns3server.controller.link.Link.capture_node") as mock: +# link = Link(project) +# link._capture_file_name = "test" +# link._capturing = True +# with open(link.capture_file_path, "w+") as f: +# f.write("hello") +# project._links = {link.id: link} +# response = async_run(asyncio.ensure_future(go())) +# assert response.status == 200 +# assert b'hello' == response.body def test_delete_link(http_controller, tmpdir, project, compute, async_run):