Skip to content

gh-77589: Add unix domain socket for Windows #137420

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions Lib/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ class HTTPServer(socketserver.TCPServer):
allow_reuse_address = True # Seems to make sense in testing environment
allow_reuse_port = False

def __init__(self, *args, **kwargs):
if sys.platform == 'win32' and hasattr(socket, 'AF_UNIX') and\
self.address_family == socket.AF_UNIX:
# reuse address with AF_UNIX is not supported on Windows
self.allow_reuse_address = False

super().__init__(*args, **kwargs)

def server_bind(self):
"""Override server_bind to store the server name."""
socketserver.TCPServer.server_bind(self)
Expand Down
5 changes: 0 additions & 5 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5536,11 +5536,6 @@ def test_invalid_family(self):
with self.assertRaises(ValueError):
multiprocessing.connection.Listener(r'\\.\test')

@unittest.skipUnless(WIN32, "skipped on non-Windows platforms")
def test_invalid_family_win32(self):
with self.assertRaises(ValueError):
multiprocessing.connection.Listener('/var/test.pipe')

#
# Issue 12098: check sys.flags of child matches that for parent
#
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 @@ -1939,6 +1939,8 @@ 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', 'AF_UNIX support for asyncio is '
'not implemented on Windows for now')
def test_create_datagram_endpoint_sock_unix(self):
fut = self.loop.create_datagram_endpoint(
lambda: MyDatagramProto(create_future=True, loop=self.loop),
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,9 @@ def test_create_server_reuse_port(self):
server.close()

def _make_unix_server(self, factory, **kwargs):
if sys.platform == 'win32':
raise unittest.SkipTest('AF_UNIX support for asyncio is not '
'implemented on Windows for now')
path = test_utils.gen_unix_socket_path()
self.addCleanup(lambda: os.path.exists(path) and os.unlink(path))

Expand Down Expand Up @@ -1072,6 +1075,8 @@ def test_create_unix_server(self):
server.close()

@unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets')
@unittest.skipIf(sys.platform == 'win32', 'AF_UNIX support for asyncio is '
'not implemented on Windows for now')
def test_create_unix_server_path_socket_error(self):
proto = MyProto(loop=self.loop)
sock = socket.socket()
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_asyncio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ def gen_unix_socket_path():

@contextlib.contextmanager
def unix_socket_path():
if sys.platform == 'win32':
raise unittest.SkipTest('AF_UNIX support for asyncio is not '
'implemented on Windows for now')
path = gen_unix_socket_path()
try:
yield path
Expand All @@ -255,6 +258,9 @@ def unix_socket_path():

@contextlib.contextmanager
def run_test_unix_server(*, use_ssl=False):
if sys.platform == 'win32':
raise unittest.SkipTest('AF_UNIX support for asyncio is not '
'implemented on Windows for now')
with unix_socket_path() as path:
yield from _run_test_server(address=path, use_ssl=use_ssl,
server_cls=SilentUnixWSGIServer,
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,8 @@ def test_output(self):
self.assertEqual(self.log_output, b'<11>h\xc3\xa4m-sp\xc3\xa4m')

def test_udp_reconnection(self):
if self.server_exception:
self.skipTest(self.server_exception)
logger = logging.getLogger("slh")
self.sl_hdlr.close()
self.handled.clear()
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_pathlib/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2747,6 +2747,8 @@ def test_is_socket_false(self):
@unittest.skipIf(
is_wasi, "Cannot create socket on WASI."
)
@unittest.skipIf(sys.platform=='win32',
"detecting if file is socket is not supported by Windows")
def test_is_socket_true(self):
P = self.cls(self.base, 'mysock')
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Expand All @@ -2763,6 +2765,25 @@ def test_is_socket_true(self):
self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False)
self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False)

@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required")
@unittest.skipUnless(sys.platform=='win32',
"socket file on Windows is a normal file")
def test_is_socket_on_windows(self):
P = self.cls(self.base, 'mysock')
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.addCleanup(sock.close)
try:
sock.bind(str(P))
except OSError as e:
if (isinstance(e, PermissionError) or
"AF_UNIX path too long" in str(e)):
self.skipTest("cannot bind Unix socket: " + str(e))
self.assertFalse(P.is_socket())
self.assertFalse(P.is_fifo())
self.assertTrue(P.is_file())
self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False)
self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False)

def test_is_block_device_false(self):
P = self.cls(self.base)
self.assertFalse((P / 'fileA').is_block_device())
Expand Down
22 changes: 21 additions & 1 deletion Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -5131,7 +5131,7 @@ def __init__(self, methodName='runTest'):

def _check_defaults(self, sock):
self.assertIsInstance(sock, socket.socket)
if hasattr(socket, 'AF_UNIX'):
if sys.platform != 'win32' and hasattr(socket, 'AF_UNIX'):
self.assertEqual(sock.family, socket.AF_UNIX)
else:
self.assertEqual(sock.family, socket.AF_INET)
Expand Down Expand Up @@ -6188,10 +6188,17 @@ def bind(self, sock, path):
else:
raise

