From 63100e8fd081b0081f8e0d50c13f768dc048ce56 Mon Sep 17 00:00:00 2001 From: Paul Monson Date: Thu, 13 Jun 2019 17:50:51 -0700 Subject: [PATCH 1/2] bpo-33408: Enable AF_UNIX support in Windows --- Lib/socket.py | 17 ++++++++++++++--- Lib/socketserver.py | 4 ++++ Lib/test/_test_multiprocessing.py | 2 +- Lib/test/test_asyncio/test_base_events.py | 2 ++ Lib/test/test_asyncore.py | 7 ++++++- Lib/test/test_pathlib.py | 1 + Lib/test/test_socket.py | 11 +++++++++-- Lib/test/test_socketserver.py | 3 +++ Lib/test/test_stat.py | 1 + Modules/socketmodule.c | 2 ++ Modules/socketmodule.h | 2 ++ PC/pyconfig.h | 3 +++ 12 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py index 3016b03104d45f..5672195064409f 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -494,11 +494,14 @@ def socketpair(family=None, type=SOCK_STREAM, proto=0): else: # Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain. - def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): + def socketpair(family=AF_UNIX, type=SOCK_STREAM, proto=0): if family == AF_INET: host = _LOCALHOST elif family == AF_INET6: host = _LOCALHOST_V6 + elif family == AF_UNIX: + from uuid import uuid4 + host = os.path.join(os.environ['TEMP'], str(uuid4())) else: raise ValueError("Only AF_INET and AF_INET6 socket address families " "are supported") @@ -511,7 +514,10 @@ def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): # setblocking(False) that prevents us from having to create a thread. lsock = socket(family, type, proto) try: - lsock.bind((host, 0)) + if family == AF_UNIX: + lsock.bind(host) + else: + lsock.bind((host, 0)) lsock.listen() # On IPv6, ignore flow_info and scope_id addr, port = lsock.getsockname()[:2] @@ -519,7 +525,10 @@ def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): try: csock.setblocking(False) try: - csock.connect((addr, port)) + if family == AF_UNIX: + csock.connect(host) + else: + csock.connect((addr, port)) except (BlockingIOError, InterruptedError): pass csock.setblocking(True) @@ -529,6 +538,8 @@ def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): raise finally: lsock.close() + if family == AF_UNIX: + os.unlink(host) return (ssock, csock) __all__.append("socketpair") diff --git a/Lib/socketserver.py b/Lib/socketserver.py index 905df9319e2fa8..adf8dc95f260a9 100644 --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -447,6 +447,10 @@ def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket.socket(self.address_family, self.socket_type) + + if sys.platform == 'win32' and self.address_family == socket.AF_UNIX: + self.allow_reuse_address = False + if bind_and_activate: try: self.server_bind() diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 2fe0def2bcd277..1f0ba95a36ca72 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -4631,7 +4631,7 @@ def test_invalid_family(self): @unittest.skipUnless(WIN32, "skipped on non-Windows platforms") def test_invalid_family_win32(self): - with self.assertRaises(ValueError): + with self.assertRaises(OSError): multiprocessing.connection.Listener('/var/test.pipe') # diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 08d4792fa72653..d13ad1ab17dc19 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1687,6 +1687,7 @@ def test_create_datagram_endpoint_sock(self): self.assertEqual('CLOSED', protocol.state) @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets') + @unittest.skipIf(sys.platform == 'win32', 'datagrams not supported') def test_create_datagram_endpoint_sock_unix(self): fut = self.loop.create_datagram_endpoint( lambda: MyDatagramProto(create_future=True, loop=self.loop), @@ -1698,6 +1699,7 @@ def test_create_datagram_endpoint_sock_unix(self): self.assertEqual('CLOSED', protocol.state) @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets') + @unittest.skipIf(sys.platform == 'win32', 'datagrams not supported') def test_create_datagram_endpoint_existing_sock_unix(self): with test_utils.unix_socket_path() as path: sock = socket.socket(socket.AF_UNIX, type=socket.SOCK_DGRAM) diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py index 3fcedb58ec18a6..7940ad28afcace 100644 --- a/Lib/test/test_asyncore.py +++ b/Lib/test/test_asyncore.py @@ -469,7 +469,12 @@ class BaseServer(asyncore.dispatcher): def __init__(self, family, addr, handler=BaseTestHandler): asyncore.dispatcher.__init__(self) self.create_socket(family) - self.set_reuse_addr() + if sys.platform == 'win32' and family == socket.AF_UNIX: + # calling set_reuse_addr() on Windows with family AF_UNIX results in: + # OSError: [WinError 10045] The attempted operation is not supported for the type of object referenced + pass + else: + self.set_reuse_addr() bind_af_aware(self.socket, addr) self.listen(5) self.handler = handler diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 069467a8459f00..33484f760b061e 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1978,6 +1978,7 @@ def test_is_socket_false(self): self.assertIs((P / 'fileA\x00').is_socket(), False) @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") + @unittest.skipIf(sys.platform == 'win32', "stat doesn't detect sockets") def test_is_socket_true(self): P = self.cls(BASE, 'mysock') sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 2705eff4794e70..5fe1555686f7b8 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -5276,8 +5276,14 @@ def bind(self, sock, path): raise def testUnbound(self): - # Issue #30205 (note getsockname() can return None on OS X) - self.assertIn(self.sock.getsockname(), ('', None)) + if sys.platform == 'win32': + # Getting the name of unbound socket on Windows + # raises an exception + self.assertRaises(OSError, self.sock.getsockname) + else: + # Issue #30205 (note getsockname() can return None on OS X) + name = self.sock.getsockname() + self.assertIn(name, ('', None)) def testStrAddr(self): # Test binding to and retrieving a normal string pathname. @@ -5293,6 +5299,7 @@ def testBytesAddr(self): self.addCleanup(support.unlink, path) self.assertEqual(self.sock.getsockname(), path) + @unittest.skipIf(sys.platform == 'win32', "ASCII path name required on Windows") def testSurrogateescapeBind(self): # Test binding to a valid non-ASCII pathname, with the # non-ASCII bytes supplied using surrogateescape encoding. diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index 8aed4b61a23744..f54a60a50be5fe 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -8,6 +8,7 @@ import select import signal import socket +import sys import tempfile import threading import unittest @@ -232,12 +233,14 @@ def test_ForkingUDPServer(self): self.dgram_examine) @requires_unix_sockets + @unittest.skipIf(sys.platform == 'win32', "no datagram support") def test_UnixDatagramServer(self): self.run_server(socketserver.UnixDatagramServer, socketserver.DatagramRequestHandler, self.dgram_examine) @requires_unix_sockets + @unittest.skipIf(sys.platform == 'win32', "no datagram support") def test_ThreadingUnixDatagramServer(self): self.run_server(socketserver.ThreadingUnixDatagramServer, socketserver.DatagramRequestHandler, diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index 17443bed0738b5..895a75ba305961 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -194,6 +194,7 @@ def test_devices(self): break @skip_unless_bind_unix_socket + @unittest.skipIf(sys.platform == 'win32', "stat and lstat don't detect sockets") def test_socket(self): with socket.socket(socket.AF_UNIX) as s: s.bind(TESTFN) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 73e64f235a6f2d..334159bb5c53b6 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -357,6 +357,8 @@ typedef struct { /* IMPORTANT: make sure the list ordered by descending build_number */ static FlagRuntimeInfo win_runtime_flags[] = { + /* available starting with Windows 10 1803 */ + {17134, "AF_UNIX"}, /* available starting with Windows 10 1709 */ {16299, "TCP_KEEPIDLE"}, {16299, "TCP_KEEPINTVL"}, diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index dff1f8f4e9ce4a..f3e53912d052f0 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -41,6 +41,8 @@ typedef int socklen_t; #ifdef HAVE_SYS_UN_H # include +#elif HAVE_AFUNIX_H +# include #else # undef AF_UNIX #endif diff --git a/PC/pyconfig.h b/PC/pyconfig.h index 9228923cbea831..66904623289222 100644 --- a/PC/pyconfig.h +++ b/PC/pyconfig.h @@ -610,6 +610,9 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */ /* Define if you have the header file. */ /* #define HAVE_SYS_UN_H 1 */ +/* Define if you have the header file. */ +#define HAVE_AFUNIX_H 1 + /* Define if you have the header file. */ /* #define HAVE_SYS_UTIME_H 1 */ From 6c9c1bd4c10efa3e7a5a855ecb86ba0fcbb1a150 Mon Sep 17 00:00:00 2001 From: Paul Monson Date: Wed, 17 Jul 2019 14:47:36 -0700 Subject: [PATCH 2/2] socketpair use AF_UNIX if available --- Lib/socket.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/socket.py b/Lib/socket.py index 5672195064409f..20e42764876e9b 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -494,7 +494,12 @@ def socketpair(family=None, type=SOCK_STREAM, proto=0): else: # Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain. - def socketpair(family=AF_UNIX, type=SOCK_STREAM, proto=0): + def socketpair(family=None, type=SOCK_STREAM, proto=0): + if family is None: + try: + family = AF_UNIX + except NameError: + family = AF_INET if family == AF_INET: host = _LOCALHOST elif family == AF_INET6: