From 60ac6d2dfeecef9fbf59018ba69d3e4e9b9673de Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 27 Nov 2018 15:06:56 +0700 Subject: [PATCH] Telnet console resize support for Docker VM. --- gns3server/compute/docker/docker_vm.py | 18 ++++++++++++++--- gns3server/utils/asyncio/telnet_server.py | 24 ++++++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 3790c5f6..a8625895 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -442,6 +442,7 @@ class DockerVM(BaseNode): if self.console_type == "telnet": yield from self._start_console() + elif self.console_type == "http" or self.console_type == "https": yield from self._start_http() @@ -592,6 +593,19 @@ class DockerVM(BaseNode): ]) self._telnet_servers.append((yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console))) + @asyncio.coroutine + def _window_size_changed_callback(self, columns, rows): + """ + Called when the console window size has been changed. + (when naws is enabled in the Telnet server) + + :param columns: number of columns + :param rows: number of rows + """ + + # resize the container TTY. + yield from self._manager.query("POST", "containers/{}/resize?h={}&w={}".format(self._cid, rows, columns)) + @asyncio.coroutine def _start_console(self): """ @@ -614,8 +628,7 @@ class DockerVM(BaseNode): output_stream = asyncio.StreamReader() input_stream = InputStream() - - telnet = AsyncioTelnetServer(reader=output_stream, writer=input_stream, echo=True) + 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((yield from asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console))) except OSError as e: @@ -625,7 +638,6 @@ class DockerVM(BaseNode): 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") - asyncio_ensure_future(self._read_console_output(self._console_websocket, output_stream)) @asyncio.coroutine diff --git a/gns3server/utils/asyncio/telnet_server.py b/gns3server/utils/asyncio/telnet_server.py index 9223fc16..1548d6b1 100644 --- a/gns3server/utils/asyncio/telnet_server.py +++ b/gns3server/utils/asyncio/telnet_server.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re + import asyncio import asyncio.subprocess import struct @@ -62,10 +62,11 @@ READ_SIZE = 1024 class TelnetConnection(object): """Default implementation of telnet connection which may but may not be used.""" - def __init__(self, reader, writer): + def __init__(self, reader, writer, window_size_changed_callback=None): self.is_closing = False self._reader = reader self._writer = writer + self._window_size_changed_callback = window_size_changed_callback @property def reader(self): @@ -85,10 +86,13 @@ class TelnetConnection(object): """Method called when client is disconnecting""" pass + @asyncio.coroutine def window_size_changed(self, columns, rows): """Method called when window size changed, only can occur when `naws` flag is enable in server configuration.""" - pass + + if self._window_size_changed_callback: + yield from self._window_size_changed_callback(columns, rows) @asyncio.coroutine def feed(self, data): @@ -116,7 +120,7 @@ class TelnetConnection(object): class AsyncioTelnetServer: MAX_NEGOTIATION_READ = 10 - def __init__(self, reader=None, writer=None, binary=True, echo=False, naws=False, connection_factory=None): + def __init__(self, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None): """ Initializes telnet server :param naws when True make a window size negotiation @@ -131,6 +135,7 @@ class AsyncioTelnetServer: self._lock = asyncio.Lock() self._reader_process = None self._current_read = None + self._window_size_changed_callback = window_size_changed_callback self._binary = binary # If echo is true when the client send data @@ -139,8 +144,8 @@ class AsyncioTelnetServer: self._echo = echo self._naws = naws - def default_connection_factory(reader, writer): - return TelnetConnection(reader, writer) + def default_connection_factory(reader, writer, window_size_changed_callback): + return TelnetConnection(reader, writer, window_size_changed_callback) if connection_factory is None: connection_factory = default_connection_factory @@ -190,7 +195,7 @@ class AsyncioTelnetServer: @asyncio.coroutine def run(self, network_reader, network_writer): # Keep track of connected clients - connection = self._connection_factory(network_reader, network_writer) + connection = self._connection_factory(network_reader, network_writer, self._window_size_changed_callback) self._connections[network_writer] = connection try: @@ -307,6 +312,7 @@ class AsyncioTelnetServer: cmd.append(buffer[location]) return op + @asyncio.coroutine def _negotiate(self, data, connection): """ Performs negotiation commands""" @@ -314,7 +320,7 @@ class AsyncioTelnetServer: if command == NAWS: if len(payload) == 4: columns, rows = struct.unpack(str('!HH'), bytes(payload)) - connection.window_size_changed(columns, rows) + yield from connection.window_size_changed(columns, rows) else: log.warning('Wrong number of NAWS bytes') else: @@ -373,7 +379,7 @@ class AsyncioTelnetServer: break # SE command is followed by IAC, remove the last two operations from stack - self._negotiate(negotiation[0:-2], connection) + yield from self._negotiate(negotiation[0:-2], connection) # This must be a 3-byte TELNET command else: