From 04022677bd40ce0275ce9ab4eb7084a3602ce6cc Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 23 Jun 2016 16:56:06 -0600 Subject: [PATCH] Use uBridge for Qemu connections. Ref #267. Handle packet captures for VPCS & Qemu nodes. Fixes #548. --- gns3server/compute/base_node.py | 54 ++++++++ gns3server/compute/docker/__init__.py | 1 + gns3server/compute/docker/docker_vm.py | 42 +++--- gns3server/compute/qemu/qemu_vm.py | 124 ++++++++++++++---- gns3server/compute/vmware/vmware_vm.py | 21 ++- gns3server/compute/vpcs/vpcs_vm.py | 57 +------- gns3server/controller/udp_link.py | 4 +- .../handlers/api/compute/qemu_handler.py | 53 +++++++- .../handlers/api/compute/vpcs_handler.py | 7 +- 9 files changed, 244 insertions(+), 119 deletions(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index 85a44396..8243f206 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -25,9 +25,11 @@ import tempfile import psutil import platform +from ..compute.port_manager import PortManager from ..utils.asyncio import wait_run_in_executor from ..ubridge.hypervisor import Hypervisor from ..ubridge.ubridge_error import UbridgeError +from .nios.nio_udp import NIOUDP from .error import NodeError @@ -491,6 +493,58 @@ class BaseNode: if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running(): yield from self._ubridge_hypervisor.stop() + @asyncio.coroutine + def _add_ubridge_udp_connection(self, bridge_name, source_nio, destination_nio): + """ + Creates a connection in uBridge. + + :param bridge_name: bridge name in uBridge + :param source_nio: source NIO instance + :param destination_nio: destination NIO instance + """ + + yield from self._ubridge_send("bridge create {name}".format(name=bridge_name)) + + if not isinstance(destination_nio, NIOUDP): + raise NodeError("Destination NIO is not UDP") + + yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=bridge_name, + lport=source_nio.lport, + rhost=source_nio.rhost, + rport=source_nio.rport)) + + yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=bridge_name, + lport=destination_nio.lport, + rhost=destination_nio.rhost, + rport=destination_nio.rport)) + + if destination_nio.capturing: + yield from self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(name=bridge_name, + pcap_file=destination_nio.pcap_output_file)) + + yield from self._ubridge_send('bridge start {name}'.format(name=bridge_name)) + + def _create_local_udp_tunnel(self): + """ + Creates a local UDP tunnel (pair of 2 NIOs, one for each direction) + + :returns: source NIO and destination NIO. + """ + + m = PortManager.instance() + lport = m.get_free_udp_port(self.project) + rport = m.get_free_udp_port(self.project) + source_nio_settings = {'lport': lport, 'rhost': '127.0.0.1', 'rport': rport, 'type': 'nio_udp'} + destination_nio_settings = {'lport': rport, 'rhost': '127.0.0.1', 'rport': lport, 'type': 'nio_udp'} + source_nio = self.manager.create_nio(self.ubridge_path, source_nio_settings) + destination_nio = self.manager.create_nio(self.ubridge_path, destination_nio_settings) + log.info("{module}: '{name}' [{id}]:local UDP tunnel created between port {port1} and {port2}".format(module=self.manager.module_name, + name=self.name, + id=self.id, + port1=lport, + port2=rport)) + return source_nio, destination_nio + @property def hw_virtualization(self): """ diff --git a/gns3server/compute/docker/__init__.py b/gns3server/compute/docker/__init__.py index 714baea0..1fbec52a 100644 --- a/gns3server/compute/docker/__init__.py +++ b/gns3server/compute/docker/__init__.py @@ -19,6 +19,7 @@ Docker server module. """ +import sys import asyncio import logging import aiohttp diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index c14e0bf4..6efbb619 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -48,7 +48,8 @@ log = logging.getLogger(__name__) class DockerVM(BaseNode): - """Docker container implementation. + """ + Docker container implementation. :param name: Docker container name :param node_id: Node identifier @@ -63,13 +64,13 @@ class DockerVM(BaseNode): :param console_http_path: Url part with the path of the web interface """ - def __init__(self, name, node_id, project, manager, image, - console=None, aux=None, start_command=None, - adapters=None, environment=None, console_type="telnet", - console_resolution="1024x768", console_http_port=80, console_http_path="/"): + def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None, + adapters=None, environment=None, console_type="telnet", console_resolution="1024x768", + console_http_port=80, console_http_path="/"): + super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type) - # If no version is specified force latest + # force the latest image if no version is specified if ":" not in image: image = "{}:latest".format(image) self._image = image @@ -91,11 +92,9 @@ class DockerVM(BaseNode): else: self.adapters = adapters - log.debug( - "{module}: {name} [{image}] initialized.".format( - module=self.manager.module_name, - name=self.name, - image=self._image)) + log.debug("{module}: {name} [{image}] initialized.".format(module=self.manager.module_name, + name=self.name, + image=self._image)) def __json__(self): return { @@ -401,14 +400,13 @@ class DockerVM(BaseNode): for volume in self._volumes: log.debug("Docker container '{name}' [{image}] fix ownership on {path}".format( name=self._name, image=self._image, path=volume)) - process = yield from asyncio.subprocess.create_subprocess_exec( - "docker", - "exec", - self._cid, - "/gns3/bin/busybox", - "sh", - "-c", - "(/gns3/bin/busybox find \"{path}\" -depth -print0 | /gns3/bin/busybox xargs -0 /gns3/bin/busybox stat -c '%a:%u:%g:%n' > \"{path}/.gns3_perms\") && /gns3/bin/busybox chmod -R u+rX \"{path}\" && /gns3/bin/busybox chown {uid}:{gid} -R \"{path}\"".format(uid=os.getuid(), gid=os.getgid(), path=volume)) + process = yield from asyncio.subprocess.create_subprocess_exec("docker", + "exec", + self._cid, + "/gns3/bin/busybox", + "sh", + "-c", + "(/gns3/bin/busybox find \"{path}\" -depth -print0 | /gns3/bin/busybox xargs -0 /gns3/bin/busybox stat -c '%a:%u:%g:%n' > \"{path}/.gns3_perms\") && /gns3/bin/busybox chmod -R u+rX \"{path}\" && /gns3/bin/busybox chown {uid}:{gid} -R \"{path}\"".format(uid=os.getuid(), gid=os.getgid(), path=volume)) yield from process.wait() @asyncio.coroutine @@ -614,10 +612,11 @@ class DockerVM(BaseNode): """ Creates a connection in uBridge. - :param nio: NIO instance or None if it's a dummu interface (if an interface is missing in ubridge you can't see it via ifconfig in the container) + :param nio: NIO instance or None if it's a dummy interface (if an interface is missing in ubridge you can't see it via ifconfig in the container) :param adapter_number: adapter number :param namespace: Container namespace (pid) """ + try: adapter = self._ethernet_adapters[adapter_number] except IndexError: @@ -670,8 +669,7 @@ class DockerVM(BaseNode): adapter = self._ethernet_adapters[adapter_number] try: - yield from self._ubridge_send("bridge delete bridge{name}".format( - name=adapter_number)) + yield from self._ubridge_send("bridge delete bridge{name}".format(name=adapter_number)) except UbridgeError as e: log.debug(str(e)) try: diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 0dd3e5a1..8cb6f202 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -75,6 +75,7 @@ class QemuVM(BaseNode): self._monitor = None self._stdout_file = "" self._execute_lock = asyncio.Lock() + self._local_udp_tunnels = {} # QEMU VM settings if qemu_path: @@ -882,8 +883,17 @@ class QemuVM(BaseNode): stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir) - log.info('QEMU VM "{}" started PID={}'.format(self._name, self._process.pid)) + if self.use_ubridge: + yield from self._start_ubridge() + for adapter_number, adapter in enumerate(self._ethernet_adapters): + nio = adapter.get_nio(0) + if nio: + yield from self._add_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number), + self._local_udp_tunnels[adapter_number][1], + nio) + + log.info('QEMU VM "{}" started PID={}'.format(self._name, self._process.pid)) self.status = "started" monitor_process(self._process, self._termination_callback) except (OSError, subprocess.SubprocessError, UnicodeEncodeError) as e: @@ -919,6 +929,7 @@ class QemuVM(BaseNode): Stops this QEMU VM. """ + yield from self._stop_ubridge() with (yield from self._execute_lock): # stop the QEMU process self._hw_virtualization = False @@ -1003,6 +1014,11 @@ class QemuVM(BaseNode): if nio and isinstance(nio, NIOUDP): self.manager.port_manager.release_udp_port(nio.lport, self._project) + for udp_tunnel in self._local_udp_tunnels.values(): + self.manager.port_manager.release_udp_port(udp_tunnel[0].lport, self._project) + self.manager.port_manager.release_udp_port(udp_tunnel[1].lport, self._project) + self._local_udp_tunnels = {} + @asyncio.coroutine def _get_vm_status(self): """ @@ -1081,27 +1097,12 @@ class QemuVM(BaseNode): adapter_number=adapter_number)) if self.is_running(): - raise QemuError("Sorry, adding a link to a started Qemu VM is not supported.") - # FIXME: does the code below work? very undocumented feature... - # dynamically configure an UDP tunnel on the QEMU VM adapter - # if nio and isinstance(nio, NIOUDP): - # if self._legacy_networking: - # yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number)) - # yield from self._control_vm("host_net_add udp vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number, - # adapter_number, - # nio.lport, - # nio.rport, - # nio.rhost)) - # else: - # # Apparently there is a bug in Qemu... - # # netdev_add [user|tap|socket|hubport|netmap],id=str[,prop=value][,...] -- add host network device - # # netdev_del id -- remove host network device - # yield from self._control_vm("netdev_del gns3-{}".format(adapter_number)) - # yield from self._control_vm("netdev_add socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, - # nio.rhost, - # nio.rport, - # self._host, - # nio.lport)) + if self.ubridge: + yield from self._add_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number), + self._local_udp_tunnels[adapter_number][1], + nio) + else: + raise QemuError("Sorry, adding a link to a started Qemu VM is not supported without using uBridge.") adapter.add_nio(0, nio) log.info('QEMU VM "{name}" [{id}]: {nio} added to adapter {adapter_number}'.format(name=self._name, @@ -1126,21 +1127,83 @@ class QemuVM(BaseNode): adapter_number=adapter_number)) if self.is_running(): - # FIXME: does the code below work? very undocumented feature... - # dynamically disable the QEMU VM adapter - yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number)) - yield from self._control_vm("host_net_add user vlan={},name=gns3-{}".format(adapter_number, adapter_number)) + if self.ubridge: + yield from self._ubridge_send("bridge delete {name}".format(name="QEMU-{}-{}".format(self._id, adapter_number))) + else: + raise QemuError("Sorry, removing a link to a started Qemu VM is not supported without using uBridge.") nio = adapter.get_nio(0) if isinstance(nio, NIOUDP): self.manager.port_manager.release_udp_port(nio.lport, self._project) adapter.remove_nio(0) + log.info('QEMU VM "{name}" [{id}]: {nio} removed from adapter {adapter_number}'.format(name=self._name, id=self._id, nio=nio, adapter_number=adapter_number)) return nio + @asyncio.coroutine + def start_capture(self, adapter_number, output_file): + """ + Starts a packet capture. + + :param adapter_number: adapter number + :param output_file: PCAP destination file for the capture + """ + + try: + adapter = self._ethernet_adapters[adapter_number] + except IndexError: + raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, + adapter_number=adapter_number)) + + if not self.use_ubridge: + raise QemuError("uBridge must be enabled in order to start packet capture") + + nio = adapter.get_nio(0) + + if not nio: + raise QemuError("Adapter {} is not connected".format(adapter_number)) + + if nio.capturing: + raise QemuError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number)) + + nio.startPacketCapture(output_file) + + if self.is_running() and self.ubridge: + yield from self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name="QEMU-{}-{}".format(self._id, adapter_number), + output_file=output_file)) + + log.info("QEMU VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name, + id=self.id, + adapter_number=adapter_number)) + + def stop_capture(self, adapter_number): + """ + Stops a packet capture. + + :param adapter_number: adapter number + """ + + try: + adapter = self._ethernet_adapters[adapter_number] + except 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.stopPacketCapture() + + if self.is_running() and self.ubridge: + yield from self._ubridge_send('bridge stop_capture {name}'.format(name="QEMU-{}-{}".format(self._id, adapter_number))) + + log.info("QEMU VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name, + id=self.id, + adapter_number=adapter_number)) @property def started(self): """ @@ -1332,7 +1395,14 @@ class QemuVM(BaseNode): for adapter_number, adapter in enumerate(self._ethernet_adapters): mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number) - nio = adapter.get_nio(0) + + if self.use_ubridge: + # use a local UDP tunnel to connect to uBridge instead + if adapter_number not in self._local_udp_tunnels: + self._local_udp_tunnels[adapter_number] = self._create_local_udp_tunnel() + nio = self._local_udp_tunnels[adapter_number][0] + else: + nio = adapter.get_nio(0) if self._legacy_networking: # legacy QEMU networking syntax (-net) if nio: diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py index 360b2c0e..d84e4fc6 100644 --- a/gns3server/compute/vmware/vmware_vm.py +++ b/gns3server/compute/vmware/vmware_vm.py @@ -298,8 +298,7 @@ class VMwareVM(BaseNode): yield from self._ubridge_send("bridge create {name}".format(name=vnet)) vmnet_interface = os.path.basename(self._vmx_pairs[vnet]) if sys.platform.startswith("linux"): - yield from self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=vnet, - interface=vmnet_interface)) + yield from self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=vnet, interface=vmnet_interface)) elif sys.platform.startswith("win"): windows_interfaces = interfaces() npf = None @@ -312,34 +311,30 @@ class VMwareVM(BaseNode): npf = interface["id"] source_mac = interface["mac_address"] if npf: - yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet, - interface=npf)) + yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet, interface=npf)) else: raise VMwareError("Could not find NPF id for VMnet interface {}".format(vmnet_interface)) if block_host_traffic: if source_mac: - yield from self._ubridge_send('bridge set_pcap_filter {name} "not ether src {mac}"'.format(name=vnet, - mac=source_mac)) + yield from self._ubridge_send('bridge set_pcap_filter {name} "not ether src {mac}"'.format(name=vnet, mac=source_mac)) else: log.warn("Could not block host network traffic on {} (no MAC address found)".format(vmnet_interface)) elif sys.platform.startswith("darwin"): - yield from self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=vnet, - interface=vmnet_interface)) + yield from self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=vnet, interface=vmnet_interface)) else: yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet, interface=vmnet_interface)) if isinstance(nio, NIOUDP): yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=vnet, - lport=nio.lport, - rhost=nio.rhost, - rport=nio.rport)) + lport=nio.lport, + rhost=nio.rhost, + rport=nio.rport)) if nio.capturing: - yield from self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(name=vnet, - pcap_file=nio.pcap_output_file)) + yield from self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(name=vnet, pcap_file=nio.pcap_output_file)) yield from self._ubridge_send('bridge start {name}'.format(name=vnet)) diff --git a/gns3server/compute/vpcs/vpcs_vm.py b/gns3server/compute/vpcs/vpcs_vm.py index 054d8382..93dd7f46 100644 --- a/gns3server/compute/vpcs/vpcs_vm.py +++ b/gns3server/compute/vpcs/vpcs_vm.py @@ -32,7 +32,6 @@ from gns3server.utils.asyncio import wait_for_process_termination from gns3server.utils.asyncio import monitor_process from gns3server.utils.asyncio import subprocess_check_output from gns3server.utils import parse_version -from gns3server.compute.port_manager import PortManager from .vpcs_error import VPCSError from ..adapters.ethernet_adapter import EthernetAdapter @@ -259,7 +258,7 @@ class VPCSVM(BaseNode): if self.use_ubridge: yield from self._start_ubridge() if nio: - yield from self._add_ubridge_connection(self._local_udp_tunnel[1], nio) + yield from self._add_ubridge_udp_connection("VPCS-{}".format(self._id), self._local_udp_tunnel[1], nio) log.info("VPCS instance {} started PID={}".format(self.name, self._process.pid)) self._started = True @@ -358,37 +357,6 @@ class VPCSVM(BaseNode): return True return False - @asyncio.coroutine - def _add_ubridge_connection(self, source_nio, destination_nio): - """ - Creates a connection in uBridge. - - :param nio: NIO instance - :param port_number: port number - """ - - bridge_name = "VPCS-{}".format(self._id) - yield from self._ubridge_send("bridge create {name}".format(name=bridge_name)) - - if not isinstance(destination_nio, NIOUDP): - raise VPCSError("Destination NIO is not UDP") - - yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=bridge_name, - lport=source_nio.lport, - rhost=source_nio.rhost, - rport=source_nio.rport)) - - yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=bridge_name, - lport=destination_nio.lport, - rhost=destination_nio.rhost, - rport=destination_nio.rport)) - - if destination_nio.capturing: - yield from self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(name=bridge_name, - pcap_file=destination_nio.pcap_output_file)) - - yield from self._ubridge_send('bridge start {name}'.format(name=bridge_name)) - @asyncio.coroutine def port_add_nio_binding(self, port_number, nio): """ @@ -408,25 +376,10 @@ class VPCSVM(BaseNode): nio=nio, port_number=port_number)) if self._started and self.ubridge: - yield from self._add_ubridge_connection(self._local_udp_tunnel[1], nio) + yield from self._add_ubridge_udp_connection("VPCS-{}".format(self._id), self._local_udp_tunnel[1], nio) return nio - def _create_local_udp_tunnel(self): - - m = PortManager.instance() - lport = m.get_free_udp_port(self.project) - rport = m.get_free_udp_port(self.project) - source_nio_settings = {'lport': lport, 'rhost': '127.0.0.1', 'rport': rport, 'type': 'nio_udp'} - destination_nio_settings = {'lport': rport, 'rhost': '127.0.0.1', 'rport': lport, 'type': 'nio_udp'} - source_nio = self.manager.create_nio(self.ubridge_path, source_nio_settings) - destination_nio = self.manager.create_nio(self.ubridge_path, destination_nio_settings) - self._local_udp_tunnel = (source_nio, destination_nio) - log.info('VPCS "{name}" [{id}]: local UDP tunnel created between port {port1} and {port2}'.format(name=self._name, - id=self.id, - port1=lport, - port2=rport)) - @asyncio.coroutine def port_remove_nio_binding(self, port_number): """ @@ -460,7 +413,7 @@ class VPCSVM(BaseNode): """ Starts a packet capture. - :param port_number: adapter number + :param port_number: port number :param output_file: PCAP destination file for the capture """ @@ -489,6 +442,7 @@ class VPCSVM(BaseNode): id=self.id, port_number=port_number)) + @asyncio.coroutine def stop_capture(self, port_number): """ Stops a packet capture. @@ -562,7 +516,8 @@ class VPCSVM(BaseNode): if self.use_ubridge: # use the local UDP tunnel to uBridge instead - self._create_local_udp_tunnel() + if not self._local_udp_tunnel: + self._local_udp_tunnel = self._create_local_udp_tunnel() nio = self._local_udp_tunnel[0] else: nio = self._ethernet_adapter.get_nio(0) diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index aa6fc85b..36463be8 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -122,11 +122,11 @@ class UDPLink(Link): # use the local node first to save bandwidth for node in self._nodes: - if node["node"].compute.id == "local" and node["node"].node_type not in ["qemu"]: + if node["node"].compute.id == "local" and node["node"].node_type not in [""]: # FIXME return node for node in self._nodes: - if node["node"].node_type not in ["qemu"]: + if node["node"].node_type not in [""]: # FIXME return node raise aiohttp.web.HTTPConflict(text="Capture is not supported for this link") diff --git a/gns3server/handlers/api/compute/qemu_handler.py b/gns3server/handlers/api/compute/qemu_handler.py index 953fcf47..201c1dc2 100644 --- a/gns3server/handlers/api/compute/qemu_handler.py +++ b/gns3server/handlers/api/compute/qemu_handler.py @@ -23,10 +23,14 @@ from aiohttp.web import HTTPConflict from gns3server.web.route import Route from gns3server.compute.project_manager import ProjectManager from gns3server.schemas.nio import NIO_SCHEMA -from gns3server.schemas.node import NODE_LIST_IMAGES_SCHEMA from gns3server.compute.qemu import Qemu from gns3server.config import Config +from gns3server.schemas.node import ( + NODE_LIST_IMAGES_SCHEMA, + NODE_CAPTURE_SCHEMA +) + from gns3server.schemas.qemu import ( QEMU_CREATE_SCHEMA, QEMU_UPDATE_SCHEMA, @@ -288,6 +292,53 @@ class QEMUHandler: yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"])) response.set_status(204) + @Route.post( + r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to start a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 200: "Capture started", + 400: "Invalid request", + 404: "Instance doesn't exist", + }, + description="Start a packet capture on a Qemu VM instance", + input=NODE_CAPTURE_SCHEMA) + def start_capture(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"]) + pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"]) + yield from vm.start_capture(adapter_number, pcap_file_path) + response.json({"pcap_file_path": pcap_file_path}) + + + @Route.post( + r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter to stop a packet capture", + "port_number": "Port on the adapter (always 0)" + }, + status_codes={ + 204: "Capture stopped", + 400: "Invalid request", + 404: "Instance doesn't exist", + }, + description="Stop a packet capture on a Qemu VM instance") + def stop_capture(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"]) + yield from vm.stop_capture(adapter_number) + response.set_status(204) + @Route.get( r"/qemu/binaries", status_codes={ diff --git a/gns3server/handlers/api/compute/vpcs_handler.py b/gns3server/handlers/api/compute/vpcs_handler.py index b4fbf6bb..f9252a8c 100644 --- a/gns3server/handlers/api/compute/vpcs_handler.py +++ b/gns3server/handlers/api/compute/vpcs_handler.py @@ -19,8 +19,8 @@ import os from aiohttp.web import HTTPConflict from gns3server.web.route import Route from gns3server.schemas.nio import NIO_SCHEMA -from gns3server.compute.vpcs import VPCS from gns3server.schemas.node import NODE_CAPTURE_SCHEMA +from gns3server.compute.vpcs import VPCS from gns3server.schemas.vpcs import ( VPCS_CREATE_SCHEMA, @@ -227,7 +227,6 @@ class VPCSHandler: yield from vm.port_remove_nio_binding(int(request.match_info["port_number"])) response.set_status(204) - @Route.post( r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture", parameters={ @@ -244,6 +243,7 @@ class VPCSHandler: description="Start a packet capture on a VPCS instance", input=NODE_CAPTURE_SCHEMA) def start_capture(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"]) @@ -267,8 +267,9 @@ class VPCSHandler: }, description="Stop a packet capture on a VPCS instance") def stop_capture(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"]) yield from vm.stop_capture(port_number) - response.set_status(204) \ No newline at end of file + response.set_status(204)