mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Use black with -l 120 param.
This commit is contained in:
1 parent
f928738bd5
commit
c021e21309
194 files changed
+6034
-4564
No files matched your search
@@ -27,10 +27,16 @@ from .traceng import TraceNG
|
||||
|
||||
MODULES = [Builtin, VPCS, VirtualBox, Dynamips, Qemu, VMware, TraceNG]
|
||||
|
||||
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
|
||||
if (
|
||||
sys.platform.startswith("linux")
|
||||
or hasattr(sys, "_called_from_test")
|
||||
or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1"
|
||||
):
|
||||
# IOU & Docker only runs on Linux but test suite works on UNIX platform
|
||||
if not sys.platform.startswith("win"):
|
||||
from .docker import Docker
|
||||
|
||||
MODULES.append(Docker)
|
||||
from .iou import IOU
|
||||
|
||||
MODULES.append(IOU)
|
||||
@@ -72,7 +72,7 @@ class BaseManager:
|
||||
"""
|
||||
|
||||
# By default we transform DockerVM => docker but you can override this (see builtins)
|
||||
return [cls._NODE_CLASS.__name__.rstrip('VM').lower()]
|
||||
return [cls._NODE_CLASS.__name__.rstrip("VM").lower()]
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
@@ -313,7 +313,9 @@ class BaseManager:
|
||||
# we are root, so we should have privileged access.
|
||||
return True
|
||||
|
||||
if os.stat(executable).st_uid == 0 and (os.stat(executable).st_mode & stat.S_ISUID or os.stat(executable).st_mode & stat.S_ISGID):
|
||||
if os.stat(executable).st_uid == 0 and (
|
||||
os.stat(executable).st_mode & stat.S_ISUID or os.stat(executable).st_mode & stat.S_ISGID
|
||||
):
|
||||
# the executable has set UID bit.
|
||||
return True
|
||||
|
||||
@@ -425,7 +427,9 @@ class BaseManager:
|
||||
# Windows path should not be send to a unix server
|
||||
if not sys.platform.startswith("win"):
|
||||
if re.match(r"^[A-Z]:", path) is not None:
|
||||
raise NodeError(f"'{path}' is not allowed on this remote server. Please only use a file from '{img_directory}'")
|
||||
raise NodeError(
|
||||
f"'{path}' is not allowed on this remote server. Please only use a file from '{img_directory}'"
|
||||
)
|
||||
|
||||
if not os.path.isabs(path):
|
||||
for directory in valid_directory_prefices:
|
||||
@@ -471,7 +475,7 @@ class BaseManager:
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
# If filename is the same
|
||||
if s[1] == file and (s[0] == '' or s[0] == os.path.basename(root)):
|
||||
if s[1] == file and (s[0] == "" or s[0] == os.path.basename(root)):
|
||||
path = os.path.normpath(os.path.join(root, s[1]))
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
@@ -540,7 +544,7 @@ class BaseManager:
|
||||
# We store the file under his final name only when the upload is finished
|
||||
tmp_path = path + ".tmp"
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
async with aiofiles.open(tmp_path, 'wb') as f:
|
||||
async with aiofiles.open(tmp_path, "wb") as f:
|
||||
async for chunk in stream:
|
||||
await f.write(chunk)
|
||||
os.chmod(tmp_path, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
|
||||
|
||||
+135
-100
@@ -36,6 +36,7 @@ from .nios.nio_udp import NIOUDP
|
||||
from .error import NodeError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -57,7 +58,20 @@ class BaseNode:
|
||||
:param wrap_aux: The auxiliary console is wrapped using AsyncioTelnetServer
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, aux_type="none", linked_clone=True, wrap_console=False, wrap_aux=False):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
linked_clone=True,
|
||||
wrap_console=False,
|
||||
wrap_aux=False,
|
||||
):
|
||||
|
||||
self._name = name
|
||||
self._usage = ""
|
||||
@@ -102,7 +116,9 @@ class BaseNode:
|
||||
# use a previously allocated auxiliary console port
|
||||
if aux_type == "vnc":
|
||||
# VNC is a special case and the range must be 5900-6000
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project, port_range_start=5900, port_range_end=6000)
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(
|
||||
self._aux, self._project, port_range_start=5900, port_range_end=6000
|
||||
)
|
||||
elif aux_type == "none":
|
||||
self._aux = None
|
||||
else:
|
||||
@@ -115,7 +131,8 @@ class BaseNode:
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(
|
||||
self._project,
|
||||
port_range_start=vnc_console_start_port_range,
|
||||
port_range_end=vnc_console_end_port_range)
|
||||
port_range_end=vnc_console_end_port_range,
|
||||
)
|
||||
elif console_type != "none":
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
@@ -123,7 +140,9 @@ class BaseNode:
|
||||
# allocate a new auxiliary console
|
||||
if aux_type == "vnc":
|
||||
# VNC is a special case and the range must be 5900-6000
|
||||
self._aux = self._manager.port_manager.get_free_tcp_port(self._project, port_range_start=5900, port_range_end=6000)
|
||||
self._aux = self._manager.port_manager.get_free_tcp_port(
|
||||
self._project, port_range_start=5900, port_range_end=6000
|
||||
)
|
||||
elif aux_type != "none":
|
||||
self._aux = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
@@ -133,10 +152,11 @@ class BaseNode:
|
||||
if self._wrap_aux:
|
||||
self._internal_aux_port = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
log.debug("{module}: {name} [{id}] initialized. Console port {console}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
console=self._console))
|
||||
log.debug(
|
||||
"{module}: {name} [{id}] initialized. Console port {console}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, console=self._console
|
||||
)
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
|
||||
@@ -221,10 +241,11 @@ class BaseNode:
|
||||
:param new_name: name
|
||||
"""
|
||||
|
||||
log.info("{module}: {name} [{id}] renamed to {new_name}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
new_name=new_name))
|
||||
log.info(
|
||||
"{module}: {name} [{id}] renamed to {new_name}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, new_name=new_name
|
||||
)
|
||||
)
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@@ -297,9 +318,7 @@ class BaseNode:
|
||||
Creates the node.
|
||||
"""
|
||||
|
||||
log.info("{module}: {name} [{id}] created".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id))
|
||||
log.info("{module}: {name} [{id}] created".format(module=self.manager.module_name, name=self.name, id=self.id))
|
||||
|
||||
async def delete(self):
|
||||
"""
|
||||
@@ -346,9 +365,9 @@ class BaseNode:
|
||||
if self._closed:
|
||||
return False
|
||||
|
||||
log.info("{module}: '{name}' [{id}]: is closing".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: is closing".format(module=self.manager.module_name, name=self.name, id=self.id)
|
||||
)
|
||||
|
||||
if self._console:
|
||||
self._manager.port_manager.release_tcp_port(self._console, self._project)
|
||||
@@ -380,8 +399,10 @@ class BaseNode:
|
||||
if not 5900 <= vnc_console_end_port_range <= 65535:
|
||||
raise NodeError("The VNC console start port range must be between 5900 and 65535")
|
||||
if vnc_console_start_port_range >= vnc_console_end_port_range:
|
||||
raise NodeError(f"The VNC console start port range value ({vnc_console_start_port_range}) "
|
||||
f"cannot be above or equal to the end value ({vnc_console_end_port_range})")
|
||||
raise NodeError(
|
||||
f"The VNC console start port range value ({vnc_console_start_port_range}) "
|
||||
f"cannot be above or equal to the end value ({vnc_console_end_port_range})"
|
||||
)
|
||||
|
||||
return vnc_console_start_port_range, vnc_console_end_port_range
|
||||
|
||||
@@ -415,13 +436,17 @@ class BaseNode:
|
||||
|
||||
if self._wrap_console and self._console_type == "telnet":
|
||||
await self._wrap_telnet_proxy(self._internal_console_port, self.console)
|
||||
log.info(f"New Telnet proxy server for console started "
|
||||
f"(internal port = {self._internal_console_port}, external port = {self.console})")
|
||||
log.info(
|
||||
f"New Telnet proxy server for console started "
|
||||
f"(internal port = {self._internal_console_port}, external port = {self.console})"
|
||||
)
|
||||
|
||||
if self._wrap_aux and self._aux_type == "telnet":
|
||||
await self._wrap_telnet_proxy(self._internal_aux_port, self.aux)
|
||||
log.info(f"New Telnet proxy server for auxiliary console started "
|
||||
f"(internal port = {self._internal_aux_port}, external port = {self.aux})")
|
||||
log.info(
|
||||
f"New Telnet proxy server for auxiliary console started "
|
||||
f"(internal port = {self._internal_aux_port}, external port = {self.aux})"
|
||||
)
|
||||
|
||||
async def stop_wrap_console(self):
|
||||
"""
|
||||
@@ -455,16 +480,19 @@ class BaseNode:
|
||||
raise NodeError(f"Node {self.name} console type is not telnet")
|
||||
|
||||
try:
|
||||
(telnet_reader, telnet_writer) = await asyncio.open_connection(self._manager.port_manager.console_host,
|
||||
self.console)
|
||||
(telnet_reader, telnet_writer) = await asyncio.open_connection(
|
||||
self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
except ConnectionError as e:
|
||||
raise NodeError(f"Cannot connect to node {self.name} telnet server: {e}")
|
||||
|
||||
log.info("Connected to Telnet server")
|
||||
|
||||
await websocket.accept()
|
||||
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute"
|
||||
f" console WebSocket")
|
||||
log.info(
|
||||
f"New client {websocket.client.host}:{websocket.client.port} has connected to compute"
|
||||
f" console WebSocket"
|
||||
)
|
||||
|
||||
async def ws_forward(telnet_writer):
|
||||
|
||||
@@ -475,8 +503,10 @@ class BaseNode:
|
||||
telnet_writer.write(data.encode())
|
||||
await telnet_writer.drain()
|
||||
except WebSocketDisconnect:
|
||||
log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute"
|
||||
f" console WebSocket")
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute"
|
||||
f" console WebSocket"
|
||||
)
|
||||
|
||||
async def telnet_forward(telnet_reader):
|
||||
|
||||
@@ -486,8 +516,9 @@ class BaseNode:
|
||||
await websocket.send_bytes(data)
|
||||
|
||||
# keep forwarding WebSocket data in both direction
|
||||
done, pending = await asyncio.wait([ws_forward(telnet_writer), telnet_forward(telnet_reader)],
|
||||
return_when=asyncio.FIRST_COMPLETED)
|
||||
done, pending = await asyncio.wait(
|
||||
[ws_forward(telnet_writer), telnet_forward(telnet_reader)], return_when=asyncio.FIRST_COMPLETED
|
||||
)
|
||||
for task in done:
|
||||
if task.exception():
|
||||
log.warning(f"Exception while forwarding WebSocket data to Telnet server {task.exception()}")
|
||||
@@ -524,14 +555,17 @@ class BaseNode:
|
||||
self._aux = None
|
||||
if aux is not None:
|
||||
if self.aux_type == "vnc":
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project, port_range_start=5900, port_range_end=6000)
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(
|
||||
aux, self._project, port_range_start=5900, port_range_end=6000
|
||||
)
|
||||
else:
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project)
|
||||
|
||||
log.info("{module}: '{name}' [{id}]: auxiliary console port set to {port}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port=aux))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: auxiliary console port set to {port}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, port=aux
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
@@ -567,15 +601,16 @@ class BaseNode:
|
||||
console,
|
||||
self._project,
|
||||
port_range_start=vnc_console_start_port_range,
|
||||
port_range_end=vnc_console_end_port_range
|
||||
port_range_end=vnc_console_end_port_range,
|
||||
)
|
||||
else:
|
||||
self._console = self._manager.port_manager.reserve_tcp_port(console, self._project)
|
||||
|
||||
log.info("{module}: '{name}' [{id}]: console port set to {port}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port=console))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: console port set to {port}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, port=console
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def console_type(self):
|
||||
@@ -609,11 +644,15 @@ class BaseNode:
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
self._console_type = console_type
|
||||
log.info("{module}: '{name}' [{id}]: console type set to {console_type} (console port is {console})".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
console_type=console_type,
|
||||
console=self.console))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: console type set to {console_type} (console port is {console})".format(
|
||||
module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
console_type=console_type,
|
||||
console=self.console,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def aux_type(self):
|
||||
@@ -647,11 +686,11 @@ class BaseNode:
|
||||
self._aux = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
self._aux_type = aux_type
|
||||
log.info("{module}: '{name}' [{id}]: console type set to {aux_type} (auxiliary console port is {aux})".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
aux_type=aux_type,
|
||||
aux=self.aux))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: console type set to {aux_type} (auxiliary console port is {aux})".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, aux_type=aux_type, aux=self.aux
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def ubridge(self):
|
||||
@@ -700,7 +739,9 @@ class BaseNode:
|
||||
try:
|
||||
await self._ubridge_hypervisor.send(command)
|
||||
except UbridgeError as e:
|
||||
raise UbridgeError(f"Error while sending command '{command}': {e}: {self._ubridge_hypervisor.read_stdout()}")
|
||||
raise UbridgeError(
|
||||
f"Error while sending command '{command}': {e}: {self._ubridge_hypervisor.read_stdout()}"
|
||||
)
|
||||
|
||||
@locking
|
||||
async def _start_ubridge(self, require_privileged_access=False):
|
||||
@@ -713,7 +754,9 @@ class BaseNode:
|
||||
return
|
||||
|
||||
if self.ubridge_path is None:
|
||||
raise NodeError("uBridge is not available, path doesn't exist, or you just installed GNS3 and need to restart your user session to refresh user permissions.")
|
||||
raise NodeError(
|
||||
"uBridge is not available, path doesn't exist, or you just installed GNS3 and need to restart your user session to refresh user permissions."
|
||||
)
|
||||
|
||||
if require_privileged_access and not self._manager.has_privileged_access(self.ubridge_path):
|
||||
raise NodeError("uBridge requires root access or the capability to interact with network adapters")
|
||||
@@ -724,7 +767,9 @@ class BaseNode:
|
||||
log.info(f"Starting new uBridge hypervisor {self._ubridge_hypervisor.host}:{self._ubridge_hypervisor.port}")
|
||||
await self._ubridge_hypervisor.start()
|
||||
if self._ubridge_hypervisor:
|
||||
log.info(f"Hypervisor {self._ubridge_hypervisor.host}:{self._ubridge_hypervisor.port} has successfully started")
|
||||
log.info(
|
||||
f"Hypervisor {self._ubridge_hypervisor.host}:{self._ubridge_hypervisor.port} has successfully started"
|
||||
)
|
||||
await self._ubridge_hypervisor.connect()
|
||||
# save if privileged are required in case uBridge needs to be restarted in self._ubridge_send()
|
||||
self._ubridge_require_privileged_access = require_privileged_access
|
||||
@@ -753,24 +798,23 @@ class BaseNode:
|
||||
if not isinstance(destination_nio, NIOUDP):
|
||||
raise NodeError("Destination NIO is not UDP")
|
||||
|
||||
await 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)
|
||||
await 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
|
||||
)
|
||||
)
|
||||
|
||||
await 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)
|
||||
await 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:
|
||||
await self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(
|
||||
name=bridge_name,
|
||||
pcap_file=destination_nio.pcap_output_file)
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{pcap_file}"'.format(
|
||||
name=bridge_name, pcap_file=destination_nio.pcap_output_file
|
||||
)
|
||||
)
|
||||
|
||||
await self._ubridge_send(f"bridge start {bridge_name}")
|
||||
@@ -796,7 +840,7 @@ class BaseNode:
|
||||
:param filters: Array of filter dictionary
|
||||
"""
|
||||
|
||||
await self._ubridge_send('bridge reset_packet_filters ' + bridge_name)
|
||||
await self._ubridge_send("bridge reset_packet_filters " + bridge_name)
|
||||
for packet_filter in self._build_filter_list(filters):
|
||||
cmd = f"bridge add_packet_filter {bridge_name} {packet_filter}"
|
||||
try:
|
||||
@@ -818,18 +862,20 @@ class BaseNode:
|
||||
i = 0
|
||||
for (filter_type, values) in filters.items():
|
||||
if isinstance(values[0], str):
|
||||
for line in values[0].split('\n'):
|
||||
for line in values[0].split("\n"):
|
||||
line = line.strip()
|
||||
yield "{filter_name} {filter_type} {filter_value}".format(
|
||||
filter_name="filter" + str(i),
|
||||
filter_type=filter_type,
|
||||
filter_value='"{}" {}'.format(line, " ".join([str(v) for v in values[1:]]))).strip()
|
||||
filter_value='"{}" {}'.format(line, " ".join([str(v) for v in values[1:]])),
|
||||
).strip()
|
||||
i += 1
|
||||
else:
|
||||
yield "{filter_name} {filter_type} {filter_value}".format(
|
||||
filter_name="filter" + str(i),
|
||||
filter_type=filter_type,
|
||||
filter_value=" ".join([str(v) for v in values]))
|
||||
filter_value=" ".join([str(v) for v in values]),
|
||||
)
|
||||
i += 1
|
||||
|
||||
async def _add_ubridge_ethernet_connection(self, bridge_name, ethernet_interface, block_host_traffic=False):
|
||||
@@ -843,9 +889,8 @@ class BaseNode:
|
||||
|
||||
if sys.platform.startswith("linux") and block_host_traffic is False:
|
||||
# on Linux we use RAW sockets by default excepting if host traffic must be blocked
|
||||
await self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=ethernet_interface)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_linux_raw {name} "{interface}"'.format(name=bridge_name, interface=ethernet_interface)
|
||||
)
|
||||
elif sys.platform.startswith("win"):
|
||||
# on Windows we use Winpcap/Npcap
|
||||
@@ -861,36 +906,32 @@ class BaseNode:
|
||||
npf_id = interface["id"]
|
||||
source_mac = interface["mac_address"]
|
||||
if npf_id:
|
||||
await self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=npf_id)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=npf_id)
|
||||
)
|
||||
else:
|
||||
raise NodeError(f"Could not find NPF id for interface {ethernet_interface}")
|
||||
|
||||
if block_host_traffic:
|
||||
if source_mac:
|
||||
await self._ubridge_send('bridge set_pcap_filter {name} "not ether src {mac}"'.format(
|
||||
name=bridge_name,
|
||||
mac=source_mac)
|
||||
await self._ubridge_send(
|
||||
'bridge set_pcap_filter {name} "not ether src {mac}"'.format(name=bridge_name, mac=source_mac)
|
||||
)
|
||||
log.info(f"PCAP filter applied on '{ethernet_interface}' for source MAC {source_mac}")
|
||||
else:
|
||||
log.warning(f"Could not block host network traffic on {ethernet_interface} (no MAC address found)")
|
||||
else:
|
||||
# on other platforms we just rely on the pcap library
|
||||
await self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=ethernet_interface)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=ethernet_interface)
|
||||
)
|
||||
source_mac = None
|
||||
for interface in interfaces():
|
||||
if interface["name"] == ethernet_interface:
|
||||
source_mac = interface["mac_address"]
|
||||
if source_mac:
|
||||
await self._ubridge_send('bridge set_pcap_filter {name} "not ether src {mac}"'.format(
|
||||
name=bridge_name,
|
||||
mac=source_mac)
|
||||
await self._ubridge_send(
|
||||
'bridge set_pcap_filter {name} "not ether src {mac}"'.format(name=bridge_name, mac=source_mac)
|
||||
)
|
||||
log.info(f"PCAP filter applied on '{ethernet_interface}' for source MAC {source_mac}")
|
||||
|
||||
@@ -904,16 +945,14 @@ class BaseNode:
|
||||
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_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(source_nio_settings)
|
||||
destination_nio = self.manager.create_nio(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)
|
||||
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
|
||||
|
||||
@@ -938,11 +977,7 @@ class BaseNode:
|
||||
percentage_left = psutil.virtual_memory().percent
|
||||
if requested_ram > available_ram:
|
||||
message = '"{}" requires {}MB of RAM to run but there is only {}MB - {}% of RAM left on "{}"'.format(
|
||||
self.name,
|
||||
requested_ram,
|
||||
available_ram,
|
||||
percentage_left,
|
||||
platform.node()
|
||||
self.name, requested_ram, available_ram, percentage_left, platform.node()
|
||||
)
|
||||
self.project.emit("log.warning", {"message": message})
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from ..base_manager import BaseManager
|
||||
from .builtin_node_factory import BuiltinNodeFactory, BUILTIN_NODES
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -39,7 +40,7 @@ class Builtin(BaseManager):
|
||||
"""
|
||||
:returns: List of node type supported by this class and computer
|
||||
"""
|
||||
types = ['cloud', 'ethernet_hub', 'ethernet_switch']
|
||||
if BUILTIN_NODES['nat'].is_supported():
|
||||
types.append('nat')
|
||||
types = ["cloud", "ethernet_hub", "ethernet_switch"]
|
||||
if BUILTIN_NODES["nat"].is_supported():
|
||||
types.append("nat")
|
||||
return types
|
||||
@@ -22,12 +22,10 @@ from .nodes.ethernet_hub import EthernetHub
|
||||
from .nodes.ethernet_switch import EthernetSwitch
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
BUILTIN_NODES = {'cloud': Cloud,
|
||||
'nat': Nat,
|
||||
'ethernet_hub': EthernetHub,
|
||||
'ethernet_switch': EthernetSwitch}
|
||||
BUILTIN_NODES = {"cloud": Cloud, "nat": Nat, "ethernet_hub": EthernetHub, "ethernet_switch": EthernetSwitch}
|
||||
|
||||
|
||||
class BuiltinNodeFactory:
|
||||
|
||||
@@ -26,6 +26,7 @@ import gns3server.utils.interfaces
|
||||
import gns3server.utils.asyncio
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -54,12 +55,14 @@ class Cloud(BaseNode):
|
||||
self._ports_mapping = []
|
||||
for interface in self._interfaces():
|
||||
if not interface["special"]:
|
||||
self._ports_mapping.append({
|
||||
"interface": interface["name"],
|
||||
"type": interface["type"],
|
||||
"port_number": len(self._ports_mapping),
|
||||
"name": interface["name"]
|
||||
})
|
||||
self._ports_mapping.append(
|
||||
{
|
||||
"interface": interface["name"],
|
||||
"type": interface["type"],
|
||||
"port_number": len(self._ports_mapping),
|
||||
"name": interface["name"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
port_number = 0
|
||||
for port in ports:
|
||||
@@ -79,23 +82,24 @@ class Cloud(BaseNode):
|
||||
host_interfaces = []
|
||||
network_interfaces = gns3server.utils.interfaces.interfaces()
|
||||
for interface in network_interfaces:
|
||||
host_interfaces.append({"name": interface["name"],
|
||||
"type": interface["type"],
|
||||
"special": interface["special"]})
|
||||
host_interfaces.append(
|
||||
{"name": interface["name"], "type": interface["type"], "special": interface["special"]}
|
||||
)
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"remote_console_host": self.remote_console_host,
|
||||
"remote_console_port": self.remote_console_port,
|
||||
"remote_console_type": self.remote_console_type,
|
||||
"remote_console_http_path": self.remote_console_http_path,
|
||||
"ports_mapping": self._ports_mapping,
|
||||
"interfaces": host_interfaces,
|
||||
"status": self.status,
|
||||
"node_directory": self.working_path
|
||||
}
|
||||
return {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"remote_console_host": self.remote_console_host,
|
||||
"remote_console_port": self.remote_console_port,
|
||||
"remote_console_type": self.remote_console_type,
|
||||
"remote_console_http_path": self.remote_console_http_path,
|
||||
"ports_mapping": self._ports_mapping,
|
||||
"interfaces": host_interfaces,
|
||||
"status": self.status,
|
||||
"node_directory": self.working_path,
|
||||
}
|
||||
|
||||
@property
|
||||
def remote_console_host(self):
|
||||
@@ -265,7 +269,7 @@ class Cloud(BaseNode):
|
||||
return True
|
||||
is_wifi = False
|
||||
else:
|
||||
if 'Wi-Fi' in line:
|
||||
if "Wi-Fi" in line:
|
||||
is_wifi = True
|
||||
return False
|
||||
|
||||
@@ -284,27 +288,27 @@ class Cloud(BaseNode):
|
||||
break
|
||||
|
||||
if not port_info:
|
||||
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(
|
||||
name=self.name,
|
||||
port_number=port_number)
|
||||
raise NodeError(
|
||||
"Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name, port_number=port_number)
|
||||
)
|
||||
|
||||
bridge_name = f"{self._id}-{port_number}"
|
||||
await self._ubridge_send(f"bridge create {bridge_name}")
|
||||
if not isinstance(nio, NIOUDP):
|
||||
raise NodeError("Source NIO is not UDP")
|
||||
await self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(
|
||||
name=bridge_name,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport)
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name, lport=nio.lport, rhost=nio.rhost, rport=nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
await self._ubridge_apply_filters(bridge_name, nio.filters)
|
||||
if port_info["type"] in ("ethernet", "tap"):
|
||||
|
||||
if not self.manager.has_privileged_access(self.ubridge_path):
|
||||
raise NodeError("uBridge requires root access or the capability to interact with Ethernet and TAP adapters")
|
||||
raise NodeError(
|
||||
"uBridge requires root access or the capability to interact with Ethernet and TAP adapters"
|
||||
)
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
await self._add_ubridge_ethernet_connection(bridge_name, port_info["interface"])
|
||||
@@ -313,7 +317,9 @@ class Cloud(BaseNode):
|
||||
if port_info["type"] == "ethernet":
|
||||
network_interfaces = [interface["name"] for interface in self._interfaces()]
|
||||
if not port_info["interface"] in network_interfaces:
|
||||
raise NodeError(f"Interface '{port_info['interface']}' could not be found on this system, please update '{self.name}'")
|
||||
raise NodeError(
|
||||
f"Interface '{port_info['interface']}' could not be found on this system, please update '{self.name}'"
|
||||
)
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
await self._add_linux_ethernet(port_info, bridge_name)
|
||||
@@ -323,23 +329,22 @@ class Cloud(BaseNode):
|
||||
await self._add_windows_ethernet(port_info, bridge_name)
|
||||
|
||||
elif port_info["type"] == "tap":
|
||||
await self._ubridge_send('bridge add_nio_tap {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=port_info["interface"])
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_tap {name} "{interface}"'.format(
|
||||
name=bridge_name, interface=port_info["interface"]
|
||||
)
|
||||
)
|
||||
|
||||
elif port_info["type"] == "udp":
|
||||
await self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(
|
||||
name=bridge_name,
|
||||
lport=port_info["lport"],
|
||||
rhost=port_info["rhost"],
|
||||
rport=port_info["rport"])
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name, lport=port_info["lport"], rhost=port_info["rhost"], rport=port_info["rport"]
|
||||
)
|
||||
)
|
||||
|
||||
if nio.capturing:
|
||||
await self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(
|
||||
name=bridge_name,
|
||||
pcap_file=nio.pcap_output_file)
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{pcap_file}"'.format(name=bridge_name, pcap_file=nio.pcap_output_file)
|
||||
)
|
||||
|
||||
await self._ubridge_send(f"bridge start {bridge_name}")
|
||||
@@ -362,18 +367,13 @@ class Cloud(BaseNode):
|
||||
break
|
||||
i += 1
|
||||
|
||||
await self._ubridge_send('bridge add_nio_tap "{name}" "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=tap)
|
||||
)
|
||||
await self._ubridge_send('brctl addif "{interface}" "{tap}"'.format(
|
||||
tap=tap,
|
||||
interface=interface)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_tap "{name}" "{interface}"'.format(name=bridge_name, interface=tap)
|
||||
)
|
||||
await self._ubridge_send('brctl addif "{interface}" "{tap}"'.format(tap=tap, interface=interface))
|
||||
else:
|
||||
await self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=interface)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_linux_raw {name} "{interface}"'.format(name=bridge_name, interface=interface)
|
||||
)
|
||||
|
||||
async def _add_osx_ethernet(self, port_info, bridge_name):
|
||||
@@ -382,20 +382,20 @@ class Cloud(BaseNode):
|
||||
"""
|
||||
|
||||
# Wireless adapters are not well supported by the libpcap on OSX
|
||||
if (await self._is_wifi_adapter_osx(port_info["interface"])):
|
||||
if await self._is_wifi_adapter_osx(port_info["interface"]):
|
||||
raise NodeError("Connecting to a Wireless adapter is not supported on Mac OS")
|
||||
if port_info["interface"].startswith("vmnet"):
|
||||
# Use a special NIO to connect to VMware vmnet interfaces on OSX (libpcap doesn't support them)
|
||||
await self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=port_info["interface"])
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_fusion_vmnet {name} "{interface}"'.format(
|
||||
name=bridge_name, interface=port_info["interface"]
|
||||
)
|
||||
)
|
||||
return
|
||||
if not gns3server.utils.interfaces.has_netmask(port_info["interface"]):
|
||||
raise NodeError(f"Interface {port_info['interface']} has no netmask, interface down?")
|
||||
await self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=port_info["interface"])
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=port_info["interface"])
|
||||
)
|
||||
|
||||
async def _add_windows_ethernet(self, port_info, bridge_name):
|
||||
@@ -405,9 +405,8 @@ class Cloud(BaseNode):
|
||||
|
||||
if not gns3server.utils.interfaces.has_netmask(port_info["interface"]):
|
||||
raise NodeError(f"Interface {port_info['interface']} has no netmask, interface down?")
|
||||
await self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=port_info["interface"])
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=port_info["interface"])
|
||||
)
|
||||
|
||||
async def add_nio(self, nio, port_number):
|
||||
@@ -421,10 +420,11 @@ class Cloud(BaseNode):
|
||||
if port_number in self._nios:
|
||||
raise NodeError(f"Port {port_number} isn't free")
|
||||
|
||||
log.info('Cloud "{name}" [{id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Cloud "{name}" [{id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
try:
|
||||
await self.start()
|
||||
await self._add_ubridge_connection(nio, port_number)
|
||||
@@ -474,11 +474,10 @@ class Cloud(BaseNode):
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
|
||||
log.info('Cloud "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number)
|
||||
log.info(
|
||||
'Cloud "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._nios[port_number]
|
||||
@@ -497,9 +496,8 @@ class Cloud(BaseNode):
|
||||
"""
|
||||
|
||||
if not [port["port_number"] for port in self._ports_mapping if port_number == port["port_number"]]:
|
||||
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(
|
||||
name=self.name,
|
||||
port_number=port_number)
|
||||
raise NodeError(
|
||||
"Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name, port_number=port_number)
|
||||
)
|
||||
|
||||
if port_number not in self._nios:
|
||||
@@ -523,14 +521,13 @@ class Cloud(BaseNode):
|
||||
raise NodeError(f"Packet capture is already activated on port {port_number}")
|
||||
nio.start_packet_capture(output_file)
|
||||
bridge_name = f"{self._id}-{port_number}"
|
||||
await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(
|
||||
name=bridge_name,
|
||||
output_file=output_file)
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{output_file}"'.format(name=bridge_name, output_file=output_file)
|
||||
)
|
||||
log.info("Cloud '{name}' [{id}]: starting packet capture on port {port_number}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number)
|
||||
log.info(
|
||||
"Cloud '{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):
|
||||
@@ -547,8 +544,8 @@ class Cloud(BaseNode):
|
||||
bridge_name = f"{self._id}-{port_number}"
|
||||
await self._ubridge_send(f"bridge stop_capture {bridge_name}")
|
||||
|
||||
log.info("Cloud'{name}' [{id}]: stopping packet capture on port {port_number}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number)
|
||||
log.info(
|
||||
"Cloud'{name}' [{id}]: stopping packet capture on port {port_number}".format(
|
||||
name=self.name, id=self.id, port_number=port_number
|
||||
)
|
||||
)
|
||||
@@ -19,6 +19,7 @@ import asyncio
|
||||
from ...base_node import BaseNode
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -39,10 +40,7 @@ class EthernetHub(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id}
|
||||
return {"name": self.name, "usage": self.usage, "node_id": self.id, "project_id": self.project.id}
|
||||
|
||||
async def create(self):
|
||||
"""
|
||||
|
||||
@@ -19,6 +19,7 @@ import asyncio
|
||||
from ...base_node import BaseNode
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -39,10 +40,7 @@ class EthernetSwitch(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id}
|
||||
return {"name": self.name, "usage": self.usage, "node_id": self.id, "project_id": self.project.id}
|
||||
|
||||
async def create(self):
|
||||
"""
|
||||
|
||||
@@ -24,6 +24,7 @@ import gns3server.utils.interfaces
|
||||
from gns3server.config import Config
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -46,22 +47,21 @@ class Nat(Cloud):
|
||||
nat_interface = Config.instance().settings.Server.default_nat_interface
|
||||
if not nat_interface:
|
||||
nat_interface = "vmnet8"
|
||||
interfaces = list(filter(lambda x: nat_interface in x.lower(),
|
||||
[interface["name"] for interface in gns3server.utils.interfaces.interfaces()]))
|
||||
interfaces = list(
|
||||
filter(
|
||||
lambda x: nat_interface in x.lower(),
|
||||
[interface["name"] for interface in gns3server.utils.interfaces.interfaces()],
|
||||
)
|
||||
)
|
||||
if not len(interfaces):
|
||||
raise NodeError(f"NAT interface {nat_interface} is missing. "
|
||||
f"You need to install VMware or use the NAT node on GNS3 VM")
|
||||
raise NodeError(
|
||||
f"NAT interface {nat_interface} is missing. "
|
||||
f"You need to install VMware or use the NAT node on GNS3 VM"
|
||||
)
|
||||
interface = interfaces[0] # take the first available interface containing the vmnet8 name
|
||||
|
||||
log.info(f"NAT node '{name}' configured to use NAT interface '{interface}'")
|
||||
ports = [
|
||||
{
|
||||
"name": "nat0",
|
||||
"type": "ethernet",
|
||||
"interface": interface,
|
||||
"port_number": 0
|
||||
}
|
||||
]
|
||||
ports = [{"name": "nat0", "type": "ethernet", "interface": interface, "port_number": 0}]
|
||||
super().__init__(name, node_id, project, manager, ports=ports)
|
||||
|
||||
@property
|
||||
@@ -84,5 +84,5 @@ class Nat(Cloud):
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"status": "started",
|
||||
"ports_mapping": self.ports_mapping
|
||||
"ports_mapping": self.ports_mapping,
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
|
||||
class ComputeError(Exception):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__()
|
||||
self._message = message
|
||||
@@ -30,24 +29,20 @@ class ComputeError(Exception):
|
||||
|
||||
|
||||
class ComputeNotFoundError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeUnauthorizedError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeForbiddenError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeTimeoutError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
@@ -46,7 +46,7 @@ class Docker(BaseManager):
|
||||
def __init__(self):
|
||||
|
||||
super().__init__()
|
||||
self._server_url = '/var/run/docker.sock'
|
||||
self._server_url = "/var/run/docker.sock"
|
||||
self._connected = False
|
||||
# Allow locking during ubridge operations
|
||||
self.ubridge_lock = asyncio.Lock()
|
||||
@@ -64,11 +64,13 @@ class Docker(BaseManager):
|
||||
self._connected = False
|
||||
raise DockerError("Can't connect to docker daemon")
|
||||
|
||||
docker_version = parse_version(version['ApiVersion'])
|
||||
docker_version = parse_version(version["ApiVersion"])
|
||||
|
||||
if docker_version < parse_version(DOCKER_MINIMUM_API_VERSION):
|
||||
raise DockerError(f"Docker version is {version['Version']}. "
|
||||
f"GNS3 requires a minimum version of {DOCKER_MINIMUM_VERSION}")
|
||||
raise DockerError(
|
||||
f"Docker version is {version['Version']}. "
|
||||
f"GNS3 requires a minimum version of {DOCKER_MINIMUM_VERSION}"
|
||||
)
|
||||
|
||||
preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION)
|
||||
if docker_version >= preferred_api_version:
|
||||
@@ -108,7 +110,7 @@ class Docker(BaseManager):
|
||||
body = await response.read()
|
||||
response.close()
|
||||
if body and len(body):
|
||||
if response.headers['CONTENT-TYPE'] == 'application/json':
|
||||
if response.headers["CONTENT-TYPE"] == "application/json":
|
||||
body = json.loads(body.decode("utf-8"))
|
||||
else:
|
||||
body = body.decode("utf-8")
|
||||
@@ -131,8 +133,8 @@ class Docker(BaseManager):
|
||||
if timeout is None:
|
||||
timeout = 60 * 60 * 24 * 31 # One month timeout
|
||||
|
||||
if path == 'version':
|
||||
url = "http://docker/v1.12/" + path # API of docker v1.0
|
||||
if path == "version":
|
||||
url = "http://docker/v1.12/" + path # API of docker v1.0
|
||||
else:
|
||||
url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path
|
||||
try:
|
||||
@@ -141,12 +143,16 @@ class Docker(BaseManager):
|
||||
if self._session is None or self._session.closed:
|
||||
connector = self.connector()
|
||||
self._session = aiohttp.ClientSession(connector=connector)
|
||||
response = await self._session.request(method,
|
||||
url,
|
||||
params=params,
|
||||
data=data,
|
||||
headers={"content-type": "application/json", },
|
||||
timeout=timeout)
|
||||
response = await self._session.request(
|
||||
method,
|
||||
url,
|
||||
params=params,
|
||||
data=data,
|
||||
headers={
|
||||
"content-type": "application/json",
|
||||
},
|
||||
timeout=timeout,
|
||||
)
|
||||
except aiohttp.ClientError as e:
|
||||
raise DockerError(f"Docker has returned an error: {e}")
|
||||
except (asyncio.TimeoutError):
|
||||
@@ -199,8 +205,10 @@ class Docker(BaseManager):
|
||||
try:
|
||||
response = await self.http_query("POST", "images/create", params={"fromImage": image}, timeout=None)
|
||||
except DockerError as e:
|
||||
raise DockerError(f"Could not pull the '{image}' image from Docker Hub, "
|
||||
f"please check your Internet connection (original error: {e})")
|
||||
raise DockerError(
|
||||
f"Could not pull the '{image}' image from Docker Hub, "
|
||||
f"please check your Internet connection (original error: {e})"
|
||||
)
|
||||
# The pull api will stream status via an HTTP JSON stream
|
||||
content = ""
|
||||
while True:
|
||||
@@ -238,9 +246,9 @@ class Docker(BaseManager):
|
||||
"""
|
||||
|
||||
images = []
|
||||
for image in (await self.query("GET", "images/json", params={"all": 0})):
|
||||
if image['RepoTags']:
|
||||
for tag in image['RepoTags']:
|
||||
for image in await self.query("GET", "images/json", params={"all": 0}):
|
||||
if image["RepoTags"]:
|
||||
for tag in image["RepoTags"]:
|
||||
if tag != "<none>:<none>":
|
||||
images.append({'image': tag})
|
||||
return sorted(images, key=lambda i: i['image'])
|
||||
images.append({"image": tag})
|
||||
return sorted(images, key=lambda i: i["image"])
|
||||
@@ -39,13 +39,10 @@ from ..base_node import BaseNode
|
||||
|
||||
from ..adapters.ethernet_adapter import EthernetAdapter
|
||||
from ..nios.nio_udp import NIOUDP
|
||||
from .docker_error import (
|
||||
DockerError,
|
||||
DockerHttp304Error,
|
||||
DockerHttp404Error
|
||||
)
|
||||
from .docker_error import DockerError, DockerHttp304Error, DockerHttp404Error
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -69,11 +66,32 @@ class DockerVM(BaseNode):
|
||||
:param extra_volumes: Additional directories to make persistent
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None,
|
||||
adapters=None, environment=None, console_type="telnet", aux_type="none", console_resolution="1024x768",
|
||||
console_http_port=80, console_http_path="/", extra_hosts=None, extra_volumes=[], memory=0, cpus=0):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
image,
|
||||
console=None,
|
||||
aux=None,
|
||||
start_command=None,
|
||||
adapters=None,
|
||||
environment=None,
|
||||
console_type="telnet",
|
||||
aux_type="none",
|
||||
console_resolution="1024x768",
|
||||
console_http_port=80,
|
||||
console_http_path="/",
|
||||
extra_hosts=None,
|
||||
extra_volumes=[],
|
||||
memory=0,
|
||||
cpus=0,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type)
|
||||
super().__init__(
|
||||
name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type
|
||||
)
|
||||
|
||||
# force the latest image if no version is specified
|
||||
if ":" not in image:
|
||||
@@ -109,10 +127,10 @@ 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):
|
||||
@@ -138,7 +156,7 @@ class DockerVM(BaseNode):
|
||||
"extra_hosts": self.extra_hosts,
|
||||
"extra_volumes": self.extra_volumes,
|
||||
"memory": self.memory,
|
||||
"cpus": self.cpus
|
||||
"cpus": self.cpus,
|
||||
}
|
||||
|
||||
def _get_free_display_port(self):
|
||||
@@ -281,7 +299,9 @@ class DockerVM(BaseNode):
|
||||
volumes.extend((image_info.get("Config", {}).get("Volumes") or {}).keys())
|
||||
for volume in self._extra_volumes:
|
||||
if not volume.strip() or volume[0] != "/" or volume.find("..") >= 0:
|
||||
raise DockerError(f"Persistent volume '{volume}' has invalid format. It must start with a '/' and not contain '..'.")
|
||||
raise DockerError(
|
||||
f"Persistent volume '{volume}' has invalid format. It must start with a '/' and not contain '..'."
|
||||
)
|
||||
volumes.extend(self._extra_volumes)
|
||||
|
||||
self._volumes = []
|
||||
@@ -292,7 +312,7 @@ class DockerVM(BaseNode):
|
||||
# remove any mount that is equal or more specific, then append this one
|
||||
self._volumes = list(filter(lambda v: not generalises(volume, v), self._volumes))
|
||||
# if there is nothing more general, append this mount
|
||||
if not [ v for v in self._volumes if generalises(v, volume) ] :
|
||||
if not [v for v in self._volumes if generalises(v, volume)]:
|
||||
self._volumes.append(volume)
|
||||
|
||||
for volume in self._volumes:
|
||||
@@ -308,7 +328,7 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
path = os.path.join(self.working_dir, "etc", "network")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
open(os.path.join(path, ".gns3_perms"), 'a').close()
|
||||
open(os.path.join(path, ".gns3_perms"), "a").close()
|
||||
os.makedirs(os.path.join(path, "if-up.d"), exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "if-down.d"), exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "if-pre-up.d"), exist_ok=True)
|
||||
@@ -316,13 +336,16 @@ class DockerVM(BaseNode):
|
||||
|
||||
if not os.path.exists(os.path.join(path, "interfaces")):
|
||||
with open(os.path.join(path, "interfaces"), "w+") as f:
|
||||
f.write("""#
|
||||
f.write(
|
||||
"""#
|
||||
# This is a sample network config uncomment lines to configure the network
|
||||
#
|
||||
|
||||
""")
|
||||
"""
|
||||
)
|
||||
for adapter in range(0, self.adapters):
|
||||
f.write("""
|
||||
f.write(
|
||||
"""
|
||||
# Static config for eth{adapter}
|
||||
#auto eth{adapter}
|
||||
#iface eth{adapter} inet static
|
||||
@@ -333,7 +356,10 @@ class DockerVM(BaseNode):
|
||||
|
||||
# DHCP config for eth{adapter}
|
||||
# auto eth{adapter}
|
||||
# iface eth{adapter} inet dhcp""".format(adapter=adapter))
|
||||
# iface eth{adapter} inet dhcp""".format(
|
||||
adapter=adapter
|
||||
)
|
||||
)
|
||||
return path
|
||||
|
||||
async def create(self):
|
||||
@@ -353,8 +379,10 @@ class DockerVM(BaseNode):
|
||||
|
||||
available_cpus = psutil.cpu_count(logical=True)
|
||||
if self._cpus > available_cpus:
|
||||
raise DockerError(f"You have allocated too many CPUs for the Docker container "
|
||||
f"(max available is {available_cpus} CPUs)")
|
||||
raise DockerError(
|
||||
f"You have allocated too many CPUs for the Docker container "
|
||||
f"(max available is {available_cpus} CPUs)"
|
||||
)
|
||||
|
||||
params = {
|
||||
"Hostname": self._name,
|
||||
@@ -369,12 +397,12 @@ class DockerVM(BaseNode):
|
||||
"Privileged": True,
|
||||
"Binds": self._mount_binds(image_infos),
|
||||
"Memory": self._memory * (1024 * 1024), # convert memory to bytes
|
||||
"NanoCpus": int(self._cpus * 1e9) # convert cpus to nano cpus
|
||||
"NanoCpus": int(self._cpus * 1e9), # convert cpus to nano cpus
|
||||
},
|
||||
"Volumes": {},
|
||||
"Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573
|
||||
"Cmd": [],
|
||||
"Entrypoint": image_infos.get("Config", {"Entrypoint": []}).get("Entrypoint")
|
||||
"Entrypoint": image_infos.get("Config", {"Entrypoint": []}).get("Entrypoint"),
|
||||
}
|
||||
|
||||
if params["Entrypoint"] is None:
|
||||
@@ -407,7 +435,7 @@ class DockerVM(BaseNode):
|
||||
variables = []
|
||||
|
||||
for var in variables:
|
||||
formatted = self._format_env(variables, var.get('value', ''))
|
||||
formatted = self._format_env(variables, var.get("value", ""))
|
||||
params["Env"].append("{}={}".format(var["name"], formatted))
|
||||
|
||||
if self._environment:
|
||||
@@ -422,7 +450,9 @@ class DockerVM(BaseNode):
|
||||
|
||||
if self._console_type == "vnc":
|
||||
await self._start_vnc()
|
||||
params["Env"].append("QT_GRAPHICSSYSTEM=native") # To fix a Qt issue: https://github.com/GNS3/gns3-server/issues/556
|
||||
params["Env"].append(
|
||||
"QT_GRAPHICSSYSTEM=native"
|
||||
) # To fix a Qt issue: https://github.com/GNS3/gns3-server/issues/556
|
||||
params["Env"].append(f"DISPLAY=:{self._display}")
|
||||
params["HostConfig"]["Binds"].append("/tmp/.X11-unix/:/tmp/.X11-unix/")
|
||||
|
||||
@@ -432,13 +462,13 @@ class DockerVM(BaseNode):
|
||||
params["Env"].append(f"GNS3_EXTRA_HOSTS={extra_hosts}")
|
||||
|
||||
result = await self.manager.query("POST", "containers/create", data=params)
|
||||
self._cid = result['Id']
|
||||
self._cid = result["Id"]
|
||||
log.info(f"Docker container '{self._name}' [{self._id}] created")
|
||||
return True
|
||||
|
||||
def _format_env(self, variables, env):
|
||||
for variable in variables:
|
||||
env = env.replace('${' + variable["name"] + '}', variable.get("value", ""))
|
||||
env = env.replace("${" + variable["name"] + "}", variable.get("value", ""))
|
||||
return env
|
||||
|
||||
def _format_extra_hosts(self, extra_hosts):
|
||||
@@ -481,9 +511,10 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
state = await self._get_container_state()
|
||||
except DockerHttp404Error:
|
||||
raise DockerError("Docker container '{name}' with ID {cid} does not exist or is not ready yet. Please try again in a few seconds.".format(
|
||||
name=self.name,
|
||||
cid=self._cid)
|
||||
raise DockerError(
|
||||
"Docker container '{name}' with ID {cid} does not exist or is not ready yet. Please try again in a few seconds.".format(
|
||||
name=self.name, cid=self._cid
|
||||
)
|
||||
)
|
||||
if state == "paused":
|
||||
await self.unpause()
|
||||
@@ -514,7 +545,7 @@ class DockerVM(BaseNode):
|
||||
|
||||
# The container can crash soon after the start, this means we can not move the interface to the container namespace
|
||||
logdata = await self._get_log()
|
||||
for line in logdata.split('\n'):
|
||||
for line in logdata.split("\n"):
|
||||
log.error(line)
|
||||
raise DockerError(logdata)
|
||||
|
||||
@@ -528,11 +559,10 @@ class DockerVM(BaseNode):
|
||||
|
||||
self._permissions_fixed = False
|
||||
self.status = "started"
|
||||
log.info("Docker container '{name}' [{image}] started listen for {console_type} on {console}".format(
|
||||
name=self._name,
|
||||
image=self._image,
|
||||
console=self.console,
|
||||
console_type=self.console_type)
|
||||
log.info(
|
||||
"Docker container '{name}' [{image}] started listen for {console_type} on {console}".format(
|
||||
name=self._name, image=self._image, console=self.console, console_type=self.console_type
|
||||
)
|
||||
)
|
||||
|
||||
async def _start_aux(self):
|
||||
@@ -544,17 +574,30 @@ class DockerVM(BaseNode):
|
||||
# https://github.com/GNS3/gns3-gui/issues/1039
|
||||
try:
|
||||
process = await asyncio.subprocess.create_subprocess_exec(
|
||||
"docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "while true; do TERM=vt100 /gns3/bin/busybox sh; done", "/dev/null",
|
||||
"docker",
|
||||
"exec",
|
||||
"-i",
|
||||
self._cid,
|
||||
"/gns3/bin/busybox",
|
||||
"script",
|
||||
"-qfc",
|
||||
"while true; do TERM=vt100 /gns3/bin/busybox sh; done",
|
||||
"/dev/null",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
stdin=asyncio.subprocess.PIPE)
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
)
|
||||
except OSError as e:
|
||||
raise DockerError(f"Could not start auxiliary console process: {e}")
|
||||
server = AsyncioTelnetServer(reader=process.stdout, writer=process.stdin, binary=True, echo=True)
|
||||
try:
|
||||
self._telnet_servers.append(await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux))
|
||||
self._telnet_servers.append(
|
||||
await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux)
|
||||
)
|
||||
except OSError as e:
|
||||
raise DockerError(f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.aux}: {e}")
|
||||
raise DockerError(
|
||||
f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.aux}: {e}"
|
||||
)
|
||||
log.debug(f"Docker container '{self.name}' started listen for auxiliary telnet on {self.aux}")
|
||||
|
||||
async def _fix_permissions(self):
|
||||
@@ -570,8 +613,11 @@ class DockerVM(BaseNode):
|
||||
await self.manager.query("POST", f"containers/{self._cid}/start")
|
||||
|
||||
for volume in self._volumes:
|
||||
log.debug("Docker container '{name}' [{image}] fix ownership on {path}".format(
|
||||
name=self._name, image=self._image, path=volume))
|
||||
log.debug(
|
||||
"Docker container '{name}' [{image}] fix ownership on {path}".format(
|
||||
name=self._name, image=self._image, path=volume
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
process = await asyncio.subprocess.create_subprocess_exec(
|
||||
@@ -582,12 +628,13 @@ class DockerVM(BaseNode):
|
||||
"sh",
|
||||
"-c",
|
||||
"("
|
||||
"/gns3/bin/busybox find \"{path}\" -depth -print0"
|
||||
'/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),
|
||||
' && /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
|
||||
),
|
||||
)
|
||||
except OSError as e:
|
||||
raise DockerError(f"Could not fix permissions for {volume}: {e}")
|
||||
@@ -607,36 +654,50 @@ class DockerVM(BaseNode):
|
||||
|
||||
if tigervnc_path:
|
||||
with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd:
|
||||
self._vnc_process = await asyncio.create_subprocess_exec(tigervnc_path,
|
||||
"-geometry", self._console_resolution,
|
||||
"-depth", "16",
|
||||
"-interface", self._manager.port_manager.console_host,
|
||||
"-rfbport", str(self.console),
|
||||
"-AlwaysShared",
|
||||
"-SecurityTypes", "None",
|
||||
f":{self._display}",
|
||||
stdout=fd, stderr=subprocess.STDOUT)
|
||||
self._vnc_process = await asyncio.create_subprocess_exec(
|
||||
tigervnc_path,
|
||||
"-geometry",
|
||||
self._console_resolution,
|
||||
"-depth",
|
||||
"16",
|
||||
"-interface",
|
||||
self._manager.port_manager.console_host,
|
||||
"-rfbport",
|
||||
str(self.console),
|
||||
"-AlwaysShared",
|
||||
"-SecurityTypes",
|
||||
"None",
|
||||
f":{self._display}",
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
else:
|
||||
if restart is False:
|
||||
self._xvfb_process = await asyncio.create_subprocess_exec("Xvfb",
|
||||
"-nolisten",
|
||||
"tcp", f":{self._display}",
|
||||
"-screen", "0",
|
||||
self._console_resolution + "x16")
|
||||
self._xvfb_process = await asyncio.create_subprocess_exec(
|
||||
"Xvfb", "-nolisten", "tcp", f":{self._display}", "-screen", "0", self._console_resolution + "x16"
|
||||
)
|
||||
|
||||
# We pass a port for TCPV6 due to a crash in X11VNC if not here: https://github.com/GNS3/gns3-server/issues/569
|
||||
with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd:
|
||||
self._vnc_process = await asyncio.create_subprocess_exec("x11vnc",
|
||||
"-forever",
|
||||
"-nopw",
|
||||
"-shared",
|
||||
"-geometry", self._console_resolution,
|
||||
"-display", f"WAIT:{self._display}",
|
||||
"-rfbport", str(self.console),
|
||||
"-rfbportv6", str(self.console),
|
||||
"-noncache",
|
||||
"-listen", self._manager.port_manager.console_host,
|
||||
stdout=fd, stderr=subprocess.STDOUT)
|
||||
self._vnc_process = await asyncio.create_subprocess_exec(
|
||||
"x11vnc",
|
||||
"-forever",
|
||||
"-nopw",
|
||||
"-shared",
|
||||
"-geometry",
|
||||
self._console_resolution,
|
||||
"-display",
|
||||
f"WAIT:{self._display}",
|
||||
"-rfbport",
|
||||
str(self.console),
|
||||
"-rfbportv6",
|
||||
str(self.console),
|
||||
"-noncache",
|
||||
"-listen",
|
||||
self._manager.port_manager.console_host,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
|
||||
async def _start_vnc(self):
|
||||
"""
|
||||
@@ -658,7 +719,9 @@ class DockerVM(BaseNode):
|
||||
# Start vncconfig for tigervnc clipboard support, connection available only after socket creation.
|
||||
tigervncconfig_path = shutil.which("vncconfig")
|
||||
if tigervnc_path and tigervncconfig_path:
|
||||
self._vncconfig_process = await asyncio.create_subprocess_exec(tigervncconfig_path, "-display", f":{self._display}", "-nowin")
|
||||
self._vncconfig_process = await asyncio.create_subprocess_exec(
|
||||
tigervncconfig_path, "-display", f":{self._display}", "-nowin"
|
||||
)
|
||||
|
||||
# sometimes the VNC process can crash
|
||||
monitor_process(self._vnc_process, self._vnc_callback)
|
||||
@@ -671,7 +734,12 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
|
||||
if returncode != 0 and self._closing is False:
|
||||
self.project.emit("log.error", {"message": f"The vnc process has stopped with return code {returncode} for node '{self.name}'. Please restart this node."})
|
||||
self.project.emit(
|
||||
"log.error",
|
||||
{
|
||||
"message": f"The vnc process has stopped with return code {returncode} for node '{self.name}'. Please restart this node."
|
||||
},
|
||||
)
|
||||
self._vnc_process = None
|
||||
|
||||
async def _start_http(self):
|
||||
@@ -681,19 +749,33 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
|
||||
log.debug("Forward HTTP for %s to %d", self.name, self._console_http_port)
|
||||
command = ["docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "nc", "127.0.0.1", str(self._console_http_port)]
|
||||
command = [
|
||||
"docker",
|
||||
"exec",
|
||||
"-i",
|
||||
self._cid,
|
||||
"/gns3/bin/busybox",
|
||||
"nc",
|
||||
"127.0.0.1",
|
||||
str(self._console_http_port),
|
||||
]
|
||||
# We replace host and port in the server answer otherwise some link could be broken
|
||||
server = AsyncioRawCommandServer(command, replaces=[
|
||||
(
|
||||
b'://127.0.0.1', # {{HOST}} mean client host
|
||||
b'://{{HOST}}',
|
||||
),
|
||||
(
|
||||
f':{self._console_http_port}'.encode(),
|
||||
f':{self.console}'.encode(),
|
||||
)
|
||||
])
|
||||
self._telnet_servers.append(await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console))
|
||||
server = AsyncioRawCommandServer(
|
||||
command,
|
||||
replaces=[
|
||||
(
|
||||
b"://127.0.0.1", # {{HOST}} mean client host
|
||||
b"://{{HOST}}",
|
||||
),
|
||||
(
|
||||
f":{self._console_http_port}".encode(),
|
||||
f":{self.console}".encode(),
|
||||
),
|
||||
],
|
||||
)
|
||||
self._telnet_servers.append(
|
||||
await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
|
||||
)
|
||||
|
||||
async def _window_size_changed_callback(self, columns, rows):
|
||||
"""
|
||||
@@ -707,14 +789,12 @@ class DockerVM(BaseNode):
|
||||
# resize the container TTY.
|
||||
await self._manager.query("POST", f"containers/{self._cid}/resize?h={rows}&w={columns}")
|
||||
|
||||
|
||||
async def _start_console(self):
|
||||
"""
|
||||
Starts streaming the console via telnet
|
||||
"""
|
||||
|
||||
class InputStream:
|
||||
|
||||
def __init__(self):
|
||||
self._data = b""
|
||||
|
||||
@@ -728,13 +808,25 @@ class DockerVM(BaseNode):
|
||||
|
||||
output_stream = asyncio.StreamReader()
|
||||
input_stream = InputStream()
|
||||
telnet = AsyncioTelnetServer(reader=output_stream, writer=input_stream, echo=True, naws=True, window_size_changed_callback=self._window_size_changed_callback)
|
||||
telnet = AsyncioTelnetServer(
|
||||
reader=output_stream,
|
||||
writer=input_stream,
|
||||
echo=True,
|
||||
naws=True,
|
||||
window_size_changed_callback=self._window_size_changed_callback,
|
||||
)
|
||||
try:
|
||||
self._telnet_servers.append(await asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console))
|
||||
self._telnet_servers.append(
|
||||
await asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console)
|
||||
)
|
||||
except OSError as e:
|
||||
raise DockerError(f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}")
|
||||
raise DockerError(
|
||||
f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"
|
||||
)
|
||||
|
||||
self._console_websocket = await self.manager.websocket_query(f"containers/{self._cid}/attach/ws?stream=1&stdin=1&stdout=1&stderr=1")
|
||||
self._console_websocket = await self.manager.websocket_query(
|
||||
f"containers/{self._cid}/attach/ws?stream=1&stdin=1&stdout=1&stderr=1"
|
||||
)
|
||||
input_stream.ws = self._console_websocket
|
||||
|
||||
output_stream.feed_data(self.name.encode() + b" console is now available... Press RETURN to get started.\r\n")
|
||||
@@ -792,8 +884,7 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
|
||||
await self.manager.query("POST", f"containers/{self._cid}/restart")
|
||||
log.info("Docker container '{name}' [{image}] restarted".format(
|
||||
name=self._name, image=self._image))
|
||||
log.info("Docker container '{name}' [{image}] restarted".format(name=self._name, image=self._image))
|
||||
|
||||
async def _clean_servers(self):
|
||||
"""
|
||||
@@ -911,8 +1002,7 @@ class DockerVM(BaseNode):
|
||||
await self.manager.query("DELETE", f"containers/{self._cid}", params={"force": 1, "v": 1})
|
||||
except DockerError:
|
||||
pass
|
||||
log.info("Docker container '{name}' [{image}] removed".format(
|
||||
name=self._name, image=self._image))
|
||||
log.info("Docker container '{name}' [{image}] removed".format(name=self._name, image=self._image))
|
||||
|
||||
if release_nio_udp_ports:
|
||||
for adapter in self._ethernet_adapters:
|
||||
@@ -936,26 +1026,37 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
for index in range(4096):
|
||||
if f"tap-gns3-e{index}" not in psutil.net_if_addrs():
|
||||
adapter.host_ifc = f"tap-gns3-e{str(index)}"
|
||||
break
|
||||
if adapter.host_ifc is None:
|
||||
raise DockerError("Adapter {adapter_number} couldn't allocate interface on Docker container '{name}'. Too many Docker interfaces already exists".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
bridge_name = f'bridge{adapter_number}'
|
||||
await self._ubridge_send(f'bridge create {bridge_name}')
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} couldn't allocate interface on Docker container '{name}'. Too many Docker interfaces already exists".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
bridge_name = f"bridge{adapter_number}"
|
||||
await self._ubridge_send(f"bridge create {bridge_name}")
|
||||
self._bridges.add(bridge_name)
|
||||
await self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format(adapter_number=adapter_number,
|
||||
hostif=adapter.host_ifc))
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_tap bridge{adapter_number} {hostif}".format(
|
||||
adapter_number=adapter_number, hostif=adapter.host_ifc
|
||||
)
|
||||
)
|
||||
log.debug("Move container %s adapter %s to namespace %s", self.name, adapter.host_ifc, self._namespace)
|
||||
try:
|
||||
await self._ubridge_send('docker move_to_ns {ifc} {ns} eth{adapter}'.format(ifc=adapter.host_ifc,
|
||||
ns=self._namespace,
|
||||
adapter=adapter_number))
|
||||
await self._ubridge_send(
|
||||
"docker move_to_ns {ifc} {ns} eth{adapter}".format(
|
||||
ifc=adapter.host_ifc, ns=self._namespace, adapter=adapter_number
|
||||
)
|
||||
)
|
||||
except UbridgeError as e:
|
||||
raise UbridgeNamespaceError(e)
|
||||
|
||||
@@ -965,20 +1066,24 @@ class DockerVM(BaseNode):
|
||||
async def _get_namespace(self):
|
||||
|
||||
result = await self.manager.query("GET", f"containers/{self._cid}/json")
|
||||
return int(result['State']['Pid'])
|
||||
return int(result["State"]["Pid"])
|
||||
|
||||
async def _connect_nio(self, adapter_number, nio):
|
||||
|
||||
bridge_name = f'bridge{adapter_number}'
|
||||
await self._ubridge_send('bridge add_nio_udp {bridge_name} {lport} {rhost} {rport}'.format(bridge_name=bridge_name,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
bridge_name = f"bridge{adapter_number}"
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {bridge_name} {lport} {rhost} {rport}".format(
|
||||
bridge_name=bridge_name, lport=nio.lport, rhost=nio.rhost, rport=nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
if nio.capturing:
|
||||
await self._ubridge_send('bridge start_capture {bridge_name} "{pcap_file}"'.format(bridge_name=bridge_name,
|
||||
pcap_file=nio.pcap_output_file))
|
||||
await self._ubridge_send(f'bridge start {bridge_name}')
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {bridge_name} "{pcap_file}"'.format(
|
||||
bridge_name=bridge_name, pcap_file=nio.pcap_output_file
|
||||
)
|
||||
)
|
||||
await self._ubridge_send(f"bridge start {bridge_name}")
|
||||
await self._ubridge_apply_filters(bridge_name, nio.filters)
|
||||
|
||||
async def adapter_add_nio_binding(self, adapter_number, nio):
|
||||
@@ -992,17 +1097,21 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._connect_nio(adapter_number, nio)
|
||||
|
||||
adapter.add_nio(0, nio)
|
||||
log.info("Docker container '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"Docker container '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(
|
||||
name=self.name, id=self._id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
@@ -1013,7 +1122,7 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
|
||||
if self.ubridge:
|
||||
bridge_name = f'bridge{adapter_number}'
|
||||
bridge_name = f"bridge{adapter_number}"
|
||||
if bridge_name in self._bridges:
|
||||
await self._ubridge_apply_filters(bridge_name, nio.filters)
|
||||
|
||||
@@ -1029,25 +1138,30 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
await self.stop_capture(adapter_number)
|
||||
if self.ubridge:
|
||||
nio = adapter.get_nio(0)
|
||||
bridge_name = f'bridge{adapter_number}'
|
||||
bridge_name = f"bridge{adapter_number}"
|
||||
await self._ubridge_send(f"bridge stop {bridge_name}")
|
||||
await self._ubridge_send('bridge remove_nio_udp bridge{adapter} {lport} {rhost} {rport}'.format(adapter=adapter_number,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
await self._ubridge_send(
|
||||
"bridge remove_nio_udp bridge{adapter} {lport} {rhost} {rport}".format(
|
||||
adapter=adapter_number, lport=nio.lport, rhost=nio.rhost, rport=nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
adapter.remove_nio(0)
|
||||
|
||||
log.info("Docker VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=adapter.host_ifc,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"Docker VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=adapter.host_ifc, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
@@ -1061,8 +1175,11 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
@@ -1097,9 +1214,11 @@ class DockerVM(BaseNode):
|
||||
for adapter_number in range(0, adapters):
|
||||
self._ethernet_adapters.append(EthernetAdapter())
|
||||
|
||||
log.info('Docker container "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapters=adapters))
|
||||
log.info(
|
||||
'Docker container "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(
|
||||
name=self._name, id=self._id, adapters=adapters
|
||||
)
|
||||
)
|
||||
|
||||
async def pull_image(self, image):
|
||||
"""
|
||||
@@ -1108,6 +1227,7 @@ class DockerVM(BaseNode):
|
||||
|
||||
def callback(msg):
|
||||
self.project.emit("log.info", {"message": msg})
|
||||
|
||||
await self.manager.pull_image(image, progress_callback=callback)
|
||||
|
||||
async def _start_ubridge_capture(self, adapter_number, output_file):
|
||||
@@ -1151,9 +1271,11 @@ class DockerVM(BaseNode):
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._start_ubridge_capture(adapter_number, output_file)
|
||||
|
||||
log.info("Docker VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"Docker VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, adapter_number):
|
||||
"""
|
||||
@@ -1169,9 +1291,11 @@ class DockerVM(BaseNode):
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._stop_ubridge_capture(adapter_number)
|
||||
|
||||
log.info("Docker VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"Docker VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def _get_log(self):
|
||||
"""
|
||||
|
||||
@@ -74,36 +74,38 @@ from .adapters.wic_1t import WIC_1T
|
||||
from .adapters.wic_2t import WIC_2T
|
||||
|
||||
|
||||
ADAPTER_MATRIX = {"C7200-IO-2FE": C7200_IO_2FE,
|
||||
"C7200-IO-FE": C7200_IO_FE,
|
||||
"C7200-IO-GE-E": C7200_IO_GE_E,
|
||||
"NM-16ESW": NM_16ESW,
|
||||
"NM-1E": NM_1E,
|
||||
"NM-1FE-TX": NM_1FE_TX,
|
||||
"NM-4E": NM_4E,
|
||||
"NM-4T": NM_4T,
|
||||
"PA-2FE-TX": PA_2FE_TX,
|
||||
"PA-4E": PA_4E,
|
||||
"PA-4T+": PA_4T,
|
||||
"PA-8E": PA_8E,
|
||||
"PA-8T": PA_8T,
|
||||
"PA-A1": PA_A1,
|
||||
"PA-FE-TX": PA_FE_TX,
|
||||
"PA-GE": PA_GE,
|
||||
"PA-POS-OC3": PA_POS_OC3}
|
||||
ADAPTER_MATRIX = {
|
||||
"C7200-IO-2FE": C7200_IO_2FE,
|
||||
"C7200-IO-FE": C7200_IO_FE,
|
||||
"C7200-IO-GE-E": C7200_IO_GE_E,
|
||||
"NM-16ESW": NM_16ESW,
|
||||
"NM-1E": NM_1E,
|
||||
"NM-1FE-TX": NM_1FE_TX,
|
||||
"NM-4E": NM_4E,
|
||||
"NM-4T": NM_4T,
|
||||
"PA-2FE-TX": PA_2FE_TX,
|
||||
"PA-4E": PA_4E,
|
||||
"PA-4T+": PA_4T,
|
||||
"PA-8E": PA_8E,
|
||||
"PA-8T": PA_8T,
|
||||
"PA-A1": PA_A1,
|
||||
"PA-FE-TX": PA_FE_TX,
|
||||
"PA-GE": PA_GE,
|
||||
"PA-POS-OC3": PA_POS_OC3,
|
||||
}
|
||||
|
||||
WIC_MATRIX = {"WIC-1ENET": WIC_1ENET,
|
||||
"WIC-1T": WIC_1T,
|
||||
"WIC-2T": WIC_2T}
|
||||
WIC_MATRIX = {"WIC-1ENET": WIC_1ENET, "WIC-1T": WIC_1T, "WIC-2T": WIC_2T}
|
||||
|
||||
|
||||
PLATFORMS_DEFAULT_RAM = {"c1700": 160,
|
||||
"c2600": 160,
|
||||
"c2691": 192,
|
||||
"c3600": 192,
|
||||
"c3725": 128,
|
||||
"c3745": 256,
|
||||
"c7200": 512}
|
||||
PLATFORMS_DEFAULT_RAM = {
|
||||
"c1700": 160,
|
||||
"c2600": 160,
|
||||
"c2691": 192,
|
||||
"c3600": 192,
|
||||
"c3725": 128,
|
||||
"c3745": 256,
|
||||
"c7200": 512,
|
||||
}
|
||||
|
||||
|
||||
class Dynamips(BaseManager):
|
||||
@@ -126,7 +128,7 @@ class Dynamips(BaseManager):
|
||||
"""
|
||||
:returns: List of node type supported by this class and computer
|
||||
"""
|
||||
return ['dynamips', 'frame_relay_switch', 'atm_switch']
|
||||
return ["dynamips", "frame_relay_switch", "atm_switch"]
|
||||
|
||||
def get_dynamips_id(self, project_id):
|
||||
"""
|
||||
@@ -301,7 +303,7 @@ class Dynamips(BaseManager):
|
||||
await hypervisor.start()
|
||||
log.info(f"Hypervisor {hypervisor.host}:{hypervisor.port} has successfully started")
|
||||
await hypervisor.connect()
|
||||
if parse_version(hypervisor.version) < parse_version('0.2.11'):
|
||||
if parse_version(hypervisor.version) < parse_version("0.2.11"):
|
||||
raise DynamipsError(f"Dynamips version must be >= 0.2.11, detected version is {hypervisor.version}")
|
||||
|
||||
return hypervisor
|
||||
@@ -408,7 +410,15 @@ class Dynamips(BaseManager):
|
||||
if ghost_file_path not in self._ghost_files:
|
||||
# create a new ghost IOS instance
|
||||
ghost_id = str(uuid4())
|
||||
ghost = Router("ghost-" + ghost_file, ghost_id, vm.project, vm.manager, platform=vm.platform, hypervisor=vm.hypervisor, ghost_flag=True)
|
||||
ghost = Router(
|
||||
"ghost-" + ghost_file,
|
||||
ghost_id,
|
||||
vm.project,
|
||||
vm.manager,
|
||||
platform=vm.platform,
|
||||
hypervisor=vm.hypervisor,
|
||||
ghost_flag=True,
|
||||
)
|
||||
try:
|
||||
await ghost.create()
|
||||
await ghost.set_image(vm.image)
|
||||
@@ -538,7 +548,7 @@ class Dynamips(BaseManager):
|
||||
with open(path, "wb") as f:
|
||||
if content:
|
||||
content = "!\n" + content.replace("\r", "")
|
||||
content = content.replace('%h', vm.name)
|
||||
content = content.replace("%h", vm.name)
|
||||
f.write(content.encode("utf-8"))
|
||||
except OSError as e:
|
||||
raise DynamipsError(f"Could not create config file '{path}': {e}")
|
||||
@@ -571,7 +581,7 @@ class Dynamips(BaseManager):
|
||||
for idlepc in idlepcs:
|
||||
match = re.search(r"^0x[0-9a-f]{8}$", idlepc.split()[0])
|
||||
if not match:
|
||||
continue
|
||||
continue
|
||||
await vm.set_idlepc(idlepc.split()[0])
|
||||
log.debug(f"Auto Idle-PC: trying idle-PC value {vm.idlepc}")
|
||||
start_time = time.time()
|
||||
@@ -615,7 +625,7 @@ class Dynamips(BaseManager):
|
||||
|
||||
# Not a Dynamips router
|
||||
if not hasattr(source_node, "startup_config_path"):
|
||||
return (await super().duplicate_node(source_node_id, destination_node_id))
|
||||
return await super().duplicate_node(source_node_id, destination_node_id)
|
||||
|
||||
try:
|
||||
with open(source_node.startup_config_path) as f:
|
||||
@@ -627,10 +637,9 @@ class Dynamips(BaseManager):
|
||||
private_config = f.read()
|
||||
except OSError:
|
||||
private_config = None
|
||||
await self.set_vm_configs(destination_node, {
|
||||
"startup_config_content": startup_config,
|
||||
"private_config_content": private_config
|
||||
})
|
||||
await self.set_vm_configs(
|
||||
destination_node, {"startup_config_content": startup_config, "private_config_content": private_config}
|
||||
)
|
||||
|
||||
# Force refresh of the name in configuration files
|
||||
new_name = destination_node.name
|
||||
|
||||
@@ -18,7 +18,6 @@ from .adapter import Adapter
|
||||
|
||||
|
||||
class GT96100_FE(Adapter):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super().__init__(interfaces=2, wics=3)
|
||||
|
||||
@@ -29,21 +29,26 @@ from .nodes.ethernet_hub import EthernetHub
|
||||
from .nodes.frame_relay_switch import FrameRelaySwitch
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = {'c1700': C1700,
|
||||
'c2600': C2600,
|
||||
'c2691': C2691,
|
||||
'c3725': C3725,
|
||||
'c3745': C3745,
|
||||
'c3600': C3600,
|
||||
'c7200': C7200}
|
||||
PLATFORMS = {
|
||||
"c1700": C1700,
|
||||
"c2600": C2600,
|
||||
"c2691": C2691,
|
||||
"c3725": C3725,
|
||||
"c3745": C3745,
|
||||
"c3600": C3600,
|
||||
"c7200": C7200,
|
||||
}
|
||||
|
||||
|
||||
DEVICES = {'atm_switch': ATMSwitch,
|
||||
'frame_relay_switch': FrameRelaySwitch,
|
||||
'ethernet_switch': EthernetSwitch,
|
||||
'ethernet_hub': EthernetHub}
|
||||
DEVICES = {
|
||||
"atm_switch": ATMSwitch,
|
||||
"frame_relay_switch": FrameRelaySwitch,
|
||||
"ethernet_switch": EthernetSwitch,
|
||||
"ethernet_hub": EthernetHub,
|
||||
}
|
||||
|
||||
|
||||
class DynamipsFactory:
|
||||
|
||||
@@ -78,7 +78,9 @@ class DynamipsHypervisor:
|
||||
while time.time() - begin < timeout:
|
||||
await asyncio.sleep(0.01)
|
||||
try:
|
||||
self._reader, self._writer = await asyncio.wait_for(asyncio.open_connection(host, self._port), timeout=1)
|
||||
self._reader, self._writer = await asyncio.wait_for(
|
||||
asyncio.open_connection(host, self._port), timeout=1
|
||||
)
|
||||
except (asyncio.TimeoutError, OSError) as e:
|
||||
last_exception = e
|
||||
continue
|
||||
@@ -242,17 +244,20 @@ class DynamipsHypervisor:
|
||||
raise DynamipsError("Not connected")
|
||||
|
||||
try:
|
||||
command = command.strip() + '\n'
|
||||
command = command.strip() + "\n"
|
||||
log.debug(f"sending {command}")
|
||||
self._writer.write(command.encode())
|
||||
await self._writer.drain()
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not send Dynamips command '{command}' to {host}:{port}: {error}, process running: {run}"
|
||||
.format(command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()))
|
||||
raise DynamipsError(
|
||||
"Could not send Dynamips command '{command}' to {host}:{port}: {error}, process running: {run}".format(
|
||||
command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
# Now retrieve the result
|
||||
data = []
|
||||
buf = ''
|
||||
buf = ""
|
||||
retries = 0
|
||||
max_retries = 10
|
||||
while True:
|
||||
@@ -272,8 +277,11 @@ class DynamipsHypervisor:
|
||||
continue
|
||||
if not chunk:
|
||||
if retries > max_retries:
|
||||
raise DynamipsError("No data returned from {host}:{port}, Dynamips process running: {run}"
|
||||
.format(host=self._host, port=self._port, run=self.is_running()))
|
||||
raise DynamipsError(
|
||||
"No data returned from {host}:{port}, Dynamips process running: {run}".format(
|
||||
host=self._host, port=self._port, run=self.is_running()
|
||||
)
|
||||
)
|
||||
else:
|
||||
retries += 1
|
||||
await asyncio.sleep(0.1)
|
||||
@@ -281,30 +289,36 @@ class DynamipsHypervisor:
|
||||
retries = 0
|
||||
buf += chunk.decode("utf-8", errors="ignore")
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not read response for '{command}' from {host}:{port}: {error}, process running: {run}"
|
||||
.format(command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()))
|
||||
raise DynamipsError(
|
||||
"Could not read response for '{command}' from {host}:{port}: {error}, process running: {run}".format(
|
||||
command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
# If the buffer doesn't end in '\n' then we can't be done
|
||||
try:
|
||||
if buf[-1] != '\n':
|
||||
if buf[-1] != "\n":
|
||||
continue
|
||||
except IndexError:
|
||||
raise DynamipsError("Could not communicate with {host}:{port}, Dynamips process running: {run}"
|
||||
.format(host=self._host, port=self._port, run=self.is_running()))
|
||||
raise DynamipsError(
|
||||
"Could not communicate with {host}:{port}, Dynamips process running: {run}".format(
|
||||
host=self._host, port=self._port, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
data += buf.split('\r\n')
|
||||
if data[-1] == '':
|
||||
data += buf.split("\r\n")
|
||||
if data[-1] == "":
|
||||
data.pop()
|
||||
buf = ''
|
||||
buf = ""
|
||||
|
||||
# Does it contain an error code?
|
||||
if self.error_re.search(data[-1]):
|
||||
raise DynamipsError(f"Dynamips error when running command '{command}': {data[-1][4:]}")
|
||||
|
||||
# Or does the last line begin with '100-'? Then we are done!
|
||||
if data[-1][:4] == '100-':
|
||||
if data[-1][:4] == "100-":
|
||||
data[-1] = data[-1][4:]
|
||||
if data[-1] == 'OK':
|
||||
if data[-1] == "OK":
|
||||
data.pop()
|
||||
break
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ from .dynamips_hypervisor import DynamipsHypervisor
|
||||
from .dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -121,17 +122,15 @@ class Hypervisor(DynamipsHypervisor):
|
||||
# add the Npcap directory to $PATH to force Dynamips to use npcap DLL instead of Winpcap (if installed)
|
||||
system_root = os.path.join(os.path.expandvars("%SystemRoot%"), "System32", "Npcap")
|
||||
if os.path.isdir(system_root):
|
||||
env["PATH"] = system_root + ';' + env["PATH"]
|
||||
env["PATH"] = system_root + ";" + env["PATH"]
|
||||
try:
|
||||
log.info(f"Starting Dynamips: {self._command}")
|
||||
self._stdout_file = os.path.join(self.working_dir, f"dynamips_i{self._id}_stdout.txt")
|
||||
log.info(f"Dynamips process logging to {self._stdout_file}")
|
||||
with open(self._stdout_file, "w", encoding="utf-8") as fd:
|
||||
self._process = await asyncio.create_subprocess_exec(*self._command,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self._working_dir,
|
||||
env=env)
|
||||
self._process = await asyncio.create_subprocess_exec(
|
||||
*self._command, stdout=fd, stderr=subprocess.STDOUT, cwd=self._working_dir, env=env
|
||||
)
|
||||
log.info(f"Dynamips process started PID={self._process.pid}")
|
||||
self._started = True
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
@@ -159,7 +158,7 @@ class Hypervisor(DynamipsHypervisor):
|
||||
except OSError as e:
|
||||
log.error(f"Cannot stop the Dynamips process: {e}")
|
||||
if self._process.returncode is None:
|
||||
log.warning(f'Dynamips hypervisor with PID={self._process.pid} is still running')
|
||||
log.warning(f"Dynamips hypervisor with PID={self._process.pid} is still running")
|
||||
|
||||
if self._stdout_file and os.access(self._stdout_file, os.W_OK):
|
||||
try:
|
||||
|
||||
@@ -23,6 +23,7 @@ import asyncio
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -130,9 +131,11 @@ class NIO:
|
||||
raise DynamipsError(f"Unknown direction {direction} to bind filter {filter_name}:")
|
||||
dynamips_direction = self._dynamips_direction[direction]
|
||||
|
||||
await self._hypervisor.send("nio bind_filter {name} {direction} {filter}".format(name=self._name,
|
||||
direction=dynamips_direction,
|
||||
filter=filter_name))
|
||||
await self._hypervisor.send(
|
||||
"nio bind_filter {name} {direction} {filter}".format(
|
||||
name=self._name, direction=dynamips_direction, filter=filter_name
|
||||
)
|
||||
)
|
||||
|
||||
if direction == "in":
|
||||
self._input_filter = filter_name
|
||||
@@ -153,8 +156,9 @@ class NIO:
|
||||
raise DynamipsError(f"Unknown direction {direction} to unbind filter:")
|
||||
dynamips_direction = self._dynamips_direction[direction]
|
||||
|
||||
await self._hypervisor.send("nio unbind_filter {name} {direction}".format(name=self._name,
|
||||
direction=dynamips_direction))
|
||||
await self._hypervisor.send(
|
||||
"nio unbind_filter {name} {direction}".format(name=self._name, direction=dynamips_direction)
|
||||
)
|
||||
|
||||
if direction == "in":
|
||||
self._input_filter = None
|
||||
@@ -187,9 +191,11 @@ class NIO:
|
||||
raise DynamipsError(f"Unknown direction {direction} to setup filter:")
|
||||
dynamips_direction = self._dynamips_direction[direction]
|
||||
|
||||
await self._hypervisor.send("nio setup_filter {name} {direction} {options}".format(name=self._name,
|
||||
direction=dynamips_direction,
|
||||
options=options))
|
||||
await self._hypervisor.send(
|
||||
"nio setup_filter {name} {direction} {options}".format(
|
||||
name=self._name, direction=dynamips_direction, options=options
|
||||
)
|
||||
)
|
||||
|
||||
if direction == "in":
|
||||
self._input_filter_options = options
|
||||
|
||||
@@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -38,17 +39,21 @@ class NIOGenericEthernet(NIO):
|
||||
def __init__(self, hypervisor, ethernet_device):
|
||||
|
||||
# create an unique name
|
||||
name = f'generic_ethernet-{uuid.uuid4()}'
|
||||
name = f"generic_ethernet-{uuid.uuid4()}"
|
||||
self._ethernet_device = ethernet_device
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
await self._hypervisor.send("nio create_gen_eth {name} {eth_device}".format(name=self._name,
|
||||
eth_device=self._ethernet_device))
|
||||
await self._hypervisor.send(
|
||||
"nio create_gen_eth {name} {eth_device}".format(name=self._name, eth_device=self._ethernet_device)
|
||||
)
|
||||
|
||||
log.info("NIO Generic Ethernet {name} created with device {device}".format(name=self._name,
|
||||
device=self._ethernet_device))
|
||||
log.info(
|
||||
"NIO Generic Ethernet {name} created with device {device}".format(
|
||||
name=self._name, device=self._ethernet_device
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def ethernet_device(self):
|
||||
@@ -62,5 +67,4 @@ class NIOGenericEthernet(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_generic_ethernet",
|
||||
"ethernet_device": self._ethernet_device}
|
||||
return {"type": "nio_generic_ethernet", "ethernet_device": self._ethernet_device}
|
||||
@@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -37,17 +38,21 @@ class NIOLinuxEthernet(NIO):
|
||||
|
||||
def __init__(self, hypervisor, ethernet_device):
|
||||
# create an unique name
|
||||
name = f'linux_ethernet-{uuid.uuid4()}'
|
||||
name = f"linux_ethernet-{uuid.uuid4()}"
|
||||
self._ethernet_device = ethernet_device
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
await self._hypervisor.send("nio create_linux_eth {name} {eth_device}".format(name=self._name,
|
||||
eth_device=self._ethernet_device))
|
||||
await self._hypervisor.send(
|
||||
"nio create_linux_eth {name} {eth_device}".format(name=self._name, eth_device=self._ethernet_device)
|
||||
)
|
||||
|
||||
log.info("NIO Linux Ethernet {name} created with device {device}".format(name=self._name,
|
||||
device=self._ethernet_device))
|
||||
log.info(
|
||||
"NIO Linux Ethernet {name} created with device {device}".format(
|
||||
name=self._name, device=self._ethernet_device
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def ethernet_device(self):
|
||||
@@ -61,5 +66,4 @@ class NIOLinuxEthernet(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_linux_ethernet",
|
||||
"ethernet_device": self._ethernet_device}
|
||||
return {"type": "nio_linux_ethernet", "ethernet_device": self._ethernet_device}
|
||||
@@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -37,7 +38,7 @@ class NIONull(NIO):
|
||||
def __init__(self, hypervisor):
|
||||
|
||||
# create an unique name
|
||||
name = f'null-{uuid.uuid4()}'
|
||||
name = f"null-{uuid.uuid4()}"
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
@@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -38,7 +39,7 @@ class NIOTAP(NIO):
|
||||
def __init__(self, hypervisor, tap_device):
|
||||
|
||||
# create an unique name
|
||||
name = f'tap-{uuid.uuid4()}'
|
||||
name = f"tap-{uuid.uuid4()}"
|
||||
self._tap_device = tap_device
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
@@ -59,5 +60,4 @@ class NIOTAP(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_tap",
|
||||
"tap_device": self._tap_device}
|
||||
return {"type": "nio_tap", "tap_device": self._tap_device}
|
||||
@@ -26,6 +26,7 @@ from .nio import NIO
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -43,7 +44,7 @@ class NIOUDP(NIO):
|
||||
def __init__(self, node, lport, rhost, rport):
|
||||
|
||||
# create an unique name
|
||||
name = f'udp-{uuid.uuid4()}'
|
||||
name = f"udp-{uuid.uuid4()}"
|
||||
self._lport = lport
|
||||
self._rhost = rhost
|
||||
self._rport = rport
|
||||
@@ -57,48 +58,40 @@ class NIOUDP(NIO):
|
||||
return
|
||||
# Ubridge is not supported
|
||||
if not hasattr(self._node, "add_ubridge_udp_connection"):
|
||||
await self._hypervisor.send("nio create_udp {name} {lport} {rhost} {rport}".format(name=self._name,
|
||||
lport=self._lport,
|
||||
rhost=self._rhost,
|
||||
rport=self._rport))
|
||||
await self._hypervisor.send(
|
||||
"nio create_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=self._name, lport=self._lport, rhost=self._rhost, rport=self._rport
|
||||
)
|
||||
)
|
||||
return
|
||||
self._local_tunnel_lport = self._node.manager.port_manager.get_free_udp_port(self._node.project)
|
||||
self._local_tunnel_rport = self._node.manager.port_manager.get_free_udp_port(self._node.project)
|
||||
self._bridge_name = f'DYNAMIPS-{self._local_tunnel_lport}-{self._local_tunnel_rport}'
|
||||
await self._hypervisor.send("nio create_udp {name} {lport} {rhost} {rport}".format(name=self._name,
|
||||
lport=self._local_tunnel_lport,
|
||||
rhost='127.0.0.1',
|
||||
rport=self._local_tunnel_rport))
|
||||
|
||||
log.info("NIO UDP {name} created with lport={lport}, rhost={rhost}, rport={rport}".format(name=self._name,
|
||||
lport=self._lport,
|
||||
rhost=self._rhost,
|
||||
rport=self._rport))
|
||||
|
||||
self._source_nio = nio_udp.NIOUDP(self._local_tunnel_rport,
|
||||
'127.0.0.1',
|
||||
self._local_tunnel_lport)
|
||||
self._destination_nio = nio_udp.NIOUDP(self._lport,
|
||||
self._rhost,
|
||||
self._rport)
|
||||
self._destination_nio.filters = self._filters
|
||||
await self._node.add_ubridge_udp_connection(
|
||||
self._bridge_name,
|
||||
self._source_nio,
|
||||
self._destination_nio
|
||||
self._bridge_name = f"DYNAMIPS-{self._local_tunnel_lport}-{self._local_tunnel_rport}"
|
||||
await self._hypervisor.send(
|
||||
"nio create_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=self._name, lport=self._local_tunnel_lport, rhost="127.0.0.1", rport=self._local_tunnel_rport
|
||||
)
|
||||
)
|
||||
|
||||
log.info(
|
||||
"NIO UDP {name} created with lport={lport}, rhost={rhost}, rport={rport}".format(
|
||||
name=self._name, lport=self._lport, rhost=self._rhost, rport=self._rport
|
||||
)
|
||||
)
|
||||
|
||||
self._source_nio = nio_udp.NIOUDP(self._local_tunnel_rport, "127.0.0.1", self._local_tunnel_lport)
|
||||
self._destination_nio = nio_udp.NIOUDP(self._lport, self._rhost, self._rport)
|
||||
self._destination_nio.filters = self._filters
|
||||
await self._node.add_ubridge_udp_connection(self._bridge_name, self._source_nio, self._destination_nio)
|
||||
|
||||
async def update(self):
|
||||
self._destination_nio.filters = self._filters
|
||||
await self._node.update_ubridge_udp_connection(
|
||||
self._bridge_name,
|
||||
self._source_nio,
|
||||
self._destination_nio)
|
||||
await self._node.update_ubridge_udp_connection(self._bridge_name, self._source_nio, self._destination_nio)
|
||||
|
||||
async def close(self):
|
||||
if self._local_tunnel_lport:
|
||||
await self._node.ubridge_delete_bridge(self._bridge_name)
|
||||
self._node.manager.port_manager.release_udp_port(self._local_tunnel_lport, self ._node.project)
|
||||
self._node.manager.port_manager.release_udp_port(self._local_tunnel_lport, self._node.project)
|
||||
if self._local_tunnel_rport:
|
||||
self._node.manager.port_manager.release_udp_port(self._local_tunnel_rport, self._node.project)
|
||||
self._node.manager.port_manager.release_udp_port(self._lport, self._node.project)
|
||||
@@ -135,7 +128,4 @@ class NIOUDP(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_udp",
|
||||
"lport": self._lport,
|
||||
"rport": self._rport,
|
||||
"rhost": self._rhost}
|
||||
return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost}
|
||||
@@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -39,20 +40,24 @@ class NIOUNIX(NIO):
|
||||
def __init__(self, hypervisor, local_file, remote_file):
|
||||
|
||||
# create an unique name
|
||||
name = f'unix-{uuid.uuid4()}'
|
||||
name = f"unix-{uuid.uuid4()}"
|
||||
self._local_file = local_file
|
||||
self._remote_file = remote_file
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
await self._hypervisor.send("nio create_unix {name} {local} {remote}".format(name=self._name,
|
||||
local=self._local_file,
|
||||
remote=self._remote_file))
|
||||
await self._hypervisor.send(
|
||||
"nio create_unix {name} {local} {remote}".format(
|
||||
name=self._name, local=self._local_file, remote=self._remote_file
|
||||
)
|
||||
)
|
||||
|
||||
log.info("NIO UNIX {name} created with local file {local} and remote file {remote}".format(name=self._name,
|
||||
local=self._local_file,
|
||||
remote=self._remote_file))
|
||||
log.info(
|
||||
"NIO UNIX {name} created with local file {local} and remote file {remote}".format(
|
||||
name=self._name, local=self._local_file, remote=self._remote_file
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def local_file(self):
|
||||
@@ -76,6 +81,4 @@ class NIOUNIX(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_unix",
|
||||
"local_file": self._local_file,
|
||||
"remote_file": self._remote_file}
|
||||
return {"type": "nio_unix", "local_file": self._local_file, "remote_file": self._remote_file}
|
||||
@@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -39,20 +40,24 @@ class NIOVDE(NIO):
|
||||
def __init__(self, hypervisor, control_file, local_file):
|
||||
|
||||
# create an unique name
|
||||
name = f'vde-{uuid.uuid4()}'
|
||||
name = f"vde-{uuid.uuid4()}"
|
||||
self._control_file = control_file
|
||||
self._local_file = local_file
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
await self._hypervisor.send("nio create_vde {name} {control} {local}".format(name=self._name,
|
||||
control=self._control_file,
|
||||
local=self._local_file))
|
||||
await self._hypervisor.send(
|
||||
"nio create_vde {name} {control} {local}".format(
|
||||
name=self._name, control=self._control_file, local=self._local_file
|
||||
)
|
||||
)
|
||||
|
||||
log.info("NIO VDE {name} created with control={control}, local={local}".format(name=self._name,
|
||||
control=self._control_file,
|
||||
local=self._local_file))
|
||||
log.info(
|
||||
"NIO VDE {name} created with control={control}, local={local}".format(
|
||||
name=self._name, control=self._control_file, local=self._local_file
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def control_file(self):
|
||||
@@ -76,6 +81,4 @@ class NIOVDE(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_vde",
|
||||
"local_file": self._local_file,
|
||||
"control_file": self._control_file}
|
||||
return {"type": "nio_vde", "local_file": self._local_file, "control_file": self._control_file}
|
||||
@@ -27,6 +27,7 @@ from ..nios.nio_udp import NIOUDP
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -57,12 +58,14 @@ class ATMSwitch(Device):
|
||||
for source, destination in self._mappings.items():
|
||||
mappings[source] = destination
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"mappings": mappings,
|
||||
"status": "started"}
|
||||
return {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"mappings": mappings,
|
||||
"status": "started",
|
||||
}
|
||||
|
||||
async def create(self):
|
||||
|
||||
@@ -82,9 +85,11 @@ class ATMSwitch(Device):
|
||||
"""
|
||||
|
||||
await self._hypervisor.send(f'atmsw rename "{self._name}" "{new_name}"')
|
||||
log.info('ATM switch "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name,
|
||||
id=self._id,
|
||||
new_name=new_name))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: renamed to "{new_name}"'.format(
|
||||
name=self._name, id=self._id, new_name=new_name
|
||||
)
|
||||
)
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@@ -163,10 +168,11 @@ class ATMSwitch(Device):
|
||||
if port_number in self._nios:
|
||||
raise DynamipsError(f"Port {port_number} isn't free")
|
||||
|
||||
log.info('ATM switch "{name}" [id={id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'ATM switch "{name}" [id={id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
self._nios[port_number] = nio
|
||||
await self.set_mappings(self._mappings)
|
||||
@@ -189,37 +195,50 @@ class ATMSwitch(Device):
|
||||
source_port, source_vpi, source_vci = source
|
||||
destination_port, destination_vpi, destination_vci = destination
|
||||
if port_number == source_port:
|
||||
log.info('ATM switch "{name}" [{id}]: unmapping VCC between port {source_port} VPI {source_vpi} VCI {source_vci} and port {destination_port} VPI {destination_vpi} VCI {destination_vci}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
source_vci=source_vci,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
destination_vci=destination_vci))
|
||||
await self.unmap_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci)
|
||||
await self.unmap_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci)
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: unmapping VCC between port {source_port} VPI {source_vpi} VCI {source_vci} and port {destination_port} VPI {destination_vpi} VCI {destination_vci}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
source_vci=source_vci,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
destination_vci=destination_vci,
|
||||
)
|
||||
)
|
||||
await self.unmap_pvc(
|
||||
source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci
|
||||
)
|
||||
await self.unmap_pvc(
|
||||
destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci
|
||||
)
|
||||
else:
|
||||
# remove the virtual paths mapped with this port/nio
|
||||
source_port, source_vpi = source
|
||||
destination_port, destination_vpi = destination
|
||||
if port_number == source_port:
|
||||
log.info('ATM switch "{name}" [{id}]: unmapping VPC between port {source_port} VPI {source_vpi} and port {destination_port} VPI {destination_vpi}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: unmapping VPC between port {source_port} VPI {source_vpi} and port {destination_port} VPI {destination_vpi}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
)
|
||||
)
|
||||
await self.unmap_vp(source_port, source_vpi, destination_port, destination_vpi)
|
||||
await self.unmap_vp(destination_port, destination_vpi, source_port, source_vpi)
|
||||
|
||||
nio = self._nios[port_number]
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
log.info('ATM switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._nios[port_number]
|
||||
return nio
|
||||
@@ -261,30 +280,48 @@ class ATMSwitch(Device):
|
||||
source_port, source_vpi, source_vci = map(int, match_source_pvc.group(1, 2, 3))
|
||||
destination_port, destination_vpi, destination_vci = map(int, match_destination_pvc.group(1, 2, 3))
|
||||
if self.has_port(destination_port):
|
||||
if (source_port, source_vpi, source_vci) not in self._active_mappings and \
|
||||
(destination_port, destination_vpi, destination_vci) not in self._active_mappings:
|
||||
log.info('ATM switch "{name}" [{id}]: mapping VCC between port {source_port} VPI {source_vpi} VCI {source_vci} and port {destination_port} VPI {destination_vpi} VCI {destination_vci}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
source_vci=source_vci,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
destination_vci=destination_vci))
|
||||
await self.map_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci)
|
||||
await self.map_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci)
|
||||
if (source_port, source_vpi, source_vci) not in self._active_mappings and (
|
||||
destination_port,
|
||||
destination_vpi,
|
||||
destination_vci,
|
||||
) not in self._active_mappings:
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: mapping VCC between port {source_port} VPI {source_vpi} VCI {source_vci} and port {destination_port} VPI {destination_vpi} VCI {destination_vci}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
source_vci=source_vci,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
destination_vci=destination_vci,
|
||||
)
|
||||
)
|
||||
await self.map_pvc(
|
||||
source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci
|
||||
)
|
||||
await self.map_pvc(
|
||||
destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci
|
||||
)
|
||||
else:
|
||||
# add the virtual paths
|
||||
source_port, source_vpi = map(int, source.split(':'))
|
||||
destination_port, destination_vpi = map(int, destination.split(':'))
|
||||
source_port, source_vpi = map(int, source.split(":"))
|
||||
destination_port, destination_vpi = map(int, destination.split(":"))
|
||||
if self.has_port(destination_port):
|
||||
if (source_port, source_vpi) not in self._active_mappings and (destination_port, destination_vpi) not in self._active_mappings:
|
||||
log.info('ATM switch "{name}" [{id}]: mapping VPC between port {source_port} VPI {source_vpi} and port {destination_port} VPI {destination_vpi}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi))
|
||||
if (source_port, source_vpi) not in self._active_mappings and (
|
||||
destination_port,
|
||||
destination_vpi,
|
||||
) not in self._active_mappings:
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: mapping VPC between port {source_port} VPI {source_vpi} and port {destination_port} VPI {destination_vpi}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
)
|
||||
)
|
||||
await self.map_vp(source_port, source_vpi, destination_port, destination_vpi)
|
||||
await self.map_vp(destination_port, destination_vpi, source_port, source_vpi)
|
||||
|
||||
@@ -307,18 +344,17 @@ class ATMSwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('atmsw create_vpc "{name}" {input_nio} {input_vpi} {output_nio} {output_vpi}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2))
|
||||
await self._hypervisor.send(
|
||||
'atmsw create_vpc "{name}" {input_nio} {input_vpi} {output_nio} {output_vpi}'.format(
|
||||
name=self._name, input_nio=nio1, input_vpi=vpi1, output_nio=nio2, output_vpi=vpi2
|
||||
)
|
||||
)
|
||||
|
||||
log.info('ATM switch "{name}" [{id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} created'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
vpi1=vpi1,
|
||||
port2=port2,
|
||||
vpi2=vpi2))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} created'.format(
|
||||
name=self._name, id=self._id, port1=port1, vpi1=vpi1, port2=port2, vpi2=vpi2
|
||||
)
|
||||
)
|
||||
|
||||
self._active_mappings[(port1, vpi1)] = (port2, vpi2)
|
||||
|
||||
@@ -341,18 +377,17 @@ class ATMSwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('atmsw delete_vpc "{name}" {input_nio} {input_vpi} {output_nio} {output_vpi}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2))
|
||||
await self._hypervisor.send(
|
||||
'atmsw delete_vpc "{name}" {input_nio} {input_vpi} {output_nio} {output_vpi}'.format(
|
||||
name=self._name, input_nio=nio1, input_vpi=vpi1, output_nio=nio2, output_vpi=vpi2
|
||||
)
|
||||
)
|
||||
|
||||
log.info('ATM switch "{name}" [{id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} deleted'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
vpi1=vpi1,
|
||||
port2=port2,
|
||||
vpi2=vpi2))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} deleted'.format(
|
||||
name=self._name, id=self._id, port1=port1, vpi1=vpi1, port2=port2, vpi2=vpi2
|
||||
)
|
||||
)
|
||||
|
||||
del self._active_mappings[(port1, vpi1)]
|
||||
|
||||
@@ -377,22 +412,23 @@ class ATMSwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('atmsw create_vcc "{name}" {input_nio} {input_vpi} {input_vci} {output_nio} {output_vpi} {output_vci}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
input_vci=vci1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2,
|
||||
output_vci=vci2))
|
||||
await self._hypervisor.send(
|
||||
'atmsw create_vcc "{name}" {input_nio} {input_vpi} {input_vci} {output_nio} {output_vpi} {output_vci}'.format(
|
||||
name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
input_vci=vci1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2,
|
||||
output_vci=vci2,
|
||||
)
|
||||
)
|
||||
|
||||
log.info('ATM switch "{name}" [{id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} created'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
vpi1=vpi1,
|
||||
vci1=vci1,
|
||||
port2=port2,
|
||||
vpi2=vpi2,
|
||||
vci2=vci2))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} created'.format(
|
||||
name=self._name, id=self._id, port1=port1, vpi1=vpi1, vci1=vci1, port2=port2, vpi2=vpi2, vci2=vci2
|
||||
)
|
||||
)
|
||||
|
||||
self._active_mappings[(port1, vpi1, vci1)] = (port2, vpi2, vci2)
|
||||
|
||||
@@ -417,22 +453,23 @@ class ATMSwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('atmsw delete_vcc "{name}" {input_nio} {input_vpi} {input_vci} {output_nio} {output_vpi} {output_vci}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
input_vci=vci1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2,
|
||||
output_vci=vci2))
|
||||
await self._hypervisor.send(
|
||||
'atmsw delete_vcc "{name}" {input_nio} {input_vpi} {input_vci} {output_nio} {output_vpi} {output_vci}'.format(
|
||||
name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
input_vci=vci1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2,
|
||||
output_vci=vci2,
|
||||
)
|
||||
)
|
||||
|
||||
log.info('ATM switch "{name}" [{id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} deleted'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
vpi1=vpi1,
|
||||
vci1=vci1,
|
||||
port2=port2,
|
||||
vpi2=vpi2,
|
||||
vci2=vci2))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} deleted'.format(
|
||||
name=self._name, id=self._id, port1=port1, vpi1=vpi1, vci1=vci1, port2=port2, vpi2=vpi2, vci2=vci2
|
||||
)
|
||||
)
|
||||
del self._active_mappings[(port1, vpi1, vci1)]
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_ATM_RFC1483"):
|
||||
@@ -453,9 +490,11 @@ class ATMSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied")
|
||||
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('ATM switch "{name}" [{id}]: starting packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: starting packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@@ -468,6 +507,8 @@ class ATMSwitch(Device):
|
||||
if not nio.capturing:
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
log.info('ATM switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: stopping packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
@@ -56,8 +56,9 @@ class Bridge(Device):
|
||||
:param new_name: New name for this bridge
|
||||
"""
|
||||
|
||||
await self._hypervisor.send('nio_bridge rename "{name}" "{new_name}"'.format(name=self._name,
|
||||
new_name=new_name))
|
||||
await self._hypervisor.send(
|
||||
'nio_bridge rename "{name}" "{new_name}"'.format(name=self._name, new_name=new_name)
|
||||
)
|
||||
|
||||
self._name = new_name
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from ..adapters.c1700_mb_1fe import C1700_MB_1FE
|
||||
from ..adapters.c1700_mb_wic1 import C1700_MB_WIC1
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -47,9 +48,23 @@ class C1700(Router):
|
||||
1710 is not supported.
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="1720"):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis="1720",
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c1700")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c1700"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 64
|
||||
@@ -63,9 +78,7 @@ class C1700(Router):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
c1700_router_info = {"iomem": self._iomem,
|
||||
"chassis": self._chassis,
|
||||
"sparsemem": self._sparsemem}
|
||||
c1700_router_info = {"iomem": self._iomem, "chassis": self._chassis, "sparsemem": self._sparsemem}
|
||||
|
||||
router_info = Router.__json__(self)
|
||||
router_info.update(c1700_router_info)
|
||||
@@ -86,7 +99,7 @@ class C1700(Router):
|
||||
|
||||
# With 1751 and 1760, WICs in WIC slot 1 show up as in slot 1, not 0
|
||||
# e.g. s1/0 not s0/2
|
||||
if self._chassis in ['1751', '1760']:
|
||||
if self._chassis in ["1751", "1760"]:
|
||||
self._create_slots(2)
|
||||
self._slots[1] = C1700_MB_WIC1()
|
||||
else:
|
||||
@@ -113,9 +126,9 @@ class C1700(Router):
|
||||
|
||||
await self._hypervisor.send(f'c1700 set_chassis "{self._name}" {chassis}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name,
|
||||
id=self._id,
|
||||
chassis=chassis))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name, id=self._id, chassis=chassis)
|
||||
)
|
||||
|
||||
self._chassis = chassis
|
||||
self._setup_chassis()
|
||||
@@ -139,8 +152,9 @@ class C1700(Router):
|
||||
|
||||
await self._hypervisor.send(f'c1700 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
@@ -27,6 +27,7 @@ from ..adapters.c2600_mb_1fe import C2600_MB_1FE
|
||||
from ..adapters.c2600_mb_2fe import C2600_MB_2FE
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -51,20 +52,36 @@ class C2600(Router):
|
||||
|
||||
# adapters to insert by default corresponding the
|
||||
# chosen chassis.
|
||||
integrated_adapters = {"2610": C2600_MB_1E,
|
||||
"2611": C2600_MB_2E,
|
||||
"2620": C2600_MB_1FE,
|
||||
"2621": C2600_MB_2FE,
|
||||
"2610XM": C2600_MB_1FE,
|
||||
"2611XM": C2600_MB_2FE,
|
||||
"2620XM": C2600_MB_1FE,
|
||||
"2621XM": C2600_MB_2FE,
|
||||
"2650XM": C2600_MB_1FE,
|
||||
"2651XM": C2600_MB_2FE}
|
||||
integrated_adapters = {
|
||||
"2610": C2600_MB_1E,
|
||||
"2611": C2600_MB_2E,
|
||||
"2620": C2600_MB_1FE,
|
||||
"2621": C2600_MB_2FE,
|
||||
"2610XM": C2600_MB_1FE,
|
||||
"2611XM": C2600_MB_2FE,
|
||||
"2620XM": C2600_MB_1FE,
|
||||
"2621XM": C2600_MB_2FE,
|
||||
"2650XM": C2600_MB_1FE,
|
||||
"2651XM": C2600_MB_2FE,
|
||||
}
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="2610"):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis="2610",
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2600")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2600"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 64
|
||||
@@ -78,9 +95,7 @@ class C2600(Router):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
c2600_router_info = {"iomem": self._iomem,
|
||||
"chassis": self._chassis,
|
||||
"sparsemem": self._sparsemem}
|
||||
c2600_router_info = {"iomem": self._iomem, "chassis": self._chassis, "sparsemem": self._sparsemem}
|
||||
|
||||
router_info = Router.__json__(self)
|
||||
router_info.update(c2600_router_info)
|
||||
@@ -123,9 +138,9 @@ class C2600(Router):
|
||||
|
||||
await self._hypervisor.send(f'c2600 set_chassis "{self._name}" {chassis}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name,
|
||||
id=self._id,
|
||||
chassis=chassis))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name, id=self._id, chassis=chassis)
|
||||
)
|
||||
self._chassis = chassis
|
||||
self._setup_chassis()
|
||||
|
||||
@@ -148,8 +163,9 @@ class C2600(Router):
|
||||
|
||||
await self._hypervisor.send(f'c2600 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
@@ -25,6 +25,7 @@ from ..adapters.gt96100_fe import GT96100_FE
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -44,9 +45,23 @@ class C2691(Router):
|
||||
:param aux_type: auxiliary console type
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis=None,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2691")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2691"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 128
|
||||
@@ -89,8 +104,9 @@ class C2691(Router):
|
||||
|
||||
await self._hypervisor.send(f'c2691 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
@@ -24,6 +24,7 @@ from .router import Router
|
||||
from ..adapters.leopard_2fe import Leopard_2FE
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -45,9 +46,23 @@ class C3600(Router):
|
||||
3620, 3640 or 3660 (default = 3640).
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="3640"):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis="3640",
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3600")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3600"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 128
|
||||
@@ -60,8 +75,7 @@ class C3600(Router):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
c3600_router_info = {"iomem": self._iomem,
|
||||
"chassis": self._chassis}
|
||||
c3600_router_info = {"iomem": self._iomem, "chassis": self._chassis}
|
||||
|
||||
router_info = Router.__json__(self)
|
||||
router_info.update(c3600_router_info)
|
||||
@@ -107,9 +121,9 @@ class C3600(Router):
|
||||
|
||||
await self._hypervisor.send(f'c3600 set_chassis "{self._name}" {chassis}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name,
|
||||
id=self._id,
|
||||
chassis=chassis))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name, id=self._id, chassis=chassis)
|
||||
)
|
||||
|
||||
self._chassis = chassis
|
||||
self._setup_chassis()
|
||||
@@ -133,8 +147,9 @@ class C3600(Router):
|
||||
|
||||
await self._hypervisor.send(f'c3600 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
@@ -25,6 +25,7 @@ from ..adapters.gt96100_fe import GT96100_FE
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -44,9 +45,23 @@ class C3725(Router):
|
||||
:param aux_type: auxiliary console type
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis=None,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3725")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3725"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 128
|
||||
@@ -89,8 +104,9 @@ class C3725(Router):
|
||||
|
||||
await self._hypervisor.send(f'c3725 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
@@ -25,6 +25,7 @@ from ..adapters.gt96100_fe import GT96100_FE
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -44,9 +45,23 @@ class C3745(Router):
|
||||
:param aux_type: auxiliary console type
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis=None,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3745")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3745"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 128
|
||||
@@ -89,8 +104,9 @@ class C3745(Router):
|
||||
|
||||
await self._hypervisor.send(f'c3745 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
@@ -27,6 +27,7 @@ from ..adapters.c7200_io_ge_e import C7200_IO_GE_E
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -47,9 +48,24 @@ class C7200(Router):
|
||||
:param npe: Default NPE
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", npe="npe-400", chassis=None):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
npe="npe-400",
|
||||
chassis=None,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c7200")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c7200"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 256
|
||||
@@ -78,10 +94,12 @@ class C7200(Router):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
c7200_router_info = {"npe": self._npe,
|
||||
"midplane": self._midplane,
|
||||
"sensors": self._sensors,
|
||||
"power_supplies": self._power_supplies}
|
||||
c7200_router_info = {
|
||||
"npe": self._npe,
|
||||
"midplane": self._midplane,
|
||||
"sensors": self._sensors,
|
||||
"power_supplies": self._power_supplies,
|
||||
}
|
||||
|
||||
router_info = Router.__json__(self)
|
||||
router_info.update(c7200_router_info)
|
||||
@@ -119,15 +137,16 @@ class C7200(Router):
|
||||
npe-225, npe-300, npe-400 and npe-g2 (PowerPC c7200 only)
|
||||
"""
|
||||
|
||||
if (await self.is_running()):
|
||||
if await self.is_running():
|
||||
raise DynamipsError("Cannot change NPE on running router")
|
||||
|
||||
await self._hypervisor.send(f'c7200 set_npe "{self._name}" {npe}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: NPE updated from {old_npe} to {new_npe}'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_npe=self._npe,
|
||||
new_npe=npe))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: NPE updated from {old_npe} to {new_npe}'.format(
|
||||
name=self._name, id=self._id, old_npe=self._npe, new_npe=npe
|
||||
)
|
||||
)
|
||||
self._npe = npe
|
||||
|
||||
@property
|
||||
@@ -149,10 +168,11 @@ class C7200(Router):
|
||||
|
||||
await self._hypervisor.send(f'c7200 set_midplane "{self._name}" {midplane}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: midplane updated from {old_midplane} to {new_midplane}'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_midplane=self._midplane,
|
||||
new_midplane=midplane))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: midplane updated from {old_midplane} to {new_midplane}'.format(
|
||||
name=self._name, id=self._id, old_midplane=self._midplane, new_midplane=midplane
|
||||
)
|
||||
)
|
||||
self._midplane = midplane
|
||||
|
||||
@property
|
||||
@@ -179,15 +199,21 @@ class C7200(Router):
|
||||
|
||||
sensor_id = 0
|
||||
for sensor in sensors:
|
||||
await self._hypervisor.send('c7200 set_temp_sensor "{name}" {sensor_id} {temp}'.format(name=self._name,
|
||||
sensor_id=sensor_id,
|
||||
temp=sensor))
|
||||
await self._hypervisor.send(
|
||||
'c7200 set_temp_sensor "{name}" {sensor_id} {temp}'.format(
|
||||
name=self._name, sensor_id=sensor_id, temp=sensor
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Router "{name}" [{id}]: sensor {sensor_id} temperature updated from {old_temp}C to {new_temp}C'.format(name=self._name,
|
||||
id=self._id,
|
||||
sensor_id=sensor_id,
|
||||
old_temp=self._sensors[sensor_id],
|
||||
new_temp=sensors[sensor_id]))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: sensor {sensor_id} temperature updated from {old_temp}C to {new_temp}C'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
sensor_id=sensor_id,
|
||||
old_temp=self._sensors[sensor_id],
|
||||
new_temp=sensors[sensor_id],
|
||||
)
|
||||
)
|
||||
|
||||
sensor_id += 1
|
||||
self._sensors = sensors
|
||||
@@ -212,14 +238,17 @@ class C7200(Router):
|
||||
|
||||
power_supply_id = 0
|
||||
for power_supply in power_supplies:
|
||||
await self._hypervisor.send('c7200 set_power_supply "{name}" {power_supply_id} {powered_on}'.format(name=self._name,
|
||||
power_supply_id=power_supply_id,
|
||||
powered_on=power_supply))
|
||||
await self._hypervisor.send(
|
||||
'c7200 set_power_supply "{name}" {power_supply_id} {powered_on}'.format(
|
||||
name=self._name, power_supply_id=power_supply_id, powered_on=power_supply
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Router "{name}" [{id}]: power supply {power_supply_id} state updated to {powered_on}'.format(name=self._name,
|
||||
id=self._id,
|
||||
power_supply_id=power_supply_id,
|
||||
powered_on=power_supply))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: power supply {power_supply_id} state updated to {powered_on}'.format(
|
||||
name=self._name, id=self._id, power_supply_id=power_supply_id, powered_on=power_supply
|
||||
)
|
||||
)
|
||||
power_supply_id += 1
|
||||
|
||||
self._power_supplies = power_supplies
|
||||
|
||||
@@ -24,6 +24,7 @@ from ..dynamips_error import DynamipsError
|
||||
from ...error import NodeError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -48,19 +49,20 @@ class EthernetHub(Bridge):
|
||||
# create 8 ports by default
|
||||
self._ports = []
|
||||
for port_number in range(0, 8):
|
||||
self._ports.append({"port_number": port_number,
|
||||
"name": f"Ethernet{port_number}"})
|
||||
self._ports.append({"port_number": port_number, "name": f"Ethernet{port_number}"})
|
||||
else:
|
||||
self._ports = ports
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"ports_mapping": self._ports,
|
||||
"status": "started"}
|
||||
return {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"ports_mapping": self._ports,
|
||||
"status": "started",
|
||||
}
|
||||
|
||||
@property
|
||||
def ports_mapping(self):
|
||||
@@ -107,7 +109,7 @@ class EthernetHub(Bridge):
|
||||
return self._mappings
|
||||
|
||||
async def delete(self):
|
||||
return (await self.close())
|
||||
return await self.close()
|
||||
|
||||
async def close(self):
|
||||
"""
|
||||
@@ -144,10 +146,11 @@ class EthernetHub(Bridge):
|
||||
|
||||
await Bridge.add_nio(self, nio)
|
||||
|
||||
log.info('Ethernet hub "{name}" [{id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet hub "{name}" [{id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
self._mappings[port_number] = nio
|
||||
|
||||
async def remove_nio(self, port_number):
|
||||
@@ -168,10 +171,11 @@ class EthernetHub(Bridge):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
await Bridge.remove_nio(self, nio)
|
||||
|
||||
log.info('Ethernet hub "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet hub "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._mappings[port_number]
|
||||
return nio
|
||||
@@ -213,9 +217,11 @@ class EthernetHub(Bridge):
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied")
|
||||
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('Ethernet hub "{name}" [{id}]: starting packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet hub "{name}" [{id}]: starting packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@@ -228,6 +234,8 @@ class EthernetHub(Bridge):
|
||||
if not nio.capturing:
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
log.info('Ethernet hub "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet hub "{name}" [{id}]: stopping packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
@@ -21,7 +21,8 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558
|
||||
|
||||
import asyncio
|
||||
from gns3server.utils import parse_version
|
||||
#from gns3server.utils.asyncio.embed_shell import EmbedShell, create_telnet_shell
|
||||
|
||||
# from gns3server.utils.asyncio.embed_shell import EmbedShell, create_telnet_shell
|
||||
|
||||
|
||||
from .device import Device
|
||||
@@ -30,6 +31,7 @@ from ..dynamips_error import DynamipsError
|
||||
from ...error import NodeError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -84,8 +86,8 @@ class EthernetSwitch(Device):
|
||||
self._nios = {}
|
||||
self._mappings = {}
|
||||
self._telnet_console = None
|
||||
#self._telnet_shell = None
|
||||
#self._telnet_server = None
|
||||
# self._telnet_shell = None
|
||||
# self._telnet_server = None
|
||||
self._console = console
|
||||
self._console_type = console_type
|
||||
|
||||
@@ -101,23 +103,24 @@ class EthernetSwitch(Device):
|
||||
# create 8 ports by default
|
||||
self._ports = []
|
||||
for port_number in range(0, 8):
|
||||
self._ports.append({"port_number": port_number,
|
||||
"name": f"Ethernet{port_number}",
|
||||
"type": "access",
|
||||
"vlan": 1})
|
||||
self._ports.append(
|
||||
{"port_number": port_number, "name": f"Ethernet{port_number}", "type": "access", "vlan": 1}
|
||||
)
|
||||
else:
|
||||
self._ports = ports
|
||||
|
||||
def __json__(self):
|
||||
|
||||
ethernet_switch_info = {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"ports_mapping": self._ports,
|
||||
"status": "started"}
|
||||
ethernet_switch_info = {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"ports_mapping": self._ports,
|
||||
"status": "started",
|
||||
}
|
||||
|
||||
return ethernet_switch_info
|
||||
|
||||
@@ -138,8 +141,10 @@ class EthernetSwitch(Device):
|
||||
|
||||
if self._console_type != console_type:
|
||||
if console_type == "telnet":
|
||||
self.project.emit("log.warning", {
|
||||
"message": f'"{self._name}": Telnet access for switches is not available in this version of GNS3'})
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{"message": f'"{self._name}": Telnet access for switches is not available in this version of GNS3'},
|
||||
)
|
||||
self._console_type = console_type
|
||||
|
||||
@property
|
||||
@@ -186,15 +191,18 @@ class EthernetSwitch(Device):
|
||||
await self._hypervisor.send(f'ethsw create "{self._name}"')
|
||||
log.info(f'Ethernet switch "{self._name}" [{self._id}] has been created')
|
||||
|
||||
#self._telnet_shell = EthernetSwitchConsole(self)
|
||||
#self._telnet_shell.prompt = self._name + '> '
|
||||
#self._telnet = create_telnet_shell(self._telnet_shell)
|
||||
#try:
|
||||
# self._telnet_shell = EthernetSwitchConsole(self)
|
||||
# self._telnet_shell.prompt = self._name + '> '
|
||||
# self._telnet = create_telnet_shell(self._telnet_shell)
|
||||
# try:
|
||||
# self._telnet_server = (await asyncio.start_server(self._telnet.run, self._manager.port_manager.console_host, self.console))
|
||||
#except OSError as e:
|
||||
# except OSError as e:
|
||||
# self.project.emit("log.warning", {"message": "Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e)})
|
||||
if self._console_type == "telnet":
|
||||
self.project.emit("log.warning", {"message": f'"{self._name}": Telnet access for switches is not available in this version of GNS3'})
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{"message": f'"{self._name}": Telnet access for switches is not available in this version of GNS3'},
|
||||
)
|
||||
self._hypervisor.devices.append(self)
|
||||
|
||||
async def set_name(self, new_name):
|
||||
@@ -205,9 +213,11 @@ class EthernetSwitch(Device):
|
||||
"""
|
||||
|
||||
await self._hypervisor.send(f'ethsw rename "{self._name}" "{new_name}"')
|
||||
log.info('Ethernet switch "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name,
|
||||
id=self._id,
|
||||
new_name=new_name))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: renamed to "{new_name}"'.format(
|
||||
name=self._name, id=self._id, new_name=new_name
|
||||
)
|
||||
)
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@@ -231,15 +241,15 @@ class EthernetSwitch(Device):
|
||||
return self._mappings
|
||||
|
||||
async def delete(self):
|
||||
return (await self.close())
|
||||
return await self.close()
|
||||
|
||||
async def close(self):
|
||||
"""
|
||||
Deletes this Ethernet switch.
|
||||
"""
|
||||
|
||||
#await self._telnet.close()
|
||||
#if self._telnet_server:
|
||||
# await self._telnet.close()
|
||||
# if self._telnet_server:
|
||||
# self._telnet_server.close()
|
||||
|
||||
for nio in self._nios.values():
|
||||
@@ -272,10 +282,11 @@ class EthernetSwitch(Device):
|
||||
|
||||
await self._hypervisor.send(f'ethsw add_nio "{self._name}" {nio}')
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
self._nios[port_number] = nio
|
||||
for port_settings in self._ports:
|
||||
if port_settings["port_number"] == port_number:
|
||||
@@ -301,10 +312,11 @@ class EthernetSwitch(Device):
|
||||
if self._hypervisor:
|
||||
await self._hypervisor.send(f'ethsw remove_nio "{self._name}" {nio}')
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._nios[port_number]
|
||||
if port_number in self._mappings:
|
||||
@@ -358,14 +370,15 @@ class EthernetSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} is not allocated")
|
||||
|
||||
nio = self._nios[port_number]
|
||||
await self._hypervisor.send('ethsw set_access_port "{name}" {nio} {vlan_id}'.format(name=self._name,
|
||||
nio=nio,
|
||||
vlan_id=vlan_id))
|
||||
await self._hypervisor.send(
|
||||
'ethsw set_access_port "{name}" {nio} {vlan_id}'.format(name=self._name, nio=nio, vlan_id=vlan_id)
|
||||
)
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: port {port} set as an access port in VLAN {vlan_id}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number,
|
||||
vlan_id=vlan_id))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: port {port} set as an access port in VLAN {vlan_id}'.format(
|
||||
name=self._name, id=self._id, port=port_number, vlan_id=vlan_id
|
||||
)
|
||||
)
|
||||
self._mappings[port_number] = ("access", vlan_id)
|
||||
|
||||
async def set_dot1q_port(self, port_number, native_vlan):
|
||||
@@ -380,14 +393,17 @@ class EthernetSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} is not allocated")
|
||||
|
||||
nio = self._nios[port_number]
|
||||
await self._hypervisor.send('ethsw set_dot1q_port "{name}" {nio} {native_vlan}'.format(name=self._name,
|
||||
nio=nio,
|
||||
native_vlan=native_vlan))
|
||||
await self._hypervisor.send(
|
||||
'ethsw set_dot1q_port "{name}" {nio} {native_vlan}'.format(
|
||||
name=self._name, nio=nio, native_vlan=native_vlan
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: port {port} set as a 802.1Q port with native VLAN {vlan_id}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number,
|
||||
vlan_id=native_vlan))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: port {port} set as a 802.1Q port with native VLAN {vlan_id}'.format(
|
||||
name=self._name, id=self._id, port=port_number, vlan_id=native_vlan
|
||||
)
|
||||
)
|
||||
|
||||
self._mappings[port_number] = ("dot1q", native_vlan)
|
||||
|
||||
@@ -403,19 +419,22 @@ class EthernetSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} is not allocated")
|
||||
|
||||
nio = self._nios[port_number]
|
||||
if ethertype != "0x8100" and parse_version(self.hypervisor.version) < parse_version('0.2.16'):
|
||||
raise DynamipsError(f"Dynamips version required is >= 0.2.16 to change the default QinQ Ethernet type, detected version is {self.hypervisor.version}")
|
||||
if ethertype != "0x8100" and parse_version(self.hypervisor.version) < parse_version("0.2.16"):
|
||||
raise DynamipsError(
|
||||
f"Dynamips version required is >= 0.2.16 to change the default QinQ Ethernet type, detected version is {self.hypervisor.version}"
|
||||
)
|
||||
|
||||
await self._hypervisor.send('ethsw set_qinq_port "{name}" {nio} {outer_vlan} {ethertype}'.format(name=self._name,
|
||||
nio=nio,
|
||||
outer_vlan=outer_vlan,
|
||||
ethertype=ethertype if ethertype != "0x8100" else ""))
|
||||
await self._hypervisor.send(
|
||||
'ethsw set_qinq_port "{name}" {nio} {outer_vlan} {ethertype}'.format(
|
||||
name=self._name, nio=nio, outer_vlan=outer_vlan, ethertype=ethertype if ethertype != "0x8100" else ""
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: port {port} set as a QinQ ({ethertype}) port with outer VLAN {vlan_id}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number,
|
||||
vlan_id=outer_vlan,
|
||||
ethertype=ethertype))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: port {port} set as a QinQ ({ethertype}) port with outer VLAN {vlan_id}'.format(
|
||||
name=self._name, id=self._id, port=port_number, vlan_id=outer_vlan, ethertype=ethertype
|
||||
)
|
||||
)
|
||||
self._mappings[port_number] = ("qinq", outer_vlan, ethertype)
|
||||
|
||||
async def get_mac_addr_table(self):
|
||||
@@ -453,9 +472,11 @@ class EthernetSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied")
|
||||
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('Ethernet switch "{name}" [{id}]: starting packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: starting packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@@ -468,6 +489,8 @@ class EthernetSwitch(Device):
|
||||
if not nio.capturing:
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
log.info('Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
@@ -26,6 +26,7 @@ from ..nios.nio_udp import NIOUDP
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -56,12 +57,14 @@ class FrameRelaySwitch(Device):
|
||||
for source, destination in self._mappings.items():
|
||||
mappings[source] = destination
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"mappings": mappings,
|
||||
"status": "started"}
|
||||
return {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"mappings": mappings,
|
||||
"status": "started",
|
||||
}
|
||||
|
||||
async def create(self):
|
||||
|
||||
@@ -81,9 +84,11 @@ class FrameRelaySwitch(Device):
|
||||
"""
|
||||
|
||||
await self._hypervisor.send(f'frsw rename "{self._name}" "{new_name}"')
|
||||
log.info('Frame Relay switch "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name,
|
||||
id=self._id,
|
||||
new_name=new_name))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: renamed to "{new_name}"'.format(
|
||||
name=self._name, id=self._id, new_name=new_name
|
||||
)
|
||||
)
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@@ -163,10 +168,11 @@ class FrameRelaySwitch(Device):
|
||||
if port_number in self._nios:
|
||||
raise DynamipsError(f"Port {port_number} isn't free")
|
||||
|
||||
log.info('Frame Relay switch "{name}" [{id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
self._nios[port_number] = nio
|
||||
await self.set_mappings(self._mappings)
|
||||
@@ -189,12 +195,16 @@ class FrameRelaySwitch(Device):
|
||||
source_port, source_dlci = source
|
||||
destination_port, destination_dlci = destination
|
||||
if port_number == source_port:
|
||||
log.info('Frame Relay switch "{name}" [{id}]: unmapping VC between port {source_port} DLCI {source_dlci} and port {destination_port} DLCI {destination_dlci}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_dlci=source_dlci,
|
||||
destination_port=destination_port,
|
||||
destination_dlci=destination_dlci))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: unmapping VC between port {source_port} DLCI {source_dlci} and port {destination_port} DLCI {destination_dlci}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_dlci=source_dlci,
|
||||
destination_port=destination_port,
|
||||
destination_dlci=destination_dlci,
|
||||
)
|
||||
)
|
||||
await self.unmap_vc(source_port, source_dlci, destination_port, destination_dlci)
|
||||
await self.unmap_vc(destination_port, destination_dlci, source_port, source_dlci)
|
||||
|
||||
@@ -202,10 +212,11 @@ class FrameRelaySwitch(Device):
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
|
||||
log.info('Frame Relay switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._nios[port_number]
|
||||
return nio
|
||||
@@ -239,16 +250,23 @@ class FrameRelaySwitch(Device):
|
||||
for source, destination in mappings.items():
|
||||
if not isinstance(source, str) or not isinstance(destination, str):
|
||||
raise DynamipsError("Invalid Frame-Relay mappings")
|
||||
source_port, source_dlci = map(int, source.split(':'))
|
||||
destination_port, destination_dlci = map(int, destination.split(':'))
|
||||
source_port, source_dlci = map(int, source.split(":"))
|
||||
destination_port, destination_dlci = map(int, destination.split(":"))
|
||||
if self.has_port(destination_port):
|
||||
if (source_port, source_dlci) not in self._active_mappings and (destination_port, destination_dlci) not in self._active_mappings:
|
||||
log.info('Frame Relay switch "{name}" [{id}]: mapping VC between port {source_port} DLCI {source_dlci} and port {destination_port} DLCI {destination_dlci}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_dlci=source_dlci,
|
||||
destination_port=destination_port,
|
||||
destination_dlci=destination_dlci))
|
||||
if (source_port, source_dlci) not in self._active_mappings and (
|
||||
destination_port,
|
||||
destination_dlci,
|
||||
) not in self._active_mappings:
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: mapping VC between port {source_port} DLCI {source_dlci} and port {destination_port} DLCI {destination_dlci}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_dlci=source_dlci,
|
||||
destination_port=destination_port,
|
||||
destination_dlci=destination_dlci,
|
||||
)
|
||||
)
|
||||
|
||||
await self.map_vc(source_port, source_dlci, destination_port, destination_dlci)
|
||||
await self.map_vc(destination_port, destination_dlci, source_port, source_dlci)
|
||||
@@ -272,18 +290,17 @@ class FrameRelaySwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('frsw create_vc "{name}" {input_nio} {input_dlci} {output_nio} {output_dlci}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_dlci=dlci1,
|
||||
output_nio=nio2,
|
||||
output_dlci=dlci2))
|
||||
await self._hypervisor.send(
|
||||
'frsw create_vc "{name}" {input_nio} {input_dlci} {output_nio} {output_dlci}'.format(
|
||||
name=self._name, input_nio=nio1, input_dlci=dlci1, output_nio=nio2, output_dlci=dlci2
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Frame Relay switch "{name}" [{id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} created'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
dlci1=dlci1,
|
||||
port2=port2,
|
||||
dlci2=dlci2))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} created'.format(
|
||||
name=self._name, id=self._id, port1=port1, dlci1=dlci1, port2=port2, dlci2=dlci2
|
||||
)
|
||||
)
|
||||
|
||||
self._active_mappings[(port1, dlci1)] = (port2, dlci2)
|
||||
|
||||
@@ -306,18 +323,17 @@ class FrameRelaySwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('frsw delete_vc "{name}" {input_nio} {input_dlci} {output_nio} {output_dlci}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_dlci=dlci1,
|
||||
output_nio=nio2,
|
||||
output_dlci=dlci2))
|
||||
await self._hypervisor.send(
|
||||
'frsw delete_vc "{name}" {input_nio} {input_dlci} {output_nio} {output_dlci}'.format(
|
||||
name=self._name, input_nio=nio1, input_dlci=dlci1, output_nio=nio2, output_dlci=dlci2
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Frame Relay switch "{name}" [{id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} deleted'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
dlci1=dlci1,
|
||||
port2=port2,
|
||||
dlci2=dlci2))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} deleted'.format(
|
||||
name=self._name, id=self._id, port1=port1, dlci1=dlci1, port2=port2, dlci2=dlci2
|
||||
)
|
||||
)
|
||||
del self._active_mappings[(port1, dlci1)]
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_FRELAY"):
|
||||
@@ -339,9 +355,11 @@ class FrameRelaySwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied")
|
||||
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('Frame relay switch "{name}" [{id}]: starting packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Frame relay switch "{name}" [{id}]: starting packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@@ -354,6 +372,8 @@ class FrameRelaySwitch(Device):
|
||||
if not nio.capturing:
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
log.info('Frame relay switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Frame relay switch "{name}" [{id}]: stopping packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -16,7 +16,6 @@
|
||||
|
||||
|
||||
class NodeError(Exception):
|
||||
|
||||
def __init__(self, message, original_exception=None):
|
||||
super().__init__(message)
|
||||
if isinstance(message, Exception):
|
||||
|
||||
@@ -26,6 +26,7 @@ from .iou_error import IOUError
|
||||
from .iou_vm import IOUVM
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
+230
-154
@@ -48,11 +48,12 @@ import gns3server.utils.images
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IOUVM(BaseNode):
|
||||
module_name = 'iou'
|
||||
module_name = "iou"
|
||||
|
||||
"""
|
||||
IOU VM implementation.
|
||||
@@ -65,13 +66,17 @@ class IOUVM(BaseNode):
|
||||
:param console_type: console type
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, application_id=None, path=None, console=None, console_type="telnet"):
|
||||
def __init__(
|
||||
self, name, node_id, project, manager, application_id=None, path=None, console=None, console_type="telnet"
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, console_type=console_type)
|
||||
|
||||
log.info('IOU "{name}" [{id}]: assigned with application ID {application_id}'.format(name=self._name,
|
||||
id=self._id,
|
||||
application_id=application_id))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: assigned with application ID {application_id}'.format(
|
||||
name=self._name, id=self._id, application_id=application_id
|
||||
)
|
||||
)
|
||||
|
||||
self._iou_process = None
|
||||
self._telnet_server = None
|
||||
@@ -170,7 +175,9 @@ class IOUVM(BaseNode):
|
||||
"""
|
||||
|
||||
try:
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(self._path, "-h", cwd=self.working_dir, stderr=True)
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(
|
||||
self._path, "-h", cwd=self.working_dir, stderr=True
|
||||
)
|
||||
match = re.search(r"-n <n>\s+Size of nvram in Kb \(default ([0-9]+)KB\)", output)
|
||||
if match:
|
||||
self.nvram = int(match.group(1))
|
||||
@@ -206,7 +213,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
# IOU images must start with the ELF magic number, be 32-bit or 64-bit, little endian
|
||||
# and have an ELF version of 1 normal IOS image are big endian!
|
||||
if elf_header_start != b'\x7fELF\x01\x01\x01' and elf_header_start != b'\x7fELF\x02\x01\x01':
|
||||
if elf_header_start != b"\x7fELF\x01\x01\x01" and elf_header_start != b"\x7fELF\x02\x01\x01":
|
||||
raise IOUError(f"'{self._path}' is not a valid IOU image")
|
||||
|
||||
if not os.access(self._path, os.X_OK):
|
||||
@@ -214,24 +221,26 @@ class IOUVM(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
iou_vm_info = {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"node_directory": self.working_path,
|
||||
"console": self._console,
|
||||
"console_type": self._console_type,
|
||||
"status": self.status,
|
||||
"project_id": self.project.id,
|
||||
"path": self.path,
|
||||
"md5sum": gns3server.utils.images.md5sum(self.path),
|
||||
"ethernet_adapters": len(self._ethernet_adapters),
|
||||
"serial_adapters": len(self._serial_adapters),
|
||||
"ram": self._ram,
|
||||
"nvram": self._nvram,
|
||||
"l1_keepalives": self._l1_keepalives,
|
||||
"use_default_iou_values": self._use_default_iou_values,
|
||||
"command_line": self.command_line,
|
||||
"application_id": self.application_id}
|
||||
iou_vm_info = {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"node_directory": self.working_path,
|
||||
"console": self._console,
|
||||
"console_type": self._console_type,
|
||||
"status": self.status,
|
||||
"project_id": self.project.id,
|
||||
"path": self.path,
|
||||
"md5sum": gns3server.utils.images.md5sum(self.path),
|
||||
"ethernet_adapters": len(self._ethernet_adapters),
|
||||
"serial_adapters": len(self._serial_adapters),
|
||||
"ram": self._ram,
|
||||
"nvram": self._nvram,
|
||||
"l1_keepalives": self._l1_keepalives,
|
||||
"use_default_iou_values": self._use_default_iou_values,
|
||||
"command_line": self.command_line,
|
||||
"application_id": self.application_id,
|
||||
}
|
||||
|
||||
iou_vm_info["path"] = self.manager.get_relative_image_path(self.path, self.project.path)
|
||||
return iou_vm_info
|
||||
@@ -281,10 +290,11 @@ class IOUVM(BaseNode):
|
||||
if self._ram == ram:
|
||||
return
|
||||
|
||||
log.info('IOU "{name}" [{id}]: RAM updated from {old_ram}MB to {new_ram}MB'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_ram=self._ram,
|
||||
new_ram=ram))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: RAM updated from {old_ram}MB to {new_ram}MB'.format(
|
||||
name=self._name, id=self._id, old_ram=self._ram, new_ram=ram
|
||||
)
|
||||
)
|
||||
|
||||
self._ram = ram
|
||||
|
||||
@@ -309,10 +319,11 @@ class IOUVM(BaseNode):
|
||||
if self._nvram == nvram:
|
||||
return
|
||||
|
||||
log.info('IOU "{name}" [{id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_nvram=self._nvram,
|
||||
new_nvram=nvram))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB'.format(
|
||||
name=self._name, id=self._id, old_nvram=self._nvram, new_nvram=nvram
|
||||
)
|
||||
)
|
||||
self._nvram = nvram
|
||||
|
||||
@BaseNode.name.setter
|
||||
@@ -383,8 +394,11 @@ class IOUVM(BaseNode):
|
||||
p = re.compile(r"([\.\w]+)\s=>\s+not found")
|
||||
missing_libs = p.findall(output)
|
||||
if missing_libs:
|
||||
raise IOUError("The following shared library dependencies cannot be found for IOU image {}: {}".format(self._path,
|
||||
", ".join(missing_libs)))
|
||||
raise IOUError(
|
||||
"The following shared library dependencies cannot be found for IOU image {}: {}".format(
|
||||
self._path, ", ".join(missing_libs)
|
||||
)
|
||||
)
|
||||
|
||||
async def _check_iou_licence(self):
|
||||
"""
|
||||
@@ -422,9 +436,9 @@ class IOUVM(BaseNode):
|
||||
if len(hostname) > 15:
|
||||
log.warning(f"Older IOU images may not boot because hostname '{hostname}' length is above 15 characters")
|
||||
if hostname not in config["license"]:
|
||||
raise IOUError(f"Hostname \"{hostname}\" not found in iourc file {self.iourc_path}")
|
||||
raise IOUError(f'Hostname "{hostname}" not found in iourc file {self.iourc_path}')
|
||||
user_ioukey = config["license"][hostname]
|
||||
if user_ioukey[-1:] != ';':
|
||||
if user_ioukey[-1:] != ";":
|
||||
raise IOUError(f"IOU key not ending with ; in iourc file {self.iourc_path}")
|
||||
if len(user_ioukey) != 17:
|
||||
raise IOUError(f"IOU key length is not 16 characters in iourc file {self.iourc_path}")
|
||||
@@ -446,13 +460,15 @@ class IOUVM(BaseNode):
|
||||
raise IOUError(f"Invalid hostid detected: {hostid}")
|
||||
for x in hostname:
|
||||
ioukey += ord(x)
|
||||
pad1 = b'\x4B\x58\x21\x81\x56\x7B\x0D\xF3\x21\x43\x9B\x7E\xAC\x1D\xE6\x8A'
|
||||
pad2 = b'\x80' + 39 * b'\0'
|
||||
ioukey = hashlib.md5(pad1 + pad2 + struct.pack('!I', ioukey) + pad1).hexdigest()[:16]
|
||||
pad1 = b"\x4B\x58\x21\x81\x56\x7B\x0D\xF3\x21\x43\x9B\x7E\xAC\x1D\xE6\x8A"
|
||||
pad2 = b"\x80" + 39 * b"\0"
|
||||
ioukey = hashlib.md5(pad1 + pad2 + struct.pack("!I", ioukey) + pad1).hexdigest()[:16]
|
||||
if ioukey != user_ioukey:
|
||||
raise IOUError("Invalid IOU license key {} detected in iourc file {} for host {}".format(user_ioukey,
|
||||
self.iourc_path,
|
||||
hostname))
|
||||
raise IOUError(
|
||||
"Invalid IOU license key {} detected in iourc file {} for host {}".format(
|
||||
user_ioukey, self.iourc_path, hostname
|
||||
)
|
||||
)
|
||||
|
||||
def _nvram_file(self):
|
||||
"""
|
||||
@@ -545,14 +561,15 @@ class IOUVM(BaseNode):
|
||||
command = await self._build_command()
|
||||
try:
|
||||
log.info(f"Starting IOU: {command}")
|
||||
self.command_line = ' '.join(command)
|
||||
self.command_line = " ".join(command)
|
||||
self._iou_process = await asyncio.create_subprocess_exec(
|
||||
*command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self.working_dir,
|
||||
env=env)
|
||||
env=env,
|
||||
)
|
||||
log.info(f"IOU instance {self._id} started PID={self._iou_process.pid}")
|
||||
self._started = True
|
||||
self.status = "started"
|
||||
@@ -576,16 +593,20 @@ class IOUVM(BaseNode):
|
||||
"""
|
||||
|
||||
if self.console and self.console_type == "telnet":
|
||||
server = AsyncioTelnetServer(reader=self._iou_process.stdout, writer=self._iou_process.stdin, binary=True,
|
||||
echo=True)
|
||||
server = AsyncioTelnetServer(
|
||||
reader=self._iou_process.stdout, writer=self._iou_process.stdin, binary=True, echo=True
|
||||
)
|
||||
try:
|
||||
self._telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host,
|
||||
self.console)
|
||||
self._telnet_server = await asyncio.start_server(
|
||||
server.run, self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
except OSError as e:
|
||||
await self.stop()
|
||||
raise IOUError(
|
||||
"Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host,
|
||||
self.console, e))
|
||||
"Could not start Telnet server on socket {}:{}: {}".format(
|
||||
self._manager.port_manager.console_host, self.console, e
|
||||
)
|
||||
)
|
||||
|
||||
async def reset_console(self):
|
||||
"""
|
||||
@@ -618,17 +639,25 @@ class IOUVM(BaseNode):
|
||||
for unit in adapter.ports.keys():
|
||||
nio = adapter.get_nio(unit)
|
||||
if nio and isinstance(nio, NIOUDP):
|
||||
await self._ubridge_send("iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(name=bridge_name,
|
||||
iol_id=self.application_id,
|
||||
bay=bay_id,
|
||||
unit=unit_id,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
await self._ubridge_send(
|
||||
"iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name,
|
||||
iol_id=self.application_id,
|
||||
bay=bay_id,
|
||||
unit=unit_id,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport,
|
||||
)
|
||||
)
|
||||
if nio.capturing:
|
||||
await self._ubridge_send('iol_bridge start_capture {name} "{output_file}" {data_link_type}'.format(name=bridge_name,
|
||||
output_file=nio.pcap_output_file,
|
||||
data_link_type=re.sub(r"^DLT_", "", nio.pcap_data_link_type)))
|
||||
await self._ubridge_send(
|
||||
'iol_bridge start_capture {name} "{output_file}" {data_link_type}'.format(
|
||||
name=bridge_name,
|
||||
output_file=nio.pcap_output_file,
|
||||
data_link_type=re.sub(r"^DLT_", "", nio.pcap_data_link_type),
|
||||
)
|
||||
)
|
||||
|
||||
await self._ubridge_apply_filters(bay_id, unit_id, nio.filters)
|
||||
unit_id += 1
|
||||
@@ -646,11 +675,13 @@ class IOUVM(BaseNode):
|
||||
self._terminate_process_iou()
|
||||
if returncode != 0:
|
||||
if returncode == -11:
|
||||
message = 'IOU VM "{}" process has stopped with return code: {} (segfault). This could be an issue with the IOU image, using a different image may fix this.\n{}'.format(self.name,
|
||||
returncode,
|
||||
self.read_iou_stdout())
|
||||
message = 'IOU VM "{}" process has stopped with return code: {} (segfault). This could be an issue with the IOU image, using a different image may fix this.\n{}'.format(
|
||||
self.name, returncode, self.read_iou_stdout()
|
||||
)
|
||||
else:
|
||||
message = f'IOU VM "{self.name}" process has stopped with return code: {returncode}\n{self.read_iou_stdout()}'
|
||||
message = (
|
||||
f'IOU VM "{self.name}" process has stopped with return code: {returncode}\n{self.read_iou_stdout()}'
|
||||
)
|
||||
log.warning(message)
|
||||
self.project.emit("log.error", {"message": message})
|
||||
if self._telnet_server:
|
||||
@@ -764,12 +795,15 @@ class IOUVM(BaseNode):
|
||||
with open(netmap_path, "w", encoding="utf-8") as f:
|
||||
for bay in range(0, 16):
|
||||
for unit in range(0, 4):
|
||||
f.write("{ubridge_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(ubridge_id=str(self.application_id + 512),
|
||||
bay=bay,
|
||||
unit=unit,
|
||||
iou_id=self.application_id))
|
||||
log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name,
|
||||
id=self._id))
|
||||
f.write(
|
||||
"{ubridge_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(
|
||||
ubridge_id=str(self.application_id + 512),
|
||||
bay=bay,
|
||||
unit=unit,
|
||||
iou_id=self.application_id,
|
||||
)
|
||||
)
|
||||
log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name, id=self._id))
|
||||
except OSError as e:
|
||||
raise IOUError(f"Could not create {netmap_path}: {e}")
|
||||
|
||||
@@ -813,7 +847,7 @@ class IOUVM(BaseNode):
|
||||
command.extend(["-m", str(self._ram)])
|
||||
|
||||
# do not let IOU create the NVRAM anymore
|
||||
#startup_config_file = self.startup_config_file
|
||||
# startup_config_file = self.startup_config_file
|
||||
# if startup_config_file:
|
||||
# command.extend(["-c", os.path.basename(startup_config_file)])
|
||||
|
||||
@@ -863,9 +897,11 @@ class IOUVM(BaseNode):
|
||||
for _ in range(0, ethernet_adapters):
|
||||
self._ethernet_adapters.append(EthernetAdapter(interfaces=4))
|
||||
|
||||
log.info('IOU "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapters=len(self._ethernet_adapters)))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(
|
||||
name=self._name, id=self._id, adapters=len(self._ethernet_adapters)
|
||||
)
|
||||
)
|
||||
|
||||
self._adapters = self._ethernet_adapters + self._serial_adapters
|
||||
|
||||
@@ -891,9 +927,11 @@ class IOUVM(BaseNode):
|
||||
for _ in range(0, serial_adapters):
|
||||
self._serial_adapters.append(SerialAdapter(interfaces=4))
|
||||
|
||||
log.info('IOU "{name}" [{id}]: number of Serial adapters changed to {adapters}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapters=len(self._serial_adapters)))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: number of Serial adapters changed to {adapters}'.format(
|
||||
name=self._name, id=self._id, adapters=len(self._serial_adapters)
|
||||
)
|
||||
)
|
||||
|
||||
self._adapters = self._ethernet_adapters + self._serial_adapters
|
||||
|
||||
@@ -909,29 +947,39 @@ class IOUVM(BaseNode):
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist for IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise IOUError(
|
||||
'Adapter {adapter_number} does not exist for IOU "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"Port {port_number} does not exist on adapter {adapter}".format(
|
||||
adapter=adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
adapter.add_nio(port_number, nio)
|
||||
log.info('IOU "{name}" [{id}]: {nio} added to {adapter_number}/{port_number}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: {nio} added to {adapter_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, nio=nio, adapter_number=adapter_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
if self.ubridge:
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
await self._ubridge_send("iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(name=bridge_name,
|
||||
iol_id=self.application_id,
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
await self._ubridge_send(
|
||||
"iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name,
|
||||
iol_id=self.application_id,
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport,
|
||||
)
|
||||
)
|
||||
await self._ubridge_apply_filters(adapter_number, port_number, nio.filters)
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, port_number, nio):
|
||||
@@ -955,15 +1003,10 @@ class IOUVM(BaseNode):
|
||||
:param filters: Array of filter dictionnary
|
||||
"""
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
location = '{bridge_name} {bay} {unit}'.format(
|
||||
bridge_name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number)
|
||||
await self._ubridge_send('iol_bridge reset_packet_filters ' + location)
|
||||
location = "{bridge_name} {bay} {unit}".format(bridge_name=bridge_name, bay=adapter_number, unit=port_number)
|
||||
await self._ubridge_send("iol_bridge reset_packet_filters " + location)
|
||||
for filter in self._build_filter_list(filters):
|
||||
cmd = 'iol_bridge add_packet_filter {} {}'.format(
|
||||
location,
|
||||
filter)
|
||||
cmd = "iol_bridge add_packet_filter {} {}".format(location, filter)
|
||||
await self._ubridge_send(cmd)
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number, port_number):
|
||||
@@ -979,28 +1022,36 @@ class IOUVM(BaseNode):
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise IOUError(
|
||||
'Adapter {adapter_number} does not exist on IOU "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"Port {port_number} does not exist on adapter {adapter}".format(
|
||||
adapter=adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
adapter.remove_nio(port_number)
|
||||
log.info('IOU "{name}" [{id}]: {nio} removed from {adapter_number}/{port_number}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: {nio} removed from {adapter_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, nio=nio, adapter_number=adapter_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
if self.ubridge:
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
await self._ubridge_send("iol_bridge delete_nio_udp {name} {bay} {unit}".format(name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number))
|
||||
await self._ubridge_send(
|
||||
"iol_bridge delete_nio_udp {name} {bay} {unit}".format(
|
||||
name=bridge_name, bay=adapter_number, unit=port_number
|
||||
)
|
||||
)
|
||||
|
||||
return nio
|
||||
|
||||
@@ -1017,18 +1068,25 @@ class IOUVM(BaseNode):
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise IOUError(
|
||||
'Adapter {adapter_number} does not exist on IOU "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"Port {port_number} does not exist on adapter {adapter}".format(
|
||||
adapter=adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
|
||||
if not nio:
|
||||
raise IOUError("NIO {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"NIO {port_number} does not exist on adapter {adapter}".format(adapter=adapter, port_number=port_number)
|
||||
)
|
||||
return nio
|
||||
|
||||
@property
|
||||
@@ -1066,13 +1124,17 @@ class IOUVM(BaseNode):
|
||||
if "IOURC" not in os.environ:
|
||||
env["IOURC"] = self.iourc_path
|
||||
try:
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(self._path, "-h", cwd=self.working_dir, env=env, stderr=True)
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(
|
||||
self._path, "-h", cwd=self.working_dir, env=env, stderr=True
|
||||
)
|
||||
if re.search(r"-l\s+Enable Layer 1 keepalive messages", output):
|
||||
command.extend(["-l"])
|
||||
else:
|
||||
raise IOUError(f"layer 1 keepalive messages are not supported by {os.path.basename(self._path)}")
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
log.warning(f"could not determine if layer 1 keepalive messages are supported by {os.path.basename(self._path)}: {e}")
|
||||
log.warning(
|
||||
f"could not determine if layer 1 keepalive messages are supported by {os.path.basename(self._path)}: {e}"
|
||||
)
|
||||
|
||||
@property
|
||||
def startup_config_content(self):
|
||||
@@ -1102,15 +1164,15 @@ class IOUVM(BaseNode):
|
||||
startup_config_path = os.path.join(self.working_dir, "startup-config.cfg")
|
||||
|
||||
if startup_config is None:
|
||||
startup_config = ''
|
||||
startup_config = ""
|
||||
|
||||
# We disallow erasing the startup config file
|
||||
if len(startup_config) == 0 and os.path.exists(startup_config_path):
|
||||
return
|
||||
|
||||
with open(startup_config_path, 'w+', encoding='utf-8') as f:
|
||||
with open(startup_config_path, "w+", encoding="utf-8") as f:
|
||||
if len(startup_config) == 0:
|
||||
f.write('')
|
||||
f.write("")
|
||||
else:
|
||||
startup_config = startup_config.replace("%h", self._name)
|
||||
f.write(startup_config)
|
||||
@@ -1153,15 +1215,15 @@ class IOUVM(BaseNode):
|
||||
private_config_path = os.path.join(self.working_dir, "private-config.cfg")
|
||||
|
||||
if private_config is None:
|
||||
private_config = ''
|
||||
private_config = ""
|
||||
|
||||
# We disallow erasing the private config file
|
||||
if len(private_config) == 0 and os.path.exists(private_config_path):
|
||||
return
|
||||
|
||||
with open(private_config_path, 'w+', encoding='utf-8') as f:
|
||||
with open(private_config_path, "w+", encoding="utf-8") as f:
|
||||
if len(private_config) == 0:
|
||||
f.write('')
|
||||
f.write("")
|
||||
else:
|
||||
private_config = private_config.replace("%h", self._name)
|
||||
f.write(private_config)
|
||||
@@ -1176,7 +1238,7 @@ class IOUVM(BaseNode):
|
||||
:returns: path to config file. None if the file doesn't exist
|
||||
"""
|
||||
|
||||
path = os.path.join(self.working_dir, 'startup-config.cfg')
|
||||
path = os.path.join(self.working_dir, "startup-config.cfg")
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
else:
|
||||
@@ -1190,7 +1252,7 @@ class IOUVM(BaseNode):
|
||||
:returns: path to config file. None if the file doesn't exist
|
||||
"""
|
||||
|
||||
path = os.path.join(self.working_dir, 'private-config.cfg')
|
||||
path = os.path.join(self.working_dir, "private-config.cfg")
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
else:
|
||||
@@ -1205,9 +1267,9 @@ class IOUVM(BaseNode):
|
||||
:returns: path to startup-config file. None if the file doesn't exist
|
||||
"""
|
||||
|
||||
path = os.path.join(self.working_dir, 'startup-config.cfg')
|
||||
path = os.path.join(self.working_dir, "startup-config.cfg")
|
||||
if os.path.exists(path):
|
||||
return 'startup-config.cfg'
|
||||
return "startup-config.cfg"
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -1219,9 +1281,9 @@ class IOUVM(BaseNode):
|
||||
:returns: path to private-config file. None if the file doesn't exist
|
||||
"""
|
||||
|
||||
path = os.path.join(self.working_dir, 'private-config.cfg')
|
||||
path = os.path.join(self.working_dir, "private-config.cfg")
|
||||
if os.path.exists(path):
|
||||
return 'private-config.cfg'
|
||||
return "private-config.cfg"
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -1288,7 +1350,7 @@ class IOUVM(BaseNode):
|
||||
except (binascii.Error, OSError) as e:
|
||||
raise IOUError(f"Could not save the startup configuration {config_path}: {e}")
|
||||
|
||||
if private_config_content and private_config_content != b'\nend\n':
|
||||
if private_config_content and private_config_content != b"\nend\n":
|
||||
config_path = os.path.join(self.working_dir, "private-config.cfg")
|
||||
try:
|
||||
config = private_config_content.decode("utf-8", errors="replace")
|
||||
@@ -1310,23 +1372,34 @@ class IOUVM(BaseNode):
|
||||
|
||||
nio = self.get_nio(adapter_number, port_number)
|
||||
if nio.capturing:
|
||||
raise IOUError("Packet capture is already activated on {adapter_number}/{port_number}".format(adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"Packet capture is already activated on {adapter_number}/{port_number}".format(
|
||||
adapter_number=adapter_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('IOU "{name}" [{id}]: starting packet capture on {adapter_number}/{port_number} to {output_file}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number,
|
||||
output_file=output_file))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: starting packet capture on {adapter_number}/{port_number} to {output_file}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number,
|
||||
output_file=output_file,
|
||||
)
|
||||
)
|
||||
|
||||
if self.ubridge:
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
await self._ubridge_send('iol_bridge start_capture {name} {bay} {unit} "{output_file}" {data_link_type}'.format(name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
output_file=output_file,
|
||||
data_link_type=re.sub(r"^DLT_", "", data_link_type)))
|
||||
await self._ubridge_send(
|
||||
'iol_bridge start_capture {name} {bay} {unit} "{output_file}" {data_link_type}'.format(
|
||||
name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
output_file=output_file,
|
||||
data_link_type=re.sub(r"^DLT_", "", data_link_type),
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, adapter_number, port_number):
|
||||
"""
|
||||
@@ -1340,12 +1413,15 @@ class IOUVM(BaseNode):
|
||||
if not nio.capturing:
|
||||
return
|
||||
nio.stop_packet_capture()
|
||||
log.info('IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, adapter_number=adapter_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
if self.ubridge:
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
await self._ubridge_send('iol_bridge stop_capture {name} {bay} {unit}'.format(name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number))
|
||||
await self._ubridge_send(
|
||||
"iol_bridge stop_capture {name} {bay} {unit}".format(
|
||||
name=bridge_name, bay=adapter_number, unit=port_number
|
||||
)
|
||||
)
|
||||
@@ -47,28 +47,28 @@ def uncompress_LZC(data):
|
||||
LZC_NUM_BITS_MIN = 9
|
||||
LZC_NUM_BITS_MAX = 16
|
||||
|
||||
in_data = bytearray(data)
|
||||
in_len = len(in_data)
|
||||
in_data = bytearray(data)
|
||||
in_len = len(in_data)
|
||||
out_data = bytearray()
|
||||
|
||||
if in_len == 0:
|
||||
return out_data
|
||||
if in_len < 3:
|
||||
raise ValueError('invalid length')
|
||||
raise ValueError("invalid length")
|
||||
if in_data[0] != 0x1F or in_data[1] != 0x9D:
|
||||
raise ValueError('invalid header')
|
||||
raise ValueError("invalid header")
|
||||
|
||||
max_bits = in_data[2] & 0x1F
|
||||
if max_bits < LZC_NUM_BITS_MIN or max_bits > LZC_NUM_BITS_MAX:
|
||||
raise ValueError('not supported')
|
||||
raise ValueError("not supported")
|
||||
num_items = 1 << max_bits
|
||||
blockmode = (in_data[2] & 0x80) != 0
|
||||
|
||||
in_pos = 3
|
||||
in_pos = 3
|
||||
start_pos = in_pos
|
||||
num_bits = LZC_NUM_BITS_MIN
|
||||
num_bits = LZC_NUM_BITS_MIN
|
||||
dict_size = 1 << num_bits
|
||||
head = 256
|
||||
head = 256
|
||||
if blockmode:
|
||||
head += 1
|
||||
first_sym = True
|
||||
@@ -89,7 +89,7 @@ def uncompress_LZC(data):
|
||||
buf, symbol = divmod(buf, dict_size)
|
||||
buf_bits -= num_bits
|
||||
except IndexError:
|
||||
raise ValueError('invalid data')
|
||||
raise ValueError("invalid data")
|
||||
|
||||
# re-initialize dictionary
|
||||
if blockmode and symbol == 256:
|
||||
@@ -108,7 +108,7 @@ def uncompress_LZC(data):
|
||||
if first_sym:
|
||||
first_sym = False
|
||||
if symbol >= 256:
|
||||
raise ValueError('invalid data')
|
||||
raise ValueError("invalid data")
|
||||
prev = symbol
|
||||
out_data.extend(comp_dict[symbol])
|
||||
continue
|
||||
@@ -124,7 +124,7 @@ def uncompress_LZC(data):
|
||||
elif symbol == head:
|
||||
comp_dict[head] = comp_dict[prev] + comp_dict[prev][0:1]
|
||||
else:
|
||||
raise ValueError('invalid data')
|
||||
raise ValueError("invalid data")
|
||||
prev = symbol
|
||||
|
||||
# output symbol
|
||||
@@ -148,42 +148,42 @@ def nvram_export(nvram):
|
||||
offset = 0
|
||||
# extract startup config
|
||||
try:
|
||||
(magic, data_format, _, _, _, _, length, _, _, _, _, _) = \
|
||||
struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=offset)
|
||||
(magic, data_format, _, _, _, _, length, _, _, _, _, _) = struct.unpack_from(
|
||||
">HHHHIIIIIHHI", nvram, offset=offset
|
||||
)
|
||||
offset += 36
|
||||
if magic != 0xABCD:
|
||||
raise ValueError('no startup config')
|
||||
if len(nvram) < offset+length:
|
||||
raise ValueError('invalid length')
|
||||
startup = nvram[offset:offset+length]
|
||||
raise ValueError("no startup config")
|
||||
if len(nvram) < offset + length:
|
||||
raise ValueError("invalid length")
|
||||
startup = nvram[offset : offset + length]
|
||||
except struct.error:
|
||||
raise ValueError('invalid length')
|
||||
raise ValueError("invalid length")
|
||||
|
||||
# uncompress startup config
|
||||
if data_format == 2:
|
||||
try:
|
||||
startup = uncompress_LZC(startup)
|
||||
except ValueError as err:
|
||||
raise ValueError('uncompress startup: ' + str(err))
|
||||
raise ValueError("uncompress startup: " + str(err))
|
||||
|
||||
private = None
|
||||
try:
|
||||
# calculate offset of private header
|
||||
length += (4 - length % 4) % 4 # alignment to multiple of 4
|
||||
length += (4 - length % 4) % 4 # alignment to multiple of 4
|
||||
offset += length
|
||||
# check for additonal offset of 4
|
||||
(magic, data_format) = struct.unpack_from('>HH', nvram, offset=offset+4)
|
||||
(magic, data_format) = struct.unpack_from(">HH", nvram, offset=offset + 4)
|
||||
if magic == 0xFEDC and data_format == 1:
|
||||
offset += 4
|
||||
|
||||
# extract private config
|
||||
(magic, data_format, _, _, length) = \
|
||||
struct.unpack_from('>HHIII', nvram, offset=offset)
|
||||
(magic, data_format, _, _, length) = struct.unpack_from(">HHIII", nvram, offset=offset)
|
||||
offset += 16
|
||||
if magic == 0xFEDC and data_format == 1:
|
||||
if len(nvram) < offset+length:
|
||||
raise ValueError('invalid length')
|
||||
private = nvram[offset:offset+length]
|
||||
if len(nvram) < offset + length:
|
||||
raise ValueError("invalid length")
|
||||
private = nvram[offset : offset + length]
|
||||
|
||||
# missing private header is not an error
|
||||
except struct.error:
|
||||
@@ -192,22 +192,19 @@ def nvram_export(nvram):
|
||||
return (startup, private)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
# Main program
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description='%(prog)s exports startup/private configuration from IOU NVRAM file.')
|
||||
parser.add_argument('nvram', metavar='NVRAM',
|
||||
help='NVRAM file')
|
||||
parser.add_argument('startup', metavar='startup-config',
|
||||
help='startup configuration')
|
||||
parser.add_argument('private', metavar='private-config', nargs='?',
|
||||
help='private configuration')
|
||||
parser = argparse.ArgumentParser(description="%(prog)s exports startup/private configuration from IOU NVRAM file.")
|
||||
parser.add_argument("nvram", metavar="NVRAM", help="NVRAM file")
|
||||
parser.add_argument("startup", metavar="startup-config", help="startup configuration")
|
||||
parser.add_argument("private", metavar="private-config", nargs="?", help="private configuration")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
fd = open(args.nvram, 'rb')
|
||||
fd = open(args.nvram, "rb")
|
||||
nvram = fd.read()
|
||||
fd.close()
|
||||
except OSError as err:
|
||||
@@ -221,14 +218,14 @@ if __name__ == '__main__':
|
||||
sys.exit(3)
|
||||
|
||||
try:
|
||||
fd = open(args.startup, 'wb')
|
||||
fd = open(args.startup, "wb")
|
||||
fd.write(startup)
|
||||
fd.close()
|
||||
if args.private is not None:
|
||||
if private is None:
|
||||
sys.stderr.write("Warning: No private config\n")
|
||||
else:
|
||||
fd = open(args.private, 'wb')
|
||||
fd = open(args.private, "wb")
|
||||
fd.write(private)
|
||||
fd.close()
|
||||
except OSError as err:
|
||||
|
||||
@@ -39,7 +39,7 @@ import struct
|
||||
|
||||
# calculate padding
|
||||
def padding(length, start_address):
|
||||
pad = -length % 4 # padding to alignment of 4
|
||||
pad = -length % 4 # padding to alignment of 4
|
||||
# extra padding if pad != 0 and big start_address
|
||||
if pad != 0 and (start_address & 0x80000000) != 0:
|
||||
pad += 4
|
||||
@@ -50,66 +50,64 @@ def padding(length, start_address):
|
||||
def checksum(data, start, end):
|
||||
chk = 0
|
||||
# calculate checksum of first two words
|
||||
for word in struct.unpack_from('>2H', data, start):
|
||||
for word in struct.unpack_from(">2H", data, start):
|
||||
chk += word
|
||||
|
||||
# add remaining words, ignoring old checksum at offset 4
|
||||
struct_format = f'>{(end - start - 6) // 2:d}H'
|
||||
for word in struct.unpack_from(struct_format, data, start+6):
|
||||
struct_format = f">{(end - start - 6) // 2:d}H"
|
||||
for word in struct.unpack_from(struct_format, data, start + 6):
|
||||
chk += word
|
||||
|
||||
# handle 16 bit overflow
|
||||
while chk >> 16:
|
||||
chk = (chk & 0xffff) + (chk >> 16)
|
||||
chk = chk ^ 0xffff
|
||||
chk = (chk & 0xFFFF) + (chk >> 16)
|
||||
chk = chk ^ 0xFFFF
|
||||
|
||||
# save checksum
|
||||
struct.pack_into('>H', data, start+4, chk)
|
||||
struct.pack_into(">H", data, start + 4, chk)
|
||||
|
||||
|
||||
# import IOU NVRAM
|
||||
# NVRAM format: https://github.com/ehlers/IOUtools/blob/master/NVRAM.md
|
||||
def nvram_import(nvram, startup, private, size):
|
||||
DEFAULT_IOS = 0x0F04 # IOS 15.4
|
||||
DEFAULT_IOS = 0x0F04 # IOS 15.4
|
||||
base_address = 0x10000000
|
||||
|
||||
# check size parameter
|
||||
if size is not None and (size < 8 or size > 1024):
|
||||
raise ValueError('invalid size')
|
||||
raise ValueError("invalid size")
|
||||
|
||||
# create new nvram if nvram is empty or has wrong size
|
||||
if nvram is None or (size is not None and len(nvram) != size*1024):
|
||||
nvram = bytearray([0] * (size*1024))
|
||||
if nvram is None or (size is not None and len(nvram) != size * 1024):
|
||||
nvram = bytearray([0] * (size * 1024))
|
||||
else:
|
||||
nvram = bytearray(nvram)
|
||||
|
||||
# check nvram size
|
||||
nvram_len = len(nvram)
|
||||
if nvram_len < 8*1024 or nvram_len > 1024*1024 or nvram_len % 1024 != 0:
|
||||
raise ValueError('invalid NVRAM length')
|
||||
if nvram_len < 8 * 1024 or nvram_len > 1024 * 1024 or nvram_len % 1024 != 0:
|
||||
raise ValueError("invalid NVRAM length")
|
||||
nvram_len = nvram_len // 2
|
||||
|
||||
# get size of current config
|
||||
config_len = 0
|
||||
try:
|
||||
(magic, _, _, ios, start_addr, _, length, _, _, _, _, _) = \
|
||||
struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=0)
|
||||
(magic, _, _, ios, start_addr, _, length, _, _, _, _, _) = struct.unpack_from(">HHHHIIIIIHHI", nvram, offset=0)
|
||||
if magic == 0xABCD:
|
||||
base_address = start_addr - 36
|
||||
config_len = 36 + length + padding(length, base_address)
|
||||
(magic, _, _, _, length) = \
|
||||
struct.unpack_from('>HHIII', nvram, offset=config_len)
|
||||
(magic, _, _, _, length) = struct.unpack_from(">HHIII", nvram, offset=config_len)
|
||||
if magic == 0xFEDC:
|
||||
config_len += 16 + length
|
||||
else:
|
||||
ios = None
|
||||
except struct.error:
|
||||
raise ValueError('unknown nvram format')
|
||||
raise ValueError("unknown nvram format")
|
||||
if config_len > nvram_len:
|
||||
raise ValueError('unknown nvram format')
|
||||
raise ValueError("unknown nvram format")
|
||||
|
||||
# calculate max. config size
|
||||
max_config = nvram_len - 2*1024 # reserve 2k for files
|
||||
max_config = nvram_len - 2 * 1024 # reserve 2k for files
|
||||
idx = max_config
|
||||
empty_sector = bytearray([0] * 1024)
|
||||
while True:
|
||||
@@ -117,11 +115,10 @@ def nvram_import(nvram, startup, private, size):
|
||||
if idx < config_len:
|
||||
break
|
||||
# if valid file header:
|
||||
(magic, _, flags, length, _) = \
|
||||
struct.unpack_from('>HHHH24s', nvram, offset=idx)
|
||||
(magic, _, flags, length, _) = struct.unpack_from(">HHHH24s", nvram, offset=idx)
|
||||
if magic == 0xDCBA and flags < 8 and length <= 992:
|
||||
max_config = idx
|
||||
elif nvram[idx:idx+1024] != empty_sector:
|
||||
elif nvram[idx : idx + 1024] != empty_sector:
|
||||
break
|
||||
|
||||
# import startup config
|
||||
@@ -131,34 +128,46 @@ def nvram_import(nvram, startup, private, size):
|
||||
# the padding of a different version, the startup config is padded
|
||||
# with '\n' to the alignment of 4.
|
||||
ios = DEFAULT_IOS
|
||||
startup += b'\n' * (-len(startup) % 4)
|
||||
new_nvram.extend(struct.pack('>HHHHIIIIIHHI',
|
||||
0xABCD, # magic
|
||||
1, # raw data
|
||||
0, # checksum, not yet calculated
|
||||
ios, # IOS version
|
||||
base_address + 36, # start address
|
||||
base_address + 36 + len(startup), # end address
|
||||
len(startup), # length
|
||||
0, 0, 0, 0, 0))
|
||||
startup += b"\n" * (-len(startup) % 4)
|
||||
new_nvram.extend(
|
||||
struct.pack(
|
||||
">HHHHIIIIIHHI",
|
||||
0xABCD, # magic
|
||||
1, # raw data
|
||||
0, # checksum, not yet calculated
|
||||
ios, # IOS version
|
||||
base_address + 36, # start address
|
||||
base_address + 36 + len(startup), # end address
|
||||
len(startup), # length
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
)
|
||||
new_nvram.extend(startup)
|
||||
new_nvram.extend([0] * padding(len(new_nvram), base_address))
|
||||
|
||||
# import private config
|
||||
if private is None:
|
||||
private = b''
|
||||
private = b""
|
||||
offset = len(new_nvram)
|
||||
new_nvram.extend(struct.pack('>HHIII',
|
||||
0xFEDC, # magic
|
||||
1, # raw data
|
||||
base_address + offset + 16, # start address
|
||||
base_address + offset + 16 + len(private), # end address
|
||||
len(private) )) # length
|
||||
new_nvram.extend(
|
||||
struct.pack(
|
||||
">HHIII",
|
||||
0xFEDC, # magic
|
||||
1, # raw data
|
||||
base_address + offset + 16, # start address
|
||||
base_address + offset + 16 + len(private), # end address
|
||||
len(private),
|
||||
)
|
||||
) # length
|
||||
new_nvram.extend(private)
|
||||
|
||||
# add rest
|
||||
if len(new_nvram) > max_config:
|
||||
raise ValueError('NVRAM size too small')
|
||||
raise ValueError("NVRAM size too small")
|
||||
new_nvram.extend([0] * (max_config - len(new_nvram)))
|
||||
new_nvram.extend(nvram[max_config:])
|
||||
|
||||
@@ -167,7 +176,7 @@ def nvram_import(nvram, startup, private, size):
|
||||
return new_nvram
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
# Main program
|
||||
import argparse
|
||||
import sys
|
||||
@@ -176,36 +185,32 @@ if __name__ == '__main__':
|
||||
try:
|
||||
value = int(string)
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError('invalid int value: ' + string)
|
||||
raise argparse.ArgumentTypeError("invalid int value: " + string)
|
||||
if value < 8 or value > 1024:
|
||||
raise argparse.ArgumentTypeError('size must be 8..1024')
|
||||
raise argparse.ArgumentTypeError("size must be 8..1024")
|
||||
return value
|
||||
|
||||
parser = argparse.ArgumentParser(description='%(prog)s imports startup/private configuration into IOU NVRAM file.')
|
||||
parser.add_argument('-c', '--create', metavar='size', type=check_size,
|
||||
help='create NVRAM file, size in kByte')
|
||||
parser.add_argument('nvram', metavar='NVRAM',
|
||||
help='NVRAM file')
|
||||
parser.add_argument('startup', metavar='startup-config',
|
||||
help='startup configuration')
|
||||
parser.add_argument('private', metavar='private-config', nargs='?',
|
||||
help='private configuration')
|
||||
parser = argparse.ArgumentParser(description="%(prog)s imports startup/private configuration into IOU NVRAM file.")
|
||||
parser.add_argument("-c", "--create", metavar="size", type=check_size, help="create NVRAM file, size in kByte")
|
||||
parser.add_argument("nvram", metavar="NVRAM", help="NVRAM file")
|
||||
parser.add_argument("startup", metavar="startup-config", help="startup configuration")
|
||||
parser.add_argument("private", metavar="private-config", nargs="?", help="private configuration")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.create is None:
|
||||
fd = open(args.nvram, 'rb')
|
||||
fd = open(args.nvram, "rb")
|
||||
nvram = fd.read()
|
||||
fd.close()
|
||||
else:
|
||||
nvram = None
|
||||
fd = open(args.startup, 'rb')
|
||||
fd = open(args.startup, "rb")
|
||||
startup = fd.read()
|
||||
fd.close()
|
||||
if args.private is None:
|
||||
private = None
|
||||
else:
|
||||
fd = open(args.private, 'rb')
|
||||
fd = open(args.private, "rb")
|
||||
private = fd.read()
|
||||
fd.close()
|
||||
except OSError as err:
|
||||
@@ -219,7 +224,7 @@ if __name__ == '__main__':
|
||||
sys.exit(3)
|
||||
|
||||
try:
|
||||
fd = open(args.nvram, 'wb')
|
||||
fd = open(args.nvram, "wb")
|
||||
fd.write(nvram)
|
||||
fd.close()
|
||||
except OSError as err:
|
||||
|
||||
@@ -50,5 +50,4 @@ class NIOEthernet(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_ethernet",
|
||||
"ethernet_device": self._ethernet_device}
|
||||
return {"type": "nio_ethernet", "ethernet_device": self._ethernet_device}
|
||||
@@ -50,5 +50,4 @@ class NIOTAP(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_tap",
|
||||
"tap_device": self._tap_device}
|
||||
return {"type": "nio_tap", "tap_device": self._tap_device}
|
||||
@@ -74,7 +74,4 @@ class NIOUDP(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_udp",
|
||||
"lport": self._lport,
|
||||
"rport": self._rport,
|
||||
"rhost": self._rhost}
|
||||
return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost}
|
||||
@@ -68,6 +68,6 @@ class NotificationManager:
|
||||
:returns: instance of NotificationManager
|
||||
"""
|
||||
|
||||
if not hasattr(NotificationManager, '_instance') or NotificationManager._instance is None:
|
||||
if not hasattr(NotificationManager, "_instance") or NotificationManager._instance is None:
|
||||
NotificationManager._instance = NotificationManager()
|
||||
return NotificationManager._instance
|
||||
@@ -19,14 +19,77 @@ from fastapi import HTTPException, status
|
||||
from gns3server.config import Config
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# These ports are disallowed by Chrome and Firefox to avoid issues, we skip them as well
|
||||
BANNED_PORTS = {1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102, 103,
|
||||
104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512, 513, 514, 515, 526,
|
||||
530, 531, 532, 540, 556, 563, 587, 601, 636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667,
|
||||
6668, 6669}
|
||||
BANNED_PORTS = {
|
||||
1,
|
||||
7,
|
||||
9,
|
||||
11,
|
||||
13,
|
||||
15,
|
||||
17,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
25,
|
||||
37,
|
||||
42,
|
||||
43,
|
||||
53,
|
||||
77,
|
||||
79,
|
||||
87,
|
||||
95,
|
||||
101,
|
||||
102,
|
||||
103,
|
||||
104,
|
||||
109,
|
||||
110,
|
||||
111,
|
||||
113,
|
||||
115,
|
||||
117,
|
||||
119,
|
||||
123,
|
||||
135,
|
||||
139,
|
||||
143,
|
||||
179,
|
||||
389,
|
||||
465,
|
||||
512,
|
||||
513,
|
||||
514,
|
||||
515,
|
||||
526,
|
||||
530,
|
||||
531,
|
||||
532,
|
||||
540,
|
||||
556,
|
||||
563,
|
||||
587,
|
||||
601,
|
||||
636,
|
||||
993,
|
||||
995,
|
||||
2049,
|
||||
3659,
|
||||
4045,
|
||||
6000,
|
||||
6665,
|
||||
6666,
|
||||
6667,
|
||||
6668,
|
||||
6669,
|
||||
}
|
||||
|
||||
|
||||
class PortManager:
|
||||
@@ -66,10 +129,12 @@ class PortManager:
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"console_port_range": self._console_port_range,
|
||||
"console_ports": list(self._used_tcp_ports),
|
||||
"udp_port_range": self._udp_port_range,
|
||||
"udp_ports": list(self._used_udp_ports)}
|
||||
return {
|
||||
"console_port_range": self._console_port_range,
|
||||
"console_ports": list(self._used_tcp_ports),
|
||||
"udp_port_range": self._udp_port_range,
|
||||
"udp_ports": list(self._used_udp_ports),
|
||||
}
|
||||
|
||||
@property
|
||||
def console_host(self):
|
||||
@@ -145,8 +210,9 @@ class PortManager:
|
||||
"""
|
||||
|
||||
if end_port < start_port:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Invalid port range {start_port}-{end_port}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail=f"Invalid port range {start_port}-{end_port}"
|
||||
)
|
||||
|
||||
last_exception = None
|
||||
for port in range(start_port, end_port + 1):
|
||||
@@ -165,9 +231,11 @@ class PortManager:
|
||||
else:
|
||||
continue
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Could not find a free port between {start_port} and {end_port} on host {host},"
|
||||
f" last exception: {last_exception}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Could not find a free port between {start_port} and {end_port} on host {host},"
|
||||
f" last exception: {last_exception}",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _check_port(host, port, socket_type):
|
||||
@@ -200,11 +268,13 @@ class PortManager:
|
||||
port_range_start = self._console_port_range[0]
|
||||
port_range_end = self._console_port_range[1]
|
||||
|
||||
port = self.find_unused_port(port_range_start,
|
||||
port_range_end,
|
||||
host=self._console_host,
|
||||
socket_type="TCP",
|
||||
ignore_ports=self._used_tcp_ports)
|
||||
port = self.find_unused_port(
|
||||
port_range_start,
|
||||
port_range_end,
|
||||
host=self._console_host,
|
||||
socket_type="TCP",
|
||||
ignore_ports=self._used_tcp_ports,
|
||||
)
|
||||
|
||||
self._used_tcp_ports.add(port)
|
||||
project.record_tcp_port(port)
|
||||
@@ -237,8 +307,10 @@ class PortManager:
|
||||
if port < port_range_start or port > port_range_end:
|
||||
old_port = port
|
||||
port = self.get_free_tcp_port(project, port_range_start=port_range_start, port_range_end=port_range_end)
|
||||
msg = f"TCP port {old_port} is outside the range {port_range_start}-{port_range_end} on host " \
|
||||
f"{self._console_host}. Port has been replaced by {port}"
|
||||
msg = (
|
||||
f"TCP port {old_port} is outside the range {port_range_start}-{port_range_end} on host "
|
||||
f"{self._console_host}. Port has been replaced by {port}"
|
||||
)
|
||||
log.debug(msg)
|
||||
return port
|
||||
try:
|
||||
@@ -274,11 +346,13 @@ class PortManager:
|
||||
|
||||
:param project: Project instance
|
||||
"""
|
||||
port = self.find_unused_port(self._udp_port_range[0],
|
||||
self._udp_port_range[1],
|
||||
host=self._udp_host,
|
||||
socket_type="UDP",
|
||||
ignore_ports=self._used_udp_ports)
|
||||
port = self.find_unused_port(
|
||||
self._udp_port_range[0],
|
||||
self._udp_port_range[1],
|
||||
host=self._udp_host,
|
||||
socket_type="UDP",
|
||||
ignore_ports=self._used_udp_ports,
|
||||
)
|
||||
|
||||
self._used_udp_ports.add(port)
|
||||
project.record_udp_port(port)
|
||||
@@ -294,12 +368,15 @@ class PortManager:
|
||||
"""
|
||||
|
||||
if port in self._used_udp_ports:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"UDP port {port} already in use on host {self._console_host}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"UDP port {port} already in use on host {self._console_host}",
|
||||
)
|
||||
if port < self._udp_port_range[0] or port > self._udp_port_range[1]:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"UDP port {port} is outside the range "
|
||||
f"{self._udp_port_range[0]}-{self._udp_port_range[1]}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"UDP port {port} is outside the range " f"{self._udp_port_range[0]}-{self._udp_port_range[1]}",
|
||||
)
|
||||
self._used_udp_ports.add(port)
|
||||
project.record_udp_port(port)
|
||||
log.debug(f"UDP port {port} has been reserved")
|
||||
|
||||
@@ -29,6 +29,7 @@ from ..utils.asyncio import wait_run_in_executor
|
||||
from ..utils.path import check_path_allowed, get_default_project_directory
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -78,11 +79,7 @@ class Project:
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {
|
||||
"name": self._name,
|
||||
"project_id": self._id,
|
||||
"variables": self._variables
|
||||
}
|
||||
return {"name": self._name, "project_id": self._id, "variables": self._variables}
|
||||
|
||||
def is_local(self):
|
||||
|
||||
@@ -225,7 +222,6 @@ class Project:
|
||||
"""
|
||||
return os.path.join(self._path, "project-files", node.manager.module_name.lower(), node.id)
|
||||
|
||||
|
||||
def tmp_working_directory(self):
|
||||
"""
|
||||
A temporary directory. Will be clean at project open and close
|
||||
@@ -296,7 +292,7 @@ class Project:
|
||||
# we need to update docker nodes when variables changes
|
||||
if original_variables != variables:
|
||||
for node in self.nodes:
|
||||
if hasattr(node, 'update'):
|
||||
if hasattr(node, "update"):
|
||||
await node.update()
|
||||
|
||||
async def close(self):
|
||||
@@ -385,6 +381,7 @@ class Project:
|
||||
|
||||
# We import it at the last time to avoid circular dependencies
|
||||
from ..compute import MODULES
|
||||
|
||||
return MODULES
|
||||
|
||||
def emit(self, action, event):
|
||||
@@ -411,7 +408,9 @@ class Project:
|
||||
file_info = {"path": path}
|
||||
|
||||
try:
|
||||
file_info["md5sum"] = await wait_run_in_executor(self._hash_file, os.path.join(dirpath, filename))
|
||||
file_info["md5sum"] = await wait_run_in_executor(
|
||||
self._hash_file, os.path.join(dirpath, filename)
|
||||
)
|
||||
except OSError:
|
||||
continue
|
||||
files.append(file_info)
|
||||
|
||||
@@ -23,6 +23,7 @@ from uuid import UUID
|
||||
from gns3server.compute.compute_error import ComputeError, ComputeNotFoundError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -91,9 +92,7 @@ class ProjectManager:
|
||||
# send a warning if used disk space is >= 90%
|
||||
if used_disk_space >= 90:
|
||||
message = 'Only {:.2f}% or less of free disk space detected in "{}" on "{}"'.format(
|
||||
100 - used_disk_space,
|
||||
project.path,
|
||||
platform.node()
|
||||
100 - used_disk_space, project.path, platform.node()
|
||||
)
|
||||
log.warning(message)
|
||||
project.emit("log.warning", {"message": message})
|
||||
@@ -106,8 +105,7 @@ class ProjectManager:
|
||||
"""
|
||||
if project_id is not None and project_id in self._projects:
|
||||
return self._projects[project_id]
|
||||
project = Project(name=name, project_id=project_id,
|
||||
path=path, variables=variables)
|
||||
project = Project(name=name, project_id=project_id, path=path, variables=variables)
|
||||
self._check_available_disk_space(project)
|
||||
self._projects[project.id] = project
|
||||
return project
|
||||
|
||||
@@ -35,6 +35,7 @@ from .utils.guest_cid import get_next_guest_cid
|
||||
from .utils.ziputils import unpack_zip
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -153,9 +154,11 @@ class Qemu(BaseManager):
|
||||
for f in os.listdir(path):
|
||||
if f.endswith("-spice"):
|
||||
continue
|
||||
if (f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe") and \
|
||||
os.access(os.path.join(path, f), os.X_OK) and \
|
||||
os.path.isfile(os.path.join(path, f)):
|
||||
if (
|
||||
(f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe")
|
||||
and os.access(os.path.join(path, f), os.X_OK)
|
||||
and os.path.isfile(os.path.join(path, f))
|
||||
):
|
||||
if archs is not None:
|
||||
for arch in archs:
|
||||
if f.endswith(arch) or f.endswith(f"{arch}.exe") or f.endswith(f"{arch}w.exe"):
|
||||
@@ -183,9 +186,11 @@ class Qemu(BaseManager):
|
||||
for path in Qemu.paths_list():
|
||||
try:
|
||||
for f in os.listdir(path):
|
||||
if (f == "qemu-img" or f == "qemu-img.exe") and \
|
||||
os.access(os.path.join(path, f), os.X_OK) and \
|
||||
os.path.isfile(os.path.join(path, f)):
|
||||
if (
|
||||
(f == "qemu-img" or f == "qemu-img.exe")
|
||||
and os.access(os.path.join(path, f), os.X_OK)
|
||||
and os.path.isfile(os.path.join(path, f))
|
||||
):
|
||||
qemu_path = os.path.join(path, f)
|
||||
version = await Qemu._get_qemu_img_version(qemu_path)
|
||||
qemu_imgs.append({"path": qemu_path, "version": version})
|
||||
@@ -255,17 +260,21 @@ class Qemu(BaseManager):
|
||||
:returns: HAXM version number. Returns None if HAXM is not installed.
|
||||
"""
|
||||
|
||||
assert(sys.platform.startswith("win"))
|
||||
assert sys.platform.startswith("win")
|
||||
import winreg
|
||||
|
||||
hkey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products")
|
||||
hkey = winreg.OpenKey(
|
||||
winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products"
|
||||
)
|
||||
version = None
|
||||
for index in range(winreg.QueryInfoKey(hkey)[0]):
|
||||
product_id = winreg.EnumKey(hkey, index)
|
||||
try:
|
||||
product_key = winreg.OpenKey(hkey, fr"{product_id}\InstallProperties")
|
||||
try:
|
||||
if winreg.QueryValueEx(product_key, "DisplayName")[0].endswith("Hardware Accelerated Execution Manager"):
|
||||
if winreg.QueryValueEx(product_key, "DisplayName")[0].endswith(
|
||||
"Hardware Accelerated Execution Manager"
|
||||
):
|
||||
version = winreg.QueryValueEx(product_key, "DisplayVersion")[0]
|
||||
break
|
||||
finally:
|
||||
@@ -310,8 +319,10 @@ class Qemu(BaseManager):
|
||||
if os.path.exists(path):
|
||||
raise QemuError(f"Could not create disk image '{path}', file already exists")
|
||||
except UnicodeEncodeError:
|
||||
raise QemuError("Could not create disk image '{}', "
|
||||
"path contains characters not supported by filesystem".format(path))
|
||||
raise QemuError(
|
||||
"Could not create disk image '{}', "
|
||||
"path contains characters not supported by filesystem".format(path)
|
||||
)
|
||||
|
||||
command = [qemu_img, "create", "-f", img_format]
|
||||
for option in sorted(options.keys()):
|
||||
|
||||
+413
-198
File diff suppressed because it is too large.
Load diff
@@ -17,6 +17,7 @@
|
||||
from ..qemu_error import QemuError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -59,10 +59,12 @@ class Qcow2:
|
||||
# } QCowHeader;
|
||||
|
||||
struct_format = ">IIQi"
|
||||
with open(self._path, 'rb') as f:
|
||||
with open(self._path, "rb") as f:
|
||||
content = f.read(struct.calcsize(struct_format))
|
||||
try:
|
||||
self.magic, self.version, self.backing_file_offset, self.backing_file_size = struct.unpack_from(struct_format, content)
|
||||
self.magic, self.version, self.backing_file_offset, self.backing_file_size = struct.unpack_from(
|
||||
struct_format, content
|
||||
)
|
||||
|
||||
except struct.error:
|
||||
raise Qcow2Error(f"Invalid file header for {self._path}")
|
||||
@@ -78,7 +80,7 @@ class Qcow2:
|
||||
:returns: None if it's not a linked clone, the path otherwise
|
||||
"""
|
||||
|
||||
with open(self._path, 'rb') as f:
|
||||
with open(self._path, "rb") as f:
|
||||
f.seek(self.backing_file_offset)
|
||||
content = f.read(self.backing_file_size)
|
||||
|
||||
|
||||
@@ -20,12 +20,14 @@ import time
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
|
||||
def pack_zip(filename, root_dir=None, base_dir=None):
|
||||
"""Create a zip archive"""
|
||||
|
||||
if filename[-4:].lower() == ".zip":
|
||||
filename = filename[:-4]
|
||||
shutil.make_archive(filename, 'zip', root_dir, base_dir)
|
||||
shutil.make_archive(filename, "zip", root_dir, base_dir)
|
||||
|
||||
|
||||
def unpack_zip(filename, extract_dir=None):
|
||||
"""Unpack a zip archive"""
|
||||
@@ -35,7 +37,7 @@ def unpack_zip(filename, extract_dir=None):
|
||||
extract_dir = os.getcwd()
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(filename, 'r') as zfile:
|
||||
with zipfile.ZipFile(filename, "r") as zfile:
|
||||
for zinfo in zfile.infolist():
|
||||
fname = os.path.join(extract_dir, zinfo.filename)
|
||||
date_time = time.mktime(zinfo.date_time + (0, 0, -1))
|
||||
|
||||
@@ -40,4 +40,4 @@ class TraceNG(BaseManager):
|
||||
:returns: TraceNGVM instance
|
||||
"""
|
||||
|
||||
return (await super().create_node(*args, **kwargs))
|
||||
return await super().create_node(*args, **kwargs)
|
||||
@@ -36,11 +36,12 @@ from ..base_node import BaseNode
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TraceNGVM(BaseNode):
|
||||
module_name = 'traceng'
|
||||
module_name = "traceng"
|
||||
|
||||
"""
|
||||
TraceNG VM implementation.
|
||||
@@ -111,16 +112,18 @@ class TraceNGVM(BaseNode):
|
||||
|
||||
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}
|
||||
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):
|
||||
"""
|
||||
@@ -161,10 +164,11 @@ class TraceNGVM(BaseNode):
|
||||
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))
|
||||
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):
|
||||
@@ -185,10 +189,11 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
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))
|
||||
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):
|
||||
"""
|
||||
@@ -207,10 +212,10 @@ class TraceNGVM(BaseNode):
|
||||
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)
|
||||
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()
|
||||
@@ -277,9 +282,9 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
log.info(f"Stopping TraceNG instance {self.name} PID={self._process.pid}")
|
||||
#if sys.platform.startswith("win32"):
|
||||
# if sys.platform.startswith("win32"):
|
||||
# self._process.send_signal(signal.CTRL_BREAK_EVENT)
|
||||
#else:
|
||||
# else:
|
||||
try:
|
||||
self._process.terminate()
|
||||
# Sometime the process may already be dead when we garbage collect
|
||||
@@ -306,17 +311,21 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
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))
|
||||
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))
|
||||
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
|
||||
|
||||
@@ -329,8 +338,11 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
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))
|
||||
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)
|
||||
|
||||
@@ -344,8 +356,11 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
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))
|
||||
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():
|
||||
@@ -356,10 +371,11 @@ class TraceNGVM(BaseNode):
|
||||
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))
|
||||
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):
|
||||
@@ -372,8 +388,11 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
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))
|
||||
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")
|
||||
@@ -393,12 +412,17 @@ class TraceNGVM(BaseNode):
|
||||
|
||||
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))
|
||||
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))
|
||||
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):
|
||||
"""
|
||||
@@ -413,11 +437,13 @@ class TraceNGVM(BaseNode):
|
||||
|
||||
nio.stop_packet_capture()
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge stop_capture {name}'.format(name=f"TraceNG-{self._id}"))
|
||||
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))
|
||||
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):
|
||||
"""
|
||||
@@ -447,7 +473,9 @@ class TraceNGVM(BaseNode):
|
||||
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
|
||||
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}")
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ from .ubridge_hypervisor import UBridgeHypervisor
|
||||
from .ubridge_error import UbridgeError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -141,7 +142,7 @@ class Hypervisor(UBridgeHypervisor):
|
||||
if sys.platform.startswith("win") or sys.platform.startswith("darwin"):
|
||||
minimum_required_version = "0.9.12"
|
||||
else:
|
||||
# uBridge version 0.9.14 is required for packet filters
|
||||
# uBridge version 0.9.14 is required for packet filters
|
||||
# to work for IOU nodes.
|
||||
minimum_required_version = "0.9.14"
|
||||
if parse_version(self._version) < parse_version(minimum_required_version):
|
||||
@@ -161,7 +162,7 @@ class Hypervisor(UBridgeHypervisor):
|
||||
# add the Npcap directory to $PATH to force uBridge to use npcap DLL instead of Winpcap (if installed)
|
||||
system_root = os.path.join(os.path.expandvars("%SystemRoot%"), "System32", "Npcap")
|
||||
if os.path.isdir(system_root):
|
||||
env["PATH"] = system_root + ';' + env["PATH"]
|
||||
env["PATH"] = system_root + ";" + env["PATH"]
|
||||
await self._check_ubridge_version(env)
|
||||
try:
|
||||
command = self._build_command()
|
||||
@@ -169,16 +170,14 @@ class Hypervisor(UBridgeHypervisor):
|
||||
self._stdout_file = os.path.join(self._working_dir, "ubridge.log")
|
||||
log.info(f"logging to {self._stdout_file}")
|
||||
with open(self._stdout_file, "w", encoding="utf-8") as fd:
|
||||
self._process = await asyncio.create_subprocess_exec(*command,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self._working_dir,
|
||||
env=env)
|
||||
self._process = await asyncio.create_subprocess_exec(
|
||||
*command, stdout=fd, stderr=subprocess.STDOUT, cwd=self._working_dir, env=env
|
||||
)
|
||||
|
||||
log.info(f"ubridge started PID={self._process.pid}")
|
||||
# recv: Bad address is received by uBridge when a docker image stops by itself
|
||||
# see https://github.com/GNS3/gns3-gui/issues/2957
|
||||
#monitor_process(self._process, self._termination_callback)
|
||||
# monitor_process(self._process, self._termination_callback)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
ubridge_stdout = self.read_stdout()
|
||||
log.error(f"Could not start ubridge: {e}\n{ubridge_stdout}")
|
||||
|
||||
@@ -20,7 +20,6 @@ Custom exceptions for the ubridge.
|
||||
|
||||
|
||||
class UbridgeError(Exception):
|
||||
|
||||
def __init__(self, message):
|
||||
Exception.__init__(self, message)
|
||||
|
||||
@@ -29,4 +28,5 @@ class UbridgeNamespaceError(Exception):
|
||||
"""
|
||||
Raised if ubridge can not move a container to a namespace
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -199,17 +199,20 @@ class UBridgeHypervisor:
|
||||
raise UbridgeError("Not connected")
|
||||
|
||||
try:
|
||||
command = command.strip() + '\n'
|
||||
command = command.strip() + "\n"
|
||||
log.debug(f"sending {command}")
|
||||
self._writer.write(command.encode())
|
||||
await self._writer.drain()
|
||||
except OSError as e:
|
||||
raise UbridgeError("Lost communication with {host}:{port} when sending command '{command}': {error}, uBridge process running: {run}"
|
||||
.format(host=self._host, port=self._port, command=command, error=e, run=self.is_running()))
|
||||
raise UbridgeError(
|
||||
"Lost communication with {host}:{port} when sending command '{command}': {error}, uBridge process running: {run}".format(
|
||||
host=self._host, port=self._port, command=command, error=e, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
# Now retrieve the result
|
||||
data = []
|
||||
buf = ''
|
||||
buf = ""
|
||||
retries = 0
|
||||
max_retries = 10
|
||||
while True:
|
||||
@@ -228,8 +231,11 @@ class UBridgeHypervisor:
|
||||
continue
|
||||
if not chunk:
|
||||
if retries > max_retries:
|
||||
raise UbridgeError("No data returned from {host}:{port} after sending command '{command}', uBridge process running: {run}"
|
||||
.format(host=self._host, port=self._port, command=command, run=self.is_running()))
|
||||
raise UbridgeError(
|
||||
"No data returned from {host}:{port} after sending command '{command}', uBridge process running: {run}".format(
|
||||
host=self._host, port=self._port, command=command, run=self.is_running()
|
||||
)
|
||||
)
|
||||
else:
|
||||
retries += 1
|
||||
await asyncio.sleep(0.5)
|
||||
@@ -237,30 +243,36 @@ class UBridgeHypervisor:
|
||||
retries = 0
|
||||
buf += chunk.decode("utf-8")
|
||||
except OSError as e:
|
||||
raise UbridgeError("Lost communication with {host}:{port} after sending command '{command}': {error}, uBridge process running: {run}"
|
||||
.format(host=self._host, port=self._port, command=command, error=e, run=self.is_running()))
|
||||
raise UbridgeError(
|
||||
"Lost communication with {host}:{port} after sending command '{command}': {error}, uBridge process running: {run}".format(
|
||||
host=self._host, port=self._port, command=command, error=e, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
# If the buffer doesn't end in '\n' then we can't be done
|
||||
try:
|
||||
if buf[-1] != '\n':
|
||||
if buf[-1] != "\n":
|
||||
continue
|
||||
except IndexError:
|
||||
raise UbridgeError("Could not communicate with {host}:{port} after sending command '{command}', uBridge process running: {run}"
|
||||
.format(host=self._host, port=self._port, command=command, run=self.is_running()))
|
||||
raise UbridgeError(
|
||||
"Could not communicate with {host}:{port} after sending command '{command}', uBridge process running: {run}".format(
|
||||
host=self._host, port=self._port, command=command, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
data += buf.split('\r\n')
|
||||
if data[-1] == '':
|
||||
data += buf.split("\r\n")
|
||||
if data[-1] == "":
|
||||
data.pop()
|
||||
buf = ''
|
||||
buf = ""
|
||||
|
||||
# Does it contain an error code?
|
||||
if self.error_re.search(data[-1]):
|
||||
raise UbridgeError(data[-1][4:])
|
||||
|
||||
# Or does the last line begin with '100-'? Then we are done!
|
||||
if data[-1][:4] == '100-':
|
||||
if data[-1][:4] == "100-":
|
||||
data[-1] = data[-1][4:]
|
||||
if data[-1] == 'OK':
|
||||
if data[-1] == "OK":
|
||||
data.pop()
|
||||
break
|
||||
|
||||
|
||||
@@ -110,7 +110,9 @@ class VirtualBox(BaseManager):
|
||||
command_string = " ".join(command)
|
||||
log.info(f"Executing VBoxManage with command: {command_string}")
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
raise VirtualBoxError(f"Could not execute VBoxManage: {e}")
|
||||
|
||||
@@ -140,7 +142,7 @@ class VirtualBox(BaseManager):
|
||||
flag_inaccessible = False
|
||||
for prop in properties:
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
name, value = prop.split(":", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name.strip() == "State" and value.strip() == "inaccessible":
|
||||
@@ -191,7 +193,7 @@ class VirtualBox(BaseManager):
|
||||
ram = 0
|
||||
for info in info_results:
|
||||
try:
|
||||
name, value = info.split('=', 1)
|
||||
name, value = info.split("=", 1)
|
||||
if name.strip() == "memory":
|
||||
ram = int(value.strip())
|
||||
break
|
||||
|
||||
@@ -38,11 +38,12 @@ from gns3server.compute.nios.nio_udp import NIOUDP
|
||||
from gns3server.compute.adapters.ethernet_adapter import EthernetAdapter
|
||||
from gns3server.compute.base_node import BaseNode
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
if sys.platform.startswith("win"):
|
||||
import msvcrt
|
||||
import win32file
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -52,9 +53,22 @@ class VirtualBoxVM(BaseNode):
|
||||
VirtualBox VM implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, vmname, linked_clone=False, console=None, console_type="telnet", adapters=0):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
vmname,
|
||||
linked_clone=False,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
adapters=0,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, linked_clone=linked_clone, console_type=console_type)
|
||||
super().__init__(
|
||||
name, node_id, project, manager, console=console, linked_clone=linked_clone, console_type=console_type
|
||||
)
|
||||
|
||||
self._uuid = None # UUID in VirtualBox
|
||||
self._maximum_adapters = 8
|
||||
@@ -74,21 +88,23 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
json = {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"project_id": self.project.id,
|
||||
"vmname": self.vmname,
|
||||
"headless": self.headless,
|
||||
"on_close": self.on_close,
|
||||
"adapters": self._adapters,
|
||||
"adapter_type": self.adapter_type,
|
||||
"ram": self.ram,
|
||||
"status": self.status,
|
||||
"use_any_adapter": self.use_any_adapter,
|
||||
"linked_clone": self.linked_clone}
|
||||
json = {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"project_id": self.project.id,
|
||||
"vmname": self.vmname,
|
||||
"headless": self.headless,
|
||||
"on_close": self.on_close,
|
||||
"adapters": self._adapters,
|
||||
"adapter_type": self.adapter_type,
|
||||
"ram": self.ram,
|
||||
"status": self.status,
|
||||
"use_any_adapter": self.use_any_adapter,
|
||||
"linked_clone": self.linked_clone,
|
||||
}
|
||||
if self.linked_clone:
|
||||
json["node_directory"] = self.working_path
|
||||
else:
|
||||
@@ -104,7 +120,7 @@ class VirtualBoxVM(BaseNode):
|
||||
properties = await self.manager.execute("list", ["systemproperties"])
|
||||
for prop in properties:
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
name, value = prop.split(":", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
self._system_properties[name.strip()] = value.strip()
|
||||
@@ -118,8 +134,8 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
results = await self.manager.execute("showvminfo", [self._uuid, "--machinereadable"])
|
||||
for info in results:
|
||||
if '=' in info:
|
||||
name, value = info.split('=', 1)
|
||||
if "=" in info:
|
||||
name, value = info.split("=", 1)
|
||||
if name == "VMState":
|
||||
return value.strip('"')
|
||||
raise VirtualBoxError(f"Could not get VM state for {self._vmname}")
|
||||
@@ -164,10 +180,14 @@ class VirtualBoxVM(BaseNode):
|
||||
found = True
|
||||
if node.project != self.project:
|
||||
if trial >= 30:
|
||||
raise VirtualBoxError(f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmname} is already used by {node.name} in project {self.project.name}")
|
||||
raise VirtualBoxError(
|
||||
f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmname} is already used by {node.name} in project {self.project.name}"
|
||||
)
|
||||
else:
|
||||
if trial >= 5:
|
||||
raise VirtualBoxError(f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmname} is already used by {node.name} in this project")
|
||||
raise VirtualBoxError(
|
||||
f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmname} is already used by {node.name} in this project"
|
||||
)
|
||||
if not found:
|
||||
return
|
||||
trial += 1
|
||||
@@ -221,8 +241,10 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
tree = ET.parse(self._linked_vbox_file())
|
||||
except ET.ParseError:
|
||||
raise VirtualBoxError("Cannot modify VirtualBox linked nodes file. "
|
||||
"File {} is corrupted.".format(self._linked_vbox_file()))
|
||||
raise VirtualBoxError(
|
||||
"Cannot modify VirtualBox linked nodes file. "
|
||||
"File {} is corrupted.".format(self._linked_vbox_file())
|
||||
)
|
||||
except OSError as e:
|
||||
raise VirtualBoxError(f"Cannot modify VirtualBox linked nodes file '{self._linked_vbox_file()}': {e}")
|
||||
|
||||
@@ -233,8 +255,10 @@ class VirtualBoxVM(BaseNode):
|
||||
currentSnapshot = machine.get("currentSnapshot")
|
||||
if currentSnapshot:
|
||||
newSnapshot = re.sub(r"\{.*\}", "{" + str(uuid.uuid4()) + "}", currentSnapshot)
|
||||
shutil.move(os.path.join(self.working_dir, self._vmname, "Snapshots", currentSnapshot) + ".vdi",
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", newSnapshot) + ".vdi")
|
||||
shutil.move(
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", currentSnapshot) + ".vdi",
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", newSnapshot) + ".vdi",
|
||||
)
|
||||
image.set("uuid", newSnapshot)
|
||||
|
||||
machine.set("uuid", "{" + self.id + "}")
|
||||
@@ -270,7 +294,7 @@ class VirtualBoxVM(BaseNode):
|
||||
# VM must be powered off to start it
|
||||
if vm_state == "saved":
|
||||
result = await self.manager.execute("guestproperty", ["get", self._uuid, "SavedByGNS3"])
|
||||
if result == ['No value set!']:
|
||||
if result == ["No value set!"]:
|
||||
raise VirtualBoxError("VirtualBox VM was not saved from GNS3")
|
||||
else:
|
||||
await self.manager.execute("guestproperty", ["delete", self._uuid, "SavedByGNS3"])
|
||||
@@ -300,13 +324,13 @@ class VirtualBoxVM(BaseNode):
|
||||
for adapter_number in range(0, self._adapters):
|
||||
nio = self._ethernet_adapters[adapter_number].get_nio(0)
|
||||
if nio:
|
||||
await self.add_ubridge_udp_connection(f"VBOX-{self._id}-{adapter_number}",
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
await self.add_ubridge_udp_connection(
|
||||
f"VBOX-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio
|
||||
)
|
||||
|
||||
await self._start_console()
|
||||
|
||||
if (await self.check_hw_virtualization()):
|
||||
if await self.check_hw_virtualization():
|
||||
self._hw_virtualization = True
|
||||
|
||||
@locking
|
||||
@@ -381,9 +405,11 @@ class VirtualBoxVM(BaseNode):
|
||||
self.status = "suspended"
|
||||
log.info(f"VirtualBox VM '{self.name}' [{self.id}] suspended")
|
||||
else:
|
||||
log.warning("VirtualBox VM '{name}' [{id}] cannot be suspended, current state: {state}".format(name=self.name,
|
||||
id=self.id,
|
||||
state=vm_state))
|
||||
log.warning(
|
||||
"VirtualBox VM '{name}' [{id}] cannot be suspended, current state: {state}".format(
|
||||
name=self.name, id=self.id, state=vm_state
|
||||
)
|
||||
)
|
||||
|
||||
async def resume(self):
|
||||
"""
|
||||
@@ -409,7 +435,7 @@ class VirtualBoxVM(BaseNode):
|
||||
properties = await self.manager.execute("list", ["hdds"])
|
||||
for prop in properties:
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
name, value = prop.split(":", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name.strip() == "Location":
|
||||
@@ -432,27 +458,36 @@ class VirtualBoxVM(BaseNode):
|
||||
for hdd_info in hdd_table:
|
||||
hdd_file = os.path.join(self.working_dir, self._vmname, "Snapshots", hdd_info["hdd"])
|
||||
if os.path.exists(hdd_file):
|
||||
log.info("VirtualBox VM '{name}' [{id}] attaching HDD {controller} {port} {device} {medium}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd_info["controller"],
|
||||
port=hdd_info["port"],
|
||||
device=hdd_info["device"],
|
||||
medium=hdd_file))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] attaching HDD {controller} {port} {device} {medium}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd_info["controller"],
|
||||
port=hdd_info["port"],
|
||||
device=hdd_info["device"],
|
||||
medium=hdd_file,
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
await self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(hdd_info["controller"],
|
||||
hdd_info["port"],
|
||||
hdd_info["device"],
|
||||
hdd_file))
|
||||
await self._storage_attach(
|
||||
'--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(
|
||||
hdd_info["controller"], hdd_info["port"], hdd_info["device"], hdd_file
|
||||
)
|
||||
)
|
||||
|
||||
except VirtualBoxError as e:
|
||||
log.warning("VirtualBox VM '{name}' [{id}] error reattaching HDD {controller} {port} {device} {medium}: {error}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd_info["controller"],
|
||||
port=hdd_info["port"],
|
||||
device=hdd_info["device"],
|
||||
medium=hdd_file,
|
||||
error=e))
|
||||
log.warning(
|
||||
"VirtualBox VM '{name}' [{id}] error reattaching HDD {controller} {port} {device} {medium}: {error}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd_info["controller"],
|
||||
port=hdd_info["port"],
|
||||
device=hdd_info["device"],
|
||||
medium=hdd_file,
|
||||
error=e,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
async def save_linked_hdds_info(self):
|
||||
@@ -468,17 +503,21 @@ class VirtualBoxVM(BaseNode):
|
||||
hdd_files = await self._get_all_hdd_files()
|
||||
vm_info = await self._get_vm_info()
|
||||
for entry, value in vm_info.items():
|
||||
match = re.search(r"^([\s\w]+)\-(\d)\-(\d)$", entry) # match Controller-PortNumber-DeviceNumber entry
|
||||
match = re.search(
|
||||
r"^([\s\w]+)\-(\d)\-(\d)$", entry
|
||||
) # match Controller-PortNumber-DeviceNumber entry
|
||||
if match:
|
||||
controller = match.group(1)
|
||||
port = match.group(2)
|
||||
device = match.group(3)
|
||||
if value in hdd_files and os.path.exists(os.path.join(self.working_dir, self._vmname, "Snapshots", os.path.basename(value))):
|
||||
log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=controller,
|
||||
port=port,
|
||||
device=device))
|
||||
if value in hdd_files and os.path.exists(
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", os.path.basename(value))
|
||||
):
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(
|
||||
name=self.name, id=self.id, controller=controller, port=port, device=device
|
||||
)
|
||||
)
|
||||
hdd_table.append(
|
||||
{
|
||||
"hdd": os.path.basename(value),
|
||||
@@ -494,9 +533,11 @@ class VirtualBoxVM(BaseNode):
|
||||
with open(hdd_info_file, "w", encoding="utf-8") as f:
|
||||
json.dump(hdd_table, f, indent=4)
|
||||
except OSError as e:
|
||||
log.warning("VirtualBox VM '{name}' [{id}] could not write HHD info file: {error}".format(name=self.name,
|
||||
id=self.id,
|
||||
error=e.strerror))
|
||||
log.warning(
|
||||
"VirtualBox VM '{name}' [{id}] could not write HHD info file: {error}".format(
|
||||
name=self.name, id=self.id, error=e.strerror
|
||||
)
|
||||
)
|
||||
|
||||
return hdd_table
|
||||
|
||||
@@ -534,22 +575,28 @@ class VirtualBoxVM(BaseNode):
|
||||
if self.linked_clone:
|
||||
hdd_table = await self.save_linked_hdds_info()
|
||||
for hdd in hdd_table.copy():
|
||||
log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd["controller"],
|
||||
port=hdd["port"],
|
||||
device=hdd["device"]))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(
|
||||
name=self.name, id=self.id, controller=hdd["controller"], port=hdd["port"], device=hdd["device"]
|
||||
)
|
||||
)
|
||||
try:
|
||||
await self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(hdd["controller"],
|
||||
hdd["port"],
|
||||
hdd["device"]))
|
||||
await self._storage_attach(
|
||||
'--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(
|
||||
hdd["controller"], hdd["port"], hdd["device"]
|
||||
)
|
||||
)
|
||||
except VirtualBoxError as e:
|
||||
log.warning("VirtualBox VM '{name}' [{id}] error detaching HDD {controller} {port} {device}: {error}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd["controller"],
|
||||
port=hdd["port"],
|
||||
device=hdd["device"],
|
||||
error=e))
|
||||
log.warning(
|
||||
"VirtualBox VM '{name}' [{id}] error detaching HDD {controller} {port} {device}: {error}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd["controller"],
|
||||
port=hdd["port"],
|
||||
device=hdd["device"],
|
||||
error=e,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
log.info(f"VirtualBox VM '{self.name}' [{self.id}] unregistering")
|
||||
@@ -623,7 +670,7 @@ class VirtualBoxVM(BaseNode):
|
||||
if ram == 0:
|
||||
return
|
||||
|
||||
await self._modify_vm(f'--memory {ram}')
|
||||
await self._modify_vm(f"--memory {ram}")
|
||||
|
||||
log.info(f"VirtualBox VM '{self.name}' [{self.id}] has set amount of RAM to {ram}")
|
||||
self._ram = ram
|
||||
@@ -688,26 +735,34 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
self._maximum_adapters = int(self._system_properties[max_adapter_string])
|
||||
except ValueError:
|
||||
log.error(f"Could not convert system property to integer: {max_adapter_string} = {self._system_properties[max_adapter_string]}")
|
||||
log.error(
|
||||
f"Could not convert system property to integer: {max_adapter_string} = {self._system_properties[max_adapter_string]}"
|
||||
)
|
||||
else:
|
||||
log.warning(f"Could not find system property '{max_adapter_string}' for chipset {chipset}")
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}] can have a maximum of {max} network adapters for chipset {chipset}".format(name=self.name,
|
||||
id=self.id,
|
||||
max=self._maximum_adapters,
|
||||
chipset=chipset.upper()))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] can have a maximum of {max} network adapters for chipset {chipset}".format(
|
||||
name=self.name, id=self.id, max=self._maximum_adapters, chipset=chipset.upper()
|
||||
)
|
||||
)
|
||||
if adapters > self._maximum_adapters:
|
||||
raise VirtualBoxError("The configured {} chipset limits the VM to {} network adapters. The chipset can be changed outside GNS3 in the VirtualBox VM settings.".format(chipset.upper(),
|
||||
self._maximum_adapters))
|
||||
raise VirtualBoxError(
|
||||
"The configured {} chipset limits the VM to {} network adapters. The chipset can be changed outside GNS3 in the VirtualBox VM settings.".format(
|
||||
chipset.upper(), self._maximum_adapters
|
||||
)
|
||||
)
|
||||
|
||||
self._ethernet_adapters.clear()
|
||||
for adapter_number in range(0, adapters):
|
||||
self._ethernet_adapters[adapter_number] = EthernetAdapter()
|
||||
|
||||
self._adapters = len(self._ethernet_adapters)
|
||||
log.info("VirtualBox VM '{name}' [{id}] has changed the number of Ethernet adapters to {adapters}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapters=adapters))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] has changed the number of Ethernet adapters to {adapters}".format(
|
||||
name=self.name, id=self.id, adapters=adapters
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def use_any_adapter(self):
|
||||
@@ -752,9 +807,11 @@ class VirtualBoxVM(BaseNode):
|
||||
"""
|
||||
|
||||
self._adapter_type = adapter_type
|
||||
log.info("VirtualBox VM '{name}' [{id}]: adapter type changed to {adapter_type}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_type=adapter_type))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: adapter type changed to {adapter_type}".format(
|
||||
name=self.name, id=self.id, adapter_type=adapter_type
|
||||
)
|
||||
)
|
||||
|
||||
async def _get_vm_info(self):
|
||||
"""
|
||||
@@ -764,10 +821,12 @@ class VirtualBoxVM(BaseNode):
|
||||
"""
|
||||
|
||||
vm_info = {}
|
||||
results = await self.manager.execute("showvminfo", ["--machinereadable", "--", self._vmname]) # "--" is to protect against vm names containing the "-" character
|
||||
results = await self.manager.execute(
|
||||
"showvminfo", ["--machinereadable", "--", self._vmname]
|
||||
) # "--" is to protect against vm names containing the "-" character
|
||||
for info in results:
|
||||
try:
|
||||
name, value = info.split('=', 1)
|
||||
name, value = info.split("=", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
vm_info[name.strip('"')] = value.strip('"')
|
||||
@@ -916,16 +975,18 @@ class VirtualBoxVM(BaseNode):
|
||||
result = await self.manager.execute("snapshot", [self._uuid, "take", "GNS3 Linked Base for clones"])
|
||||
log.debug(f"GNS3 snapshot created: {result}")
|
||||
|
||||
args = [self._uuid,
|
||||
"--snapshot",
|
||||
"GNS3 Linked Base for clones",
|
||||
"--options",
|
||||
"link",
|
||||
"--name",
|
||||
self.name,
|
||||
"--basefolder",
|
||||
self.working_dir,
|
||||
"--register"]
|
||||
args = [
|
||||
self._uuid,
|
||||
"--snapshot",
|
||||
"GNS3 Linked Base for clones",
|
||||
"--options",
|
||||
"link",
|
||||
"--name",
|
||||
self.name,
|
||||
"--basefolder",
|
||||
self.working_dir,
|
||||
"--register",
|
||||
]
|
||||
|
||||
result = await self.manager.execute("clonevm", args)
|
||||
log.debug(f"VirtualBox VM: {result} cloned")
|
||||
@@ -959,14 +1020,18 @@ class VirtualBoxVM(BaseNode):
|
||||
self._remote_pipe = await asyncio_open_serial(pipe_name)
|
||||
except OSError as e:
|
||||
raise VirtualBoxError(f"Could not open serial pipe '{pipe_name}': {e}")
|
||||
server = AsyncioTelnetServer(reader=self._remote_pipe,
|
||||
writer=self._remote_pipe,
|
||||
binary=True,
|
||||
echo=True)
|
||||
server = AsyncioTelnetServer(reader=self._remote_pipe, writer=self._remote_pipe, binary=True, echo=True)
|
||||
try:
|
||||
self._telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
|
||||
self._telnet_server = await asyncio.start_server(
|
||||
server.run, self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
except OSError as e:
|
||||
self.project.emit("log.warning", {"message": f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"})
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{
|
||||
"message": f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"
|
||||
},
|
||||
)
|
||||
|
||||
async def _stop_remote_console(self):
|
||||
"""
|
||||
@@ -1010,18 +1075,23 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
"Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
# check if trying to connect to a nat, bridged, host-only or any other special adapter
|
||||
nic_attachments = await self._get_nic_attachements(self._maximum_adapters)
|
||||
attachment = nic_attachments[adapter_number]
|
||||
if attachment in ("nat", "bridged", "intnet", "hostonly", "natnetwork"):
|
||||
if not self._use_any_adapter:
|
||||
raise VirtualBoxError("Attachment '{attachment}' is already configured on adapter {adapter_number}. "
|
||||
"Please remove it or allow VirtualBox VM '{name}' to use any adapter.".format(attachment=attachment,
|
||||
adapter_number=adapter_number,
|
||||
name=self.name))
|
||||
raise VirtualBoxError(
|
||||
"Attachment '{attachment}' is already configured on adapter {adapter_number}. "
|
||||
"Please remove it or allow VirtualBox VM '{name}' to use any adapter.".format(
|
||||
attachment=attachment, adapter_number=adapter_number, name=self.name
|
||||
)
|
||||
)
|
||||
elif self.is_running():
|
||||
# dynamically configure an UDP tunnel attachment if the VM is already running
|
||||
local_nio = self._local_udp_tunnels[adapter_number][0]
|
||||
@@ -1034,19 +1104,23 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
if self.is_running():
|
||||
try:
|
||||
await self.add_ubridge_udp_connection(f"VBOX-{self._id}-{adapter_number}",
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
await self.add_ubridge_udp_connection(
|
||||
f"VBOX-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio
|
||||
)
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
"Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
await self._control_vm(f"setlinkstate{adapter_number + 1} on")
|
||||
|
||||
adapter.add_nio(0, nio)
|
||||
log.info("VirtualBox VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
@@ -1058,16 +1132,19 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
if self.is_running():
|
||||
try:
|
||||
await self.update_ubridge_udp_connection(f"VBOX-{self._id}-{adapter_number}",
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
await self.update_ubridge_udp_connection(
|
||||
f"VBOX-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio
|
||||
)
|
||||
if nio.suspend:
|
||||
await self._control_vm(f"setlinkstate{adapter_number + 1} off")
|
||||
else:
|
||||
await self._control_vm(f"setlinkstate{adapter_number + 1} on")
|
||||
except IndexError:
|
||||
raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
'Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
@@ -1081,8 +1158,11 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
"Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
await self.stop_capture(adapter_number)
|
||||
if self.is_running():
|
||||
@@ -1096,10 +1176,11 @@ class VirtualBoxVM(BaseNode):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
adapter.remove_nio(0)
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
return nio
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
@@ -1114,8 +1195,11 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
adapter = self.ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
"Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
@@ -1144,12 +1228,17 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
nio.start_packet_capture(output_file)
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=f"VBOX-{self._id}-{adapter_number}",
|
||||
output_file=output_file))
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{output_file}"'.format(
|
||||
name=f"VBOX-{self._id}-{adapter_number}", output_file=output_file
|
||||
)
|
||||
)
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, adapter_number):
|
||||
"""
|
||||
@@ -1164,8 +1253,10 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
nio.stop_packet_capture()
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge stop_capture {name}'.format(name=f"VBOX-{self._id}-{adapter_number}"))
|
||||
await self._ubridge_send("bridge stop_capture {name}".format(name=f"VBOX-{self._id}-{adapter_number}"))
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
@@ -70,6 +70,7 @@ class VMware(BaseManager):
|
||||
def _find_vmrun_registry(regkey):
|
||||
|
||||
import winreg
|
||||
|
||||
try:
|
||||
# default path not used, let's look in the registry
|
||||
hkey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, regkey)
|
||||
@@ -124,6 +125,7 @@ class VMware(BaseManager):
|
||||
def _find_vmware_version_registry(regkey):
|
||||
|
||||
import winreg
|
||||
|
||||
version = None
|
||||
try:
|
||||
# default path not used, let's look in the registry
|
||||
@@ -210,7 +212,9 @@ class VMware(BaseManager):
|
||||
else:
|
||||
if sys.platform.startswith("darwin"):
|
||||
if not os.path.isdir("/Applications/VMware Fusion.app"):
|
||||
raise VMwareError("VMware Fusion is not installed in the standard location /Applications/VMware Fusion.app")
|
||||
raise VMwareError(
|
||||
"VMware Fusion is not installed in the standard location /Applications/VMware Fusion.app"
|
||||
)
|
||||
self._host_type = "fusion"
|
||||
return # FIXME: no version checking on Mac OS X but we support all versions of fusion
|
||||
|
||||
@@ -244,6 +248,7 @@ class VMware(BaseManager):
|
||||
def _get_vmnet_interfaces_registry():
|
||||
|
||||
import winreg
|
||||
|
||||
vmnet_interfaces = []
|
||||
regkey = r"SOFTWARE\Wow6432Node\VMware, Inc.\VMnetLib\VMnetConfig"
|
||||
try:
|
||||
@@ -320,7 +325,9 @@ class VMware(BaseManager):
|
||||
def allocate_vmnet(self):
|
||||
|
||||
if not self._vmnets:
|
||||
raise VMwareError(f"No VMnet interface available between vmnet{self._vmnet_start_range} and vmnet{self._vmnet_end_range}. Go to preferences VMware / Network / Configure to add more interfaces.")
|
||||
raise VMwareError(
|
||||
f"No VMnet interface available between vmnet{self._vmnet_start_range} and vmnet{self._vmnet_end_range}. Go to preferences VMware / Network / Configure to add more interfaces."
|
||||
)
|
||||
return self._vmnets.pop(0)
|
||||
|
||||
def refresh_vmnet_list(self, ubridge=True):
|
||||
@@ -363,12 +370,12 @@ class VMware(BaseManager):
|
||||
|
||||
while True:
|
||||
try:
|
||||
return (await self._execute(subcommand, args, timeout=timeout, log_level=log_level))
|
||||
return await self._execute(subcommand, args, timeout=timeout, log_level=log_level)
|
||||
except VMwareError as e:
|
||||
# We can fail to detect that it's VMware player instead of Workstation (due to marketing change Player is now Player Workstation)
|
||||
if self.host_type == "ws" and "VIX_SERVICEPROVIDER_VMWARE_WORKSTATION" in str(e):
|
||||
self._host_type = "player"
|
||||
return (await self._execute(subcommand, args, timeout=timeout, log_level=log_level))
|
||||
return await self._execute(subcommand, args, timeout=timeout, log_level=log_level)
|
||||
else:
|
||||
if trial <= 0:
|
||||
raise e
|
||||
@@ -388,19 +395,25 @@ class VMware(BaseManager):
|
||||
command_string = " ".join([shlex_quote(c) for c in command])
|
||||
log.log(log_level, f"Executing vmrun with command: {command_string}")
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
raise VMwareError(f"Could not execute vmrun: {e}")
|
||||
|
||||
try:
|
||||
stdout_data, _ = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
raise VMwareError(f"vmrun has timed out after {timeout} seconds!\nTry to run {command_string} in a terminal to see more details.\n\nMake sure GNS3 and VMware run under the same user and whitelist vmrun.exe in your antivirus.")
|
||||
raise VMwareError(
|
||||
f"vmrun has timed out after {timeout} seconds!\nTry to run {command_string} in a terminal to see more details.\n\nMake sure GNS3 and VMware run under the same user and whitelist vmrun.exe in your antivirus."
|
||||
)
|
||||
|
||||
if process.returncode:
|
||||
# vmrun print errors on stdout
|
||||
vmrun_error = stdout_data.decode("utf-8", errors="ignore")
|
||||
raise VMwareError(f"vmrun has returned an error: {vmrun_error}\nTry to run {command_string} in a terminal to see more details.\nAnd make sure GNS3 and VMware run under the same user.")
|
||||
raise VMwareError(
|
||||
f"vmrun has returned an error: {vmrun_error}\nTry to run {command_string} in a terminal to see more details.\nAnd make sure GNS3 and VMware run under the same user."
|
||||
)
|
||||
|
||||
return stdout_data.decode("utf-8", errors="ignore").splitlines()
|
||||
|
||||
@@ -487,7 +500,7 @@ class VMware(BaseManager):
|
||||
# skip the shebang
|
||||
line = f.readline().decode(encoding, errors="ignore")
|
||||
try:
|
||||
key, value = line.split('=', 1)
|
||||
key, value = line.split("=", 1)
|
||||
if key.strip().lower() == ".encoding":
|
||||
file_encoding = value.strip('" ')
|
||||
try:
|
||||
@@ -502,7 +515,7 @@ class VMware(BaseManager):
|
||||
with open(path, encoding=encoding, errors="ignore") as f:
|
||||
for line in f.read().splitlines():
|
||||
try:
|
||||
key, value = line.split('=', 1)
|
||||
key, value = line.split("=", 1)
|
||||
pairs[key.strip().lower()] = value.strip('" ')
|
||||
except ValueError:
|
||||
continue
|
||||
@@ -574,7 +587,7 @@ class VMware(BaseManager):
|
||||
for key, value in pairs.items():
|
||||
if key.startswith("vmlist"):
|
||||
try:
|
||||
vm_entry, variable_name = key.split('.', 1)
|
||||
vm_entry, variable_name = key.split(".", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if vm_entry not in vm_entries:
|
||||
@@ -586,7 +599,11 @@ class VMware(BaseManager):
|
||||
for vm_settings in vm_entries.values():
|
||||
if "displayname" in vm_settings and "config" in vm_settings:
|
||||
if os.path.exists(vm_settings["config"]):
|
||||
log.debug('Found VM named "{}" with VMX file "{}"'.format(vm_settings["displayname"], vm_settings["config"]))
|
||||
log.debug(
|
||||
'Found VM named "{}" with VMX file "{}"'.format(
|
||||
vm_settings["displayname"], vm_settings["config"]
|
||||
)
|
||||
)
|
||||
vmware_vms.append({"vmname": vm_settings["displayname"], "vmx_path": vm_settings["config"]})
|
||||
return vmware_vms
|
||||
|
||||
@@ -657,10 +674,11 @@ class VMware(BaseManager):
|
||||
if sys.platform.startswith("win"):
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
|
||||
path = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
|
||||
ctypes.windll.shell32.SHGetFolderPathW(None, 5, None, 0, path)
|
||||
documents_folder = path.value
|
||||
return [fr'{documents_folder}\My Virtual Machines', fr'{documents_folder}\Virtual Machines']
|
||||
return [fr"{documents_folder}\My Virtual Machines", fr"{documents_folder}\Virtual Machines"]
|
||||
elif sys.platform.startswith("darwin"):
|
||||
return [os.path.expanduser("~/Documents/Virtual Machines.localized")]
|
||||
else:
|
||||
@@ -691,8 +709,11 @@ class VMware(BaseManager):
|
||||
if "prefvmx.defaultvmpath" in pairs:
|
||||
default_vm_path = pairs["prefvmx.defaultvmpath"]
|
||||
if not os.path.isdir(default_vm_path):
|
||||
raise VMwareError('Could not find or access the default VM directory: "{default_vm_path}". Please change "prefvmx.defaultvmpath={default_vm_path}" in "{vmware_preferences_path}"'.format(default_vm_path=default_vm_path,
|
||||
vmware_preferences_path=vmware_preferences_path))
|
||||
raise VMwareError(
|
||||
'Could not find or access the default VM directory: "{default_vm_path}". Please change "prefvmx.defaultvmpath={default_vm_path}" in "{vmware_preferences_path}"'.format(
|
||||
default_vm_path=default_vm_path, vmware_preferences_path=vmware_preferences_path
|
||||
)
|
||||
)
|
||||
vmware_vms = self._get_vms_from_directory(default_vm_path)
|
||||
|
||||
if not vmware_vms:
|
||||
@@ -707,7 +728,7 @@ class VMware(BaseManager):
|
||||
|
||||
# look for VMX paths in the preferences file in case not all VMs are in a default directory
|
||||
for key, value in pairs.items():
|
||||
m = re.match(r'pref.mruVM(\d+)\.filename', key)
|
||||
m = re.match(r"pref.mruVM(\d+)\.filename", key)
|
||||
if m:
|
||||
display_name = f"pref.mruVM{m.group(1)}.displayName"
|
||||
if display_name in pairs:
|
||||
@@ -730,7 +751,7 @@ class VMware(BaseManager):
|
||||
return path
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
loop = asyncio.get_event_loop()
|
||||
vmware = VMware.instance()
|
||||
print("=> Check version")
|
||||
|
||||
@@ -34,6 +34,7 @@ from ..base_node import BaseNode
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -43,9 +44,13 @@ class VMwareVM(BaseNode):
|
||||
VMware VM implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, vmx_path, linked_clone=False, console=None, console_type="telnet"):
|
||||
def __init__(
|
||||
self, name, node_id, project, manager, vmx_path, linked_clone=False, console=None, console_type="telnet"
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone)
|
||||
super().__init__(
|
||||
name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone
|
||||
)
|
||||
|
||||
self._vmx_pairs = OrderedDict()
|
||||
self._telnet_server = None
|
||||
@@ -72,21 +77,23 @@ class VMwareVM(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
json = {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"project_id": self.project.id,
|
||||
"vmx_path": self.vmx_path,
|
||||
"headless": self.headless,
|
||||
"on_close": self.on_close,
|
||||
"adapters": self._adapters,
|
||||
"adapter_type": self.adapter_type,
|
||||
"use_any_adapter": self.use_any_adapter,
|
||||
"status": self.status,
|
||||
"node_directory": self.working_path,
|
||||
"linked_clone": self.linked_clone}
|
||||
json = {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"project_id": self.project.id,
|
||||
"vmx_path": self.vmx_path,
|
||||
"headless": self.headless,
|
||||
"on_close": self.on_close,
|
||||
"adapters": self._adapters,
|
||||
"adapter_type": self.adapter_type,
|
||||
"use_any_adapter": self.use_any_adapter,
|
||||
"status": self.status,
|
||||
"node_directory": self.working_path,
|
||||
"linked_clone": self.linked_clone,
|
||||
}
|
||||
return json
|
||||
|
||||
@property
|
||||
@@ -147,10 +154,14 @@ class VMwareVM(BaseNode):
|
||||
found = True
|
||||
if node.project != self.project:
|
||||
if trial >= 30:
|
||||
raise VMwareError(f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmx_path} is already used by {node.name} in project {self.project.name}")
|
||||
raise VMwareError(
|
||||
f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmx_path} is already used by {node.name} in project {self.project.name}"
|
||||
)
|
||||
else:
|
||||
if trial >= 5:
|
||||
raise VMwareError(f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmx_path} is already used by {node.name} in this project")
|
||||
raise VMwareError(
|
||||
f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmx_path} is already used by {node.name} in this project"
|
||||
)
|
||||
if not found:
|
||||
return
|
||||
trial += 1
|
||||
@@ -187,11 +198,9 @@ class VMwareVM(BaseNode):
|
||||
|
||||
# create the linked clone based on the base snapshot
|
||||
new_vmx_path = os.path.join(self.working_dir, self.name + ".vmx")
|
||||
await self._control_vm("clone",
|
||||
new_vmx_path,
|
||||
"linked",
|
||||
f"-snapshot={base_snapshot_name}",
|
||||
f"-cloneName={self.name}")
|
||||
await self._control_vm(
|
||||
"clone", new_vmx_path, "linked", f"-snapshot={base_snapshot_name}", f"-cloneName={self.name}"
|
||||
)
|
||||
|
||||
try:
|
||||
vmsd_pairs = self.manager.parse_vmware_file(vmsd_path)
|
||||
@@ -265,14 +274,20 @@ class VMwareVM(BaseNode):
|
||||
vmware_adapter_type = "e1000"
|
||||
else:
|
||||
vmware_adapter_type = adapter_type
|
||||
ethernet_adapter = {f"ethernet{adapter_number}.present": "TRUE",
|
||||
f"ethernet{adapter_number}.addresstype": "generated",
|
||||
f"ethernet{adapter_number}.generatedaddressoffset": "0",
|
||||
f"ethernet{adapter_number}.virtualdev": vmware_adapter_type}
|
||||
ethernet_adapter = {
|
||||
f"ethernet{adapter_number}.present": "TRUE",
|
||||
f"ethernet{adapter_number}.addresstype": "generated",
|
||||
f"ethernet{adapter_number}.generatedaddressoffset": "0",
|
||||
f"ethernet{adapter_number}.virtualdev": vmware_adapter_type,
|
||||
}
|
||||
self._vmx_pairs.update(ethernet_adapter)
|
||||
|
||||
connection_type = f"ethernet{adapter_number}.connectiontype"
|
||||
if not self._use_any_adapter and connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"):
|
||||
if (
|
||||
not self._use_any_adapter
|
||||
and connection_type in self._vmx_pairs
|
||||
and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly")
|
||||
):
|
||||
continue
|
||||
|
||||
self._vmx_pairs[f"ethernet{adapter_number}.connectiontype"] = "custom"
|
||||
@@ -339,15 +354,16 @@ class VMwareVM(BaseNode):
|
||||
await self._add_ubridge_ethernet_connection(vnet, vmnet_interface, block_host_traffic)
|
||||
|
||||
if isinstance(nio, NIOUDP):
|
||||
await self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=vnet,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=vnet, lport=nio.lport, rhost=nio.rhost, rport=nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
if nio.capturing:
|
||||
await self._ubridge_send(f'bridge start_capture {vnet} "{nio.pcap_output_file}"')
|
||||
|
||||
await self._ubridge_send(f'bridge start {vnet}')
|
||||
await self._ubridge_send(f"bridge start {vnet}")
|
||||
await self._ubridge_apply_filters(vnet, nio.filters)
|
||||
|
||||
async def _update_ubridge_connection(self, adapter_number, nio):
|
||||
@@ -388,8 +404,9 @@ class VMwareVM(BaseNode):
|
||||
raise VMwareError(f"vnet {vnet} not in VMX file")
|
||||
if not self._ubridge_hypervisor:
|
||||
raise VMwareError("Cannot start the packet capture: uBridge is not running")
|
||||
await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=vnet,
|
||||
output_file=output_file))
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{output_file}"'.format(name=vnet, output_file=output_file)
|
||||
)
|
||||
|
||||
async def _stop_ubridge_capture(self, adapter_number):
|
||||
"""
|
||||
@@ -425,7 +442,7 @@ class VMwareVM(BaseNode):
|
||||
if self.status == "started":
|
||||
return
|
||||
|
||||
if (await self.is_running()):
|
||||
if await self.is_running():
|
||||
raise VMwareError("The VM is already running in VMware")
|
||||
|
||||
ubridge_path = self.ubridge_path
|
||||
@@ -475,7 +492,7 @@ class VMwareVM(BaseNode):
|
||||
await self._stop_ubridge()
|
||||
|
||||
try:
|
||||
if (await self.is_running()):
|
||||
if await self.is_running():
|
||||
if self.on_close == "save_vm_state":
|
||||
await self._control_vm("suspend")
|
||||
elif self.on_close == "shutdown_signal":
|
||||
@@ -492,7 +509,10 @@ class VMwareVM(BaseNode):
|
||||
# remove the adapters managed by GNS3
|
||||
for adapter_number in range(0, self._adapters):
|
||||
vnet = f"ethernet{adapter_number}.vnet"
|
||||
if self._get_vmx_setting(vnet) or self._get_vmx_setting(f"ethernet{adapter_number}.connectiontype") is None:
|
||||
if (
|
||||
self._get_vmx_setting(vnet)
|
||||
or self._get_vmx_setting(f"ethernet{adapter_number}.connectiontype") is None
|
||||
):
|
||||
if vnet in self._vmx_pairs:
|
||||
vmnet = os.path.basename(self._vmx_pairs[vnet])
|
||||
if not self.manager.is_managed_vmnet(vmnet):
|
||||
@@ -656,9 +676,11 @@ class VMwareVM(BaseNode):
|
||||
self._ethernet_adapters[adapter_number] = EthernetAdapter()
|
||||
|
||||
self._adapters = len(self._ethernet_adapters)
|
||||
log.info("VMware VM '{name}' [{id}] has changed the number of Ethernet adapters to {adapters}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapters=adapters))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}] has changed the number of Ethernet adapters to {adapters}".format(
|
||||
name=self.name, id=self.id, adapters=adapters
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def adapter_type(self):
|
||||
@@ -679,9 +701,11 @@ class VMwareVM(BaseNode):
|
||||
"""
|
||||
|
||||
self._adapter_type = adapter_type
|
||||
log.info("VMware VM '{name}' [{id}]: adapter type changed to {adapter_type}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_type=adapter_type))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: adapter type changed to {adapter_type}".format(
|
||||
name=self.name, id=self.id, adapter_type=adapter_type
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def use_any_adapter(self):
|
||||
@@ -718,35 +742,46 @@ class VMwareVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VMwareError(
|
||||
"Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
self._read_vmx_file()
|
||||
# check if trying to connect to a nat, bridged or host-only adapter
|
||||
if self._get_vmx_setting(f"ethernet{adapter_number}.present", "TRUE"):
|
||||
# check for the connection type
|
||||
connection_type = f"ethernet{adapter_number}.connectiontype"
|
||||
if not self._use_any_adapter and connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"):
|
||||
if (await self.is_running()):
|
||||
raise VMwareError("Attachment '{attachment}' is configured on network adapter {adapter_number}. "
|
||||
"Please stop VMware VM '{name}' to link to this adapter and allow GNS3 to change the attachment type.".format(attachment=self._vmx_pairs[connection_type],
|
||||
adapter_number=adapter_number,
|
||||
name=self.name))
|
||||
if (
|
||||
not self._use_any_adapter
|
||||
and connection_type in self._vmx_pairs
|
||||
and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly")
|
||||
):
|
||||
if await self.is_running():
|
||||
raise VMwareError(
|
||||
"Attachment '{attachment}' is configured on network adapter {adapter_number}. "
|
||||
"Please stop VMware VM '{name}' to link to this adapter and allow GNS3 to change the attachment type.".format(
|
||||
attachment=self._vmx_pairs[connection_type], adapter_number=adapter_number, name=self.name
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise VMwareError("Attachment '{attachment}' is already configured on network adapter {adapter_number}. "
|
||||
"Please remove it or allow VMware VM '{name}' to use any adapter.".format(attachment=self._vmx_pairs[connection_type],
|
||||
adapter_number=adapter_number,
|
||||
name=self.name))
|
||||
|
||||
raise VMwareError(
|
||||
"Attachment '{attachment}' is already configured on network adapter {adapter_number}. "
|
||||
"Please remove it or allow VMware VM '{name}' to use any adapter.".format(
|
||||
attachment=self._vmx_pairs[connection_type], adapter_number=adapter_number, name=self.name
|
||||
)
|
||||
)
|
||||
|
||||
adapter.add_nio(0, nio)
|
||||
if self._started and self._ubridge_hypervisor:
|
||||
await self._add_ubridge_connection(nio, adapter_number)
|
||||
|
||||
log.info("VMware VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
@@ -760,8 +795,11 @@ class VMwareVM(BaseNode):
|
||||
try:
|
||||
await self._update_ubridge_connection(adapter_number, nio)
|
||||
except IndexError:
|
||||
raise VMwareError('Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise VMwareError(
|
||||
'Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
@@ -775,8 +813,11 @@ class VMwareVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VMwareError(
|
||||
"Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
await self.stop_capture(adapter_number)
|
||||
nio = adapter.get_nio(0)
|
||||
@@ -786,10 +827,11 @@ class VMwareVM(BaseNode):
|
||||
if self._started and self._ubridge_hypervisor:
|
||||
await self._delete_ubridge_connection(adapter_number)
|
||||
|
||||
log.info("VMware VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
return nio
|
||||
|
||||
@@ -805,8 +847,11 @@ class VMwareVM(BaseNode):
|
||||
try:
|
||||
adapter = self.ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VMwareError(
|
||||
"Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
if not nio:
|
||||
@@ -837,11 +882,13 @@ class VMwareVM(BaseNode):
|
||||
"""
|
||||
|
||||
pipe_name = self._get_pipe_name()
|
||||
serial_port = {"serial0.present": "TRUE",
|
||||
"serial0.filetype": "pipe",
|
||||
"serial0.filename": pipe_name,
|
||||
"serial0.pipe.endpoint": "server",
|
||||
"serial0.startconnected": "TRUE"}
|
||||
serial_port = {
|
||||
"serial0.present": "TRUE",
|
||||
"serial0.filetype": "pipe",
|
||||
"serial0.filename": pipe_name,
|
||||
"serial0.pipe.endpoint": "server",
|
||||
"serial0.startconnected": "TRUE",
|
||||
}
|
||||
self._vmx_pairs.update(serial_port)
|
||||
|
||||
async def _start_console(self):
|
||||
@@ -855,14 +902,18 @@ class VMwareVM(BaseNode):
|
||||
self._remote_pipe = await asyncio_open_serial(self._get_pipe_name())
|
||||
except OSError as e:
|
||||
raise VMwareError(f"Could not open serial pipe '{pipe_name}': {e}")
|
||||
server = AsyncioTelnetServer(reader=self._remote_pipe,
|
||||
writer=self._remote_pipe,
|
||||
binary=True,
|
||||
echo=True)
|
||||
server = AsyncioTelnetServer(reader=self._remote_pipe, writer=self._remote_pipe, binary=True, echo=True)
|
||||
try:
|
||||
self._telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
|
||||
self._telnet_server = await asyncio.start_server(
|
||||
server.run, self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
except OSError as e:
|
||||
self.project.emit("log.warning", {"message": f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"})
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{
|
||||
"message": f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"
|
||||
},
|
||||
)
|
||||
|
||||
async def _stop_remote_console(self):
|
||||
"""
|
||||
@@ -911,9 +962,11 @@ class VMwareVM(BaseNode):
|
||||
if self._started:
|
||||
await self._start_ubridge_capture(adapter_number, output_file)
|
||||
|
||||
log.info("VMware VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, adapter_number):
|
||||
"""
|
||||
@@ -930,6 +983,8 @@ class VMwareVM(BaseNode):
|
||||
if self._started:
|
||||
await self._stop_ubridge_capture(adapter_number)
|
||||
|
||||
log.info("VMware VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
@@ -40,11 +40,12 @@ from ..base_node import BaseNode
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VPCSVM(BaseNode):
|
||||
module_name = 'vpcs'
|
||||
module_name = "vpcs"
|
||||
|
||||
"""
|
||||
VPCS VM implementation.
|
||||
@@ -121,14 +122,16 @@ class VPCSVM(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"name": self.name,
|
||||
"node_id": self.id,
|
||||
"node_directory": self.working_path,
|
||||
"status": self.status,
|
||||
"console": self._console,
|
||||
"console_type": self._console_type,
|
||||
"project_id": self.project.id,
|
||||
"command_line": self.command_line}
|
||||
return {
|
||||
"name": self.name,
|
||||
"node_id": self.id,
|
||||
"node_directory": self.working_path,
|
||||
"status": self.status,
|
||||
"console": self._console,
|
||||
"console_type": self._console_type,
|
||||
"project_id": self.project.id,
|
||||
"command_line": self.command_line,
|
||||
}
|
||||
|
||||
def _vpcs_path(self):
|
||||
"""
|
||||
@@ -153,7 +156,7 @@ class VPCSVM(BaseNode):
|
||||
if self.script_file:
|
||||
content = self.startup_script
|
||||
content = content.replace(self._name, new_name)
|
||||
escaped_name = new_name.replace('\\', '')
|
||||
escaped_name = new_name.replace("\\", "")
|
||||
content = re.sub(r"^set pcname .+$", "set pcname " + escaped_name, content, flags=re.MULTILINE)
|
||||
self.startup_script = content
|
||||
|
||||
@@ -184,10 +187,10 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
try:
|
||||
startup_script_path = os.path.join(self.working_dir, 'startup.vpc')
|
||||
with open(startup_script_path, "w+", encoding='utf-8') as f:
|
||||
startup_script_path = os.path.join(self.working_dir, "startup.vpc")
|
||||
with open(startup_script_path, "w+", encoding="utf-8") as f:
|
||||
if startup_script is None:
|
||||
f.write('')
|
||||
f.write("")
|
||||
else:
|
||||
startup_script = startup_script.replace("%h", self._name)
|
||||
f.write(startup_script)
|
||||
@@ -228,12 +231,10 @@ class VPCSVM(BaseNode):
|
||||
if sys.platform.startswith("win32"):
|
||||
flags = subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
with open(self._vpcs_stdout_file, "w", encoding="utf-8") as fd:
|
||||
self.command_line = ' '.join(command)
|
||||
self._process = await asyncio.create_subprocess_exec(*command,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self.working_dir,
|
||||
creationflags=flags)
|
||||
self.command_line = " ".join(command)
|
||||
self._process = await asyncio.create_subprocess_exec(
|
||||
*command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir, creationflags=flags
|
||||
)
|
||||
monitor_process(self._process, self._termination_callback)
|
||||
|
||||
await self._start_ubridge()
|
||||
@@ -265,7 +266,10 @@ class VPCSVM(BaseNode):
|
||||
await self._stop_ubridge()
|
||||
await super().stop()
|
||||
if returncode != 0:
|
||||
self.project.emit("log.error", {"message": f"VPCS process has stopped, return code: {returncode}\n{self.read_vpcs_stdout()}"})
|
||||
self.project.emit(
|
||||
"log.error",
|
||||
{"message": f"VPCS process has stopped, return code: {returncode}\n{self.read_vpcs_stdout()}"},
|
||||
)
|
||||
|
||||
async def stop(self):
|
||||
"""
|
||||
@@ -365,17 +369,21 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
raise VPCSError(
|
||||
"Port {port_number} doesn't exist on adapter {adapter}".format(
|
||||
adapter=self._ethernet_adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
if self.is_running():
|
||||
await self.add_ubridge_udp_connection(f"VPCS-{self._id}", self._local_udp_tunnel[1], nio)
|
||||
|
||||
self._ethernet_adapter.add_nio(port_number, nio)
|
||||
log.info('VPCS "{name}" [{id}]: {nio} added to port {port_number}'.format(name=self._name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'VPCS "{name}" [{id}]: {nio} added to port {port_number}'.format(
|
||||
name=self._name, id=self.id, nio=nio, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
return nio
|
||||
|
||||
@@ -388,8 +396,11 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
raise VPCSError(
|
||||
"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"VPCS-{self._id}", self._local_udp_tunnel[1], nio)
|
||||
|
||||
@@ -403,8 +414,11 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
raise VPCSError(
|
||||
"Port {port_number} doesn't exist on adapter {adapter}".format(
|
||||
adapter=self._ethernet_adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
await self.stop_capture(port_number)
|
||||
if self.is_running():
|
||||
@@ -415,10 +429,11 @@ class VPCSVM(BaseNode):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
self._ethernet_adapter.remove_nio(port_number)
|
||||
|
||||
log.info('VPCS "{name}" [{id}]: {nio} removed from port {port_number}'.format(name=self._name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'VPCS "{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):
|
||||
@@ -431,8 +446,11 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
raise VPCSError(
|
||||
"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 VPCSError(f"Port {port_number} is not connected")
|
||||
@@ -452,12 +470,15 @@ class VPCSVM(BaseNode):
|
||||
|
||||
nio.start_packet_capture(output_file)
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=f"VPCS-{self._id}",
|
||||
output_file=output_file))
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{output_file}"'.format(name=f"VPCS-{self._id}", output_file=output_file)
|
||||
)
|
||||
|
||||
log.info("VPCS '{name}' [{id}]: starting packet capture on port {port_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
"VPCS '{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):
|
||||
"""
|
||||
@@ -472,11 +493,13 @@ class VPCSVM(BaseNode):
|
||||
|
||||
nio.stop_packet_capture()
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge stop_capture {name}'.format(name=f"VPCS-{self._id}"))
|
||||
await self._ubridge_send("bridge stop_capture {name}".format(name=f"VPCS-{self._id}"))
|
||||
|
||||
log.info("VPCS '{name}' [{id}]: stopping packet capture on port {port_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
"VPCS '{name}' [{id}]: stopping packet capture on port {port_number}".format(
|
||||
name=self.name, id=self.id, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
def _build_command(self):
|
||||
"""
|
||||
@@ -516,7 +539,9 @@ class VPCSVM(BaseNode):
|
||||
|
||||
command = [self._vpcs_path()]
|
||||
command.extend(["-p", str(self._internal_console_port)]) # listen to console port
|
||||
command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset
|
||||
command.extend(
|
||||
["-m", str(self._manager.get_mac_id(self.id))]
|
||||
) # the unique ID is used to set the MAC address offset
|
||||
command.extend(["-i", "1"]) # option to start only one VPC instance
|
||||
command.extend(["-F"]) # option to avoid the daemonization of VPCS
|
||||
if self._vpcs_version >= parse_version("0.8b"):
|
||||
@@ -533,7 +558,9 @@ class VPCSVM(BaseNode):
|
||||
command.extend(["-s", str(nio.lport)]) # source UDP port
|
||||
command.extend(["-c", str(nio.rport)]) # destination UDP port
|
||||
try:
|
||||
command.extend(["-t", socket.gethostbyname(nio.rhost)]) # destination host, we need to resolve the hostname because VPCS doesn't support it
|
||||
command.extend(
|
||||
["-t", socket.gethostbyname(nio.rhost)]
|
||||
) # destination host, we need to resolve the hostname because VPCS doesn't support it
|
||||
except socket.gaierror as e:
|
||||
raise VPCSError(f"Can't resolve hostname {nio.rhost}")
|
||||
|
||||
@@ -550,7 +577,7 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
# use the default VPCS file if it exists
|
||||
path = os.path.join(self.working_dir, 'startup.vpc')
|
||||
path = os.path.join(self.working_dir, "startup.vpc")
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
else:
|
||||
|
||||
Reference in new issue
Block a user