# This file is part of Xpra. # Copyright (C) 2012-2020 Antoine Martin # Copyright (C) 2010 Nathaniel Smith # Xpra is released under the terms of the GNU GPL v2, or, at your option, any # later version. See the file COPYING for details. import signal import threading from xpra.net.bytestreams import untilConcludes from xpra.util import repr_ellipsized, envint, envbool from xpra.os_util import hexstr, force_quit, POSIX from xpra.log import Logger log = Logger("proxy") SHOW_DATA = envbool("XPRA_PROXY_SHOW_DATA") PROXY_BUFFER_SIZE = envint("XPRA_PROXY_BUFFER_SIZE", 65536) def noretry(_e): return False class XpraProxy: """ This is the proxy command that runs when one uses the hidden subcommand "xpra _proxy" or when forwarding data using the tcp-proxy option. It simply forwards stdin/stdout to the server socket. """ def __repr__(self): return "XpraProxy(%s: %s - %s)" % (self._name, self._client_conn, self._server_conn) def __init__(self, name, client_conn, server_conn, quit_cb=None): self._name = name self._client_conn = client_conn self._server_conn = server_conn self._quit_cb = quit_cb or self.do_quit self._closed = False self._to_client = threading.Thread(target=self._to_client_loop) self._to_client.setDaemon(True) self._to_server = threading.Thread(target=self._to_server_loop) self._to_server.setDaemon(True) self._exit_code = 0 signal.signal(signal.SIGINT, self.signal_quit) signal.signal(signal.SIGTERM, self.signal_quit) if POSIX: signal.signal(signal.SIGHUP, self.signal_quit) signal.signal(signal.SIGPIPE, self.signal_quit) def start_threads(self): self._to_client.start() self._to_server.start() def run(self): log("XpraProxy.run() %s", self._name) self.start_threads() self._to_client.join() self._to_server.join() log("XpraProxy.run() %s: all the threads have ended, calling quit() to close the connections", self._name) self.quit() def _to_client_loop(self): self._copy_loop("<-server %s" % self._name, self._server_conn, self._client_conn) self._closed = True def _to_server_loop(self): self._copy_loop("->server %s" % self._name, self._client_conn, self._server_conn) self._closed = True def _copy_loop(self, log_name, from_conn, to_conn): #log("XpraProxy._copy_loop(%s, %s, %s)", log_name, from_conn, to_conn) try: while not self._closed: log("%s: waiting for data", log_name) buf = untilConcludes(self.is_active, noretry, from_conn.read, PROXY_BUFFER_SIZE) if not buf: log("%s: connection lost", log_name) return if SHOW_DATA: log("%s: %s bytes: %s", log_name, len(buf), repr_ellipsized(buf)) log("%s: %s", log_name, repr_ellipsized(hexstr(buf))) while buf and not self._closed: log("%s: writing %s bytes", log_name, len(buf)) written = untilConcludes(self.is_active, noretry, to_conn.write, buf) buf = buf[written:] log("%s: written %s bytes", log_name, written) log("%s copy loop ended", log_name) except Exception: log("%s", log_name, exc_info=True) finally: self.quit() def is_active(self): return not self._closed def signal_quit(self, signum, _frame=None): self._exit_code = 128+signum self.quit() def quit(self, *args): log("XpraProxy.quit(%s) %s: closing connections", args, self._name) self._closed = True quit_cb = self._quit_cb try: self._client_conn.close() except OSError: pass try: self._server_conn.close() except OSError: pass if quit_cb: self._quit_cb = None quit_cb(self) def do_quit(self, _proxy): force_quit(self._exit_code)