From 5c518f63d7a169326b572cfa5886b15a6876f4f1 Mon Sep 17 00:00:00 2001 From: Deepak Kodihalli Date: Mon, 23 Apr 2018 03:26:38 -0500 Subject: Expose host serial console over a websocket Expose host serial console over a "/console" route, using a websocket. An authenticated client can access the host serial console via reads/writes to this websocket. Change-Id: I0f63a3844e777d4f4c45194c85a63c9f10a91744 Signed-off-by: Deepak Kodihalli --- module/obmc/wsgi/apps/rest_dbus.py | 83 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) (limited to 'module/obmc') diff --git a/module/obmc/wsgi/apps/rest_dbus.py b/module/obmc/wsgi/apps/rest_dbus.py index 6374add..3eac4f4 100644 --- a/module/obmc/wsgi/apps/rest_dbus.py +++ b/module/obmc/wsgi/apps/rest_dbus.py @@ -44,6 +44,8 @@ if have_wsock: except ImportError: # python 3 from gi.repository import GObject as gobject import gevent + from gevent import socket + from gevent import Greenlet DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.DBus.Error.UnknownInterface' DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' @@ -916,6 +918,85 @@ class EventHandler(RouteHandler): notifier = EventNotifier(wsock, filters) +class HostConsoleHandler(RouteHandler): + ''' Handles the /console route, for clients to be able + read/write the host serial console. The way this is + done is by exposing a websocket that's mirrored to an + abstract UNIX domain socket, which is the source for + the console data. ''' + + verbs = ['GET'] + # Naming the route console0, because the numbering will help + # on multi-bmc/multi-host systems. + rules = ['/console0'] + + def __init__(self, app, bus): + super(HostConsoleHandler, self).__init__( + app, bus, self.verbs, self.rules) + + def find(self, **kw): + pass + + def setup(self, **kw): + pass + + def read_wsock(self, wsock, sock): + while True: + try: + incoming = wsock.receive() + if incoming: + # Read websocket, write to UNIX socket + sock.send(incoming) + except Exception as e: + sock.close() + return + + def read_sock(self, sock, wsock): + max_sock_read_len = 4096 + while True: + try: + outgoing = sock.recv(max_sock_read_len) + if outgoing: + # Read UNIX socket, write to websocket + wsock.send(outgoing) + except Exception as e: + wsock.close() + return + + def send_ping(self, wsock) : + # Most webservers close websockets after 60 seconds of + # inactivity. Make sure to send a ping before that. + timeout = 45 + payload = "ping" + # the ping payload can be anything, the receiver has to just + # return the same back. + while True: + gevent.sleep(timeout) + wsock.send_frame(payload, wsock.OPCODE_PING) + + def do_get(self): + wsock = request.environ.get('wsgi.websocket') + if not wsock: + abort(400, 'Expected WebSocket based request.') + + # A UNIX domain socket structure defines a 108-byte pathname. The + # server in this case, obmc-console-server, expects a 108-byte path. + socket_name = "\0obmc-console" + trailing_bytes = "\0" * (108 - len(socket_name)) + socket_path = socket_name + trailing_bytes + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + + try: + sock.connect(socket_path) + except Exception as e: + abort(500, str(e)) + + wsock_reader = Greenlet.spawn(self.read_wsock, wsock, sock) + sock_reader = Greenlet.spawn(self.read_sock, sock, wsock) + ping_sender = Greenlet.spawn(self.send_ping, wsock) + gevent.joinall([wsock_reader, sock_reader, ping_sender]) + + class ImagePutHandler(RouteHandler): ''' Handles the /upload/image/ route. ''' @@ -1386,6 +1467,7 @@ class App(Bottle): self.download_dump_get_handler = DownloadDumpHandler(self, self.bus) if self.have_wsock: self.event_handler = EventHandler(self, self.bus) + self.host_console_handler = HostConsoleHandler(self, self.bus) self.instance_handler = InstanceHandler(self, self.bus) def install_handlers(self): @@ -1402,6 +1484,7 @@ class App(Bottle): self.download_dump_get_handler.install() if self.have_wsock: self.event_handler.install() + self.host_console_handler.install() # this has to come last, since it matches everything self.instance_handler.install() -- cgit v1.2.1