Skip to content

bpo-33408: Enable AF_UNIX support in Windows #14823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions Lib/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,11 +494,19 @@ 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=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:
host = _LOCALHOST_V6
elif family == AF_UNIX:
from uuid import uuid4
host = os.path.join(os.environ['TEMP'], str(uuid4()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not a fan of this - can we use unnamed sockets instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abstract sockets (starting from NULL in the name) is another options.
Web says that Windows supports it.

else:
raise ValueError("Only AF_INET and AF_INET6 socket address families "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update this message?

"are supported")
Expand All @@ -511,15 +519,21 @@ 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]
csock = socket(family, type, proto)
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)
Expand All @@ -529,6 +543,8 @@ def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
raise
finally:
lsock.close()
if family == AF_UNIX:
os.unlink(host)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't like this at all :)

return (ssock, csock)
__all__.append("socketpair")

Expand Down
4 changes: 4 additions & 0 deletions Lib/socketserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

#
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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)
Expand Down
7 changes: 6 additions & 1 deletion Lib/test/test_asyncore.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_reuse_addr seems to handle all OSError silently - why do we need to skip it here?

pass
else:
self.set_reuse_addr()
bind_af_aware(self.socket, addr)
self.listen(5)
self.handler = handler
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make more sense than returning None? Which is apparently a valid return value

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.
Expand All @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_socketserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import select
import signal
import socket
import sys
import tempfile
import threading
import unittest
Expand Down Expand Up @@ -232,12 +233,14 @@ def test_ForkingUDPServer(self):
self.dgram_examine)

@requires_unix_sockets
@unittest.skipIf(sys.platform == 'win32', "no datagram support")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing a lot of these - is there any more specific way to detect datagram support? The socket module already has a number of conditional constants, so maybe one of those will help?

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,
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_stat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
2 changes: 2 additions & 0 deletions Modules/socketmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ typedef int socklen_t;

#ifdef HAVE_SYS_UN_H
# include <sys/un.h>
#elif HAVE_AFUNIX_H
# include <afunix.h>
#else
# undef AF_UNIX
#endif
Expand Down
3 changes: 3 additions & 0 deletions PC/pyconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sys/un.h> header file. */
/* #define HAVE_SYS_UN_H 1 */

/* Define if you have the <afunix.h> header file. */
#define HAVE_AFUNIX_H 1

/* Define if you have the <sys/utime.h> header file. */
/* #define HAVE_SYS_UTIME_H 1 */

Expand Down