From 867cb21ba068a0dae02f5da1721f73fb9afabbfb Mon Sep 17 00:00:00 2001 From: Michal Srb Date: Mon, 31 Jul 2017 16:16:24 +0200 Subject: [PATCH 1/2] Add support for inetd. With the --inetd parameter, websockify doesn't require the source_addr and source_port paramters and it expects that stdin is already opened and listening socket. This way websockify can be used with (x)inetd or as a systemd socket-activated service. --- websockify/websocketproxy.py | 54 ++++++++++++++++++++++------------ websockify/websockifyserver.py | 28 +++++++++++------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/websockify/websocketproxy.py b/websockify/websocketproxy.py index 5a6d90e..6fd773a 100755 --- a/websockify/websocketproxy.py +++ b/websockify/websocketproxy.py @@ -297,12 +297,17 @@ class WebSocketProxy(websockifyserver.WebSockifyServer): else: dst_string = "%s:%s" % (self.target_host, self.target_port) - if self.token_plugin: - msg = " - proxying from %s:%s to targets generated by %s" % ( - self.listen_host, self.listen_port, type(self.token_plugin).__name__) + if self.listen_fd != None: + src_string = "inetd" else: - msg = " - proxying from %s:%s to %s" % ( - self.listen_host, self.listen_port, dst_string) + src_string = "%s:%s" % (self.listen_host, self.listen_port) + + if self.token_plugin: + msg = " - proxying from %s to targets generated by %s" % ( + src_string, type(self.token_plugin).__name__) + else: + msg = " - proxying from %s to %s" % ( + src_string, dst_string) if self.ssl_target: msg += " (using SSL)" @@ -389,6 +394,8 @@ def websockify_init(): help="connect to SSL target as SSL client") parser.add_option("--unix-target", help="connect to unix socket target", metavar="FILE") + parser.add_option("--inetd", + help="inetd mode, receive listening socket from stdin", action="store_true") parser.add_option("--web", default=None, metavar="DIR", help="run webserver on same port. Serve files from DIR.") parser.add_option("--wrap-mode", default="exit", metavar="MODE", @@ -457,15 +464,10 @@ def websockify_init(): del opts.target_cfg - # Sanity checks - if len(args) < 2 and not (opts.token_plugin or opts.unix_target): - parser.error("Too few arguments") if sys.argv.count('--'): opts.wrap_cmd = args[1:] else: opts.wrap_cmd = None - if len(args) > 2: - parser.error("Too many arguments") if not websockifyserver.ssl and opts.ssl_target: parser.error("SSL target requested and Python SSL module not loaded."); @@ -473,28 +475,42 @@ def websockify_init(): if opts.ssl_only and not os.path.exists(opts.cert): parser.error("SSL only and %s not found" % opts.cert) - # Parse host:port and convert ports to numbers - if args[0].count(':') > 0: - opts.listen_host, opts.listen_port = args[0].rsplit(':', 1) - opts.listen_host = opts.listen_host.strip('[]') + if opts.inetd: + opts.listen_fd = sys.stdin.fileno() else: - opts.listen_host, opts.listen_port = '', args[0] + if len(args) < 1: + parser.error("Too few arguments") + arg = args.pop(0) + # Parse host:port and convert ports to numbers + if arg.count(':') > 0: + opts.listen_host, opts.listen_port = arg.rsplit(':', 1) + opts.listen_host = opts.listen_host.strip('[]') + else: + opts.listen_host, opts.listen_port = '', arg - try: opts.listen_port = int(opts.listen_port) - except: parser.error("Error parsing listen port") + try: opts.listen_port = int(opts.listen_port) + except: parser.error("Error parsing listen port") + + del opts.inetd if opts.wrap_cmd or opts.unix_target or opts.token_plugin: opts.target_host = None opts.target_port = None else: - if args[1].count(':') > 0: - opts.target_host, opts.target_port = args[1].rsplit(':', 1) + if len(args) < 1: + parser.error("Too few arguments") + arg = args.pop(0) + if arg.count(':') > 0: + opts.target_host, opts.target_port = arg.rsplit(':', 1) opts.target_host = opts.target_host.strip('[]') else: parser.error("Error parsing target") try: opts.target_port = int(opts.target_port) except: parser.error("Error parsing target port") + if len(args) > 0 and opts.wrap_cmd == None: + parser.error("Too many arguments") + if opts.token_plugin is not None: if '.' not in opts.token_plugin: opts.token_plugin = ( diff --git a/websockify/websockifyserver.py b/websockify/websockifyserver.py index 4b1fbb0..b2be9dc 100644 --- a/websockify/websockifyserver.py +++ b/websockify/websockifyserver.py @@ -316,8 +316,8 @@ class WebSockifyServer(object): class Terminate(Exception): pass - def __init__(self, RequestHandlerClass, listen_host='', - listen_port=None, source_is_ipv6=False, + def __init__(self, RequestHandlerClass, listen_fd=None, + listen_host='', listen_port=None, source_is_ipv6=False, verbose=False, cert='', key='', ssl_only=None, daemon=False, record='', web='', file_only=False, @@ -328,6 +328,7 @@ class WebSockifyServer(object): # settings self.RequestHandlerClass = RequestHandlerClass self.verbose = verbose + self.listen_fd = listen_fd self.listen_host = listen_host self.listen_port = listen_port self.prefer_ipv6 = source_is_ipv6 @@ -371,8 +372,11 @@ class WebSockifyServer(object): # Show configuration self.msg("WebSocket server settings:") - self.msg(" - Listen on %s:%s", - self.listen_host, self.listen_port) + if self.listen_fd != None: + self.msg(" - Listen for inetd connections") + else: + self.msg(" - Listen on %s:%s", + self.listen_host, self.listen_port) if self.web: if self.file_only: self.msg(" - Web server (no directory listings). Web root: %s", self.web) @@ -669,12 +673,16 @@ class WebSockifyServer(object): is a WebSockets client then call new_websocket_client() method (which must be overridden) for each new client connection. """ - lsock = self.socket(self.listen_host, self.listen_port, False, - self.prefer_ipv6, - tcp_keepalive=self.tcp_keepalive, - tcp_keepcnt=self.tcp_keepcnt, - tcp_keepidle=self.tcp_keepidle, - tcp_keepintvl=self.tcp_keepintvl) + + if self.listen_fd != None: + lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM) + else: + lsock = self.socket(self.listen_host, self.listen_port, False, + self.prefer_ipv6, + tcp_keepalive=self.tcp_keepalive, + tcp_keepcnt=self.tcp_keepcnt, + tcp_keepidle=self.tcp_keepidle, + tcp_keepintvl=self.tcp_keepintvl) if self.daemon: keepfd = self.get_log_fd() From 2c0e8cb8f474360ce36624c1499a480fea376654 Mon Sep 17 00:00:00 2001 From: Michal Srb Date: Tue, 1 Aug 2017 15:35:34 +0200 Subject: [PATCH 2/2] Fix inetd mode on Python 2. In python 2 the ssl.wrap_socket doesn't work on sockets created using socket.fromfd. The workaround is to wrap the socket returned by socket.fromfd into another socket object using the private _sock constructor parameter. --- websockify/websockifyserver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/websockify/websockifyserver.py b/websockify/websockifyserver.py index b2be9dc..0d16540 100644 --- a/websockify/websockifyserver.py +++ b/websockify/websockifyserver.py @@ -676,6 +676,10 @@ class WebSockifyServer(object): if self.listen_fd != None: lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM) + if sys.hexversion < 0x3000000: + # For python 2 we have to wrap the "raw" socket into a socket object, + # otherwise ssl wrap_socket doesn't work. + lsock = socket.socket(_sock=lsock) else: lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6,