novnc / websockify

Websockify is a WebSocket to TCP proxy/bridge. This allows a browser to connect to any application/server/service.
GNU Lesser General Public License v3.0
3.88k stars 768 forks source link

Support for Listening to unix socket [+ git patch for v0.10.0] #539

Open shiomax opened 1 year ago

shiomax commented 1 year ago

I´m using websockify and two other services behind NGINX in a docker container. Listening to an IP in this setup is not ideal. Allocates an extra port and a Unix socket should also be slightly faster.

I´ve already implemented a patch for websockify v0.10.0.

This would add two more options

  --unix-listen=FILE    listen to unix socket
  --unix-listen-mode=LISTEN_SOCK_MODE
                        specify mode for unix socket (defaults to 0600)

I used 0600 as the default access modifier as that's what tigervnc seems to default to.

Can work on a merge request if this seems fine to you.

You can apply the patch against either the git tag v0.10.0 or the tar releases (but I have not tested if this works with the latest commit) Either with

diff --git a/websockify/websocketproxy.py b/websockify/websocketproxy.py
index 09d7882..16199f0 100644
--- a/websockify/websocketproxy.py
+++ b/websockify/websocketproxy.py
@@ -11,7 +11,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates

 '''

-import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl
+import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl, stat
 from socketserver import ThreadingMixIn
 from http.server import HTTPServer

@@ -112,7 +112,9 @@ Traffic Legend:
                              self.server.target_host, self.server.target_port, e)
             raise self.CClose(1011, "Failed to connect to downstream server")

-        self.request.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+        # Option unavailable when listening to unix socket
+        if not self.server.listen_sock:
+            self.request.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
         if not self.server.wrap_cmd and not self.server.unix_target:
             tsock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)

@@ -467,6 +469,12 @@ def websockify_init():
     parser.add_option("--ssl-ciphers", action="store",
             help="list of ciphers allowed for connection. For a list of "
             "supported ciphers run `openssl ciphers`")
+    parser.add_option("--unix-listen",
+            dest="listen_sock",
+            help="listen to unix socket", metavar="FILE")
+    parser.add_option("--unix-listen-mode",
+            dest="listen_sock_mode", default=None,
+            help="specify mode for unix socket (defaults to 0600)")
     parser.add_option("--unix-target",
             help="connect to unix socket target", metavar="FILE")
     parser.add_option("--inetd",
@@ -617,6 +625,16 @@ def websockify_init():

     if opts.inetd:
         opts.listen_fd = sys.stdin.fileno()
+    elif opts.listen_sock:
+        if opts.listen_sock_mode:
+            try:
+                # Parse octal notation (like 750)
+                opts.listen_sock_mode = int(opts.listen_sock_mode, 8)
+            except ValueError:
+                parser.error("Error parsing listen unix socket mode")
+        else:
+            # Default to 0600 (Owner Read/Write)
+            opts.listen_sock_mode = stat.S_IREAD | stat.S_IWRITE
     else:
         if len(args) < 1:
             parser.error("Too few arguments")
diff --git a/websockify/websockifyserver.py b/websockify/websockifyserver.py
index 0199e42..948c34b 100644
--- a/websockify/websockifyserver.py
+++ b/websockify/websockifyserver.py
@@ -325,12 +325,15 @@ class WebSockifyServer():
             file_only=False,
             run_once=False, timeout=0, idle_timeout=0, traffic=False,
             tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
-            tcp_keepintvl=None, ssl_ciphers=None, ssl_options=0):
+            tcp_keepintvl=None, ssl_ciphers=None, ssl_options=0,
+            listen_sock=None, listen_sock_mode=None):

         # settings
         self.RequestHandlerClass = RequestHandlerClass
         self.verbose        = verbose
         self.listen_fd      = listen_fd
+        self.listen_sock         = listen_sock
+        self.listen_sock_mode    = listen_sock_mode
         self.listen_host    = listen_host
         self.listen_port    = listen_port
         self.prefer_ipv6    = source_is_ipv6
@@ -387,6 +390,8 @@ class WebSockifyServer():
         self.msg("WebSocket server settings:")
         if self.listen_fd != None:
             self.msg("  - Listen for inetd connections")
+        elif self.listen_sock != None:
+            self.msg("  - Listen on unix socket %s", self.listen_sock)
         else:
             self.msg("  - Listen on %s:%s",
                     self.listen_host, self.listen_port)
@@ -700,6 +705,17 @@ class WebSockifyServer():

         if self.listen_fd != None:
             lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM)
+        elif self.listen_sock != None:
+            # Make sure the socket does not already exist
+            try:
+                os.unlink(self.listen_sock)
+            except OSError:
+                if os.path.exists(self.listen_sock):
+                    raise
+            lsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            lsock.bind(self.listen_sock)
+            os.chmod(self.listen_sock, self.listen_sock_mode)
+            lsock.listen(100)
         else:
             lsock = self.socket(self.listen_host, self.listen_port, False,
                                 self.prefer_ipv6,
@@ -766,6 +782,9 @@ class WebSockifyServer():
                             ready = select.select([lsock], [], [], 1)[0]
                             if lsock in ready:
                                 startsock, address = lsock.accept()
+                                # Unix Socket will not report address (empty string), but address[0] is logged a bunch
+                                if self.listen_sock != None:
+                                    address = [ self.listen_sock ]
                             else:
                                 continue
                         except self.Terminate:
CendioOssman commented 1 year ago

Seems reasonable. Please send this as a pull request and we can look at the details.