Remove traceng code.

This commit is contained in:
grossmj 2021-04-13 19:03:23 +09:30
parent fbd5e12e7b
commit c59fc375f2
8 changed files with 5 additions and 562 deletions

View File

@ -23,9 +23,8 @@ from .virtualbox import VirtualBox
from .dynamips import Dynamips from .dynamips import Dynamips
from .qemu import Qemu from .qemu import Qemu
from .vmware import VMware from .vmware import VMware
from .traceng import TraceNG
MODULES = [Builtin, VPCS, VirtualBox, Dynamips, Qemu, VMware, TraceNG] MODULES = [Builtin, VPCS, VirtualBox, Dynamips, Qemu, VMware]
if ( if (
sys.platform.startswith("linux") sys.platform.startswith("linux")

View File

@ -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 <http://www.gnu.org/licenses/>.
"""
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)

View File

@ -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 <http://www.gnu.org/licenses/>.
"""
Custom exceptions for the TraceNG module.
"""
from ..error import NodeError
class TraceNGError(NodeError):
pass

View File

@ -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 <http://www.gnu.org/licenses/>.
"""
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

View File

@ -397,7 +397,6 @@ class Link:
for node in self._nodes: for node in self._nodes:
if node["node"].node_type in ( if node["node"].node_type in (
"vpcs", "vpcs",
"traceng",
"vmware", "vmware",
"dynamips", "dynamips",
"qemu", "qemu",

View File

@ -139,7 +139,7 @@ class Node:
:returns: Boolean True if the node is always running :returns: Boolean True if the node is always running
like ethernet switch 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 @property
def id(self): def id(self):
@ -753,7 +753,7 @@ class Node:
PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name=f"e{port_number}") PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name=f"e{port_number}")
) )
port_number += 1 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")) self._ports.append(PortFactory("Ethernet0", 0, 0, 0, "ethernet", short_name="e0"))
elif self._node_type in ("cloud", "nat"): elif self._node_type in ("cloud", "nat"):
port_number = 0 port_number = 0

View File

@ -36,7 +36,6 @@ class NodeType(str, Enum):
docker = "docker" docker = "docker"
dynamips = "dynamips" dynamips = "dynamips"
vpcs = "vpcs" vpcs = "vpcs"
traceng = "traceng"
virtualbox = "virtualbox" virtualbox = "virtualbox"
vmware = "vmware" vmware = "vmware"
iou = "iou" iou = "iou"

View File

@ -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")) response = await client.get(app.url_path_for("get_capabilities"))
assert response.status_code == status.HTTP_200_OK 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__, 'version': __version__,
'platform': sys.platform, 'platform': sys.platform,
'cpus': psutil.cpu_count(logical=True), '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")) response = await client.get(app.url_path_for("get_capabilities"))
assert response.status_code == status.HTTP_200_OK 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__, 'version': __version__,
'platform': sys.platform, 'platform': sys.platform,
'cpus': psutil.cpu_count(logical=True), 'cpus': psutil.cpu_count(logical=True),