8000 bpo-37228: Fix loop.create_datagram_endpoint()'s usage of SO_REUSEADD… · python/cpython@79c2974 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 79c2974

Browse files
miss-islingtonaeros
authored andcommitted
bpo-37228: Fix loop.create_datagram_endpoint()'s usage of SO_REUSEADDR (GH-17311) (#17529)
(cherry picked from commit ab513a3) Co-authored-by: Kyle Stanley <aeros167@gmail.com>
1 parent b22183f commit 79c2974

File tree

4 files changed

+67
-29
lines changed

4 files changed

+67
-29
lines changed

Doc/library/asyncio-eventloop.rst

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,21 @@ Opening network connections
461461
reuse_address=None, reuse_port=None, \
462462
allow_broadcast=None, sock=None)
463463

464+
.. note::
465+
The parameter *reuse_address* is no longer supported, as using
466+
:py:data:`~sockets.SO_REUSEADDR` poses a significant security concern for
467+
UDP. Explicitly passing ``reuse_address=True`` will raise an exception.
468+
469+
When multiple processes with differing UIDs assign sockets to an
470+
indentical UDP socket address with ``SO_REUSEADDR``, incoming packets can
471+
become randomly distributed among the sockets.
472+
473+
For supported platforms, *reuse_port* can be used as a replacement for
474+
similar functionality. With *reuse_port*,
475+
:py:data:`~sockets.SO_REUSEPORT` is used instead, which specifically
476+
prevents processes with differing UIDs from assigning sockets to the same
477+
socket address.
478+
464479
Create a datagram connection.
465480

466481
The socket family can be either :py:data:`~socket.AF_INET`,
@@ -489,11 +504,6 @@ Opening network connections
489504
resolution. If given, these should all be integers from the
490505
corresponding :mod:`socket` module constants.
491506

492-
* *reuse_address* tells the kernel to reuse a local socket in
493-
``TIME_WAIT`` state, without waiting for its natural timeout to
494-
expire. If not specified will automatically be set to ``True`` on
495-
Unix.
496-
497507
* *reuse_port* tells the kernel to allow this endpoint to be bound to the
498508
same port as other existing endpoints are bound to, so long as they all
499509
set this flag when being created. This option is not supported on Windows
@@ -515,6 +525,10 @@ Opening network connections
515525
The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
516526
*allow_broadcast*, and *sock* parameters were added.
517527

528+
.. versionchanged:: 3.8.1
529+
The *reuse_address* parameter is no longer supported due to security
530+
concerns.
531+
518532
.. versionchanged:: 3.8
519533
Added support for Windows.
520534

Lib/asyncio/base_events.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@
6666
# Maximum timeout passed to select to avoid OS limitations
6767
MAXIMUM_SELECT_TIMEOUT = 24 * 3600
6868

69+
# Used for deprecation and removal of `loop.create_datagram_endpoint()`'s
70+
# *reuse_address* parameter
71+
_unset = object()
72+
6973

