Telnet console resize support for Docker VM.

This commit is contained in:
grossmj 2018-11-27 15:06:56 +07:00
parent c0a0a13bdd
commit 60ac6d2dfe
2 changed files with 30 additions and 12 deletions

View File

@ -442,6 +442,7 @@ class DockerVM(BaseNode):
if self.console_type == "telnet": if self.console_type == "telnet":
yield from self._start_console() yield from self._start_console()
elif self.console_type == "http" or self.console_type == "https": elif self.console_type == "http" or self.console_type == "https":
yield from self._start_http() 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))) 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 @asyncio.coroutine
def _start_console(self): def _start_console(self):
""" """
@ -614,8 +628,7 @@ class DockerVM(BaseNode):
output_stream = asyncio.StreamReader() output_stream = asyncio.StreamReader()
input_stream = InputStream() 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)
try: try:
self._telnet_servers.append((yield from asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console))) self._telnet_servers.append((yield from asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console)))
except OSError as e: except OSError as e:
@ -625,7 +638,6 @@ class DockerVM(BaseNode):
input_stream.ws = self._console_websocket 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") 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_ensure_future(self._read_console_output(self._console_websocket, output_stream))
@asyncio.coroutine @asyncio.coroutine

View File

@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import asyncio import asyncio
import asyncio.subprocess import asyncio.subprocess
import struct import struct
@ -62,10 +62,11 @@ READ_SIZE = 1024
class TelnetConnection(object): class TelnetConnection(object):
"""Default implementation of telnet connection which may but may not be used.""" """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.is_closing = False
self._reader = reader self._reader = reader
self._writer = writer self._writer = writer
self._window_size_changed_callback = window_size_changed_callback
@property @property
def reader(self): def reader(self):
@ -85,10 +86,13 @@ class TelnetConnection(object):
"""Method called when client is disconnecting""" """Method called when client is disconnecting"""
pass pass
@asyncio.coroutine
def window_size_changed(self, columns, rows): def window_size_changed(self, columns, rows):
"""Method called when window size changed, only can occur when """Method called when window size changed, only can occur when
`naws` flag is enable in server configuration.""" `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 @asyncio.coroutine
def feed(self, data): def feed(self, data):
@ -116,7 +120,7 @@ class TelnetConnection(object):
class AsyncioTelnetServer: class AsyncioTelnetServer:
MAX_NEGOTIATION_READ = 10 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 Initializes telnet server
:param naws when True make a window size negotiation :param naws when True make a window size negotiation
@ -131,6 +135,7 @@ class AsyncioTelnetServer:
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
self._reader_process = None self._reader_process = None
self._current_read = None self._current_read = None
self._window_size_changed_callback = window_size_changed_callback
self._binary = binary self._binary = binary
# If echo is true when the client send data # If echo is true when the client send data
@ -139,8 +144,8 @@ class AsyncioTelnetServer:
self._echo = echo self._echo = echo
self._naws = naws self._naws = naws
def default_connection_factory(reader, writer): def default_connection_factory(reader, writer, window_size_changed_callback):
return TelnetConnection(reader, writer) return TelnetConnection(reader, writer, window_size_changed_callback)
if connection_factory is None: if connection_factory is None:
connection_factory = default_connection_factory connection_factory = default_connection_factory
@ -190,7 +195,7 @@ class AsyncioTelnetServer:
@asyncio.coroutine @asyncio.coroutine
def run(self, network_reader, network_writer): def run(self, network_reader, network_writer):
# Keep track of connected clients # 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 self._connections[network_writer] = connection
try: try:
@ -307,6 +312,7 @@ class AsyncioTelnetServer:
cmd.append(buffer[location]) cmd.append(buffer[location])
return op return op
@asyncio.coroutine
def _negotiate(self, data, connection): def _negotiate(self, data, connection):
""" Performs negotiation commands""" """ Performs negotiation commands"""
@ -314,7 +320,7 @@ class AsyncioTelnetServer:
if command == NAWS: if command == NAWS:
if len(payload) == 4: if len(payload) == 4:
columns, rows = struct.unpack(str('!HH'), bytes(payload)) columns, rows = struct.unpack(str('!HH'), bytes(payload))
connection.window_size_changed(columns, rows) yield from connection.window_size_changed(columns, rows)
else: else:
log.warning('Wrong number of NAWS bytes') log.warning('Wrong number of NAWS bytes')
else: else:
@ -373,7 +379,7 @@ class AsyncioTelnetServer:
break break
# SE command is followed by IAC, remove the last two operations from stack # 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 # This must be a 3-byte TELNET command
else: else: