Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0003a02
Add unix domain socket for Windows
aisk Aug 4, 2025
2eb7917
Skip some tests on socketserver test
aisk Aug 4, 2025
49a7149
Fix more tests
aisk Aug 5, 2025
e1c5ceb
Detect if we have AF_UNIX header
aisk Aug 5, 2025
175a96e
Suppress compiler warning
aisk Aug 5, 2025
a70f834
Fix test in mp module
aisk Aug 5, 2025
5f93fbb
Disable reuse_address in http.server on Windows with AF_UNIX
aisk Aug 6, 2025
c759ee7
Fix build on wasi
aisk Aug 6, 2025
7ce46a7
Fix build
aisk Aug 6, 2025
4d45624
Fix test in logging
aisk Aug 7, 2025
fb21652
Skip testSurrogateescapeBind on Windows
aisk Aug 8, 2025
510ef3f
Skip another test
aisk Aug 9, 2025
dd07aec
Update tests
aisk Aug 11, 2025
9c68464
Using __has_include to detect header files
aisk Aug 11, 2025
f1a26bb
Merge branch 'main' into windows-unix-socket
aisk Feb 10, 2026
0db1880
Merge branch 'main' into windows-unix-socket
aisk Jun 23, 2026
ea593f7
Re-raise unexpected OSError in test_is_socket_on_windows
aisk Jun 23, 2026
13c8b06
fix typos
aisk Jun 23, 2026
cf4f112
update docs for AF_UNIX on Windows
aisk Jun 23, 2026
1e895c8
follow pep8
aisk Jun 23, 2026
603143e
match errno instead of error message
aisk Jun 23, 2026
d9b5e19
Update news entry
aisk Jun 23, 2026
16804b8
Merge remote-tracking branch 'upstream/main' into windows-unix-socket
aisk Jul 31, 2026
16bb71b
Skip copyfile socket tests on Windows
aisk Jul 31, 2026
31ae760
Cast whole expression to silence conversion warning
aisk Jul 31, 2026
4eb2094
Only define HAVE_AFUNIX_H when the header is available
aisk Jul 31, 2026
74812b6
Skip SO_REUSEADDR for AF_UNIX in socketserver instead of http.server
aisk Jul 31, 2026
7601b08
Improve skip reasons for Windows AF_UNIX tests
aisk Jul 31, 2026
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
4 changes: 4 additions & 0 deletions Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,10 @@ The AF_* and SOCK_* constants are now :class:`AddressFamily` and
defined then this protocol is unsupported. More constants may be available
depending on the system.

.. versionchanged:: next
:const:`AF_UNIX` is now available on Windows. It requires Windows 10
version 1803 or newer.

.. data:: AF_UNSPEC

:const:`AF_UNSPEC` means that
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,14 @@ shlex
(Contributed by Jay Berry in :gh:`148846`.)


socket
------

* Unix domain sockets (:const:`~socket.AF_UNIX`) are now supported on Windows,
which requires Windows 10 version 1803 or newer.
(Contributed by An Long in :gh:`77589`.)


symtable
--------

Expand Down
8 changes: 7 additions & 1 deletion Lib/socketserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,13 @@ def server_bind(self):
May be overridden.

