diff --git a/gns3server/compute/__init__.py b/gns3server/compute/__init__.py index e618b768..afd48650 100644 --- a/gns3server/compute/__init__.py +++ b/gns3server/compute/__init__.py @@ -23,9 +23,8 @@ from .virtualbox import VirtualBox from .dynamips import Dynamips from .qemu import Qemu from .vmware import VMware -from .traceng import TraceNG -MODULES = [Builtin, VPCS, VirtualBox, Dynamips, Qemu, VMware, TraceNG] +MODULES = [Builtin, VPCS, VirtualBox, Dynamips, Qemu, VMware] if ( sys.platform.startswith("linux") diff --git a/gns3server/compute/traceng/__init__.py b/gns3server/compute/traceng/__init__.py deleted file mode 100644 index c6c24b43..00000000 --- a/gns3server/compute/traceng/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# -# Copyright (C) 2018 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 . - -""" -TraceNG server module. -""" - -import asyncio - -from ..base_manager import BaseManager -from .traceng_error import TraceNGError -from .traceng_vm import TraceNGVM - - -class TraceNG(BaseManager): - - _NODE_CLASS = TraceNGVM - - def __init__(self): - - super().__init__() - - async def create_node(self, *args, **kwargs): - """ - Creates a new TraceNG VM. - - :returns: TraceNGVM instance - """ - - return await super().create_node(*args, **kwargs) diff --git a/gns3server/compute/traceng/traceng_error.py b/gns3server/compute/traceng/traceng_error.py deleted file mode 100644 index 2a6a1323..00000000 --- a/gns3server/compute/traceng/traceng_error.py +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (C) 2018 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 . - -""" -Custom exceptions for the TraceNG module. -""" - -from ..error import NodeError - - -class TraceNGError(NodeError): - - pass diff --git a/gns3server/compute/traceng/traceng_vm.py b/gns3server/compute/traceng/traceng_vm.py deleted file mode 100644 index 15cd4d70..00000000 --- a/gns3server/compute/traceng/traceng_vm.py +++ /dev/null @@ -1,485 +0,0 @@ -# -# Copyright (C) 2018 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 . - -""" -TraceNG VM management in order to run a TraceNG VM. -""" - -import sys -import os -import socket -import subprocess -import asyncio -import shutil -import ipaddress - -from gns3server.utils.asyncio import wait_for_process_termination -from gns3server.utils.asyncio import monitor_process - -from .traceng_error import TraceNGError -from ..adapters.ethernet_adapter import EthernetAdapter -from ..nios.nio_udp import NIOUDP -from ..base_node import BaseNode - - -import logging - -log = logging.getLogger(__name__) - - -class TraceNGVM(BaseNode): - module_name = "traceng" - - """ - TraceNG VM implementation. - - :param name: TraceNG VM name - :param node_id: Node identifier - :param project: Project instance - :param manager: Manager instance - :param console: TCP console port - """ - - def __init__(self, name, node_id, project, manager, console=None, console_type="none"): - - super().__init__(name, node_id, project, manager, console=console, console_type=console_type) - self._process = None - self._started = False - self._ip_address = None - self._default_destination = None - self._destination = None - self._local_udp_tunnel = None - self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface - - @property - def ethernet_adapter(self): - return self._ethernet_adapter - - async def close(self): - """ - Closes this TraceNG VM. - """ - - if not (await super().close()): - return False - - nio = self._ethernet_adapter.get_nio(0) - if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport, self._project) - - if self._local_udp_tunnel: - self.manager.port_manager.release_udp_port(self._local_udp_tunnel[0].lport, self._project) - self.manager.port_manager.release_udp_port(self._local_udp_tunnel[1].lport, self._project) - self._local_udp_tunnel = None - - await self._stop_ubridge() - - if self.is_running(): - self._terminate_process() - - return True - - async def _check_requirements(self): - """ - Check if TraceNG is available. - """ - - path = self._traceng_path() - if not path: - raise TraceNGError("No path to a TraceNG executable has been set") - - # This raise an error if ubridge is not available - self.ubridge_path - - if not os.path.isfile(path): - raise TraceNGError(f"TraceNG program '{path}' is not accessible") - - if not os.access(path, os.X_OK): - raise TraceNGError(f"TraceNG program '{path}' is not executable") - - def __json__(self): - - return { - "name": self.name, - "ip_address": self.ip_address, - "default_destination": self._default_destination, - "node_id": self.id, - "node_directory": self.working_path, - "status": self.status, - "console": self._console, - "console_type": "none", - "project_id": self.project.id, - "command_line": self.command_line, - } - - def _traceng_path(self): - """ - Returns the TraceNG executable path. - - :returns: path to TraceNG - """ - - search_path = self._manager.config.get_section_config("TraceNG").get("traceng_path", "traceng") - path = shutil.which(search_path) - # shutil.which return None if the path doesn't exists - if not path: - return search_path - return path - - @property - def ip_address(self): - """ - Returns the IP address for this node. - - :returns: IP address - """ - - return self._ip_address - - @ip_address.setter - def ip_address(self, ip_address): - """ - Sets the IP address of this node. - - :param ip_address: IP address - """ - - try: - if ip_address: - ipaddress.IPv4Address(ip_address) - except ipaddress.AddressValueError: - raise TraceNGError(f"Invalid IP address: {ip_address}\n") - - self._ip_address = ip_address - log.info( - "{module}: {name} [{id}] set IP address to {ip_address}".format( - module=self.manager.module_name, name=self.name, id=self.id, ip_address=ip_address - ) - ) - - @property - def default_destination(self): - """ - Returns the default destination IP/host for this node. - - :returns: destination IP/host - """ - - return self._default_destination - - @default_destination.setter - def default_destination(self, destination): - """ - Sets the destination IP/host for this node. - - :param destination: destination IP/host - """ - - self._default_destination = destination - log.info( - "{module}: {name} [{id}] set default destination to {destination}".format( - module=self.manager.module_name, name=self.name, id=self.id, destination=destination - ) - ) - - async def start(self, destination=None): - """ - Starts the TraceNG process. - """ - - if not sys.platform.startswith("win"): - raise TraceNGError("Sorry, TraceNG can only run on Windows") - await self._check_requirements() - if not self.is_running(): - nio = self._ethernet_adapter.get_nio(0) - command = self._build_command(destination) - await self._stop_ubridge() # make use we start with a fresh uBridge instance - try: - log.info(f"Starting TraceNG: {command}") - flags = 0 - if hasattr(subprocess, "CREATE_NEW_CONSOLE"): - flags = subprocess.CREATE_NEW_CONSOLE - self.command_line = " ".join(command) - self._process = await asyncio.create_subprocess_exec( - *command, cwd=self.working_dir, creationflags=flags - ) - monitor_process(self._process, self._termination_callback) - - await self._start_ubridge() - if nio: - await self.add_ubridge_udp_connection(f"TraceNG-{self._id}", self._local_udp_tunnel[1], nio) - - log.info(f"TraceNG instance {self.name} started PID={self._process.pid}") - self._started = True - self.status = "started" - except (OSError, subprocess.SubprocessError) as e: - log.error(f"Could not start TraceNG {self._traceng_path()}: {e}\n") - raise TraceNGError(f"Could not start TraceNG {self._traceng_path()}: {e}\n") - - def _termination_callback(self, returncode): - """ - Called when the process has stopped. - - :param returncode: Process returncode - """ - - if self._started: - log.info("TraceNG process has stopped, return code: %d", returncode) - self._started = False - self.status = "stopped" - self._process = None - if returncode != 0: - self.project.emit("log.error", {"message": f"TraceNG process has stopped, return code: {returncode}\n"}) - - async def stop(self): - """ - Stops the TraceNG process. - """ - - await self._stop_ubridge() - if self.is_running(): - self._terminate_process() - if self._process.returncode is None: - try: - await wait_for_process_termination(self._process, timeout=3) - except asyncio.TimeoutError: - if self._process.returncode is None: - try: - self._process.kill() - except OSError as e: - log.error(f"Cannot stop the TraceNG process: {e}") - if self._process.returncode is None: - log.warning(f'TraceNG VM "{self._name}" with PID={self._process.pid} is still running') - - self._process = None - self._started = False - await super().stop() - - async def reload(self): - """ - Reloads the TraceNG process (stop & start). - """ - - await self.stop() - await self.start(self._destination) - - def _terminate_process(self): - """ - Terminate the process if running - """ - - log.info(f"Stopping TraceNG instance {self.name} PID={self._process.pid}") - # if sys.platform.startswith("win32"): - # self._process.send_signal(signal.CTRL_BREAK_EVENT) - # else: - try: - self._process.terminate() - # Sometime the process may already be dead when we garbage collect - except ProcessLookupError: - pass - - def is_running(self): - """ - Checks if the TraceNG process is running - - :returns: True or False - """ - - if self._process and self._process.returncode is None: - return True - return False - - async def port_add_nio_binding(self, port_number, nio): - """ - Adds a port NIO binding. - - :param port_number: port number - :param nio: NIO instance to add to 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, port_number=port_number - ) - ) - - if self.is_running(): - await self.add_ubridge_udp_connection(f"TraceNG-{self._id}", self._local_udp_tunnel[1], nio) - - self._ethernet_adapter.add_nio(port_number, nio) - log.info( - 'TraceNG "{name}" [{id}]: {nio} added to port {port_number}'.format( - name=self._name, id=self.id, nio=nio, port_number=port_number - ) - ) - - 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 on adapter {adapter}".format( - adapter=self._ethernet_adapter, port_number=port_number - ) - ) - if self.is_running(): - await self.update_ubridge_udp_connection(f"TraceNG-{self._id}", self._local_udp_tunnel[1], nio) - - async def port_remove_nio_binding(self, port_number): - """ - Removes 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 in adapter {adapter}".format( - adapter=self._ethernet_adapter, port_number=port_number - ) - ) - - await self.stop_capture(port_number) - if self.is_running(): - await self._ubridge_send("bridge delete {name}".format(name=f"TraceNG-{self._id}")) - - nio = self._ethernet_adapter.get_nio(port_number) - if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport, self._project) - self._ethernet_adapter.remove_nio(port_number) - - log.info( - 'TraceNG "{name}" [{id}]: {nio} removed from port {port_number}'.format( - name=self._name, id=self.id, nio=nio, 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(f"Port {port_number} is not connected") - return nio - - async def start_capture(self, port_number, output_file): - """ - Starts a packet capture. - - :param port_number: port number - :param output_file: PCAP destination file for the capture - """ - - nio = self.get_nio(port_number) - if nio.capturing: - raise TraceNGError(f"Packet capture is already activated on port {port_number}") - - nio.start_packet_capture(output_file) - if self.ubridge: - await self._ubridge_send( - 'bridge start_capture {name} "{output_file}"'.format( - name=f"TraceNG-{self._id}", output_file=output_file - ) - ) - - log.info( - "TraceNG '{name}' [{id}]: starting packet capture on port {port_number}".format( - name=self.name, id=self.id, port_number=port_number - ) - ) - - async def stop_capture(self, port_number): - """ - Stops a packet capture. - - :param port_number: port number - """ - - nio = self.get_nio(port_number) - if not nio.capturing: - return - - nio.stop_packet_capture() - if self.ubridge: - await self._ubridge_send("bridge stop_capture {name}".format(name=f"TraceNG-{self._id}")) - - log.info( - "TraceNG '{name}' [{id}]: stopping packet capture on port {port_number}".format( - name=self.name, id=self.id, port_number=port_number - ) - ) - - def _build_command(self, destination): - """ - Command to start the TraceNG process. - (to be passed to subprocess.Popen()) - """ - - if not destination: - # use the default destination if no specific destination provided - destination = self.default_destination - if not destination: - raise TraceNGError("Please provide a host or IP address to trace") - if not self.ip_address: - raise TraceNGError("Please configure an IP address for this TraceNG node") - if self.ip_address == destination: - raise TraceNGError("Destination cannot be the same as the IP address") - - self._destination = destination - command = [self._traceng_path()] - # use the local UDP tunnel to uBridge instead - if not self._local_udp_tunnel: - self._local_udp_tunnel = self._create_local_udp_tunnel() - nio = self._local_udp_tunnel[0] - if nio and isinstance(nio, NIOUDP): - # UDP tunnel - command.extend(["-u"]) # enable UDP tunnel - command.extend(["-c", str(nio.lport)]) # source UDP port - command.extend(["-v", str(nio.rport)]) # destination UDP port - try: - command.extend( - ["-b", socket.gethostbyname(nio.rhost)] - ) # destination host, we need to resolve the hostname because TraceNG doesn't support it - except socket.gaierror as e: - raise TraceNGError(f"Can't resolve hostname {nio.rhost}: {e}") - - command.extend(["-s", "ICMP"]) # Use ICMP probe type by default - command.extend(["-f", self._ip_address]) # source IP address to trace from - command.extend([destination]) # host or IP to trace - return command diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index a88dce33..b2ab3819 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -397,7 +397,6 @@ class Link: for node in self._nodes: if node["node"].node_type in ( "vpcs", - "traceng", "vmware", "dynamips", "qemu", diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index dea6c4cd..80e8c5d6 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -139,7 +139,7 @@ class Node: :returns: Boolean True if the node is always running like ethernet switch """ - return self.node_type not in ("qemu", "docker", "dynamips", "vpcs", "vmware", "virtualbox", "iou", "traceng") + return self.node_type not in ("qemu", "docker", "dynamips", "vpcs", "vmware", "virtualbox", "iou") @property def id(self): @@ -753,7 +753,7 @@ class Node: PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name=f"e{port_number}") ) port_number += 1 - elif self._node_type in ("vpcs", "traceng"): + elif self._node_type in ("vpcs"): self._ports.append(PortFactory("Ethernet0", 0, 0, 0, "ethernet", short_name="e0")) elif self._node_type in ("cloud", "nat"): port_number = 0 diff --git a/gns3server/schemas/nodes.py b/gns3server/schemas/nodes.py index a80aa05c..3a6f2802 100644 --- a/gns3server/schemas/nodes.py +++ b/gns3server/schemas/nodes.py @@ -36,7 +36,6 @@ class NodeType(str, Enum): docker = "docker" dynamips = "dynamips" vpcs = "vpcs" - traceng = "traceng" virtualbox = "virtualbox" vmware = "vmware" iou = "iou" diff --git a/tests/api/routes/compute/test_capabilities.py b/tests/api/routes/compute/test_capabilities.py index 2b76c5a3..e6d4724d 100644 --- a/tests/api/routes/compute/test_capabilities.py +++ b/tests/api/routes/compute/test_capabilities.py @@ -34,7 +34,7 @@ async def test_get(app: FastAPI, client: AsyncClient, windows_platform) -> None: response = await client.get(app.url_path_for("get_capabilities")) assert response.status_code == status.HTTP_200_OK - assert response.json() == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'traceng', 'docker', 'iou'], + assert response.json() == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'docker', 'iou'], 'version': __version__, 'platform': sys.platform, 'cpus': psutil.cpu_count(logical=True), @@ -48,7 +48,7 @@ async def test_get_on_gns3vm(app: FastAPI, client: AsyncClient, on_gns3vm) -> No response = await client.get(app.url_path_for("get_capabilities")) assert response.status_code == status.HTTP_200_OK - assert response.json() == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'traceng', 'docker', 'iou'], + assert response.json() == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'docker', 'iou'], 'version': __version__, 'platform': sys.platform, 'cpus': psutil.cpu_count(logical=True),