1# This file is part of Xpra. 2# Copyright (C) 2012-2020 Antoine Martin <antoine@xpra.org> 3# Copyright (C) 2010 Nathaniel Smith <njs@pobox.com> 4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5# later version. See the file COPYING for details. 6 7import signal 8import threading 9 10from xpra.net.bytestreams import untilConcludes 11from xpra.util import repr_ellipsized, envint, envbool 12from xpra.os_util import hexstr, force_quit, POSIX 13from xpra.log import Logger 14 15log = Logger("proxy") 16 17SHOW_DATA = envbool("XPRA_PROXY_SHOW_DATA") 18PROXY_BUFFER_SIZE = envint("XPRA_PROXY_BUFFER_SIZE", 65536) 19 20 21def noretry(_e): 22 return False 23 24class XpraProxy: 25 """ 26 This is the proxy command that runs 27 when one uses the hidden subcommand 28 "xpra _proxy" or when forwarding data 29 using the tcp-proxy option. 30 It simply forwards stdin/stdout to 31 the server socket. 32 """ 33 34 def __repr__(self): 35 return "XpraProxy(%s: %s - %s)" % (self._name, self._client_conn, self._server_conn) 36 37 def __init__(self, name, client_conn, server_conn, quit_cb=None): 38 self._name = name 39 self._client_conn = client_conn 40 self._server_conn = server_conn 41 self._quit_cb = quit_cb or self.do_quit 42 self._closed = False 43 self._to_client = threading.Thread(target=self._to_client_loop) 44 self._to_client.setDaemon(True) 45 self._to_server = threading.Thread(target=self._to_server_loop) 46 self._to_server.setDaemon(True) 47 self._exit_code = 0 48 signal.signal(signal.SIGINT, self.signal_quit) 49 signal.signal(signal.SIGTERM, self.signal_quit) 50 if POSIX: 51 signal.signal(signal.SIGHUP, self.signal_quit) 52 signal.signal(signal.SIGPIPE, self.signal_quit) 53 54 def start_threads(self): 55 self._to_client.start() 56 self._to_server.start() 57 58 def run(self): 59 log("XpraProxy.run() %s", self._name) 60 self.start_threads() 61 self._to_client.join() 62 self._to_server.join() 63 log("XpraProxy.run() %s: all the threads have ended, calling quit() to close the connections", self._name) 64 self.quit() 65 66 def _to_client_loop(self): 67 self._copy_loop("<-server %s" % self._name, self._server_conn, self._client_conn) 68 self._closed = True 69 70 def _to_server_loop(self): 71 self._copy_loop("->server %s" % self._name, self._client_conn, self._server_conn) 72 self._closed = True 73 74 def _copy_loop(self, log_name, from_conn, to_conn): 75 #log("XpraProxy._copy_loop(%s, %s, %s)", log_name, from_conn, to_conn) 76 try: 77 while not self._closed: 78 log("%s: waiting for data", log_name) 79 buf = untilConcludes(self.is_active, noretry, from_conn.read, PROXY_BUFFER_SIZE) 80 if not buf: 81 log("%s: connection lost", log_name) 82 return 83 if SHOW_DATA: 84 log("%s: %s bytes: %s", log_name, len(buf), repr_ellipsized(buf)) 85 log("%s: %s", log_name, repr_ellipsized(hexstr(buf))) 86 while buf and not self._closed: 87 log("%s: writing %s bytes", log_name, len(buf)) 88 written = untilConcludes(self.is_active, noretry, to_conn.write, buf) 89 buf = buf[written:] 90 log("%s: written %s bytes", log_name, written) 91 log("%s copy loop ended", log_name) 92 except Exception: 93 log("%s", log_name, exc_info=True) 94 finally: 95 self.quit() 96 97 def is_active(self): 98 return not self._closed 99 100 def signal_quit(self, signum, _frame=None): 101 self._exit_code = 128+signum 102 self.quit() 103 104 def quit(self, *args): 105 log("XpraProxy.quit(%s) %s: closing connections", args, self._name) 106 self._closed = True 107 quit_cb = self._quit_cb 108 try: 109 self._client_conn.close() 110 except OSError: 111 pass 112 try: 113 self._server_conn.close() 114 except OSError: 115 pass 116 if quit_cb: 117 self._quit_cb = None 118 quit_cb(self) 119 120 def do_quit(self, _proxy): 121 force_quit(self._exit_code) 122