"""
if self.allow_reuse_address and hasattr(socket, "SO_REUSEADDR"):
# On Windows, SO_REUSEADDR is not supported with AF_UNIX: setting
# it succeeds but the following bind() fails with WSAEOPNOTSUPP.
if (
self.allow_reuse_address and hasattr(socket, "SO_REUSEADDR")
and not (sys.platform == "win32" and hasattr(socket, "AF_UNIX")
and self.address_family == socket.AF_UNIX)
):
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Since Linux 6.12.9, SO_REUSEPORT is not allowed
# on other address families than AF_INET/AF_INET6.
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 @@ -5906,11 +5906,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 @@ -2042,6 +2042,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 @@ -1036,6 +1036,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 @@ -1073,6 +1076,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 @@ -2105,6 +2105,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 @@ -2860,6 +2860,8 @@ def test_is_socket_false(self):
@unittest.skipIf(
is_wasi, "Cannot create socket on WASI."
)
@unittest.skipIf(sys.platform == 'win32',
"socket file is a regular file on Windows")
def test_is_socket_true(self):
P = self.cls(self.base, 'mysock')
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Expand All @@ -2877,6 +2879,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', "Windows-specific behavior")
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))
raise
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
4 changes: 4 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,8 @@ def test_copyfile_named_pipe(self):
os.remove(TESTFN)

@socket_helper.skip_unless_bind_unix_socket
@unittest.skipIf(sys.platform == 'win32',
'socket file is not a special file on Windows')
def test_copyfile_socket(self):
sock_path = os.path.join(self.mkdtemp(), 'sock')
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Expand Down Expand Up @@ -1604,6 +1606,8 @@ def test_copyfile_symlink_to_named_pipe(self):

@os_helper.skip_unless_symlink
@socket_helper.skip_unless_bind_unix_socket
@unittest.skipIf(sys.platform == 'win32',
'socket file is not a special file on Windows')
def test_copyfile_symlink_to_socket(self):
sock_path = os.path.join(self.mkdtemp(), 'sock')
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Expand Down
28 changes: 27 additions & 1 deletion Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -5321,7 +5321,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 @@ -6378,10 +6378,20 @@ def bind(self, sock, path):
else:
raise

@unittest.skipIf(sys.platform == 'win32',
'getsockname() raises OSError on an unbound socket on '
'Windows')
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):
with self.assertRaises(OSError) as cm:
self.sock.getsockname()
self.assertEqual(cm.exception.winerror, errno.WSAEINVAL)

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

@unittest.skipIf(sys.platform == 'win32',
'Windows replaces invalid UTF-8 sequences in the path '
'with U+FFFD, so the socket file is created under a '
'different name')
def testSurrogateescapeBind(self):
# Test binding to a valid non-ASCII pathname, with the
# non-ASCII bytes supplied using surrogateescape encoding.
Expand All @@ -6405,6 +6419,10 @@ def testSurrogateescapeBind(self):
self.addCleanup(os_helper.unlink, path)
self.assertEqual(self.sock.getsockname(), path)

@unittest.skipIf(sys.platform == 'win32',
'Windows replaces invalid UTF-8 sequences in the path '
'with U+FFFD, so the socket file is created under a '
'different name')
def testUnencodableAddr(self):
# Test binding to a pathname that cannot be encoded in the
# file system encoding.
Expand All @@ -6417,10 +6435,18 @@ def testUnencodableAddr(self):

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

@unittest.skipUnless(sys.platform == 'win32',
'Windows-specific 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 @@ -223,6 +224,8 @@ def test_ForkingUDPServer(self):
self.dgram_examine)

@requires_unix_sockets
@unittest.skipIf(sys.platform == "win32",
"AF_UNIX with SOCK_DGRAM is not supported on Windows")
@unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions,
"gh-140702: Test fails regularly on iOS simulator on GitHub Actions")
def test_UnixDatagramServer(self):
Expand All @@ -231,6 +234,8 @@ def test_UnixDatagramServer(self):
self.dgram_examine)

@requires_unix_sockets
@unittest.skipIf(sys.platform == "win32",
"AF_UNIX with SOCK_DGRAM is not supported on Windows")
@unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions,
"gh-140702: Test fails regularly on iOS simulator on GitHub Actions")
def test_ThreadingUnixDatagramServer(self):
Expand All @@ -240,6 +245,8 @@ def test_ThreadingUnixDatagramServer(self):

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

@socket_helper.skip_unless_bind_unix_socket
@unittest.skipIf(sys.platform == 'win32',
'socket file is a regular file 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', 'Windows-specific behavior')
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,2 @@
Unix domain sockets (:const:`socket.AF_UNIX`) are now supported on Windows,
which requires Windows 10 version 1803 or newer. Patch by An Long.
4 changes: 3 additions & 1 deletion Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,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 @@ -1920,7 +1922,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 defined(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 @@ -673,6 +673,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. */
#ifdef __has_include
# if __has_include(<afunix.h>)
# define HAVE_AFUNIX_H 1
# endif
#endif

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

Expand Down
Loading