@unittest.skipIf(sys.platform == 'win32',
'Windows will raise Error if is not bound')
def testUnbound(self):
# Issue #30205 (note getsockname() can return None on OS X)
self.assertIn(self.sock.getsockname(), ('', None))

@unittest.skipUnless(sys.platform == 'win32',
'Windows-specific behavior')
def test_unbound_on_windows(self):
self.assertRaisesRegex(OSError, 'WinError 10022', self.sock.getsockname)

def testStrAddr(self):
# Test binding to and retrieving a normal string pathname.
path = os.path.abspath(os_helper.TESTFN)
Expand All @@ -6206,6 +6213,8 @@ def testBytesAddr(self):
self.addCleanup(os_helper.unlink, path)
self.assertEqual(self.sock.getsockname(), path)

@unittest.skipIf(sys.platform == 'win32',
'surrogateescape file path is not supported on Windows')
def testSurrogateescapeBind(self):
# Test binding to a valid non-ASCII pathname, with the
# non-ASCII bytes supplied using surrogateescape encoding.
Expand All @@ -6215,6 +6224,9 @@ def testSurrogateescapeBind(self):
self.addCleanup(os_helper.unlink, path)
self.assertEqual(self.sock.getsockname(), path)

@unittest.skipIf(sys.platform == 'win32',
'Windows have a bug which can\'t unlink sock file with '
'TESTFN_UNENCODABLE in it\'s name')
def testUnencodableAddr(self):
# Test binding to a pathname that cannot be encoded in the
# file system encoding.
Expand All @@ -6227,10 +6239,18 @@ def testUnencodableAddr(self):

@unittest.skipIf(sys.platform in ('linux', 'android'),
'Linux behavior is tested by TestLinuxAbstractNamespace')
@unittest.skipIf(sys.platform == 'win32',
'Windows allow bind on empty path')
def testEmptyAddress(self):
# Test that binding empty address fails.
self.assertRaises(OSError, self.sock.bind, "")

@unittest.skipUnless(sys.platform == 'win32',
'Windows-specified behavior')
def test_empty_address_on_windows(self):
self.sock.bind('')
self.assertEqual(self.sock.getsockname(), '')


class BufferIOTest(SocketConnectedTest):
"""
Expand Down
7 changes: 7 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 threading
import unittest
import socketserver
Expand Down Expand Up @@ -218,18 +219,24 @@ def test_ForkingUDPServer(self):
self.dgram_examine)

@requires_unix_sockets
@unittest.skipIf(sys.platform=="win32",
"Unix with Dadagram is not supported on Windows")
def test_UnixDatagramServer(self):
self.run_server(socketserver.UnixDatagramServer,
socketserver.DatagramRequestHandler,
self.dgram_examine)

@requires_unix_sockets
@unittest.skipIf(sys.platform=="win32",
"Unix with Dadagram is not supported on Windows")
def test_ThreadingUnixDatagramServer(self):
self.run_server(socketserver.ThreadingUnixDatagramServer,
socketserver.DatagramRequestHandler,
self.dgram_examine)

@requires_unix_sockets
@unittest.skipIf(sys.platform=="win32",
"Unix with Dadagram is not supported on Windows")
@requires_forking
def test_ForkingUnixDatagramServer(self):
self.run_server(socketserver.ForkingUnixDatagramServer,
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/test_stat.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,23 @@ def test_devices(self):
break

@socket_helper.skip_unless_bind_unix_socket
@unittest.skipIf(sys.platform=='win32', "didn't work on Windows")
def test_socket(self):
with socket.socket(socket.AF_UNIX) as s:
s.bind(TESTFN)
st_mode, modestr = self.get_mode()
self.assertEqual(modestr[0], 's')
self.assertS_IS("SOCK", st_mode)

@socket_helper.skip_unless_bind_unix_socket
@unittest.skipUnless(sys.platform=='win32', "didn't work on Windows")
def test_socket_on_windows(self):
with socket.socket(socket.AF_UNIX) as s:
s.bind(TESTFN)
st_mode, modestr = self.get_mode()
self.assertNotEqual(modestr[0], 's')
self.assertS_IS("REG", st_mode)

def test_module_attributes(self):
for key, value in self.stat_struct.items():
modvalue = getattr(self.statmod, key)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add Unix domain socket on Windows. Patched by AN Long.
4 changes: 3 additions & 1 deletion Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,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 Expand Up @@ -1900,7 +1902,7 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
addr->sun_path[path.len] = 0;

/* including the tailing NUL */
*len_ret = path.len + offsetof(struct sockaddr_un, sun_path) + 1;
*len_ret = (int)path.len + offsetof(struct sockaddr_un, sun_path) + 1;
}
addr->sun_family = s->sock_family;
memcpy(addr->sun_path, path.buf, path.len);
Expand Down
2 changes: 2 additions & 0 deletions Modules/socketmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,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
7 changes: 7 additions & 0 deletions PC/pyconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,13 @@ 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. */
#if defined(__has_include) && __has_include(<afunix.h>)
#define HAVE_AFUNIX_H 1
#else
#define HAVE_AFUNIX_H 0
#endif

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

Expand Down
Loading