From 9bc0287540b1bbfea6c02e4683d31054ef69a396 Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 27 Jun 2014 09:42:34 -0600 Subject: [PATCH] New feature: packet capture for IOU (not working, issue with iouyap). --- gns3server/modules/iou/__init__.py | 86 +++++++++++++++++ gns3server/modules/iou/iou_device.py | 93 ++++++++++++++++++- gns3server/modules/iou/nios/nio.py | 79 ++++++++++++++++ .../modules/iou/nios/nio_generic_ethernet.py | 5 +- gns3server/modules/iou/nios/nio_tap.py | 5 +- gns3server/modules/iou/nios/nio_udp.py | 5 +- gns3server/modules/iou/schemas.py | 70 ++++++++++++++ gns3server/modules/vpcs/vpcs_device.py | 2 +- 8 files changed, 339 insertions(+), 6 deletions(-) create mode 100644 gns3server/modules/iou/nios/nio.py diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index b42c8030..fad5eb91 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -44,6 +44,8 @@ from .schemas import IOU_RELOAD_SCHEMA from .schemas import IOU_ALLOCATE_UDP_PORT_SCHEMA from .schemas import IOU_ADD_NIO_SCHEMA from .schemas import IOU_DELETE_NIO_SCHEMA +from .schemas import IOU_START_CAPTURE_SCHEMA +from .schemas import IOU_STOP_CAPTURE_SCHEMA import logging log = logging.getLogger(__name__) @@ -669,6 +671,90 @@ class IOU(IModule): self.send_response(True) + @IModule.route("iou.start_capture") + def start_capture(self, request): + """ + Starts a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - slot (slot number) + - port (port number) + - port_id (port identifier) + - capture_file_name + + Optional request parameters: + - data_link_type (PCAP DLT_* value) + + Response parameters: + - port_id (port identifier) + - capture_file_path (path to the capture file) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, IOU_START_CAPTURE_SCHEMA): + return + + # get the instance + iou_instance = self.get_iou_instance(request["id"]) + if not iou_instance: + return + + slot = request["slot"] + port = request["port"] + capture_file_name = request["capture_file_name"] + data_link_type = request.get("data_link_type") + + try: + capture_file_path = os.path.join(self._working_dir, "captures", capture_file_name) + iou_instance.start_capture(slot, port, capture_file_path, data_link_type) + except IOUError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"], + "capture_file_path": capture_file_path} + self.send_response(response) + + @IModule.route("iou.stop_capture") + def stop_capture(self, request): + """ + Stops a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - slot (slot number) + - port (port number) + - port_id (port identifier) + + Response parameters: + - port_id (port identifier) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, IOU_STOP_CAPTURE_SCHEMA): + return + + # get the instance + iou_instance = self.get_iou_instance(request["id"]) + if not iou_instance: + return + + slot = request["slot"] + port = request["port"] + try: + iou_instance.stop_capture(slot, port) + except IOUError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"]} + self.send_response(response) + @IModule.route("iou.echo") def echo(self, request): """ diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 44cf0831..09ca54c6 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -371,7 +371,7 @@ class IOUDevice(object): if self._id in self._instances: self._instances.remove(self._id) - if self.console: + if self.console and self.console in self._allocated_console_ports: self._allocated_console_ports.remove(self.console) log.info("IOU device {name} [id={id}] has been deleted".format(name=self._name, @@ -442,7 +442,24 @@ class IOUDevice(object): connection = {"eth_dev": "{ethernet_device}".format(ethernet_device=nio.ethernet_device)} if connection: - config["{iouyap_id}:{bay}/{unit}".format(iouyap_id=str(self._id + 512), bay=bay_id, unit=unit_id)] = connection + interface = "{iouyap_id}:{bay}/{unit}".format(iouyap_id=str(self._id + 512), bay=bay_id, unit=unit_id) + config[interface] = connection + + if nio.capturing: + pcap_data_link_type = nio.pcap_data_link_type.upper() + if pcap_data_link_type == "DLT_PPP_SERIAL": + pcap_protocol = "ppp" + elif pcap_data_link_type == "DLT_C_HDLC": + pcap_protocol = "hdlc" + elif pcap_data_link_type == "DLT_FRELAY": + pcap_protocol = "fr" + else: + pcap_protocol = "ethernet" + capture_info = {"pcap_file": "{pcap_file}".format(pcap_file=nio.pcap_output_file), + "pcap_protocol": pcap_protocol, + "pcap_overwrite": "y"} + config[interface].update(capture_info) + unit_id += 1 bay_id += 1 @@ -987,3 +1004,75 @@ class IOUDevice(object): adapters=len(self._serial_adapters))) self._slots = self._ethernet_adapters + self._serial_adapters + + def start_capture(self, slot_id, port_id, output_file, data_link_type="DLT_EN10MB"): + """ + Starts a packet capture. + + :param slot_id: slot ID + :param port_id: port ID + :param port: allocated port + :param output_file: PCAP destination file for the capture + :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB + """ + + try: + adapter = self._slots[slot_id] + except IndexError: + raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name, + slot_id=slot_id)) + + if not adapter.port_exists(port_id): + raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter, + port_id=port_id)) + + nio = adapter.get_nio(port_id) + if nio.capturing: + raise IOUError("Packet capture is already activated on {slot_id}/{port_id}".format(slot_id=slot_id, + port_id=port_id)) + + try: + os.makedirs(os.path.dirname(output_file)) + except FileExistsError: + pass + except OSError as e: + raise IOUError("Could not create captures directory {}".format(e)) + + nio.startPacketCapture(output_file, data_link_type) + + log.info("IOU {name} [id={id}]: starting packet capture on {slot_id}/{port_id}".format(name=self._name, + id=self._id, + slot_id=slot_id, + port_id=port_id)) + + if self.is_iouyap_running(): + self._update_iouyap_config() + os.kill(self._iouyap_process.pid, signal.SIGHUP) + + def stop_capture(self, slot_id, port_id): + """ + Stops a packet capture. + + :param slot_id: slot ID + :param port_id: port ID + """ + + try: + adapter = self._slots[slot_id] + except IndexError: + raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name, + slot_id=slot_id)) + + if not adapter.port_exists(port_id): + raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter, + port_id=port_id)) + + nio = adapter.get_nio(port_id) + nio.stopPacketCapture() + log.info("IOU {name} [id={id}]: stopping packet capture on {slot_id}/{port_id}".format(name=self._name, + id=self._id, + slot_id=slot_id, + port_id=port_id)) + if self.is_iouyap_running(): + self._update_iouyap_config() + os.kill(self._iouyap_process.pid, signal.SIGHUP) diff --git a/gns3server/modules/iou/nios/nio.py b/gns3server/modules/iou/nios/nio.py new file mode 100644 index 00000000..197d4817 --- /dev/null +++ b/gns3server/modules/iou/nios/nio.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Base interface for NIOs. +""" + + +class NIO(object): + """ + IOU NIO. + """ + + def __init__(self): + + self._capturing = False + self._pcap_output_file = "" + self._pcap_data_link_type = "" + + def startPacketCapture(self, pcap_output_file, pcap_data_link_type="DLT_EN10MB"): + """ + + :param pcap_output_file: PCAP destination file for the capture + :param pcap_data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB + """ + + self._capturing = True + self._pcap_output_file = pcap_output_file + self._pcap_data_link_type = pcap_data_link_type + + def stopPacketCapture(self): + + self._capturing = False + self._pcap_output_file = "" + self._pcap_data_link_type = "" + + @property + def capturing(self): + """ + Returns either a capture is configured on this NIO. + + :returns: boolean + """ + + return self._capturing + + @property + def pcap_output_file(self): + """ + Returns the path to the PCAP output file. + + :returns: path to the PCAP output file + """ + + return self._pcap_output_file + + @property + def pcap_data_link_type(self): + """ + Returns the PCAP data link type + + :returns: PCAP data link type (DLT_* value) + """ + + return self._pcap_data_link_type diff --git a/gns3server/modules/iou/nios/nio_generic_ethernet.py b/gns3server/modules/iou/nios/nio_generic_ethernet.py index 130fb2ff..45c89b4e 100644 --- a/gns3server/modules/iou/nios/nio_generic_ethernet.py +++ b/gns3server/modules/iou/nios/nio_generic_ethernet.py @@ -19,8 +19,10 @@ Interface for generic Ethernet NIOs (PCAP library). """ +from .nio import NIO -class NIO_GenericEthernet(object): + +class NIO_GenericEthernet(NIO): """ NIO generic Ethernet NIO. @@ -29,6 +31,7 @@ class NIO_GenericEthernet(object): def __init__(self, ethernet_device): + NIO.__init__(self) self._ethernet_device = ethernet_device @property diff --git a/gns3server/modules/iou/nios/nio_tap.py b/gns3server/modules/iou/nios/nio_tap.py index ee550e7b..3164e933 100644 --- a/gns3server/modules/iou/nios/nio_tap.py +++ b/gns3server/modules/iou/nios/nio_tap.py @@ -19,8 +19,10 @@ Interface for TAP NIOs (UNIX based OSes only). """ +from .nio import NIO -class NIO_TAP(object): + +class NIO_TAP(NIO): """ IOU TAP NIO. @@ -29,6 +31,7 @@ class NIO_TAP(object): def __init__(self, tap_device): + NIO.__init__(self) self._tap_device = tap_device @property diff --git a/gns3server/modules/iou/nios/nio_udp.py b/gns3server/modules/iou/nios/nio_udp.py index 3142d70e..41ffbc4f 100644 --- a/gns3server/modules/iou/nios/nio_udp.py +++ b/gns3server/modules/iou/nios/nio_udp.py @@ -19,8 +19,10 @@ Interface for UDP NIOs. """ +from .nio import NIO -class NIO_UDP(object): + +class NIO_UDP(NIO): """ IOU UDP NIO. @@ -33,6 +35,7 @@ class NIO_UDP(object): def __init__(self, lport, rhost, rport): + NIO.__init__(self) self._lport = lport self._rhost = rhost self._rport = rport diff --git a/gns3server/modules/iou/schemas.py b/gns3server/modules/iou/schemas.py index b6a33dc7..7d317606 100644 --- a/gns3server/modules/iou/schemas.py +++ b/gns3server/modules/iou/schemas.py @@ -382,3 +382,73 @@ IOU_DELETE_NIO_SCHEMA = { "additionalProperties": False, "required": ["id", "slot", "port"] } + +IOU_START_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on an IOU instance port", + "type": "object", + "properties": { + "id": { + "description": "IOU device instance ID", + "type": "integer" + }, + "slot": { + "description": "Slot number", + "type": "integer", + "minimum": 0, + "maximum": 15 + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 0, + "maximum": 3 + }, + "port_id": { + "description": "Unique port identifier for the IOU instance", + "type": "integer" + }, + "capture_file_name": { + "description": "Capture file name", + "type": "string", + "minLength": 1, + }, + "data_link_type": { + "description": "PCAP data link type", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "slot", "port", "port_id", "capture_file_name"] +} + +IOU_STOP_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to stop a packet capture on an IOU instance port", + "type": "object", + "properties": { + "id": { + "description": "IOU device instance ID", + "type": "integer" + }, + "slot": { + "description": "Slot number", + "type": "integer", + "minimum": 0, + "maximum": 15 + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 0, + "maximum": 3 + }, + "port_id": { + "description": "Unique port identifier for the IOU instance", + "type": "integer" + }, + }, + "additionalProperties": False, + "required": ["id", "slot", "port", "port_id"] +} diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index cb3c4f06..8fb674ed 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -304,7 +304,7 @@ class VPCSDevice(object): if self._id in self._instances: self._instances.remove(self._id) - if self.console: + if self.console and self.console in self._allocated_console_ports: self._allocated_console_ports.remove(self.console) log.info("VPCS device {name} [id={id}] has been deleted".format(name=self._name,