7074
def _format_handle(handle):
7175
cb = handle._callback
@@ -1201,7 +1205,7 @@ async def start_tls(self, transport, protocol, sslcontext, *,
12011205
async def create_datagram_endpoint(self, protocol_factory,
12021206
local_addr=None, remote_addr=None, *,
12031207
family=0, proto=0, flags=0,
1204-
reuse_address=None, reuse_port=None,
1208+
reuse_address=_unset, reuse_port=None,
12051209
allow_broadcast=None, sock=None):
12061210
"""Create datagram connection."""
12071211
if sock is not None:
@@ -1210,7 +1214,7 @@ async def create_datagram_endpoint(self, protocol_factory,
12101214
f'A UDP Socket was expected, got {sock!r}')
12111215
if (local_addr or remote_addr or
12121216
family or proto or flags or
1213-
reuse_address or reuse_port or allow_broadcast):
1217+
reuse_port or allow_broadcast):
12141218
# show the problematic kwargs in exception msg
12151219
opts = dict(local_addr=local_addr, remote_addr=remote_addr,
12161220
family=family, proto=proto, flags=flags,
@@ -1277,8 +1281,18 @@ async def create_datagram_endpoint(self, protocol_factory,
12771281

12781282
exceptions = []
12791283

1280-
if reuse_address is None:
1281-
reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
1284+
# bpo-37228
1285+
if reuse_address is not _unset:
1286+
if reuse_address:
1287+
raise ValueError("Passing `reuse_address=True` is no "
1288+
"longer supported, as the usage of "
1289+
"SO_REUSEPORT in UDP poses a significant "
1290+
"security concern.")
1291+
else:
1292+
warnings.warn("The *reuse_address* parameter has been "
1293+
"deprecated as of 3.5.10 and is scheduled "
1294+
"for removal in 3.11.", DeprecationWarning,
1295+
stacklevel=2)
12821296

12831297
for ((family, proto),
12841298
(local_address, remote_address)) in addr_pairs_info:
@@ -1287,9 +1301,6 @@ async def create_datagram_endpoint(self, protocol_factory,
12871301
try:
12881302
sock = socket.socket(
12891303
family=family, type=socket.SOCK_DGRAM, proto=proto)
1290-
if reuse_address:
1291-
sock.setsockopt(
1292-
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
12931304
if reuse_port:
12941305
_set_reuseport(sock)
12951306
if allow_broadcast:

Lib/test/test_asyncio/test_base_events.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,10 +1732,6 @@ class FakeSock:
17321732
MyDatagramProto, flags=1, sock=FakeSock())
17331733
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
17341734

1735-
fut = self.loop.create_datagram_endpoint(
1736-
MyDatagramProto, reuse_address=True, sock=FakeSock())
1737-
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
1738-
17391735
fut = self.loop.create_datagram_endpoint(
17401736
MyDatagramProto, reuse_port=True, sock=FakeSock())
17411737
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
@@ -1746,7 +1742,6 @@ class FakeSock:
17461742

17471743
def test_create_datagram_endpoint_sockopts(self):
17481744
# Socket options should not be applied unless asked for.
1749-
# SO_REUSEADDR defaults to on for UNIX.
17501745
# SO_REUSEPORT is not available on all platforms.
17511746

17521747
coro = self.loop.create_datagram_endpoint(
@@ -1755,18 +1750,8 @@ def test_create_datagram_endpoint_sockopts(self):
17551750
transport, protocol = self.loop.run_until_complete(coro)
17561751
sock = transport.get_extra_info('socket')
17571752

1758-
reuse_address_default_on = (
1759-
os.name == 'posix' and sys.platform != 'cygwin')
17601753
reuseport_supported = hasattr(socket, 'SO_REUSEPORT')
17611754

1762-
if reuse_address_default_on:
1763-
self.assertTrue(
1764-
sock.getsockopt(
1765-
socket.SOL_SOCKET, socket.SO_REUSEADDR))
1766-
else:
1767-
self.assertFalse(
1768-
sock.getsockopt(
1769-
socket.SOL_SOCKET, socket.SO_REUSEADDR))
17701755
if reuseport_supported:
17711756
self.assertFalse(
17721757
sock.getsockopt(
@@ -1782,13 +1767,12 @@ def test_create_datagram_endpoint_sockopts(self):
17821767
coro = self.loop.create_datagram_endpoint(
17831768
lambda: MyDatagramProto(create_future=True, loop=self.loop),
17841769
local_addr=('127.0.0.1', 0),
1785-
reuse_address=True,
17861770
reuse_port=reuseport_supported,
17871771
allow_broadcast=True)
17881772
transport, protocol = self.loop.run_until_complete(coro)
17891773
sock = transport.get_extra_info('socket')
17901774

1791-
self.assertTrue(
1775+
self.assertFalse(
17921776
sock.getsockopt(
17931777
socket.SOL_SOCKET, socket.SO_REUSEADDR))
17941778
if reuseport_supported:
@@ -1803,6 +1787,29 @@ def test_create_datagram_endpoint_sockopts(self):
18031787
self.loop.run_until_complete(protocol.done)
18041788
self.assertEqual('CLOSED', protocol.state)
18051789

1790+
def test_create_datagram_endpoint_reuse_address_error(self):
1791+
# bpo-37228: Ensure that explicit passing of `reuse_address=True`
1792+
# raises an error, as it is not safe to use SO_REUSEADDR when using UDP
1793+
1794+
coro = self.loop.create_datagram_endpoint(
1795+
lambda: MyDatagramProto(create_future=True, loop=self.loop),
1796+
local_addr=('127.0.0.1', 0),
1797+
reuse_address=True)
1798+
1799+
with self.assertRaises(ValueError):
1800+
self.loop.run_until_complete(coro)
1801+
1802+
def test_create_datagram_endpoint_reuse_address_warning(self):
1803+
# bpo-37228: Deprecate *reuse_address* parameter
1804+
1805+
coro = self.loop.create_datagram_endpoint(
1806+
lambda: MyDatagramProto(create_future=True, loop=self.loop),
1807+
local_addr=('127.0.0.1', 0),
1808+
reuse_address=False)
1809+
1810+
with self.assertWarns(DeprecationWarning):
1811+
self.loop.run_until_complete(coro)
1812+
18061813
@patch_socket
18071814
def test_create_datagram_endpoint_nosoreuseport(self, m_socket):
18081815
del m_socket.SO_REUSEPORT
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Due to significant security concerns, the *reuse_address* parameter of
2+
:meth:`asyncio.loop.create_datagram_endpoint` is no longer supported. This is
3+
because of the behavior of ``SO_REUSEADDR`` in UDP. For more details, see the
4+
documentation for ``loop.create_datagram_endpoint()``.
5+
(Contributed by Kyle Stanley, Antoine Pitrou, and Yury Selivanov in
6+
:issue:`37228`.)

0 commit comments

Comments
 (0)
0