From 37bd4067b0c900335f5ac78815272e1703cd54d7 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Sun, 28 Jan 2024 06:32:15 -0500 Subject: [PATCH 01/25] Its a brave new world. Add Spikefish Telnet Proxy Muxer to rep. --- .../utils/asyncio/SFTelnetProxyMuxer.py | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100755 gns3server/utils/asyncio/SFTelnetProxyMuxer.py diff --git a/gns3server/utils/asyncio/SFTelnetProxyMuxer.py b/gns3server/utils/asyncio/SFTelnetProxyMuxer.py new file mode 100755 index 00000000..93a37451 --- /dev/null +++ b/gns3server/utils/asyncio/SFTelnetProxyMuxer.py @@ -0,0 +1,205 @@ +import socket +import asyncio +import telnetlib3 +import pdb +import logging + + +# Configure logging +logging.basicConfig( + level=logging.DEBUG, # Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) + format='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)', + datefmt='%Y-%m-%d %H:%M:%S' +) + +class SFTelnetProxyMuxer: + def __init__(self, remote_ip, remote_port, listen_ip, listen_port): + self.remote_ip = remote_ip + self.remote_port = remote_port + self.remote_info = f"('{self.remote_ip}', {self.remote_port})" + self.listen_ip = listen_ip + self.listen_port = listen_port + self.clients = set() + self.server = None + self.remote_reader = None + self.remote_writer = None + self.lock = asyncio.Lock() # Lock for coordinating access to the remote server + # Telnet protocol constants + self.IAC = b"\xff" # Interpret as Command + # Telnet NOP command. Will be used as a heartbeat to clients. + self.NOP = b"\xf1" + # Telnet Are You There + self.AYT = b"\xf6" + + logging.debug("TCPProxy init complete") + + async def handle_client(self, reader, writer): + client_info = writer.get_extra_info('peername') + sock = writer.get_extra_info('socket') + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + logging.debug(f"New client connected: {client_info}") + self.clients.add(writer) + #logging.debug(f"Write idle: {writer.protocol.idle}") + + try: + await asyncio.sleep(1) + while True: + try: + # Set a timeout for the read operation, without should the socket closes after timeout. + data = await asyncio.shield(asyncio.wait_for(reader.read((4*1024*1024)), timeout=2.0)) + if not data: + logging.debug(f"No data. Not sure if this is possible.") + break + if reader.at_eof(): + logging.info(f"Client {client_info} closed tcp session with eof.") + writer.close() + self.clients.discard(writer) + break + + async with self.lock: + if self.remote_writer is not None: + logging.debug(f"Sending data from from client {client_info} to server {self.remote_info}") + self.remote_writer.write(data) + await self.remote_writer.drain() + continue + + except asyncio.TimeoutError: + logging.warning(f"No data read from {client_info}, send heartbeat to test client socket.") + try: + logging.warning(f"Heatbeat: Are you there {client_info}?") + #pdb.set_trace() + writer.send_iac(self.IAC + self.NOP) + await writer.drain() + continue + except asyncio.TimeoutError: + logging.warning(f"Heatbeat: No reply from {client_info}, closing socket.") + writer.close() + self.clients.discard(writer) + break + except Exception as e: + logging.warning(f"Heateat: Unknown error from {client_info}, closing socket. Exeption {e}") + writer.close() + self.clients.discard(writer) + break + finally: + logging.warning(f"Heatbeat: {client_info} Yes I am.") + except Exception as e: + logging.exception(f"Error in handling data from client {client_info}:") + writer.close() + self.clients.discard(writer) + break + + except Exception as e: + logging.exception(f"Error in managing client {client_info}: {e}") + + finally: + # Safely remove the writer from clients set and close the connection + writer.close() + self.clients.discard(writer) + logging.debug(f"Client {client_info} disconnected. Remaining clients: {len(list(self.clients))}") + logging.debug(f"Connection with client {client_info} closed.") + + + async def broadcast_to_clients(self, data): + if not self.clients: + logging.debug(f"Warning: No clients connected, ignoring data.") + return + + for writer in set(self.clients): + client_info = writer.get_extra_info('peername') + try: + #logging.debug(f"Clients connected: {writer}, sending data: {data}") + writer.write(data) + await asyncio.wait_for(writer.drain(), timeout=2.0) + except Exception as e: + logging.debug(f"Lost connection to client {client_info}") + writer.close() + self.clients.discard(writer) + + async def handle_remote_server(self): + logging.debug("Start handler for remote server") + while True: + await asyncio.sleep(1) + try: + self.remote_reader, self.remote_writer = await telnetlib3.open_connection( + host=self.remote_ip, port=self.remote_port + ) + sock = self.remote_writer.get_extra_info('socket') + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + while True: + + try: + #data = await self.remote_reader.read((4*1024*1024)) + data = await asyncio.shield(asyncio.wait_for(self.remote_reader.read((4*1024*1024)), timeout=2.0)) + if self.remote_reader.at_eof(): + logging.info(f"Remote server {self.remote_info} closed tcp session with eof.") + break + except asyncio.TimeoutError: + logging.warning(f"No data from server {self.remote_info}, send heartbeat to test socket.") + try: + logging.warning(f"Heatbeat: Are you there {self.remote_info}?") + # NOP and AYT cause QEMU to spam everyone's console with junk. + # This causes everyone to close the session and eof tcp which makes me sad. + # Will need to research more... or did i call this wrong and just fix it? + #self.remote_writer.send_iac(self.IAC + self.NOP) + await self.remote_writer.drain() + continue + except Exception as e: + logging.warning(f"Heateat: Unknown error from {self.remote_info}, closing socket. Exeption {e}") + self.remote_writer.close() + break + finally: + logging.warning(f"Heatbeat: {self.remote_info} Yes I am.") + + except Exception as e: + logging.debug("Failed to read socket data exception: {e}") + break + #if not self.clients: + # logging.debug("No clients connected, but console data found. Skipping.") + # continue + #logging.debug("Sending data to clients data: {data}") + await self.broadcast_to_clients(data) + except ConnectionRefusedError as e: + error_msg = f"Warning: Connection to remote server {self.remote_info} refused." + logging.debug(error_msg) + await self.broadcast_to_clients(f"\r{error_msg}\n\r") + + except TimeoutError as e: + error_msg = f"Warning: Connection to remote server {self.remote_info} timedout." + logging.debug(error_msg) + await self.broadcast_to_clients(f"\r{error_msg}\n\r") + + except Exception as e: + error_msg = f"Warning: Connection to remote server {self.remote_info} unknown error: {e}." + logging.debug(error_msg) + await self.broadcast_to_clients(f"\r{error_msg}\n\r") + + async def start_proxy(self): + logging.debug("Starting telnet proxy.") + asyncio.create_task(self.handle_remote_server()) + self.server = await telnetlib3.create_server( + host=self.listen_ip, port=self.listen_port, + shell=self.handle_client + ) + async with self.server: + logging.debug("Startup of telnet proxy complete.") + await self.server.wait_closed() + + async def shutdown(self): + # [shutdown method implementation remains the same] + logging.debug("Debug message") + pass + +if __name__ == "__main__": + + ## Example usage + logging.debug("Start proxy") + proxy = SFTelnetProxyMuxer(remote_ip='127.0.0.1', remote_port=7000, listen_ip='0.0.0.0', listen_port=8888) + try: + asyncio.wait_for(asyncio.run(proxy.start_proxy()), timeout=30) + except OSError as e: + logging.debug(f"Can't start proxy: {e}") + + # To shut down the proxy + # asyncio.run(proxy.shutdown()) + From e39b3171859cf47478a676e1203b478f16cd1984 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Sun, 28 Jan 2024 08:29:21 -0500 Subject: [PATCH 02/25] rename SFTelnetProxyMuxer.py to sftelnetproxymuxer.py. Set all inital options to False. Add check exception if remote_port or listen_port aren't defined. --- ...netProxyMuxer.py => sftelnetproxymuxer.py} | 92 ++++++++++--------- 1 file changed, 50 insertions(+), 42 deletions(-) rename gns3server/utils/asyncio/{SFTelnetProxyMuxer.py => sftelnetproxymuxer.py} (65%) diff --git a/gns3server/utils/asyncio/SFTelnetProxyMuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py similarity index 65% rename from gns3server/utils/asyncio/SFTelnetProxyMuxer.py rename to gns3server/utils/asyncio/sftelnetproxymuxer.py index 93a37451..67d6bd5b 100755 --- a/gns3server/utils/asyncio/SFTelnetProxyMuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -3,20 +3,24 @@ import asyncio import telnetlib3 import pdb import logging - +log = logging.getLogger(__name__) # Configure logging -logging.basicConfig( - level=logging.DEBUG, # Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) - format='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)', - datefmt='%Y-%m-%d %H:%M:%S' -) +#log.basicConfig( +# level=log.DEBUG, # Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) +# format='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)', +# datefmt='%Y-%m-%d %H:%M:%S' +#) class SFTelnetProxyMuxer: - def __init__(self, remote_ip, remote_port, listen_ip, listen_port): + def __init__(self, remote_ip=None, remote_port=None, listen_ip=None, listen_port=None, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None): + if remote_ip == None: + remote_ip = '127.0.0.1' self.remote_ip = remote_ip self.remote_port = remote_port self.remote_info = f"('{self.remote_ip}', {self.remote_port})" + if listen_ip == None: + listen_ip = '0.0.0.0' self.listen_ip = listen_ip self.listen_port = listen_port self.clients = set() @@ -30,16 +34,20 @@ class SFTelnetProxyMuxer: self.NOP = b"\xf1" # Telnet Are You There self.AYT = b"\xf6" + log.debug("SFTelnetProxyMuxer init complete") + if not remote_port: + raise ValueError("remote_port is a required value") + if not listen_port: + raise ValueError("listen_port is a required value") - logging.debug("TCPProxy init complete") async def handle_client(self, reader, writer): client_info = writer.get_extra_info('peername') sock = writer.get_extra_info('socket') sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - logging.debug(f"New client connected: {client_info}") + log.debug(f"New client connected: {client_info}") self.clients.add(writer) - #logging.debug(f"Write idle: {writer.protocol.idle}") + #log.debug(f"Write idle: {writer.protocol.idle}") try: await asyncio.sleep(1) @@ -48,76 +56,76 @@ class SFTelnetProxyMuxer: # Set a timeout for the read operation, without should the socket closes after timeout. data = await asyncio.shield(asyncio.wait_for(reader.read((4*1024*1024)), timeout=2.0)) if not data: - logging.debug(f"No data. Not sure if this is possible.") + log.debug(f"No data. Not sure if this is possible.") break if reader.at_eof(): - logging.info(f"Client {client_info} closed tcp session with eof.") + log.info(f"Client {client_info} closed tcp session with eof.") writer.close() self.clients.discard(writer) break async with self.lock: if self.remote_writer is not None: - logging.debug(f"Sending data from from client {client_info} to server {self.remote_info}") + log.debug(f"Sending data from from client {client_info} to server {self.remote_info}") self.remote_writer.write(data) await self.remote_writer.drain() continue except asyncio.TimeoutError: - logging.warning(f"No data read from {client_info}, send heartbeat to test client socket.") + log.warning(f"No data read from {client_info}, send heartbeat to test client socket.") try: - logging.warning(f"Heatbeat: Are you there {client_info}?") + log.warning(f"Heatbeat: Are you there {client_info}?") #pdb.set_trace() writer.send_iac(self.IAC + self.NOP) await writer.drain() continue except asyncio.TimeoutError: - logging.warning(f"Heatbeat: No reply from {client_info}, closing socket.") + log.warning(f"Heatbeat: No reply from {client_info}, closing socket.") writer.close() self.clients.discard(writer) break except Exception as e: - logging.warning(f"Heateat: Unknown error from {client_info}, closing socket. Exeption {e}") + log.warning(f"Heateat: Unknown error from {client_info}, closing socket. Exeption {e}") writer.close() self.clients.discard(writer) break finally: - logging.warning(f"Heatbeat: {client_info} Yes I am.") + log.warning(f"Heatbeat: {client_info} Yes I am.") except Exception as e: - logging.exception(f"Error in handling data from client {client_info}:") + log.exception(f"Error in handling data from client {client_info}:") writer.close() self.clients.discard(writer) break except Exception as e: - logging.exception(f"Error in managing client {client_info}: {e}") + log.exception(f"Error in managing client {client_info}: {e}") finally: # Safely remove the writer from clients set and close the connection writer.close() self.clients.discard(writer) - logging.debug(f"Client {client_info} disconnected. Remaining clients: {len(list(self.clients))}") - logging.debug(f"Connection with client {client_info} closed.") + log.debug(f"Client {client_info} disconnected. Remaining clients: {len(list(self.clients))}") + log.debug(f"Connection with client {client_info} closed.") async def broadcast_to_clients(self, data): if not self.clients: - logging.debug(f"Warning: No clients connected, ignoring data.") + log.debug(f"Warning: No clients connected, ignoring data.") return for writer in set(self.clients): client_info = writer.get_extra_info('peername') try: - #logging.debug(f"Clients connected: {writer}, sending data: {data}") + #log.debug(f"Clients connected: {writer}, sending data: {data}") writer.write(data) await asyncio.wait_for(writer.drain(), timeout=2.0) except Exception as e: - logging.debug(f"Lost connection to client {client_info}") + log.debug(f"Lost connection to client {client_info}") writer.close() self.clients.discard(writer) async def handle_remote_server(self): - logging.debug("Start handler for remote server") + log.debug("Start handler for remote server") while True: await asyncio.sleep(1) try: @@ -132,12 +140,12 @@ class SFTelnetProxyMuxer: #data = await self.remote_reader.read((4*1024*1024)) data = await asyncio.shield(asyncio.wait_for(self.remote_reader.read((4*1024*1024)), timeout=2.0)) if self.remote_reader.at_eof(): - logging.info(f"Remote server {self.remote_info} closed tcp session with eof.") + log.info(f"Remote server {self.remote_info} closed tcp session with eof.") break except asyncio.TimeoutError: - logging.warning(f"No data from server {self.remote_info}, send heartbeat to test socket.") + log.warning(f"No data from server {self.remote_info}, send heartbeat to test socket.") try: - logging.warning(f"Heatbeat: Are you there {self.remote_info}?") + log.warning(f"Heatbeat: Are you there {self.remote_info}?") # NOP and AYT cause QEMU to spam everyone's console with junk. # This causes everyone to close the session and eof tcp which makes me sad. # Will need to research more... or did i call this wrong and just fix it? @@ -145,60 +153,60 @@ class SFTelnetProxyMuxer: await self.remote_writer.drain() continue except Exception as e: - logging.warning(f"Heateat: Unknown error from {self.remote_info}, closing socket. Exeption {e}") + log.warning(f"Heateat: Unknown error from {self.remote_info}, closing socket. Exeption {e}") self.remote_writer.close() break finally: - logging.warning(f"Heatbeat: {self.remote_info} Yes I am.") + log.warning(f"Heatbeat: {self.remote_info} Yes I am.") except Exception as e: - logging.debug("Failed to read socket data exception: {e}") + log.debug("Failed to read socket data exception: {e}") break #if not self.clients: - # logging.debug("No clients connected, but console data found. Skipping.") + # log.debug("No clients connected, but console data found. Skipping.") # continue - #logging.debug("Sending data to clients data: {data}") + #log.debug("Sending data to clients data: {data}") await self.broadcast_to_clients(data) except ConnectionRefusedError as e: error_msg = f"Warning: Connection to remote server {self.remote_info} refused." - logging.debug(error_msg) + log.debug(error_msg) await self.broadcast_to_clients(f"\r{error_msg}\n\r") except TimeoutError as e: error_msg = f"Warning: Connection to remote server {self.remote_info} timedout." - logging.debug(error_msg) + log.debug(error_msg) await self.broadcast_to_clients(f"\r{error_msg}\n\r") except Exception as e: error_msg = f"Warning: Connection to remote server {self.remote_info} unknown error: {e}." - logging.debug(error_msg) + log.debug(error_msg) await self.broadcast_to_clients(f"\r{error_msg}\n\r") async def start_proxy(self): - logging.debug("Starting telnet proxy.") + log.debug("Starting telnet proxy.") asyncio.create_task(self.handle_remote_server()) self.server = await telnetlib3.create_server( host=self.listen_ip, port=self.listen_port, shell=self.handle_client ) async with self.server: - logging.debug("Startup of telnet proxy complete.") + log.debug("Startup of telnet proxy complete.") await self.server.wait_closed() async def shutdown(self): # [shutdown method implementation remains the same] - logging.debug("Debug message") + log.debug("Debug message") pass if __name__ == "__main__": ## Example usage - logging.debug("Start proxy") + log.debug("Start proxy") proxy = SFTelnetProxyMuxer(remote_ip='127.0.0.1', remote_port=7000, listen_ip='0.0.0.0', listen_port=8888) try: asyncio.wait_for(asyncio.run(proxy.start_proxy()), timeout=30) except OSError as e: - logging.debug(f"Can't start proxy: {e}") + log.debug(f"Can't start proxy: {e}") # To shut down the proxy # asyncio.run(proxy.shutdown()) From 1c4347b05889a206f9f9c1bb0f8cac650bb7a216 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Sun, 28 Jan 2024 08:33:10 -0500 Subject: [PATCH 03/25] Add sftelnetproxymuxer support --- gns3server/compute/base_node.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index 639a5d41..efdfaf18 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -31,7 +31,8 @@ from aiohttp.web import WebSocketResponse from gns3server.utils.interfaces import interfaces from ..compute.port_manager import PortManager from ..utils.asyncio import wait_run_in_executor, locking -from ..utils.asyncio.telnet_server import AsyncioTelnetServer +#from ..utils.asyncio.telnet_server import AsyncioTelnet +from ..utils.asyncio.sftelnetproxymuxer import SFTelnetProxyMuxer from ..ubridge.hypervisor import Hypervisor from ..ubridge.ubridge_error import UbridgeError from .nios.nio_udp import NIOUDP @@ -375,6 +376,7 @@ class BaseNode: if not self._wrap_console or self._console_type != "telnet": return remaining_trial = 60 + log.info(f"Internal_console_port: {self._internal_console_port}") while True: try: (self._wrap_console_reader, self._wrap_console_writer) = await asyncio.open_connection( @@ -387,6 +389,7 @@ class BaseNode: raise e await asyncio.sleep(0.1) remaining_trial -= 1 + ## no longer needed. SFTelnetProxyMuxer handles client handshake await AsyncioTelnetServer.write_client_intro(self._wrap_console_writer, echo=True) server = AsyncioTelnetServer( reader=self._wrap_console_reader, @@ -395,6 +398,8 @@ class BaseNode: echo=True ) # warning: this will raise OSError exception if there is a problem... + log.info(f"self._manager.port_manager.console_host: {self._manager.port_manager.console_host}") + log.info(f"self.console {self.console}") self._wrapper_telnet_server = await asyncio.start_server( server.run, self._manager.port_manager.console_host, From 157dc207b7b6282e377fec53db8efad55692b239 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Sun, 28 Jan 2024 08:37:46 -0500 Subject: [PATCH 04/25] Move log statment to be last line of init --- gns3server/utils/asyncio/sftelnetproxymuxer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 67d6bd5b..0fdd6415 100755 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -34,11 +34,11 @@ class SFTelnetProxyMuxer: self.NOP = b"\xf1" # Telnet Are You There self.AYT = b"\xf6" - log.debug("SFTelnetProxyMuxer init complete") if not remote_port: raise ValueError("remote_port is a required value") if not listen_port: raise ValueError("listen_port is a required value") + log.debug("SFTelnetProxyMuxer init complete") async def handle_client(self, reader, writer): From f9fd629641892b1c99342be843940134b92531e7 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Sun, 28 Jan 2024 08:39:08 -0500 Subject: [PATCH 05/25] Correct comment about shield --- gns3server/utils/asyncio/sftelnetproxymuxer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 0fdd6415..185d2f1d 100755 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -53,7 +53,7 @@ class SFTelnetProxyMuxer: await asyncio.sleep(1) while True: try: - # Set a timeout for the read operation, without should the socket closes after timeout. + # Set a timeout for the read operation, without should() the socket closes after timeout. data = await asyncio.shield(asyncio.wait_for(reader.read((4*1024*1024)), timeout=2.0)) if not data: log.debug(f"No data. Not sure if this is possible.") From d8a6e7b90bf58a45236f2620f71ae3d558c428cc Mon Sep 17 00:00:00 2001 From: John Fleming Date: Sun, 28 Jan 2024 08:55:24 -0500 Subject: [PATCH 06/25] Add shutdown method to sftelnetproxymuxer.py --- .../utils/asyncio/sftelnetproxymuxer.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 185d2f1d..6944b588 100755 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -194,9 +194,27 @@ class SFTelnetProxyMuxer: await self.server.wait_closed() async def shutdown(self): - # [shutdown method implementation remains the same] + if self.remote_writer: + try: + log.debug(f"Shuting down tcp session to {self.remote_server}") + self.remote_writer.close() + await self.server.wait_closed() + except Exception as e: + log.debug(f"Failed to shutdown {self.remote_server}: {e}") + pass + + for client in self.clients: + try: + try: + client_info = client.get_extra_info('peername') + except: + client_info = "Unknown" + log.debug("Shuting down tcp session to {client_info}") + client.close() + await.client.wait_closed() + except Exception as e: + log.debug("Debug message") - pass if __name__ == "__main__": From ada755cd4be1621d968a8dcdfef8f8269892b084 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Sun, 28 Jan 2024 08:58:01 -0500 Subject: [PATCH 07/25] Add SFTelnetProxyMuxer support to start --- gns3server/compute/base_node.py | 64 ++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index efdfaf18..fff28c00 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -31,7 +31,7 @@ from aiohttp.web import WebSocketResponse from gns3server.utils.interfaces import interfaces from ..compute.port_manager import PortManager from ..utils.asyncio import wait_run_in_executor, locking -#from ..utils.asyncio.telnet_server import AsyncioTelnet +from ..utils.asyncio.telnet_server import AsyncioTelnetServer from ..utils.asyncio.sftelnetproxymuxer import SFTelnetProxyMuxer from ..ubridge.hypervisor import Hypervisor from ..ubridge.ubridge_error import UbridgeError @@ -375,36 +375,42 @@ class BaseNode: if not self._wrap_console or self._console_type != "telnet": return - remaining_trial = 60 - log.info(f"Internal_console_port: {self._internal_console_port}") - while True: - try: - (self._wrap_console_reader, self._wrap_console_writer) = await asyncio.open_connection( - host="127.0.0.1", - port=self._internal_console_port - ) - break - except (OSError, ConnectionRefusedError) as e: - if remaining_trial <= 0: - raise e - await asyncio.sleep(0.1) - remaining_trial -= 1 + #remaining_trial = 60 + #log.info(f"Internal_console_port: {self._internal_console_port}") + # self._internal_console_port == qemu listner port for example + #while True: + # try: + # (self._wrap_console_reader, self._wrap_console_writer) = await asyncio.open_connection( + # host="127.0.0.1", + # port=self._internal_console_port + # ) + # break + # except (OSError, ConnectionRefusedError) as e: + # if remaining_trial <= 0: + # raise e + # await asyncio.sleep(0.1) + # remaining_trial -= 1 ## no longer needed. SFTelnetProxyMuxer handles client handshake - await AsyncioTelnetServer.write_client_intro(self._wrap_console_writer, echo=True) - server = AsyncioTelnetServer( - reader=self._wrap_console_reader, - writer=self._wrap_console_writer, - binary=True, - echo=True - ) + #await AsyncioTelnetServer.write_client_intro(self._wrap_console_writer, echo=True) + #server = AsyncioTelnetServer( + # reader=self._wrap_console_reader, + # writer=self._wrap_console_writer, + # binary=True, + # echo=True + #) + server = SFTelnetProxyMuxer(binary=True, echo=True, remote_port=self._internal_console_port, listen_port=self.console) + await server.start_proxy() # warning: this will raise OSError exception if there is a problem... - log.info(f"self._manager.port_manager.console_host: {self._manager.port_manager.console_host}") - log.info(f"self.console {self.console}") - self._wrapper_telnet_server = await asyncio.start_server( - server.run, - self._manager.port_manager.console_host, - self.console - ) + #log.info(f"self._manager.port_manager.console_host: {self._manager.port_manager.console_host}") + #log.info(f"self.console {self.console}") + # self._manager.port_manager.console_host == bind ip + # self.console == bind port + #self._wrapper_telnet_server = await asyncio.start_server( + # server.run, + # self._manager.port_manager.console_host, + # self.console + #) + async def stop_wrap_console(self): """ From 9774fe9468166665433587fc9fb072f235dac343 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Sun, 28 Jan 2024 09:05:57 -0500 Subject: [PATCH 08/25] Add shutdown method --- gns3server/compute/base_node.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index fff28c00..2503f31e 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -399,7 +399,7 @@ class BaseNode: # echo=True #) server = SFTelnetProxyMuxer(binary=True, echo=True, remote_port=self._internal_console_port, listen_port=self.console) - await server.start_proxy() + self._wrapper_telnet_server = await server.start_proxy() # warning: this will raise OSError exception if there is a problem... #log.info(f"self._manager.port_manager.console_host: {self._manager.port_manager.console_host}") #log.info(f"self.console {self.console}") @@ -417,16 +417,18 @@ class BaseNode: Stops the telnet proxy. """ - if self._wrapper_telnet_server: - self._wrap_console_writer.close() - if sys.version_info >= (3, 7, 0): - try: - await self._wrap_console_writer.wait_closed() - except ConnectionResetError: - pass - self._wrapper_telnet_server.close() - await self._wrapper_telnet_server.wait_closed() - self._wrapper_telnet_server = None + #if self._wrapper_telnet_server: + # self._wrap_console_writer.close() + # if sys.version_info >= (3, 7, 0): + # try: + # await self._wrap_console_writer.wait_closed() + # except ConnectionResetError: + # pass + # self._wrapper_telnet_server.close() + # await self._wrapper_telnet_server.wait_closed() + # self._wrapper_telnet_server = None + self._wrapper_telnet_server.shutdown() + self._wrapper_telnet_server = None async def reset_wrap_console(self): """ From b759d31482aeb28033c2d25af3da256e08a55a0d Mon Sep 17 00:00:00 2001 From: John Fleming Date: Tue, 30 Jan 2024 20:55:13 -0500 Subject: [PATCH 09/25] Add debug stuff to show the telnet console death issue. --- gns3server/utils/asyncio/telnet_server.py | 42 ++++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/gns3server/utils/asyncio/telnet_server.py b/gns3server/utils/asyncio/telnet_server.py index b8829847..e81eba77 100644 --- a/gns3server/utils/asyncio/telnet_server.py +++ b/gns3server/utils/asyncio/telnet_server.py @@ -62,6 +62,7 @@ READ_SIZE = 1024 class TelnetConnection(object): """Default implementation of telnet connection which may but may not be used.""" def __init__(self, reader, writer, window_size_changed_callback=None): + log.debug(f"Start TelnetConnection init") self.is_closing = False self._reader = reader self._writer = writer @@ -69,6 +70,7 @@ class TelnetConnection(object): @property def reader(self): + log.debug(f"Start TelnetConnection reader") return self._reader @property @@ -77,15 +79,18 @@ class TelnetConnection(object): async def connected(self): """Method called when client is connected""" + log.debug(f"Start TelnetConnection connected") pass async def disconnected(self): """Method called when client is disconnecting""" + log.debug(f"Start TelnetConnection disconnected") pass async def window_size_changed(self, columns, rows): """Method called when window size changed, only can occur when `naws` flag is enable in server configuration.""" + log.debug(f"Start TelnetConnection window_size_changed") if self._window_size_changed_callback: await self._window_size_changed_callback(columns, rows) @@ -95,12 +100,14 @@ class TelnetConnection(object): Handles incoming data :return: """ + log.debug(f"Start TelnetConnection feed") def send(self, data): """ Sending data back to client :return: """ + log.debug(f"Start TelnetConnection send") data = data.decode().replace("\n", "\r\n") self.writer.write(data.encode()) @@ -109,6 +116,7 @@ class TelnetConnection(object): Closes current connection :return: """ + log.debug(f"Start TelnetConnection close") self.is_closing = True @@ -116,6 +124,7 @@ class AsyncioTelnetServer: MAX_NEGOTIATION_READ = 10 def __init__(self, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None): + log.debug(f"Start AsyncioTelnetServer init") """ Initializes telnet server :param naws when True make a window size negotiation @@ -140,6 +149,7 @@ class AsyncioTelnetServer: self._naws = naws def default_connection_factory(reader, writer, window_size_changed_callback): + log.debug(f"Start connection factory") return TelnetConnection(reader, writer, window_size_changed_callback) if connection_factory is None: @@ -149,6 +159,7 @@ class AsyncioTelnetServer: @staticmethod async def write_client_intro(writer, echo=False): + log.debug(f"Start async write_clien_intro") # Send initial telnet session opening if echo: writer.write(bytes([IAC, WILL, ECHO])) @@ -159,6 +170,7 @@ class AsyncioTelnetServer: await writer.drain() async def _write_intro(self, writer, binary=False, echo=False, naws=False): + log.debug(f"Start async _write_intro") # Send initial telnet session opening if echo: writer.write(bytes([IAC, WILL, ECHO])) @@ -187,14 +199,10 @@ class AsyncioTelnetServer: async def run(self, network_reader, network_writer): + log.debug(f"Start async run") sock = network_writer.get_extra_info("socket") sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - # 60 sec keep alives, close tcp session after 4 missed - # Will keep a firewall from aging out telnet console. - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4) #log.debug("New connection from {}".format(sock.getpeername())) # Keep track of connected clients @@ -206,9 +214,12 @@ class AsyncioTelnetServer: await connection.connected() await self._process(network_reader, network_writer, connection) except ConnectionError: + log.info("Async ConnectionError") async with self._lock: network_writer.close() # await network_writer.wait_closed() # this doesn't work in Python 3.6 + log.debug(f"self._reader_process == network_reader") + log.debug(f"{self._reader_process} == {network_reader}") if self._reader_process == network_reader: self._reader_process = None # Cancel current read from this reader @@ -219,6 +230,7 @@ class AsyncioTelnetServer: del self._connections[network_writer] async def close(self): + log.debug(f"Start async close") for writer, connection in self._connections.items(): try: writer.write_eof() @@ -229,29 +241,40 @@ class AsyncioTelnetServer: continue async def client_connected_hook(self): + log.debug(f"Start async client_connected_hook") pass async def _get_reader(self, network_reader): """ Get a reader or None if another reader is already reading. """ + log.debug(f"Start async _get_reader") async with self._lock: if self._reader_process is None: self._reader_process = network_reader if self._reader: + log.debug(f"self._reader_process == network_reader") + log.debug(f"{self._reader_process} == {network_reader}") if self._reader_process == network_reader: self._current_read = asyncio.ensure_future(self._reader.read(READ_SIZE)) return self._current_read + + log.debug(f"_get_reader Returning None") return None async def _process(self, network_reader, network_writer, connection): + log.debug(f"Start async _process") network_read = asyncio.ensure_future(network_reader.read(READ_SIZE)) reader_read = await self._get_reader(network_reader) while True: + log.debug(f"__process True loop") if reader_read is None: + log.debug(f"__process reader_read is None") reader_read = await self._get_reader(network_reader) + log.debug(f"__process reader_read is still 2nd None") if reader_read is None: + log.debug(f"__process reader_read is still 3rd None") done, pending = await asyncio.wait( [ network_read, @@ -259,13 +282,19 @@ class AsyncioTelnetServer: timeout=1, return_when=asyncio.FIRST_COMPLETED) else: + log.debug(f"__process reader_read else") done, pending = await asyncio.wait( [ network_read, reader_read ], return_when=asyncio.FIRST_COMPLETED) + log.debug(f"__process just before coro done check") + log.debug(f"") + log.debug(f"") + log.debug(f"") for coro in done: + log.debug(f"__process coro can has done?") data = coro.result() if coro == network_read: if network_reader.at_eof(): @@ -303,6 +332,7 @@ class AsyncioTelnetServer: async def _read(self, cmd, buffer, location, reader): """ Reads next op from the buffer or reader""" + log.debug(f"Start async _read") try: op = buffer[location] cmd.append(op) @@ -315,6 +345,7 @@ class AsyncioTelnetServer: async def _negotiate(self, data, connection): """ Performs negotiation commands""" + log.debug(f"Start async _negotiate") command, payload = data[0], data[1:] if command == NAWS: @@ -327,6 +358,7 @@ class AsyncioTelnetServer: log.debug("Not supported negotiation sequence, received {} bytes", len(data)) async def _IAC_parser(self, buf, network_reader, network_writer, connection): + log.debug(f"Start async _IAC_parser") """ Processes and removes any Telnet commands from the buffer. From 357dea430d929ee88e34271d9cd5c243ee2bb3cd Mon Sep 17 00:00:00 2001 From: John Fleming <31658656+spikefishjohn@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:12:01 -0500 Subject: [PATCH 10/25] Update sftelnetproxymuxer.py await was missing space --- gns3server/utils/asyncio/sftelnetproxymuxer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 6944b588..11fe4974 100755 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -211,7 +211,7 @@ class SFTelnetProxyMuxer: client_info = "Unknown" log.debug("Shuting down tcp session to {client_info}") client.close() - await.client.wait_closed() + await client.wait_closed() except Exception as e: log.debug("Debug message") From d82a3085eb0f09fd558bc55ea1ca74c299d6db93 Mon Sep 17 00:00:00 2001 From: John Fleming <31658656+spikefishjohn@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:13:55 -0500 Subject: [PATCH 11/25] Update sftelnetproxymuxer.py Add something under exception. --- gns3server/utils/asyncio/sftelnetproxymuxer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 11fe4974..57644116 100755 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -213,6 +213,7 @@ class SFTelnetProxyMuxer: client.close() await client.wait_closed() except Exception as e: + log.debug(f"Closing client connect {client_info} failed {e}") log.debug("Debug message") From df907028eb9d5ac8bf5f886b127cb930c82002a4 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 01:20:56 -0500 Subject: [PATCH 12/25] First POC of working telnet proxy. Still needs work to add functionality of eixsting telnet_server.py but no longer throws exception when deleting project. --- gns3server/compute/base_node.py | 47 +--------------- .../utils/asyncio/sftelnetproxymuxer.py | 53 +++++++++++-------- 2 files changed, 34 insertions(+), 66 deletions(-) mode change 100755 => 100644 gns3server/utils/asyncio/sftelnetproxymuxer.py diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index 2503f31e..0141ec04 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -375,60 +375,17 @@ class BaseNode: if not self._wrap_console or self._console_type != "telnet": return - #remaining_trial = 60 - #log.info(f"Internal_console_port: {self._internal_console_port}") - # self._internal_console_port == qemu listner port for example - #while True: - # try: - # (self._wrap_console_reader, self._wrap_console_writer) = await asyncio.open_connection( - # host="127.0.0.1", - # port=self._internal_console_port - # ) - # break - # except (OSError, ConnectionRefusedError) as e: - # if remaining_trial <= 0: - # raise e - # await asyncio.sleep(0.1) - # remaining_trial -= 1 - ## no longer needed. SFTelnetProxyMuxer handles client handshake - #await AsyncioTelnetServer.write_client_intro(self._wrap_console_writer, echo=True) - #server = AsyncioTelnetServer( - # reader=self._wrap_console_reader, - # writer=self._wrap_console_writer, - # binary=True, - # echo=True #) server = SFTelnetProxyMuxer(binary=True, echo=True, remote_port=self._internal_console_port, listen_port=self.console) self._wrapper_telnet_server = await server.start_proxy() - # warning: this will raise OSError exception if there is a problem... - #log.info(f"self._manager.port_manager.console_host: {self._manager.port_manager.console_host}") - #log.info(f"self.console {self.console}") - # self._manager.port_manager.console_host == bind ip - # self.console == bind port - #self._wrapper_telnet_server = await asyncio.start_server( - # server.run, - # self._manager.port_manager.console_host, - # self.console - #) - async def stop_wrap_console(self): """ Stops the telnet proxy. """ - #if self._wrapper_telnet_server: - # self._wrap_console_writer.close() - # if sys.version_info >= (3, 7, 0): - # try: - # await self._wrap_console_writer.wait_closed() - # except ConnectionResetError: - # pass - # self._wrapper_telnet_server.close() - # await self._wrapper_telnet_server.wait_closed() - # self._wrapper_telnet_server = None - self._wrapper_telnet_server.shutdown() - self._wrapper_telnet_server = None + await self._wrapper_telnet_server.shutdown() + #self._wrapper_telnet_server = None async def reset_wrap_console(self): """ diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py old mode 100755 new mode 100644 index 57644116..5ef1e13d --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -1,7 +1,6 @@ import socket import asyncio import telnetlib3 -import pdb import logging log = logging.getLogger(__name__) @@ -13,11 +12,12 @@ log = logging.getLogger(__name__) #) class SFTelnetProxyMuxer: - def __init__(self, remote_ip=None, remote_port=None, listen_ip=None, listen_port=None, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None): + def __init__(self, remote_ip=None, remote_port=None, listen_ip=None, listen_port=None, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None, heartbeattimer=None): if remote_ip == None: remote_ip = '127.0.0.1' self.remote_ip = remote_ip self.remote_port = remote_port + # make the remote_info look like the same format as client_info later from sock('peername') self.remote_info = f"('{self.remote_ip}', {self.remote_port})" if listen_ip == None: listen_ip = '0.0.0.0' @@ -38,8 +38,11 @@ class SFTelnetProxyMuxer: raise ValueError("remote_port is a required value") if not listen_port: raise ValueError("listen_port is a required value") - log.debug("SFTelnetProxyMuxer init complete") - + # how often do we check the remote telnet server is up and each telnet client connected to gns3 is up. + self.heartbeattimer = heartbeattimer + if not heartbeattimer: + self.heartbeattimer = 30 + self.isshutdown = False async def handle_client(self, reader, writer): client_info = writer.get_extra_info('peername') @@ -51,10 +54,10 @@ class SFTelnetProxyMuxer: try: await asyncio.sleep(1) - while True: + while True and not self.isshutdown: try: # Set a timeout for the read operation, without should() the socket closes after timeout. - data = await asyncio.shield(asyncio.wait_for(reader.read((4*1024*1024)), timeout=2.0)) + data = await asyncio.shield(asyncio.wait_for(reader.read((4*1024*1024)), timeout=self.heartbeattimer)) if not data: log.debug(f"No data. Not sure if this is possible.") break @@ -75,7 +78,6 @@ class SFTelnetProxyMuxer: log.warning(f"No data read from {client_info}, send heartbeat to test client socket.") try: log.warning(f"Heatbeat: Are you there {client_info}?") - #pdb.set_trace() writer.send_iac(self.IAC + self.NOP) await writer.drain() continue @@ -126,7 +128,7 @@ class SFTelnetProxyMuxer: async def handle_remote_server(self): log.debug("Start handler for remote server") - while True: + while True and not self.isshutdown: await asyncio.sleep(1) try: self.remote_reader, self.remote_writer = await telnetlib3.open_connection( @@ -134,11 +136,11 @@ class SFTelnetProxyMuxer: ) sock = self.remote_writer.get_extra_info('socket') sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - while True: + while True and not self.isshutdown: try: #data = await self.remote_reader.read((4*1024*1024)) - data = await asyncio.shield(asyncio.wait_for(self.remote_reader.read((4*1024*1024)), timeout=2.0)) + data = await asyncio.shield(asyncio.wait_for(self.remote_reader.read((4*1024*1024)), timeout=self.heartbeattimer)) if self.remote_reader.at_eof(): log.info(f"Remote server {self.remote_info} closed tcp session with eof.") break @@ -189,20 +191,23 @@ class SFTelnetProxyMuxer: host=self.listen_ip, port=self.listen_port, shell=self.handle_client ) - async with self.server: - log.debug("Startup of telnet proxy complete.") - await self.server.wait_closed() + #async with self.server: + # log.debug("Startup of telnet proxy complete.") + # await self.server.wait_closed() + return self async def shutdown(self): - if self.remote_writer: + log.debug(f"Set shutdown") + self.isshutdown = True + + if self.server: try: - log.debug(f"Shuting down tcp session to {self.remote_server}") - self.remote_writer.close() + log.debug(f"Shuting down tcp listen port {self.remote_port}") + self.server.close() await self.server.wait_closed() except Exception as e: - log.debug(f"Failed to shutdown {self.remote_server}: {e}") - pass - + log.debug(f"Failed to shutdown listen port: {self.remote_port} {e}") + for client in self.clients: try: try: @@ -214,8 +219,14 @@ class SFTelnetProxyMuxer: await client.wait_closed() except Exception as e: log.debug(f"Closing client connect {client_info} failed {e}") - - log.debug("Debug message") + + if self.remote_writer: + try: + self.remote_writer.close() + #await self.remote_writer.wait_closed() + except Exception as e: + log.debug(f"Failed to shutdown listen port: {self.remote_info} {e}") + log.debug("No remaining work to do for shutdown.") if __name__ == "__main__": From 97e639a02fde17dd4e774204d6e7182214f9a8e8 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 01:37:24 -0500 Subject: [PATCH 13/25] Try to add POC for working telnet proxy muxer again. --- gns3server/compute/base_node.py | 5 +++-- gns3server/utils/asyncio/sftelnetproxymuxer.py | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index 0141ec04..d7652c47 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -384,8 +384,9 @@ class BaseNode: Stops the telnet proxy. """ - await self._wrapper_telnet_server.shutdown() - #self._wrapper_telnet_server = None + if self._wrapper_telnet_server: + await self._wrapper_telnet_server.shutdown() + #self._wrapper_telnet_server = None async def reset_wrap_console(self): """ diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 5ef1e13d..71c13402 100644 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -199,7 +199,6 @@ class SFTelnetProxyMuxer: async def shutdown(self): log.debug(f"Set shutdown") self.isshutdown = True - if self.server: try: log.debug(f"Shuting down tcp listen port {self.remote_port}") @@ -207,7 +206,7 @@ class SFTelnetProxyMuxer: await self.server.wait_closed() except Exception as e: log.debug(f"Failed to shutdown listen port: {self.remote_port} {e}") - + for client in self.clients: try: try: @@ -219,13 +218,13 @@ class SFTelnetProxyMuxer: await client.wait_closed() except Exception as e: log.debug(f"Closing client connect {client_info} failed {e}") - if self.remote_writer: try: self.remote_writer.close() #await self.remote_writer.wait_closed() except Exception as e: log.debug(f"Failed to shutdown listen port: {self.remote_info} {e}") + log.debug("No remaining work to do for shutdown.") if __name__ == "__main__": From 3a35f0b04d5ab179e17a1138b86abfaff1af0278 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 01:42:03 -0500 Subject: [PATCH 14/25] Add telnetlib3 to requirements. Following @KCarmichael lead on this one. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index df39d266..dc9c217e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ importlib-resources>=1.3; python_version < '3.9' truststore>=0.8.0; python_version >= '3.10' setuptools>=60.8.1; python_version >= '3.7' setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6 +telnetlib3>=2.0.4 From 2a1fbdb2ab49fa562397d84c10a1f7bc0c4840e2 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 01:49:09 -0500 Subject: [PATCH 15/25] add missing ; --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dc9c217e..8105ee30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ importlib-resources>=1.3; python_version < '3.9' truststore>=0.8.0; python_version >= '3.10' setuptools>=60.8.1; python_version >= '3.7' setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6 -telnetlib3>=2.0.4 +telnetlib3>=2.0.4; From b5d5c4a681e4b985cfed8efab8d7381d76a85cfb Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 01:53:27 -0500 Subject: [PATCH 16/25] I'll git this right any min now. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8105ee30..772b5f1e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ importlib-resources>=1.3; python_version < '3.9' truststore>=0.8.0; python_version >= '3.10' setuptools>=60.8.1; python_version >= '3.7' setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6 -telnetlib3>=2.0.4; +telnetlib3>=2.0.4; python_version >= '3.7' From 5c36e431ebbc165518f08f1f252436d6ff9948ae Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 02:03:26 -0500 Subject: [PATCH 17/25] Uhh... remove python 3.6 support? --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 6763ce51..4d1f5390 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-20.04 # Downgrade Ubuntu to 20.04 to fix missing Python 3.6 strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 From be82ac869b80cc4846a1c714d704727bcc0f7acf Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 02:11:19 -0500 Subject: [PATCH 18/25] Add note about telnetlib3 only supporting python 3.6 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 772b5f1e..e097043f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ importlib-resources>=1.3; python_version < '3.9' truststore>=0.8.0; python_version >= '3.10' setuptools>=60.8.1; python_version >= '3.7' setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6 -telnetlib3>=2.0.4; python_version >= '3.7' +telnetlib3>=2.0.4; python_version >= '3.7' # 2.0.0 only support 3.7 and higher. From bae8aee21129ce1ad4413d99e5584e41fe6ff780 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 02:44:20 -0500 Subject: [PATCH 19/25] Looks like even with a 30 second timeout you can still get no data. Change flow control from break to continue to keep main loop runnning. --- gns3server/utils/asyncio/sftelnetproxymuxer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 71c13402..cc20876e 100644 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -59,8 +59,8 @@ class SFTelnetProxyMuxer: # Set a timeout for the read operation, without should() the socket closes after timeout. data = await asyncio.shield(asyncio.wait_for(reader.read((4*1024*1024)), timeout=self.heartbeattimer)) if not data: - log.debug(f"No data. Not sure if this is possible.") - break + log.debug(f"No data from socket read, start over read loop.") + continue if reader.at_eof(): log.info(f"Client {client_info} closed tcp session with eof.") writer.close() From 4bfdc2049c3b5f4bc1bcb4255b3a08d7957ed222 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 11:19:24 -0500 Subject: [PATCH 20/25] Create patch branch that has orignal telnet_server.py in it as well as new telnet proxy muxer. --- gns3server/utils/asyncio/telnet_server.py | 42 +++-------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/gns3server/utils/asyncio/telnet_server.py b/gns3server/utils/asyncio/telnet_server.py index e81eba77..b8829847 100644 --- a/gns3server/utils/asyncio/telnet_server.py +++ b/gns3server/utils/asyncio/telnet_server.py @@ -62,7 +62,6 @@ READ_SIZE = 1024 class TelnetConnection(object): """Default implementation of telnet connection which may but may not be used.""" def __init__(self, reader, writer, window_size_changed_callback=None): - log.debug(f"Start TelnetConnection init") self.is_closing = False self._reader = reader self._writer = writer @@ -70,7 +69,6 @@ class TelnetConnection(object): @property def reader(self): - log.debug(f"Start TelnetConnection reader") return self._reader @property @@ -79,18 +77,15 @@ class TelnetConnection(object): async def connected(self): """Method called when client is connected""" - log.debug(f"Start TelnetConnection connected") pass async def disconnected(self): """Method called when client is disconnecting""" - log.debug(f"Start TelnetConnection disconnected") pass async def window_size_changed(self, columns, rows): """Method called when window size changed, only can occur when `naws` flag is enable in server configuration.""" - log.debug(f"Start TelnetConnection window_size_changed") if self._window_size_changed_callback: await self._window_size_changed_callback(columns, rows) @@ -100,14 +95,12 @@ class TelnetConnection(object): Handles incoming data :return: """ - log.debug(f"Start TelnetConnection feed") def send(self, data): """ Sending data back to client :return: """ - log.debug(f"Start TelnetConnection send") data = data.decode().replace("\n", "\r\n") self.writer.write(data.encode()) @@ -116,7 +109,6 @@ class TelnetConnection(object): Closes current connection :return: """ - log.debug(f"Start TelnetConnection close") self.is_closing = True @@ -124,7 +116,6 @@ class AsyncioTelnetServer: MAX_NEGOTIATION_READ = 10 def __init__(self, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None): - log.debug(f"Start AsyncioTelnetServer init") """ Initializes telnet server :param naws when True make a window size negotiation @@ -149,7 +140,6 @@ class AsyncioTelnetServer: self._naws = naws def default_connection_factory(reader, writer, window_size_changed_callback): - log.debug(f"Start connection factory") return TelnetConnection(reader, writer, window_size_changed_callback) if connection_factory is None: @@ -159,7 +149,6 @@ class AsyncioTelnetServer: @staticmethod async def write_client_intro(writer, echo=False): - log.debug(f"Start async write_clien_intro") # Send initial telnet session opening if echo: writer.write(bytes([IAC, WILL, ECHO])) @@ -170,7 +159,6 @@ class AsyncioTelnetServer: await writer.drain() async def _write_intro(self, writer, binary=False, echo=False, naws=False): - log.debug(f"Start async _write_intro") # Send initial telnet session opening if echo: writer.write(bytes([IAC, WILL, ECHO])) @@ -199,10 +187,14 @@ class AsyncioTelnetServer: async def run(self, network_reader, network_writer): - log.debug(f"Start async run") sock = network_writer.get_extra_info("socket") sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + # 60 sec keep alives, close tcp session after 4 missed + # Will keep a firewall from aging out telnet console. + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4) #log.debug("New connection from {}".format(sock.getpeername())) # Keep track of connected clients @@ -214,12 +206,9 @@ class AsyncioTelnetServer: await connection.connected() await self._process(network_reader, network_writer, connection) except ConnectionError: - log.info("Async ConnectionError") async with self._lock: network_writer.close() # await network_writer.wait_closed() # this doesn't work in Python 3.6 - log.debug(f"self._reader_process == network_reader") - log.debug(f"{self._reader_process} == {network_reader}") if self._reader_process == network_reader: self._reader_process = None # Cancel current read from this reader @@ -230,7 +219,6 @@ class AsyncioTelnetServer: del self._connections[network_writer] async def close(self): - log.debug(f"Start async close") for writer, connection in self._connections.items(): try: writer.write_eof() @@ -241,40 +229,29 @@ class AsyncioTelnetServer: continue async def client_connected_hook(self): - log.debug(f"Start async client_connected_hook") pass async def _get_reader(self, network_reader): """ Get a reader or None if another reader is already reading. """ - log.debug(f"Start async _get_reader") async with self._lock: if self._reader_process is None: self._reader_process = network_reader if self._reader: - log.debug(f"self._reader_process == network_reader") - log.debug(f"{self._reader_process} == {network_reader}") if self._reader_process == network_reader: self._current_read = asyncio.ensure_future(self._reader.read(READ_SIZE)) return self._current_read - - log.debug(f"_get_reader Returning None") return None async def _process(self, network_reader, network_writer, connection): - log.debug(f"Start async _process") network_read = asyncio.ensure_future(network_reader.read(READ_SIZE)) reader_read = await self._get_reader(network_reader) while True: - log.debug(f"__process True loop") if reader_read is None: - log.debug(f"__process reader_read is None") reader_read = await self._get_reader(network_reader) - log.debug(f"__process reader_read is still 2nd None") if reader_read is None: - log.debug(f"__process reader_read is still 3rd None") done, pending = await asyncio.wait( [ network_read, @@ -282,19 +259,13 @@ class AsyncioTelnetServer: timeout=1, return_when=asyncio.FIRST_COMPLETED) else: - log.debug(f"__process reader_read else") done, pending = await asyncio.wait( [ network_read, reader_read ], return_when=asyncio.FIRST_COMPLETED) - log.debug(f"__process just before coro done check") - log.debug(f"") - log.debug(f"") - log.debug(f"") for coro in done: - log.debug(f"__process coro can has done?") data = coro.result() if coro == network_read: if network_reader.at_eof(): @@ -332,7 +303,6 @@ class AsyncioTelnetServer: async def _read(self, cmd, buffer, location, reader): """ Reads next op from the buffer or reader""" - log.debug(f"Start async _read") try: op = buffer[location] cmd.append(op) @@ -345,7 +315,6 @@ class AsyncioTelnetServer: async def _negotiate(self, data, connection): """ Performs negotiation commands""" - log.debug(f"Start async _negotiate") command, payload = data[0], data[1:] if command == NAWS: @@ -358,7 +327,6 @@ class AsyncioTelnetServer: log.debug("Not supported negotiation sequence, received {} bytes", len(data)) async def _IAC_parser(self, buf, network_reader, network_writer, connection): - log.debug(f"Start async _IAC_parser") """ Processes and removes any Telnet commands from the buffer. From 83b1fdcd72ce73042eccfb695a53970f8ff3853b Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 21:30:43 -0500 Subject: [PATCH 21/25] Remove unused init options and no data check for remote server --- gns3server/utils/asyncio/sftelnetproxymuxer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index cc20876e..83ffaee1 100644 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -12,7 +12,7 @@ log = logging.getLogger(__name__) #) class SFTelnetProxyMuxer: - def __init__(self, remote_ip=None, remote_port=None, listen_ip=None, listen_port=None, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None, heartbeattimer=None): + def __init__(self, remote_ip=None, remote_port=None,listen_ip=None, listen_port=None, reader=None, writer=None, heartbeattimer=None): if remote_ip == None: remote_ip = '127.0.0.1' self.remote_ip = remote_ip @@ -141,6 +141,9 @@ class SFTelnetProxyMuxer: try: #data = await self.remote_reader.read((4*1024*1024)) data = await asyncio.shield(asyncio.wait_for(self.remote_reader.read((4*1024*1024)), timeout=self.heartbeattimer)) + if not data: + log.debug(f"No data from remote telnet server {self.remote_info}.") + continue if self.remote_reader.at_eof(): log.info(f"Remote server {self.remote_info} closed tcp session with eof.") break From b8df0bb39489d7a1c5be56ebbebe0c621e68c3c3 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 21:33:46 -0500 Subject: [PATCH 22/25] Set all logs to debug level and remove commented out log config. --- .../utils/asyncio/sftelnetproxymuxer.py | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 83ffaee1..12af9c8a 100644 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -4,13 +4,6 @@ import telnetlib3 import logging log = logging.getLogger(__name__) -# Configure logging -#log.basicConfig( -# level=log.DEBUG, # Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) -# format='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)', -# datefmt='%Y-%m-%d %H:%M:%S' -#) - class SFTelnetProxyMuxer: def __init__(self, remote_ip=None, remote_port=None,listen_ip=None, listen_port=None, reader=None, writer=None, heartbeattimer=None): if remote_ip == None: @@ -50,7 +43,6 @@ class SFTelnetProxyMuxer: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) log.debug(f"New client connected: {client_info}") self.clients.add(writer) - #log.debug(f"Write idle: {writer.protocol.idle}") try: await asyncio.sleep(1) @@ -62,7 +54,7 @@ class SFTelnetProxyMuxer: log.debug(f"No data from socket read, start over read loop.") continue if reader.at_eof(): - log.info(f"Client {client_info} closed tcp session with eof.") + log.debug(f"Client {client_info} closed tcp session with eof.") writer.close() self.clients.discard(writer) break @@ -75,32 +67,32 @@ class SFTelnetProxyMuxer: continue except asyncio.TimeoutError: - log.warning(f"No data read from {client_info}, send heartbeat to test client socket.") + log.debug(f"No data read from {client_info}, send heartbeat to test client socket.") try: - log.warning(f"Heatbeat: Are you there {client_info}?") + log.debug(f"Heatbeat: Are you there {client_info}?") writer.send_iac(self.IAC + self.NOP) await writer.drain() continue except asyncio.TimeoutError: - log.warning(f"Heatbeat: No reply from {client_info}, closing socket.") + log.debug(f"Heatbeat: No reply from {client_info}, closing socket.") writer.close() self.clients.discard(writer) break except Exception as e: - log.warning(f"Heateat: Unknown error from {client_info}, closing socket. Exeption {e}") + log.debug(f"Heateat: Unknown error from {client_info}, closing socket. Exeption {e}") writer.close() self.clients.discard(writer) break finally: - log.warning(f"Heatbeat: {client_info} Yes I am.") + log.debug(f"Heatbeat: {client_info} Yes I am.") except Exception as e: - log.exception(f"Error in handling data from client {client_info}:") + log.debyg(f"Error in handling data from client {client_info}:") writer.close() self.clients.discard(writer) break except Exception as e: - log.exception(f"Error in managing client {client_info}: {e}") + log.debug(f"Error in managing client {client_info}: {e}") finally: # Safely remove the writer from clients set and close the connection @@ -145,12 +137,12 @@ class SFTelnetProxyMuxer: log.debug(f"No data from remote telnet server {self.remote_info}.") continue if self.remote_reader.at_eof(): - log.info(f"Remote server {self.remote_info} closed tcp session with eof.") + log.debug(f"Remote server {self.remote_info} closed tcp session with eof.") break except asyncio.TimeoutError: - log.warning(f"No data from server {self.remote_info}, send heartbeat to test socket.") + log.debug(f"No data from server {self.remote_info}, send heartbeat to test socket.") try: - log.warning(f"Heatbeat: Are you there {self.remote_info}?") + log.debug(f"Heatbeat: Are you there {self.remote_info}?") # NOP and AYT cause QEMU to spam everyone's console with junk. # This causes everyone to close the session and eof tcp which makes me sad. # Will need to research more... or did i call this wrong and just fix it? @@ -158,11 +150,11 @@ class SFTelnetProxyMuxer: await self.remote_writer.drain() continue except Exception as e: - log.warning(f"Heateat: Unknown error from {self.remote_info}, closing socket. Exeption {e}") + log.debug(f"Heateat: Unknown error from {self.remote_info}, closing socket. Exeption {e}") self.remote_writer.close() break finally: - log.warning(f"Heatbeat: {self.remote_info} Yes I am.") + log.debug(f"Heatbeat: {self.remote_info} Yes I am.") except Exception as e: log.debug("Failed to read socket data exception: {e}") From 288c5660525e3480ea62b24fb36d2303c70aa6ca Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 21:40:50 -0500 Subject: [PATCH 23/25] add a . to comment. add a space to init. Add checks for reader and writer --- gns3server/utils/asyncio/sftelnetproxymuxer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 12af9c8a..292da0b1 100644 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -5,7 +5,7 @@ import logging log = logging.getLogger(__name__) class SFTelnetProxyMuxer: - def __init__(self, remote_ip=None, remote_port=None,listen_ip=None, listen_port=None, reader=None, writer=None, heartbeattimer=None): + def __init__(self, remote_ip=None, remote_port=None, listen_ip=None, listen_port=None, reader=None, writer=None, heartbeattimer=None): if remote_ip == None: remote_ip = '127.0.0.1' self.remote_ip = remote_ip @@ -16,6 +16,12 @@ class SFTelnetProxyMuxer: listen_ip = '0.0.0.0' self.listen_ip = listen_ip self.listen_port = listen_port + # use reader for remote server info + if reader: + self.reader = reader + # use writer for remote server info + if writer: + self.writer = writer self.clients = set() self.server = None self.remote_reader = None @@ -25,7 +31,7 @@ class SFTelnetProxyMuxer: self.IAC = b"\xff" # Interpret as Command # Telnet NOP command. Will be used as a heartbeat to clients. self.NOP = b"\xf1" - # Telnet Are You There + # Telnet Are You There. self.AYT = b"\xf6" if not remote_port: raise ValueError("remote_port is a required value") From 0e47ceb1bd61bd2ed77461a36cd4e0803f9f1285 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Wed, 31 Jan 2024 21:53:56 -0500 Subject: [PATCH 24/25] First attempt at supporting passing in reader and writer. --- gns3server/utils/asyncio/sftelnetproxymuxer.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 292da0b1..7ebac203 100644 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -129,11 +129,18 @@ class SFTelnetProxyMuxer: while True and not self.isshutdown: await asyncio.sleep(1) try: - self.remote_reader, self.remote_writer = await telnetlib3.open_connection( - host=self.remote_ip, port=self.remote_port - ) - sock = self.remote_writer.get_extra_info('socket') - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if self.remote_ip and self.remote_port: + self.remote_reader, self.remote_writer = await telnetlib3.open_connection( + host=self.remote_ip, port=self.remote_port + ) + sock = self.remote_writer.get_extra_info('socket') + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + elsif self.remote_reader and self.remote_writer: + if self.remote_reader.at_eof() or self.remote_writer.at_eof(): + break + else: + raise ValueError("Server state incorrect. self.remote_reader or self.remote_writer close (eof)." + while True and not self.isshutdown: try: From 351536ffed7d33b25b857ce1e85c4ce937da0e84 Mon Sep 17 00:00:00 2001 From: John Fleming Date: Fri, 2 Feb 2024 21:51:36 -0500 Subject: [PATCH 25/25] No idea what I changed here. Oops --- .../utils/asyncio/sftelnetproxymuxer.py | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/gns3server/utils/asyncio/sftelnetproxymuxer.py b/gns3server/utils/asyncio/sftelnetproxymuxer.py index 7ebac203..7ee74640 100644 --- a/gns3server/utils/asyncio/sftelnetproxymuxer.py +++ b/gns3server/utils/asyncio/sftelnetproxymuxer.py @@ -2,6 +2,7 @@ import socket import asyncio import telnetlib3 import logging +import pdb log = logging.getLogger(__name__) class SFTelnetProxyMuxer: @@ -127,19 +128,22 @@ class SFTelnetProxyMuxer: async def handle_remote_server(self): log.debug("Start handler for remote server") while True and not self.isshutdown: - await asyncio.sleep(1) + log.debug("main run loop") try: if self.remote_ip and self.remote_port: + log.debug(f"Looks like we're running a server {self.listen_ip}.") self.remote_reader, self.remote_writer = await telnetlib3.open_connection( host=self.remote_ip, port=self.remote_port ) sock = self.remote_writer.get_extra_info('socket') sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - elsif self.remote_reader and self.remote_writer: + elif self.remote_reader and self.remote_writer and not self.remote_ip and not self.remote_port: + log.debug(f"Looks like we're running with reader and writer.") if self.remote_reader.at_eof() or self.remote_writer.at_eof(): + log.debug(f"reader/writer EOFed.") break else: - raise ValueError("Server state incorrect. self.remote_reader or self.remote_writer close (eof)." + raise ValueError("Server state incorrect. self.remote_reader or self.remote_writer close (eof).") while True and not self.isshutdown: @@ -192,6 +196,8 @@ class SFTelnetProxyMuxer: log.debug(error_msg) await self.broadcast_to_clients(f"\r{error_msg}\n\r") + log.debug("end run loop") + async def start_proxy(self): log.debug("Starting telnet proxy.") asyncio.create_task(self.handle_remote_server()) @@ -235,16 +241,28 @@ class SFTelnetProxyMuxer: log.debug("No remaining work to do for shutdown.") + + if __name__ == "__main__": - ## Example usage - log.debug("Start proxy") - proxy = SFTelnetProxyMuxer(remote_ip='127.0.0.1', remote_port=7000, listen_ip='0.0.0.0', listen_port=8888) - try: - asyncio.wait_for(asyncio.run(proxy.start_proxy()), timeout=30) - except OSError as e: - log.debug(f"Can't start proxy: {e}") + async def main(): + logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s' + ) + #pdb.set_trace() + ## Example usage + log.debug("Start proxy") + + try: + #await asyncio.sleep(0) + server = SFTelnetProxyMuxer(remote_ip="10.1.18.100", remote_port=5003, listen_ip="0.0.0.0", listen_port=5000) + _wrapper_telnet_server = await server.start_proxy() + #await proxy.start_proxy() + except OSError as e: + log.debug(f"Can't start proxy: {e}") - # To shut down the proxy - # asyncio.run(proxy.shutdown()) + # To shut down the proxy + # asyncio.run(proxy.shutdown()) + asyncio.run(main())