From 66b8600dd53cb5bb3f7de666651caef92c48c81d Mon Sep 17 00:00:00 2001 From: Ananth Bhaskararaman Date: Fri, 7 Apr 2023 02:52:35 +0530 Subject: [PATCH] Add vsock wsgi server --- src/waitress/adjustments.py | 21 ++++++++++++- src/waitress/compat.py | 4 ++- src/waitress/server.py | 60 ++++++++++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/waitress/adjustments.py b/src/waitress/adjustments.py index f7dfc363..28e6df83 100644 --- a/src/waitress/adjustments.py +++ b/src/waitress/adjustments.py @@ -17,7 +17,7 @@ import socket import warnings -from .compat import HAS_IPV6, WIN +from .compat import CPYTHON, HAS_IPV6, LINUX, WIN from .proxy_headers import PROXY_HEADERS truthy = frozenset(("t", "true", "y", "yes", "on", "1")) @@ -130,6 +130,7 @@ class Adjustments: ("asyncore_use_poll", asbool), ("unix_socket", str), ("unix_socket_perms", asoctal), + ("vsock_socket", str), ("sockets", as_socket_list), ("channel_request_lookahead", int), ("server_name", str), @@ -254,6 +255,9 @@ class Adjustments: # Path to a Unix domain socket to use. unix_socket_perms = 0o600 + # Path to a vsock socket to use. + vsock_socket = None + # The socket options to set on receiving a connection. It is a list of # (level, optname, value) tuples. TCP_NODELAY disables the Nagle # algorithm for writes (Waitress already buffers its writes). @@ -302,12 +306,27 @@ def __init__(self, **kw): if "sockets" in kw and "unix_socket" in kw: raise ValueError("unix_socket may not be set if sockets is set") + if "sockets" in kw and "vsock_socket" in kw: + raise ValueError("vsock_socket may not be set if sockets is set") + if "unix_socket" in kw and ("host" in kw or "port" in kw): raise ValueError("unix_socket may not be set if host or port is set") if "unix_socket" in kw and "listen" in kw: raise ValueError("unix_socket may not be set if listen is set") + if "vsock_socket" in kw and not (LINUX and CPYTHON): + raise ValueError("vsock_socket is not supported on this platform") + + if "vsock_socket" in kw and ("host" in kw or "port" in kw): + raise ValueError("vsock_socket may not be set if host or port is set") + + if "vsock_socket" in kw and "listen" in kw: + raise ValueError("vsock_socket may not be set if listen is set") + + if "vsock_socket" in kw and "unix_socket" in kw: + raise ValueError("vsock_socket may not be set if unix_socket is set") + if "send_bytes" in kw: warnings.warn( "send_bytes will be removed in a future release", DeprecationWarning diff --git a/src/waitress/compat.py b/src/waitress/compat.py index 67543b9c..fd177fb6 100644 --- a/src/waitress/compat.py +++ b/src/waitress/compat.py @@ -6,8 +6,10 @@ import sys import warnings -# True if we are running on Windows +# Platform detection. WIN = platform.system() == "Windows" +LINUX = platform.system() == "Linux" +CPYTHON = platform.python_implementation() == "CPython" MAXINT = sys.maxsize HAS_IPV6 = socket.has_ipv6 diff --git a/src/waitress/server.py b/src/waitress/server.py index a4730848..4bdae205 100644 --- a/src/waitress/server.py +++ b/src/waitress/server.py @@ -68,6 +68,18 @@ def create_server( sockinfo=sockinfo, ) + if adj.vsock_socket and hasattr(socket, "AF_VSOCK"): + sockinfo = (socket.AF_VSOCK, socket.SOCK_STREAM, None, None) + return VsockWSGIServer( + application, + map, + _start, + _sock, + dispatcher=dispatcher, + adj=adj, + sockinfo=sockinfo, + ) + effective_listen = [] last_serv = None if not adj.sockets: @@ -118,6 +130,20 @@ def create_server( effective_listen.append( (last_serv.effective_host, last_serv.effective_port) ) + elif hasattr(socket, "AF_VSOCK") and sock.family == socket.AF_VSOCK: + last_serv = VsockWSGIServer( + application, + map, + _start, + sock, + dispatcher=dispatcher, + adj=adj, + bind_socket=False, + sockinfo=sockinfo, + ) + effective_listen.append( + (last_serv.effective_host, last_serv.effective_port) + ) # We are running a single server, so we can just return the last server, # saves us from having to create one more object @@ -376,7 +402,6 @@ def set_socket_options(self, conn): if hasattr(socket, "AF_UNIX"): - class UnixWSGIServer(BaseWSGIServer): def __init__( self, @@ -415,6 +440,39 @@ def getsockname(self): def fix_addr(self, addr): return ("localhost", None) +if hasattr(socket, "AF_VSOCK"): + class VsockWSGIServer(BaseWSGIServer): + def __init__( + self, + application, + map=None, + _start=True, # test shim + _sock=None, # test shim + dispatcher=None, # dispatcher + adj=None, # adjustments + sockinfo=None, # opaque object + **kw + ): + if sockinfo is None: + sockinfo = (socket.AF_VSOCK, socket.SOCK_STREAM, None, None) + + super().__init__( + application, + map=map, + _start=_start, + _sock=_sock, + dispatcher=dispatcher, + adj=adj, + sockinfo=sockinfo, + **kw, + ) + + def bind_server_socket(self): + self.bind(self.adj.vsock_socket) + + def getsockname(self): + return ("vsock", self.socket.getsockname()) + # Compatibility alias. WSGIServer = TcpWSGIServer