From 606f773f3db344fc24093ae79f10562a4f56091e Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 26 Jun 2014 03:06:58 -0600 Subject: [PATCH] New feature: packet capture for IOS routers. --- gns3server/modules/dynamips/backends/vm.py | 86 +++++++++++++++++++++ gns3server/modules/dynamips/nios/nio.py | 4 +- gns3server/modules/dynamips/nodes/router.py | 65 ++++++++++++++++ gns3server/modules/dynamips/schemas/vm.py | 70 +++++++++++++++++ gns3server/version.py | 2 +- 5 files changed, 225 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index 57d12a99..04e5fb75 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -56,6 +56,8 @@ from ..schemas.vm import VM_STOP_SCHEMA from ..schemas.vm import VM_SUSPEND_SCHEMA from ..schemas.vm import VM_RELOAD_SCHEMA from ..schemas.vm import VM_UPDATE_SCHEMA +from ..schemas.vm import VM_START_CAPTURE_SCHEMA +from ..schemas.vm import VM_STOP_CAPTURE_SCHEMA from ..schemas.vm import VM_SAVE_CONFIG_SCHEMA from ..schemas.vm import VM_IDLEPCS_SCHEMA from ..schemas.vm import VM_ALLOCATE_UDP_PORT_SCHEMA @@ -484,6 +486,90 @@ class VM(object): self.send_response(response) + @IModule.route("dynamips.vm.start_capture") + def vm_start_capture(self, request): + """ + Starts a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port_id (port identifier) + - slot (slot number) + - port (port number) + - 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, VM_START_CAPTURE_SCHEMA): + return + + # get the router instance + router = self.get_device_instance(request["id"], self._routers) + if not router: + 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(router.hypervisor.working_dir, "captures", capture_file_name) + router.start_capture(slot, port, capture_file_path, data_link_type) + except DynamipsError 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("dynamips.vm.stop_capture") + def vm_stop_capture(self, request): + """ + Stops a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port_id (port identifier) + - slot (slot number) + - port (port number) + + Response parameters: + - port_id (port identifier) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, VM_STOP_CAPTURE_SCHEMA): + return + + # get the router instance + router = self.get_device_instance(request["id"], self._routers) + if not router: + return + + slot = request["slot"] + port = request["port"] + try: + router.stop_capture(slot, port) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"]} + self.send_response(response) + @IModule.route("dynamips.vm.save_config") def vm_save_config(self, request): """ diff --git a/gns3server/modules/dynamips/nios/nio.py b/gns3server/modules/dynamips/nios/nio.py index 04af1380..1fd61bf9 100644 --- a/gns3server/modules/dynamips/nios/nio.py +++ b/gns3server/modules/dynamips/nios/nio.py @@ -57,6 +57,8 @@ class NIO(object): Deletes this NIO. """ + if self._input_filter or self._output_filter: + self.unbind_filter("both") self._hypervisor.send("nio delete {}".format(self._name)) log.info("NIO {name} has been deleted".format(name=self._name)) @@ -134,7 +136,7 @@ class NIO(object): def setup_filter(self, direction, options): """ - Setups a packet filter binded with this NIO. + Setups a packet filter bound with this NIO. Filter "freq_drop" has 1 argument "". It will drop everything with a -1 frequency, drop every Nth packet with a diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 6b8f9228..7fc15e5a 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -1539,6 +1539,71 @@ class Router(object): slot_id=slot_id, port_id=port_id)) + 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 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 DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name, + slot_id=slot_id)) + if not adapter.port_exists(port_id): + raise DynamipsError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter, + port_id=port_id)) + + data_link_type = data_link_type.lower() + if data_link_type.startswith("dlt_"): + data_link_type = data_link_type[4:] + + nio = adapter.get_nio(port_id) + + if nio.input_filter[0] is not None and nio.output_filter[0] is not None: + raise DynamipsError("Port {port_id} has already a filter applied on {adapter}".format(adapter=adapter, + port_id=port_id)) + + try: + os.makedirs(os.path.dirname(output_file)) + except FileExistsError: + pass + except OSError as e: + raise DynamipsError("Could not create captures directory {}".format(e)) + + nio.bind_filter("both", "capture") + nio.setup_filter("both", "{} {}".format(data_link_type, output_file)) + + log.info("router {name} [id={id}]: capturing on port {slot_id}/{port_id}".format(name=self._name, + id=self._id, + nio_name=nio.name, + slot_id=slot_id, + port_id=port_id)) + + 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 DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name, + slot_id=slot_id)) + if not adapter.port_exists(port_id): + raise DynamipsError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter, + port_id=port_id)) + + nio = adapter.get_nio(port_id) + nio.unbind_filter("both") + def _create_slots(self, numslots): """ Creates the appropriate number of slots for this router. diff --git a/gns3server/modules/dynamips/schemas/vm.py b/gns3server/modules/dynamips/schemas/vm.py index 3d4d1078..241e059d 100644 --- a/gns3server/modules/dynamips/schemas/vm.py +++ b/gns3server/modules/dynamips/schemas/vm.py @@ -372,6 +372,76 @@ VM_UPDATE_SCHEMA = { "required": ["id"] } +VM_START_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on a VM instance port", + "type": "object", + "properties": { + "id": { + "description": "VM instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the VM instance", + "type": "integer" + }, + "slot": { + "description": "Slot number", + "type": "integer", + "minimum": 0, + "maximum": 6 + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 0, + "maximum": 49 # maximum is 16 for regular port numbers, WICs port numbers start at 16, 32 or 48 + }, + "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", "port_id", "slot", "port", "capture_file_name"] +} + +VM_STOP_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to stop a packet capture on a VM instance port", + "type": "object", + "properties": { + "id": { + "description": "VM instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the VM instance", + "type": "integer" + }, + "slot": { + "description": "Slot number", + "type": "integer", + "minimum": 0, + "maximum": 6 + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 0, + "maximum": 49 # maximum is 16 for regular port numbers, WICs port numbers start at 16, 32 or 48 + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "slot", "port"] +} + VM_SAVE_CONFIG_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to save the configs for VM instance", diff --git a/gns3server/version.py b/gns3server/version.py index beca4a33..c15d4ef7 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a7.dev2" +__version__ = "1.0a7.dev3" __version_info__ = (1, 0, 0, -99)